From 0d93ec4c10d146ffe287e7f4430ee66ad5832a17 Mon Sep 17 00:00:00 2001
From: zhou zhou <3272660260@qq.com>
Date: 星期五, 10 四月 2026 16:08:20 +0800
Subject: [PATCH] #页面优化
---
.gitignore | 1
rsf-design/src/api/preparation-item.js | 20
rsf-design/src/views/basic-info/bas-station/modules/bas-station-tag-cell.vue | 71
rsf-design/src/views/basic-info/bas-station/index.vue | 262 +
rsf-design/src/views/orders/asn-order-log/modules/asn-order-item-log-panel.vue | 447 +++
rsf-design/src/views/orders/asn-order/asnOrderPage.helpers.js | 258 +
rsf-design/src/views/basic-info/bas-container/modules/bas-container-areas-editor.vue | 266 +
rsf-design/src/views/orders/delivery/index.vue | 352 ++
rsf-design/src/views/orders/preparation-item/preparationItemTable.columns.js | 66
rsf-design/src/views/orders/preparation/modules/preparation-task-dialog.vue | 259 ++
rsf-design/src/views/orders/asn-order-log/asnOrderLogTable.columns.js | 49
rsf-design/src/views/orders/asn-order/modules/asn-order-dialog.vue | 370 ++
rsf-design/src/views/orders/preparation/preparationPage.helpers.js | 70
rsf-design/src/api/asn-order.js | 57
rsf-design/src/api/delivery.js | 60
rsf-design/src/views/orders/asn-order-log/modules/asn-order-log-detail-drawer.vue | 62
rsf-design/src/views/orders/delivery-item/deliveryItemTable.columns.js | 86
rsf-design/src/views/orders/delivery/deliveryPage.helpers.js | 118
rsf-design/src/views/orders/asn-order/asnOrderTable.columns.js | 66
rsf-design/src/views/orders/asn-order/modules/asn-order-material-dialog.vue | 269 ++
rsf-design/src/views/orders/preparation/index.vue | 135 +
rsf-design/src/views/orders/preparation-item/index.vue | 371 ++
rsf-design/src/api/bas-station.js | 41
rsf-design/src/views/orders/asn-order/modules/asn-order-item-dialog.vue | 342 ++
rsf-design/src/locales/langs/en.json | 186 +
rsf-design/src/locales/langs/zh.json | 186 +
rsf-design/src/views/orders/delivery-item/deliveryItemPage.helpers.js | 124
rsf-design/src/views/orders/preparation-item/modules/preparation-item-dialog.vue | 238 +
rsf-design/src/views/orders/preparation/preparationTable.columns.js | 11
rsf-design/src/views/basic-info/bas-station/modules/bas-station-init-dialog.vue | 239 +
rsf-design/src/views/orders/asn-order-log/index.vue | 95
rsf-design/src/views/orders/delivery-item/modules/delivery-item-dialog.vue | 271 ++
rsf-design/src/views/orders/delivery/deliveryTable.columns.js | 56
rsf-design/src/views/orders/preparation/modules/preparation-wave-dialog.vue | 97
rsf-design/src/views/orders/delivery-item/modules/delivery-item-manage-panel.vue | 441 +++
rsf-design/src/views/orders/preparation/modules/preparation-generate-dialog.vue | 301 ++
rsf-design/src/views/orders/asn-order/modules/asn-order-create-by-po-dialog.vue | 31
rsf-design/src/views/basic-info/bas-station/basStationTable.columns.js | 46
rsf-design/src/views/orders/asn-order/modules/asn-order-detail-drawer.vue | 64
rsf-design/src/views/orders/delivery-item/index.vue | 200 -
rsf-design/src/api/preparation.js | 27
rsf-design/src/views/orders/delivery/modules/delivery-manage-dialog.vue | 86
rsf-design/src/views/orders/asn-order-log/asnOrderLogPage.helpers.js | 12
rsf-design/src/views/basic-info/bas-station/basStationPage.helpers.js | 267 +
rsf-design/src/views/orders/asn-order/index.vue | 440 +++
rsf-design/src/views/orders/preparation-item/preparationItemPage.helpers.js | 112
46 files changed, 6,932 insertions(+), 696 deletions(-)
diff --git a/.gitignore b/.gitignore
index dbeb5f5..a0cadb6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -41,3 +41,4 @@
/log.path_IS_UNDEFINED/*.log
/log.path_IS_UNDEFINED/*/*.log
.worktrees/
+.playwright-cli/
diff --git a/rsf-design/src/api/asn-order.js b/rsf-design/src/api/asn-order.js
index dc822da..d5e6ef0 100644
--- a/rsf-design/src/api/asn-order.js
+++ b/rsf-design/src/api/asn-order.js
@@ -34,6 +34,7 @@
return {
current: params.current || 1,
pageSize: params.pageSize || params.size || 20,
+ orderBy: params.orderBy || 'create_time desc',
...filterParams(params, ['current', 'pageSize', 'size'])
}
}
@@ -96,6 +97,33 @@
})
}
+export function fetchSaveAsnOrderWithItems(payload = {}) {
+ return request.post({
+ url: '/asnOrder/items/save',
+ params: payload
+ })
+}
+
+export function fetchUpdateAsnOrderWithItems(payload = {}) {
+ return request.post({
+ url: '/asnOrder/items/update',
+ params: payload
+ })
+}
+
+export function fetchDeleteAsnOrder(ids) {
+ return request.post({
+ url: `/asnOrder/remove/${normalizeIds(ids)}`
+ })
+}
+
+export function fetchInspectAsnOrder(payload = []) {
+ return request.post({
+ url: '/asnOrder/inspect',
+ params: payload
+ })
+}
+
export function fetchPurchaseFilterPage(params = {}) {
return request.post({
url: '/purchase/filters/page',
@@ -117,6 +145,12 @@
})
}
+export function fetchEnabledAsnOrderFields() {
+ return request.get({
+ url: '/fields/enable/list'
+ })
+}
+
export async function fetchExportAsnOrderReport(payload = {}, options = {}) {
return fetch(`${import.meta.env.VITE_API_URL}/asnOrder/export`, {
method: 'POST',
@@ -127,3 +161,26 @@
body: JSON.stringify(payload)
})
}
+
+export async function fetchDownloadAsnOrderTemplate(payload = {}, options = {}) {
+ return fetch(`${import.meta.env.VITE_API_URL}/asnOrderItem/template/download`, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ ...(options.headers || {})
+ },
+ body: JSON.stringify(payload)
+ })
+}
+
+export function fetchImportAsnOrder(file) {
+ const formData = new FormData()
+ formData.append('file', file)
+ return request.post({
+ url: '/asnOrderItem/import',
+ data: formData,
+ headers: {
+ 'Content-Type': 'multipart/form-data'
+ }
+ })
+}
diff --git a/rsf-design/src/api/bas-station.js b/rsf-design/src/api/bas-station.js
index 25a40c2..8546ede 100644
--- a/rsf-design/src/api/bas-station.js
+++ b/rsf-design/src/api/bas-station.js
@@ -34,7 +34,8 @@
return {
current: params.current || 1,
pageSize: params.pageSize || params.size || 20,
- ...filterParams(params, ['current', 'pageSize', 'size'])
+ orderBy: normalizeText(params.orderBy) || 'create_time desc',
+ ...filterParams(params, ['current', 'pageSize', 'size', 'orderBy'])
}
}
@@ -48,6 +49,14 @@
? Number(params.type)
: void 0,
useStatus: normalizeText(params.useStatus),
+ inAble:
+ params.inAble !== undefined && params.inAble !== null && params.inAble !== ''
+ ? Number(params.inAble)
+ : void 0,
+ outAble:
+ params.outAble !== undefined && params.outAble !== null && params.outAble !== ''
+ ? Number(params.outAble)
+ : void 0,
area:
params.area !== undefined && params.area !== null && params.area !== ''
? Number(params.area)
@@ -56,13 +65,23 @@
params.isCrossZone !== undefined && params.isCrossZone !== null && params.isCrossZone !== ''
? Number(params.isCrossZone)
: void 0,
+ crossZoneArea: normalizeText(params.crossZoneArea),
isWcs:
params.isWcs !== undefined && params.isWcs !== null && params.isWcs !== ''
? Number(params.isWcs)
: void 0,
+ wcsData: normalizeText(params.wcsData),
+ containerType:
+ params.containerType !== undefined &&
+ params.containerType !== null &&
+ params.containerType !== ''
+ ? Number(params.containerType)
+ : void 0,
barcode: normalizeText(params.barcode),
autoTransfer:
- params.autoTransfer !== undefined && params.autoTransfer !== null && params.autoTransfer !== ''
+ params.autoTransfer !== undefined &&
+ params.autoTransfer !== null &&
+ params.autoTransfer !== ''
? Number(params.autoTransfer)
: void 0,
status:
@@ -75,7 +94,9 @@
}
return Object.fromEntries(
- Object.entries(searchParams).filter(([, value]) => value !== '' && value !== void 0 && value !== null)
+ Object.entries(searchParams).filter(
+ ([, value]) => value !== '' && value !== void 0 && value !== null
+ )
)
}
@@ -93,7 +114,9 @@
? { area: Number(formData.area) }
: {}),
useStatus: normalizeText(formData.useStatus) || '',
- ...(formData.isCrossZone !== undefined && formData.isCrossZone !== null && formData.isCrossZone !== ''
+ ...(formData.isCrossZone !== undefined &&
+ formData.isCrossZone !== null &&
+ formData.isCrossZone !== ''
? { isCrossZone: Number(formData.isCrossZone) }
: {}),
...(Array.isArray(formData.areaIds) && formData.areaIds.length
@@ -104,10 +127,16 @@
: {}),
wcsData: normalizeText(formData.wcsData) || '',
...(Array.isArray(formData.containerTypes) && formData.containerTypes.length
- ? { containerTypes: formData.containerTypes.map((id) => Number(id)).filter((id) => !Number.isNaN(id)) }
+ ? {
+ containerTypes: formData.containerTypes
+ .map((id) => Number(id))
+ .filter((id) => !Number.isNaN(id))
+ }
: {}),
barcode: normalizeText(formData.barcode) || '',
- ...(formData.autoTransfer !== undefined && formData.autoTransfer !== null && formData.autoTransfer !== ''
+ ...(formData.autoTransfer !== undefined &&
+ formData.autoTransfer !== null &&
+ formData.autoTransfer !== ''
? { autoTransfer: Number(formData.autoTransfer) }
: {}),
...(formData.inAble !== undefined && formData.inAble !== null && formData.inAble !== ''
diff --git a/rsf-design/src/api/delivery.js b/rsf-design/src/api/delivery.js
index 0b33f8a..38e9ef4 100644
--- a/rsf-design/src/api/delivery.js
+++ b/rsf-design/src/api/delivery.js
@@ -32,9 +32,27 @@
export function buildDeliverySearchParams(params = {}) {
const result = {}
- ;['condition', 'code', 'platId', 'type', 'wkType', 'source', 'timeStart', 'timeEnd', 'memo'].forEach((key) => {
+ ;[
+ 'condition',
+ 'code',
+ 'platId',
+ 'type',
+ 'wkType',
+ 'source',
+ 'platCode',
+ 'timeStart',
+ 'timeEnd',
+ 'startTime',
+ 'endTime',
+ 'memo'
+ ].forEach((key) => {
const value = normalizeText(params[key])
if (value) result[key] = value
+ })
+ ;['anfme', 'qty', 'workQty'].forEach((key) => {
+ if (params[key] !== '' && params[key] !== undefined && params[key] !== null) {
+ result[key] = Number(params[key])
+ }
})
if (params.status !== '' && params.status !== undefined && params.status !== null) {
result.status = Number(params.status)
@@ -49,6 +67,7 @@
return {
current: params.current || 1,
pageSize: params.pageSize || params.size || 20,
+ orderBy: normalizeText(params.orderBy) || 'create_time desc',
...buildDeliverySearchParams(params)
}
}
@@ -74,7 +93,8 @@
'splrBatch',
'timeStart',
'timeEnd',
- 'memo'
+ 'memo',
+ 'fieldsIndex'
].forEach((key) => {
const value = normalizeText(params[key])
if (value) result[key] = value
@@ -92,6 +112,7 @@
return {
current: params.current || 1,
pageSize: params.pageSize || params.size || 20,
+ orderBy: normalizeText(params.orderBy) || 'create_time desc',
...buildDeliveryItemSearchParams(params)
}
}
@@ -134,6 +155,12 @@
})
}
+export function fetchDeleteDeliveryMany(ids) {
+ return request.post({
+ url: `/delivery/remove/${normalizeIds(ids)}`
+ })
+}
+
export function fetchSaveDelivery(payload = {}) {
return request.post({
url: '/delivery/save',
@@ -145,6 +172,18 @@
return request.post({
url: '/delivery/update',
params: payload
+ })
+}
+
+export function fetchImportDelivery(file) {
+ const formData = new FormData()
+ formData.append('file', file)
+ return request.post({
+ url: '/delivery/import',
+ data: formData,
+ headers: {
+ 'Content-Type': 'multipart/form-data'
+ }
})
}
@@ -173,6 +212,12 @@
})
}
+export function fetchDeleteDeliveryItemMany(ids) {
+ return request.post({
+ url: `/deliveryItem/remove/${normalizeIds(ids)}`
+ })
+}
+
export function fetchSaveDeliveryItem(payload = {}) {
return request.post({
url: '/deliveryItem/save',
@@ -198,3 +243,14 @@
body: JSON.stringify(buildDeliveryExportParams(payload))
})
}
+
+export async function fetchDownloadDeliveryTemplate(payload = {}, options = {}) {
+ return fetch(`${import.meta.env.VITE_API_URL}/delivery/template/download`, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ ...(options.headers || {})
+ },
+ body: JSON.stringify(payload)
+ })
+}
diff --git a/rsf-design/src/api/preparation-item.js b/rsf-design/src/api/preparation-item.js
index 4a0b7e2..57b5df6 100644
--- a/rsf-design/src/api/preparation-item.js
+++ b/rsf-design/src/api/preparation-item.js
@@ -66,6 +66,26 @@
})
}
+export function fetchSavePreparationItem(payload = {}) {
+ return request.post({
+ url: '/outStockItem/save',
+ params: payload
+ })
+}
+
+export function fetchUpdatePreparationItem(payload = {}) {
+ return request.post({
+ url: '/outStockItem/update',
+ params: payload
+ })
+}
+
+export function fetchDeletePreparationItem(ids) {
+ return request.post({
+ url: `/outStockItem/remove/${normalizeIds(ids)}`
+ })
+}
+
export async function fetchExportPreparationItemReport(payload = {}, options = {}) {
return fetch(`${import.meta.env.VITE_API_URL}/outStockItem/export`, {
method: 'POST',
diff --git a/rsf-design/src/api/preparation.js b/rsf-design/src/api/preparation.js
index 0049199..5a7cdfc 100644
--- a/rsf-design/src/api/preparation.js
+++ b/rsf-design/src/api/preparation.js
@@ -51,6 +51,13 @@
return request.post({ url: '/preparation/page', params: buildPreparationPageParams(params) })
}
+export function fetchPreparationDialogPage(params = {}) {
+ return request.post({
+ url: '/deliveryItem/filters/page',
+ params: buildPreparationPageParams(params)
+ })
+}
+
export function fetchPreparationItemPage(params = {}) {
return request.post({
url: '/outStockItem/page',
@@ -78,6 +85,26 @@
return request.get({ url: `/preparation/cancel/${id}` })
}
+export function fetchGeneratePreparationOrders(payload = {}) {
+ return request.post({ url: '/preparation/generate/orders', params: payload })
+}
+
+export function fetchGeneratePreparationWave(payload = {}) {
+ return request.post({ url: '/preparation/generate/wave', params: payload })
+}
+
+export function fetchPreparationTaskPreview(payload = {}) {
+ return request.post({ url: '/preparation/order/getOutTaskItems', params: payload })
+}
+
+export function fetchGeneratePreparationTasks(payload = {}) {
+ return request.post({ url: '/preparation/generate/tasks', params: payload })
+}
+
+export function fetchPreparationTaskSites() {
+ return request.get({ url: '/preparation/tasks/sites' })
+}
+
export async function fetchExportPreparationReport(payload = {}, options = {}) {
return fetch(`${import.meta.env.VITE_API_URL}/preparation/export`, {
method: 'POST',
diff --git a/rsf-design/src/locales/langs/en.json b/rsf-design/src/locales/langs/en.json
index f4ef9d8..e5ed2d3 100644
--- a/rsf-design/src/locales/langs/en.json
+++ b/rsf-design/src/locales/langs/en.json
@@ -1192,6 +1192,10 @@
"reportTitle": "ASN Report",
"entity": "ASN",
"buttons": {
+ "create": "New ASN",
+ "import": "Import",
+ "downloadTemplate": "Download Template",
+ "inspection": "Batch Inspection",
"createByPo": "Create by PO"
},
"search": {
@@ -1201,8 +1205,14 @@
"codePlaceholder": "Enter ASN No.",
"poCode": "PO No.",
"poCodePlaceholder": "Enter PO No.",
+ "poId": "PO ID",
+ "type": "Order Type",
"wkType": "Business Type",
- "wkTypePlaceholder": "Enter business type",
+ "anfme": "Delivery Qty",
+ "qty": "Received Qty",
+ "logisNo": "Logistics No.",
+ "arrTime": "Estimated Arrival",
+ "memo": "Remark",
"exceStatus": "Document Status",
"supplierName": "Supplier",
"supplierPlaceholder": "Enter supplier",
@@ -1213,7 +1223,14 @@
"condition": "Enter ASN No./PO No./Supplier",
"code": "Enter ASN No.",
"poCode": "Enter PO No.",
- "wkType": "Enter business type",
+ "poId": "Enter PO ID",
+ "type": "Select order type",
+ "wkType": "Select business type",
+ "anfme": "Enter delivery qty",
+ "qty": "Enter received qty",
+ "logisNo": "Enter logistics No.",
+ "arrTime": "Select estimated arrival time",
+ "memo": "Enter remark",
"supplierName": "Enter supplier",
"purchaseUserName": "Enter purchaser"
},
@@ -1229,6 +1246,8 @@
"actions": {
"view": "View Detail",
"items": "Receiving Items",
+ "edit": "Edit",
+ "delete": "Delete",
"print": "Print",
"complete": "Complete"
},
@@ -1256,6 +1275,77 @@
"actionFailed": "ASN action failed",
"detailTimeout": "ASN detail items timed out and waiting has stopped",
"itemsTimeout": "ASN detail items timed out and waiting has stopped"
+ },
+ "dialog": {
+ "createTitle": "Create ASN",
+ "editTitle": "Edit ASN",
+ "addMaterial": "Add Material",
+ "deleteSelected": "Delete Selected",
+ "itemCount": "{count} item(s)",
+ "materialDuplicate": "The selected materials already exist in current items",
+ "placeholder": {
+ "type": "Select order type",
+ "wkType": "Select business type",
+ "poCode": "Enter PO No.",
+ "logisNo": "Enter logistics No.",
+ "arrTime": "Select estimated arrival time",
+ "memo": "Enter remark"
+ },
+ "validation": {
+ "type": "Please select order type",
+ "wkType": "Please select business type",
+ "items": "Please add at least one material item",
+ "anfme": "Expected quantity must be greater than 0"
+ }
+ },
+ "materialDialog": {
+ "title": "Select Material",
+ "selected": "Selected",
+ "unselected": "Available",
+ "search": {
+ "name": "Material Name",
+ "code": "Material Code",
+ "groupId": "Material Group"
+ },
+ "placeholder": {
+ "name": "Enter material name",
+ "code": "Enter material code",
+ "groupId": "Select material group"
+ },
+ "table": {
+ "code": "Material Code",
+ "name": "Material Name",
+ "group": "Material Group",
+ "spec": "Specification",
+ "model": "Model",
+ "status": "Selection Status"
+ },
+ "messages": {
+ "groupTimeout": "Material group loading timed out and waiting has stopped"
+ }
+ },
+ "messages": {
+ "createSuccess": "ASN created successfully",
+ "createFailed": "Failed to create ASN",
+ "updateSuccess": "ASN updated successfully",
+ "updateFailed": "Failed to update ASN",
+ "deleteTitle": "Delete Confirmation",
+ "deleteConfirm": "Are you sure you want to delete ASN {code}?",
+ "deleteSuccess": "ASN deleted successfully",
+ "detailTimeout": "ASN detail loading timed out and waiting has stopped",
+ "detailFailed": "Failed to load ASN detail",
+ "inspectionTitle": "Inspection Confirmation",
+ "inspectionConfirm": "Are you sure you want to inspect the selected {count} ASN(s)?",
+ "inspectionSuccess": "ASN inspection submitted successfully",
+ "inspectionFailed": "Failed to submit ASN inspection",
+ "inspectionSelectRequired": "Please select the ASN records to inspect first",
+ "importSuccess": "ASN import succeeded",
+ "importFailed": "ASN import failed",
+ "templateDownloadSuccess": "Template downloaded successfully",
+ "templateDownloadFailed": "Failed to download template",
+ "typeOptionsTimeout": "Order type options timed out and waiting has stopped",
+ "wkTypeOptionsTimeout": "Business type options timed out and waiting has stopped",
+ "fieldOptionsTimeout": "Extended field loading timed out and waiting has stopped"
},
"createByPoDialog": {
"title": "Create by PO",
@@ -1287,6 +1377,9 @@
}
},
"table": {
+ "purchaseOrgName": "Purchasing Org",
+ "businessTime": "Purchase Date",
+ "supplierId": "Supplier Code",
"poItemId": "PO Line No.",
"expectedQty": "Expected Qty",
"receivedQty": "Received Qty",
@@ -1556,6 +1649,8 @@
"search": {
"condition": "Keyword",
"conditionPlaceholder": "Enter No./ERP master order/platform order",
+ "timeStart": "Created From",
+ "timeEnd": "Created To",
"code": "No.",
"codePlaceholder": "Enter No.",
"platId": "ERP Master Order ID",
@@ -1566,6 +1661,12 @@
"wkTypePlaceholder": "Enter business type",
"source": "Order Source",
"sourcePlaceholder": "Enter order source",
+ "anfme": "Outbound Qty",
+ "qty": "Outbound Done Qty",
+ "workQty": "Working Qty",
+ "platCode": "Platform Order No.",
+ "startTime": "Planned Outbound Time",
+ "endTime": "Planned Outbound End Time",
"exceStatus": "Execution Status",
"exceStatusPlaceholder": "Enter execution status",
"memo": "Remark",
@@ -1573,13 +1674,26 @@
},
"placeholder": {
"condition": "Enter No./ERP master order/platform order",
+ "timeStart": "Select created from",
+ "timeEnd": "Select created to",
"code": "Enter No.",
"platId": "Enter ERP master order ID",
"type": "Enter order type",
"wkType": "Enter business type",
"source": "Enter order source",
+ "anfme": "Enter outbound qty",
+ "qty": "Enter outbound done qty",
+ "workQty": "Enter working qty",
+ "platCode": "Enter platform order No.",
+ "startTime": "Select planned outbound time",
+ "endTime": "Select planned outbound end time",
+ "status": "Select status",
"exceStatus": "Enter execution status",
"memo": "Enter remark"
+ },
+ "buttons": {
+ "import": "Import",
+ "downloadTemplate": "Download Template"
},
"status": {
"normal": "Normal",
@@ -1591,8 +1705,13 @@
},
"actions": {
"view": "View Detail",
+ "edit": "Edit",
"items": "Items",
"delete": "Delete"
+ },
+ "manage": {
+ "title": "Edit DO",
+ "baseInfo": "Order Information"
},
"detail": {
"title": "Handover Order Detail",
@@ -1640,7 +1759,11 @@
"messages": {
"itemsTimeout": "DO items timed out and waiting has stopped",
"detailTimeout": "DO detail timed out and waiting has stopped",
- "detailLoadFailed": "Failed to load DO detail"
+ "detailLoadFailed": "Failed to load DO detail",
+ "importSuccess": "DO imported successfully",
+ "importFailed": "Failed to import DO",
+ "templateDownloadSuccess": "Template downloaded successfully",
+ "templateDownloadFailed": "Failed to download template"
}
},
"deliveryItem": {
@@ -1653,11 +1776,58 @@
"deliveryCodePlaceholder": "Enter DO No.",
"platItemId": "Platform Line No.",
"platItemIdPlaceholder": "Enter platform line No.",
+ "fieldsIndexPlaceholder": "Enter field index",
"matnrCodePlaceholder": "Enter material code",
"maktxPlaceholder": "Enter material name",
"supplierName": "Supplier Name",
"supplierNamePlaceholder": "Enter supplier name",
- "supplierBatchPlaceholder": "Enter supplier batch"
+ "supplierCodePlaceholder": "Enter supplier code",
+ "supplierBatchPlaceholder": "Enter supplier batch",
+ "statusPlaceholder": "Select status",
+ "timeStart": "Created From",
+ "timeStartPlaceholder": "Select created from",
+ "timeEnd": "Created To",
+ "timeEndPlaceholder": "Select created to",
+ "memoPlaceholder": "Enter remark"
+ },
+ "actions": {
+ "add": "Add Item"
+ },
+ "dialog": {
+ "titleAdd": "Add DO Item",
+ "titleEdit": "Edit DO Item",
+ "deliveryId": "DO ID",
+ "platItemId": "Platform Line No.",
+ "matnrCode": "Material Code",
+ "maktx": "Material Name",
+ "fieldsIndex": "Field Index",
+ "unit": "Unit",
+ "anfme": "Qty",
+ "qty": "Outbound Qty",
+ "printQty": "Print Qty",
+ "splrName": "Supplier Name",
+ "splrCode": "Supplier Code",
+ "splrBatch": "Supplier Batch",
+ "status": "Status",
+ "memo": "Remark",
+ "placeholder": {
+ "platItemId": "Enter platform line No.",
+ "matnrCode": "Enter material code",
+ "maktx": "Enter material name",
+ "fieldsIndex": "Enter field index",
+ "unit": "Enter unit",
+ "anfme": "Enter qty",
+ "qty": "Enter outbound qty",
+ "printQty": "Enter print qty",
+ "splrName": "Enter supplier name",
+ "splrCode": "Enter supplier code",
+ "splrBatch": "Enter supplier batch",
+ "status": "Select status",
+ "memo": "Enter remark"
+ },
+ "validation": {
+ "anfme": "Enter qty"
+ }
},
"table": {
"deliveryId": "DO ID",
@@ -1690,7 +1860,13 @@
},
"messages": {
"detailTimeout": "DO item detail timed out and waiting has stopped",
- "detailFailed": "Failed to load DO item detail"
+ "detailFailed": "Failed to load DO item detail",
+ "createSuccess": "DO item created successfully",
+ "createFailed": "Failed to create DO item",
+ "updateSuccess": "DO item updated successfully",
+ "updateFailed": "Failed to update DO item",
+ "deleteConfirm": "Are you sure you want to delete DO item {code}?",
+ "actionFailed": "DO item action failed"
}
},
"transfer": {
diff --git a/rsf-design/src/locales/langs/zh.json b/rsf-design/src/locales/langs/zh.json
index 170cebe..27b8c95 100644
--- a/rsf-design/src/locales/langs/zh.json
+++ b/rsf-design/src/locales/langs/zh.json
@@ -1194,6 +1194,10 @@
"reportTitle": "鍏ュ簱閫氱煡鍗曟姤琛�",
"entity": "鍏ュ簱閫氱煡鍗�",
"buttons": {
+ "create": "鏂板缓鍏ュ簱閫氱煡鍗�",
+ "import": "瀵煎叆",
+ "downloadTemplate": "涓嬭浇妯℃澘",
+ "inspection": "鎵归噺鎶ユ",
"createByPo": "鎸塒O寤哄崟"
},
"search": {
@@ -1203,8 +1207,14 @@
"codePlaceholder": "璇疯緭鍏� ASN 鍗曞彿",
"poCode": "PO鍗曞彿",
"poCodePlaceholder": "璇疯緭鍏� PO 鍗曞彿",
+ "poId": "PO鍗旾D",
+ "type": "鍗曟嵁绫诲瀷",
"wkType": "涓氬姟绫诲瀷",
- "wkTypePlaceholder": "璇疯緭鍏ヤ笟鍔$被鍨�",
+ "anfme": "閫佽揣鏁伴噺",
+ "qty": "宸叉敹鏁伴噺",
+ "logisNo": "鐗╂祦鍗曞彿",
+ "arrTime": "棰勮鍒拌揪鏃堕棿",
+ "memo": "澶囨敞",
"exceStatus": "鍗曟嵁鐘舵��",
"supplierName": "渚涘簲鍟�",
"supplierPlaceholder": "璇疯緭鍏ヤ緵搴斿晢",
@@ -1215,7 +1225,14 @@
"condition": "璇疯緭鍏� ASN 鍗曞彿/PO 鍗曞彿/渚涘簲鍟�",
"code": "璇疯緭鍏� ASN 鍗曞彿",
"poCode": "璇疯緭鍏� PO 鍗曞彿",
- "wkType": "璇疯緭鍏ヤ笟鍔$被鍨�",
+ "poId": "璇疯緭鍏� PO 鍗曞彿 ID",
+ "type": "璇烽�夋嫨鍗曟嵁绫诲瀷",
+ "wkType": "璇烽�夋嫨涓氬姟绫诲瀷",
+ "anfme": "璇疯緭鍏ラ�佽揣鏁伴噺",
+ "qty": "璇疯緭鍏ュ凡鏀舵暟閲�",
+ "logisNo": "璇疯緭鍏ョ墿娴佸崟鍙�",
+ "arrTime": "璇烽�夋嫨棰勮鍒拌揪鏃堕棿",
+ "memo": "璇疯緭鍏ュ娉�",
"supplierName": "璇疯緭鍏ヤ緵搴斿晢",
"purchaseUserName": "璇疯緭鍏ラ噰璐憳"
},
@@ -1231,6 +1248,8 @@
"actions": {
"view": "鏌ョ湅璇︽儏",
"items": "鏀惰揣鏄庣粏",
+ "edit": "缂栬緫",
+ "delete": "鍒犻櫎",
"print": "鎵撳嵃",
"complete": "瀹屾垚"
},
@@ -1258,6 +1277,77 @@
"actionFailed": "鍏ュ簱閫氱煡鍗曟搷浣滃け璐�",
"detailTimeout": "鍏ュ簱閫氱煡鍗曟槑缁嗗姞杞借秴鏃讹紝宸插仠姝㈢瓑寰�",
"itemsTimeout": "鍏ュ簱閫氱煡鍗曟槑缁嗗姞杞借秴鏃讹紝宸插仠姝㈢瓑寰�"
+ },
+ "dialog": {
+ "createTitle": "鏂板鍏ュ簱閫氱煡鍗�",
+ "editTitle": "缂栬緫鍏ュ簱閫氱煡鍗�",
+ "addMaterial": "鏂板鐗╂枡",
+ "deleteSelected": "鍒犻櫎閫変腑",
+ "itemCount": "褰撳墠鍏� {count} 鏉℃槑缁�",
+ "materialDuplicate": "鎵�閫夌墿鏂欏凡瀛樺湪浜庡綋鍓嶆槑缁嗕腑",
+ "placeholder": {
+ "type": "璇烽�夋嫨鍗曟嵁绫诲瀷",
+ "wkType": "璇烽�夋嫨涓氬姟绫诲瀷",
+ "poCode": "璇疯緭鍏� PO 鍗曞彿",
+ "logisNo": "璇疯緭鍏ョ墿娴佸崟鍙�",
+ "arrTime": "璇烽�夋嫨棰勮鍒拌揪鏃堕棿",
+ "memo": "璇疯緭鍏ュ娉�"
+ },
+ "validation": {
+ "type": "璇烽�夋嫨鍗曟嵁绫诲瀷",
+ "wkType": "璇烽�夋嫨涓氬姟绫诲瀷",
+ "items": "璇疯嚦灏戞坊鍔犱竴鏉$墿鏂欐槑缁�",
+ "anfme": "鐗╂枡鏄庣粏鐨勫簲鏀舵暟閲忓繀椤诲ぇ浜� 0"
+ }
+ },
+ "materialDialog": {
+ "title": "閫夋嫨鐗╂枡",
+ "selected": "宸查�夋嫨",
+ "unselected": "鍙�夋嫨",
+ "search": {
+ "name": "鐗╂枡鍚嶇О",
+ "code": "鐗╂枡缂栫爜",
+ "groupId": "鐗╂枡鍒嗙粍"
+ },
+ "placeholder": {
+ "name": "璇疯緭鍏ョ墿鏂欏悕绉�",
+ "code": "璇疯緭鍏ョ墿鏂欑紪鐮�",
+ "groupId": "璇烽�夋嫨鐗╂枡鍒嗙粍"
+ },
+ "table": {
+ "code": "鐗╂枡缂栫爜",
+ "name": "鐗╂枡鍚嶇О",
+ "group": "鐗╂枡鍒嗙粍",
+ "spec": "瑙勬牸",
+ "model": "鍨嬪彿",
+ "status": "閫夋嫨鐘舵��"
+ },
+ "messages": {
+ "groupTimeout": "鐗╂枡鍒嗙粍鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟"
+ }
+ },
+ "messages": {
+ "createSuccess": "鍏ュ簱閫氱煡鍗曟柊澧炴垚鍔�",
+ "createFailed": "鍏ュ簱閫氱煡鍗曟柊澧炲け璐�",
+ "updateSuccess": "鍏ュ簱閫氱煡鍗曟洿鏂版垚鍔�",
+ "updateFailed": "鍏ュ簱閫氱煡鍗曟洿鏂板け璐�",
+ "deleteTitle": "鍒犻櫎纭",
+ "deleteConfirm": "纭畾鍒犻櫎鍏ュ簱閫氱煡鍗� {code} 鍚楋紵",
+ "deleteSuccess": "鍏ュ簱閫氱煡鍗曞垹闄ゆ垚鍔�",
+ "detailTimeout": "鍏ュ簱閫氱煡鍗曡鎯呭姞杞借秴鏃讹紝宸插仠姝㈢瓑寰�",
+ "detailFailed": "鑾峰彇鍏ュ簱閫氱煡鍗曡鎯呭け璐�",
+ "inspectionTitle": "鎶ユ纭",
+ "inspectionConfirm": "纭畾瀵归�変腑鐨� {count} 寮犲叆搴撻�氱煡鍗曟墽琛屾姤妫�鍚楋紵",
+ "inspectionSuccess": "鍏ュ簱閫氱煡鍗曟姤妫�鎴愬姛",
+ "inspectionFailed": "鍏ュ簱閫氱煡鍗曟姤妫�澶辫触",
+ "inspectionSelectRequired": "璇峰厛閫夋嫨闇�瑕佹姤妫�鐨勫叆搴撻�氱煡鍗�",
+ "importSuccess": "鍏ュ簱閫氱煡鍗曞鍏ユ垚鍔�",
+ "importFailed": "鍏ュ簱閫氱煡鍗曞鍏ュけ璐�",
+ "templateDownloadSuccess": "妯℃澘涓嬭浇鎴愬姛",
+ "templateDownloadFailed": "妯℃澘涓嬭浇澶辫触",
+ "typeOptionsTimeout": "鍗曟嵁绫诲瀷閫夐」鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟",
+ "wkTypeOptionsTimeout": "涓氬姟绫诲瀷閫夐」鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟",
+ "fieldOptionsTimeout": "鎵╁睍瀛楁鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟"
},
"createByPoDialog": {
"title": "鎸塒O寤哄崟",
@@ -1289,6 +1379,9 @@
}
},
"table": {
+ "purchaseOrgName": "閲囪喘缁勭粐",
+ "businessTime": "閲囪喘鏃ユ湡",
+ "supplierId": "渚涘簲鍟嗙紪鐮�",
"poItemId": "PO琛屽彿",
"expectedQty": "搴旀敹鏁伴噺",
"receivedQty": "宸叉敹鏁伴噺",
@@ -1564,6 +1657,8 @@
"search": {
"condition": "鍏抽敭瀛�",
"conditionPlaceholder": "璇疯緭鍏ュ崟鍙�/ERP涓诲崟鏍囪瘑/骞冲彴鍗曞彿",
+ "timeStart": "鍒涘缓寮�濮嬫椂闂�",
+ "timeEnd": "鍒涘缓缁撴潫鏃堕棿",
"code": "鍗曞彿",
"codePlaceholder": "璇疯緭鍏ュ崟鍙�",
"platId": "ERP涓诲崟鏍囪瘑",
@@ -1574,6 +1669,12 @@
"wkTypePlaceholder": "璇疯緭鍏ヤ笟鍔$被鍨�",
"source": "鍗曟嵁鏉ユ簮",
"sourcePlaceholder": "璇疯緭鍏ュ崟鎹潵婧�",
+ "anfme": "鍑哄簱鏁伴噺",
+ "qty": "宸插嚭搴撴暟閲�",
+ "workQty": "鎵ц涓暟閲�",
+ "platCode": "骞冲彴鍗曞彿",
+ "startTime": "璁″垝鍑哄簱鏃堕棿",
+ "endTime": "璁″垝鍑哄簱缁撴潫鏃堕棿",
"exceStatus": "鎵ц鐘舵��",
"exceStatusPlaceholder": "璇疯緭鍏ユ墽琛岀姸鎬�",
"memo": "澶囨敞",
@@ -1581,13 +1682,26 @@
},
"placeholder": {
"condition": "璇疯緭鍏ュ崟鍙�/ERP涓诲崟鏍囪瘑/骞冲彴鍗曞彿",
+ "timeStart": "璇烽�夋嫨鍒涘缓寮�濮嬫椂闂�",
+ "timeEnd": "璇烽�夋嫨鍒涘缓缁撴潫鏃堕棿",
"code": "璇疯緭鍏ュ崟鍙�",
"platId": "璇疯緭鍏RP涓诲崟鏍囪瘑",
"type": "璇疯緭鍏ュ崟鎹被鍨�",
"wkType": "璇疯緭鍏ヤ笟鍔$被鍨�",
"source": "璇疯緭鍏ュ崟鎹潵婧�",
+ "anfme": "璇疯緭鍏ュ嚭搴撴暟閲�",
+ "qty": "璇疯緭鍏ュ凡鍑哄簱鏁伴噺",
+ "workQty": "璇疯緭鍏ユ墽琛屼腑鏁伴噺",
+ "platCode": "璇疯緭鍏ュ钩鍙板崟鍙�",
+ "startTime": "璇烽�夋嫨璁″垝鍑哄簱鏃堕棿",
+ "endTime": "璇烽�夋嫨璁″垝鍑哄簱缁撴潫鏃堕棿",
+ "status": "璇烽�夋嫨鐘舵��",
"exceStatus": "璇疯緭鍏ユ墽琛岀姸鎬�",
"memo": "璇疯緭鍏ュ娉�"
+ },
+ "buttons": {
+ "import": "瀵煎叆",
+ "downloadTemplate": "涓嬭浇妯℃澘"
},
"status": {
"normal": "姝e父",
@@ -1599,8 +1713,13 @@
},
"actions": {
"view": "鏌ョ湅璇︽儏",
+ "edit": "缂栬緫",
"items": "鏄庣粏",
"delete": "鍒犻櫎"
+ },
+ "manage": {
+ "title": "缂栬緫DO鍗�",
+ "baseInfo": "涓诲崟淇℃伅"
},
"detail": {
"title": "浜ゆ帴鍗曡鎯�",
@@ -1648,7 +1767,11 @@
"messages": {
"itemsTimeout": "DO鍗曟槑缁嗗姞杞借秴鏃讹紝宸插仠姝㈢瓑寰�",
"detailTimeout": "DO鍗曡鎯呭姞杞借秴鏃讹紝宸插仠姝㈢瓑寰�",
- "detailLoadFailed": "DO鍗曡鎯呭姞杞藉け璐�"
+ "detailLoadFailed": "DO鍗曡鎯呭姞杞藉け璐�",
+ "importSuccess": "DO鍗曞鍏ユ垚鍔�",
+ "importFailed": "DO鍗曞鍏ュけ璐�",
+ "templateDownloadSuccess": "妯℃澘涓嬭浇鎴愬姛",
+ "templateDownloadFailed": "妯℃澘涓嬭浇澶辫触"
}
},
"deliveryItem": {
@@ -1661,11 +1784,58 @@
"deliveryCodePlaceholder": "璇疯緭鍏O鍗曞彿",
"platItemId": "骞冲彴琛屽彿",
"platItemIdPlaceholder": "璇疯緭鍏ュ钩鍙拌鍙�",
+ "fieldsIndexPlaceholder": "璇疯緭鍏ュ瓧娈电储寮�",
"matnrCodePlaceholder": "璇疯緭鍏ョ墿鏂欑紪鐮�",
"maktxPlaceholder": "璇疯緭鍏ョ墿鏂欏悕绉�",
"supplierName": "渚涘簲鍟嗗悕绉�",
"supplierNamePlaceholder": "璇疯緭鍏ヤ緵搴斿晢鍚嶇О",
- "supplierBatchPlaceholder": "璇疯緭鍏ヤ緵搴斿晢鎵规"
+ "supplierCodePlaceholder": "璇疯緭鍏ヤ緵搴斿晢缂栫爜",
+ "supplierBatchPlaceholder": "璇疯緭鍏ヤ緵搴斿晢鎵规",
+ "statusPlaceholder": "璇烽�夋嫨鐘舵��",
+ "timeStart": "鍒涘缓寮�濮嬫椂闂�",
+ "timeStartPlaceholder": "璇烽�夋嫨鍒涘缓寮�濮嬫椂闂�",
+ "timeEnd": "鍒涘缓缁撴潫鏃堕棿",
+ "timeEndPlaceholder": "璇烽�夋嫨鍒涘缓缁撴潫鏃堕棿",
+ "memoPlaceholder": "璇疯緭鍏ュ娉�"
+ },
+ "actions": {
+ "add": "鏂板鏄庣粏"
+ },
+ "dialog": {
+ "titleAdd": "鏂板DO鍗曟槑缁�",
+ "titleEdit": "缂栬緫DO鍗曟槑缁�",
+ "deliveryId": "DO鍗旾D",
+ "platItemId": "骞冲彴琛屽彿",
+ "matnrCode": "鐗╂枡缂栫爜",
+ "maktx": "鐗╂枡鍚嶇О",
+ "fieldsIndex": "瀛楁绱㈠紩",
+ "unit": "鍗曚綅",
+ "anfme": "鏁伴噺",
+ "qty": "宸插嚭鏁伴噺",
+ "printQty": "鎵撳嵃鏁伴噺",
+ "splrName": "渚涘簲鍟嗗悕绉�",
+ "splrCode": "渚涘簲鍟嗙紪鐮�",
+ "splrBatch": "渚涘簲鍟嗘壒娆�",
+ "status": "鐘舵��",
+ "memo": "澶囨敞",
+ "placeholder": {
+ "platItemId": "璇疯緭鍏ュ钩鍙拌鍙�",
+ "matnrCode": "璇疯緭鍏ョ墿鏂欑紪鐮�",
+ "maktx": "璇疯緭鍏ョ墿鏂欏悕绉�",
+ "fieldsIndex": "璇疯緭鍏ュ瓧娈电储寮�",
+ "unit": "璇疯緭鍏ュ崟浣�",
+ "anfme": "璇疯緭鍏ユ暟閲�",
+ "qty": "璇疯緭鍏ュ凡鍑烘暟閲�",
+ "printQty": "璇疯緭鍏ユ墦鍗版暟閲�",
+ "splrName": "璇疯緭鍏ヤ緵搴斿晢鍚嶇О",
+ "splrCode": "璇疯緭鍏ヤ緵搴斿晢缂栫爜",
+ "splrBatch": "璇疯緭鍏ヤ緵搴斿晢鎵规",
+ "status": "璇烽�夋嫨鐘舵��",
+ "memo": "璇疯緭鍏ュ娉�"
+ },
+ "validation": {
+ "anfme": "璇疯緭鍏ユ暟閲�"
+ }
},
"table": {
"deliveryId": "DO鍗旾D",
@@ -1698,7 +1868,13 @@
},
"messages": {
"detailTimeout": "DO鍗曟槑缁嗚鎯呭姞杞借秴鏃讹紝宸插仠姝㈢瓑寰�",
- "detailFailed": "鑾峰彇DO鍗曟槑缁嗚鎯呭け璐�"
+ "detailFailed": "鑾峰彇DO鍗曟槑缁嗚鎯呭け璐�",
+ "createSuccess": "DO鍗曟槑缁嗘柊澧炴垚鍔�",
+ "createFailed": "DO鍗曟槑缁嗘柊澧炲け璐�",
+ "updateSuccess": "DO鍗曟槑缁嗕慨鏀规垚鍔�",
+ "updateFailed": "DO鍗曟槑缁嗕慨鏀瑰け璐�",
+ "deleteConfirm": "纭畾鍒犻櫎DO鍗曟槑缁� {code} 鍚楋紵",
+ "actionFailed": "DO鍗曟槑缁嗘搷浣滃け璐�"
}
},
"transfer": {
diff --git a/rsf-design/src/views/basic-info/bas-container/modules/bas-container-areas-editor.vue b/rsf-design/src/views/basic-info/bas-container/modules/bas-container-areas-editor.vue
index 2ce6b2e..9d66a21 100644
--- a/rsf-design/src/views/basic-info/bas-container/modules/bas-container-areas-editor.vue
+++ b/rsf-design/src/views/basic-info/bas-container/modules/bas-container-areas-editor.vue
@@ -1,9 +1,9 @@
<template>
- <div class="space-y-3">
- <div class="flex flex-wrap items-center gap-2">
+ <div class="areas-editor">
+ <div class="areas-editor__toolbar">
<ElSelect
v-model="selectedAreaId"
- class="min-w-0 flex-1"
+ class="areas-editor__select"
clearable
filterable
placeholder="璇烽�夋嫨鍙叆搴撳尯"
@@ -15,44 +15,73 @@
:value="option.value"
/>
</ElSelect>
- <ElButton :disabled="selectedAreaId === '' || selectedAreaId === void 0" @click="handleAddArea">
+ <ElButton
+ :disabled="selectedAreaId === '' || selectedAreaId === void 0"
+ @click="handleAddArea"
+ >
娣诲姞
</ElButton>
+ <span class="areas-editor__toolbar-tip">
+ 宸查�� {{ selectedAreas.length }} 涓簱鍖猴紝鍙皟鏁撮『搴�
+ </span>
</div>
- <ElEmpty v-if="!selectedAreas.length" description="鏆傛棤鍙叆搴撳尯" />
+ <div class="areas-editor__panel">
+ <template v-if="selectedAreas.length">
+ <div class="areas-editor__header">
+ <span>搴忓彿</span>
+ <span>搴撳尯</span>
+ <span>鎺掑簭</span>
+ <span>鎿嶄綔</span>
+ </div>
- <div v-else class="space-y-2">
- <div
- v-for="(item, index) in selectedAreas"
- :key="item.id"
- class="flex flex-wrap items-center gap-2 rounded-lg border border-[var(--art-border-color)] px-3 py-2"
- >
- <div class="flex w-10 shrink-0 items-center justify-center text-sm text-[var(--art-text-secondary)]">
- {{ index + 1 }}
- </div>
- <div class="min-w-0 flex-1">
- <div class="truncate font-medium text-[var(--art-text-primary)]">
- {{ resolveAreaLabel(item) }}
+ <ElScrollbar max-height="240px">
+ <div class="areas-editor__list">
+ <div v-for="(item, index) in selectedAreas" :key="item.id" class="areas-editor__row">
+ <div class="areas-editor__index">
+ {{ index + 1 }}
+ </div>
+ <div class="areas-editor__name">
+ <div class="areas-editor__name-text">
+ {{ resolveAreaLabel(item) }}
+ </div>
+ <div class="areas-editor__meta"> ID: {{ item.id }} </div>
+ </div>
+ <div class="areas-editor__sort">
+ <ElInputNumber
+ :model-value="item.sort"
+ :min="1"
+ :controls-position="'right'"
+ class="areas-editor__sort-input"
+ @update:model-value="handleSortChange(item.id, $event)"
+ />
+ </div>
+ <div class="areas-editor__actions">
+ <ElButton text size="small" :disabled="index === 0" @click="handleMoveUp(index)">
+ 涓婄Щ
+ </ElButton>
+ <ElButton
+ text
+ size="small"
+ :disabled="index === selectedAreas.length - 1"
+ @click="handleMoveDown(index)"
+ >
+ 涓嬬Щ
+ </ElButton>
+ <ElButton text size="small" type="danger" @click="handleRemove(item.id)">
+ 鍒犻櫎
+ </ElButton>
+ </div>
+ </div>
</div>
- <div class="text-xs text-[var(--art-text-secondary)]">
- ID: {{ item.id }}
- </div>
- </div>
- <div class="flex items-center gap-2">
- <ElInputNumber
- :model-value="item.sort"
- :min="1"
- :controls-position="'right'"
- class="!w-24"
- @update:model-value="handleSortChange(item.id, $event)"
- />
- <ElButton text :disabled="index === 0" @click="handleMoveUp(index)">涓婄Щ</ElButton>
- <ElButton text :disabled="index === selectedAreas.length - 1" @click="handleMoveDown(index)">
- 涓嬬Щ
- </ElButton>
- <ElButton text type="danger" @click="handleRemove(item.id)">鍒犻櫎</ElButton>
- </div>
+ </ElScrollbar>
+ </template>
+
+ <div v-else class="areas-editor__empty">
+ <div class="areas-editor__empty-title">鏆傛棤鍙叆搴撳尯</div>
+ <div class="areas-editor__empty-tip"
+ >浠庝笂鏂归�夋嫨搴撳尯鍚庣偣鍑烩�滄坊鍔犫�濓紝鍐嶆寜鎺掑簭鍊兼垨涓婁笅绉诲姩璋冩暣椤哄簭</div
+ >
</div>
</div>
</div>
@@ -70,13 +99,14 @@
const selectedAreaId = ref('')
const selectedAreas = computed(() => normalizeSelectedAreas(modelValue.value))
- const areaOptionMap = computed(() =>
- new Map(
- (Array.isArray(props.areaOptions) ? props.areaOptions : [])
- .map((item) => normalizeAreaOption(item))
- .filter(Boolean)
- .map((item) => [String(item.value), item.label])
- )
+ const areaOptionMap = computed(
+ () =>
+ new Map(
+ (Array.isArray(props.areaOptions) ? props.areaOptions : [])
+ .map((item) => normalizeAreaOption(item))
+ .filter(Boolean)
+ .map((item) => [String(item.value), item.label])
+ )
)
const availableAreaOptions = computed(() => {
const selectedIds = new Set(selectedAreas.value.map((item) => String(item.id)))
@@ -95,7 +125,9 @@
}
return {
value: Number(value),
- label: String(option.label || option.name || option.areaName || option.code || option.areaCode || value)
+ label: String(
+ option.label || option.name || option.areaName || option.code || option.areaCode || value
+ )
}
}
@@ -175,3 +207,151 @@
syncModel(selectedAreas.value.filter((item) => Number(item.id) !== Number(id)))
}
</script>
+
+<style scoped>
+ .areas-editor {
+ display: flex;
+ flex-direction: column;
+ gap: 12px;
+ width: 100%;
+ }
+
+ .areas-editor__toolbar {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ gap: 8px;
+ }
+
+ .areas-editor__select {
+ width: 320px;
+ max-width: 100%;
+ }
+
+ .areas-editor__toolbar-tip {
+ color: var(--art-text-secondary);
+ font-size: 12px;
+ line-height: 1.5;
+ }
+
+ .areas-editor__panel {
+ border: 1px solid var(--art-border-color);
+ border-radius: 12px;
+ background: var(--art-bg-color);
+ overflow: hidden;
+ }
+
+ .areas-editor__header {
+ display: grid;
+ grid-template-columns: 56px minmax(0, 1fr) 112px 190px;
+ gap: 12px;
+ align-items: center;
+ padding: 10px 16px;
+ background: var(--el-fill-color-light);
+ color: var(--art-text-secondary);
+ font-size: 12px;
+ line-height: 1;
+ }
+
+ .areas-editor__list {
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+ padding: 12px 16px;
+ }
+
+ .areas-editor__row {
+ display: grid;
+ grid-template-columns: 56px minmax(0, 1fr) 112px 190px;
+ gap: 12px;
+ align-items: center;
+ border: 1px solid var(--art-border-color);
+ border-radius: 10px;
+ padding: 10px 12px;
+ background: var(--el-bg-color-page);
+ }
+
+ .areas-editor__index {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ color: var(--art-text-secondary);
+ font-size: 13px;
+ }
+
+ .areas-editor__name {
+ min-width: 0;
+ }
+
+ .areas-editor__name-text {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ color: var(--art-text-primary);
+ font-weight: 500;
+ line-height: 1.4;
+ }
+
+ .areas-editor__meta {
+ margin-top: 2px;
+ color: var(--art-text-secondary);
+ font-size: 12px;
+ line-height: 1.4;
+ }
+
+ .areas-editor__sort {
+ display: flex;
+ justify-content: flex-start;
+ }
+
+ :deep(.areas-editor__sort-input) {
+ width: 100px;
+ }
+
+ .areas-editor__actions {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ gap: 4px;
+ }
+
+ .areas-editor__empty {
+ display: flex;
+ min-height: 136px;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ gap: 8px;
+ padding: 20px 16px;
+ text-align: center;
+ }
+
+ .areas-editor__empty-title {
+ color: var(--art-text-primary);
+ font-size: 14px;
+ font-weight: 500;
+ line-height: 1.4;
+ }
+
+ .areas-editor__empty-tip {
+ max-width: 420px;
+ color: var(--art-text-secondary);
+ font-size: 12px;
+ line-height: 1.6;
+ }
+
+ @media (max-width: 900px) {
+ .areas-editor__header {
+ display: none;
+ }
+
+ .areas-editor__row {
+ grid-template-columns: 48px minmax(0, 1fr);
+ }
+
+ .areas-editor__sort,
+ .areas-editor__actions {
+ grid-column: 2;
+ }
+ }
+</style>
diff --git a/rsf-design/src/views/basic-info/bas-station/basStationPage.helpers.js b/rsf-design/src/views/basic-info/bas-station/basStationPage.helpers.js
index 6750a27..b8a49d8 100644
--- a/rsf-design/src/views/basic-info/bas-station/basStationPage.helpers.js
+++ b/rsf-design/src/views/basic-info/bas-station/basStationPage.helpers.js
@@ -91,9 +91,14 @@
stationId: '',
type: '',
useStatus: '',
+ inAble: '',
+ outAble: '',
area: '',
isCrossZone: '',
+ crossZoneArea: '',
isWcs: '',
+ wcsData: '',
+ containerType: '',
barcode: '',
autoTransfer: '',
status: '',
@@ -122,6 +127,25 @@
outAble: 0,
status: 1,
memo: ''
+ }
+}
+
+export function createBasStationInitState() {
+ return {
+ type: 0,
+ useStatus: '',
+ areaIds: [],
+ containerTypes: [],
+ inAble: 0,
+ outAble: 0,
+ rows: [createBasStationInitRow()]
+ }
+}
+
+export function createBasStationInitRow() {
+ return {
+ stationName: '',
+ stationId: ''
}
}
@@ -203,6 +227,14 @@
? Number(params.type)
: void 0,
useStatus: normalizeText(params.useStatus),
+ inAble:
+ params.inAble !== undefined && params.inAble !== null && params.inAble !== ''
+ ? Number(params.inAble)
+ : void 0,
+ outAble:
+ params.outAble !== undefined && params.outAble !== null && params.outAble !== ''
+ ? Number(params.outAble)
+ : void 0,
area:
params.area !== undefined && params.area !== null && params.area !== ''
? Number(params.area)
@@ -211,13 +243,23 @@
params.isCrossZone !== undefined && params.isCrossZone !== null && params.isCrossZone !== ''
? Number(params.isCrossZone)
: void 0,
+ crossZoneArea: normalizeText(params.crossZoneArea),
isWcs:
params.isWcs !== undefined && params.isWcs !== null && params.isWcs !== ''
? Number(params.isWcs)
: void 0,
+ wcsData: normalizeText(params.wcsData),
+ containerType:
+ params.containerType !== undefined &&
+ params.containerType !== null &&
+ params.containerType !== ''
+ ? Number(params.containerType)
+ : void 0,
barcode: normalizeText(params.barcode),
autoTransfer:
- params.autoTransfer !== undefined && params.autoTransfer !== null && params.autoTransfer !== ''
+ params.autoTransfer !== undefined &&
+ params.autoTransfer !== null &&
+ params.autoTransfer !== ''
? Number(params.autoTransfer)
: void 0,
status:
@@ -230,7 +272,9 @@
}
return Object.fromEntries(
- Object.entries(searchParams).filter(([, value]) => value !== '' && value !== void 0 && value !== null)
+ Object.entries(searchParams).filter(
+ ([, value]) => value !== '' && value !== void 0 && value !== null
+ )
)
}
@@ -238,6 +282,7 @@
return {
current: params.current || 1,
pageSize: params.pageSize || params.size || 20,
+ orderBy: normalizeText(params.orderBy) || 'create_time desc',
...buildBasStationSearchParams(params)
}
}
@@ -256,10 +301,14 @@
? { area: Number(formData.area) }
: {}),
useStatus: normalizeText(formData.useStatus) || '',
- ...(formData.isCrossZone !== void 0 && formData.isCrossZone !== null && formData.isCrossZone !== ''
+ ...(formData.isCrossZone !== void 0 &&
+ formData.isCrossZone !== null &&
+ formData.isCrossZone !== ''
? { isCrossZone: Number(formData.isCrossZone) }
: {}),
- ...(normalizeIdArray(formData.areaIds).length ? { areaIds: normalizeIdArray(formData.areaIds).map((item) => item.id) } : {}),
+ ...(normalizeIdArray(formData.areaIds).length
+ ? { areaIds: normalizeIdArray(formData.areaIds).map((item) => item.id) }
+ : {}),
...(formData.isWcs !== void 0 && formData.isWcs !== null && formData.isWcs !== ''
? { isWcs: Number(formData.isWcs) }
: {}),
@@ -268,7 +317,9 @@
? { containerTypes: normalizePlainIdArray(formData.containerTypes) }
: {}),
barcode: normalizeText(formData.barcode) || '',
- ...(formData.autoTransfer !== void 0 && formData.autoTransfer !== null && formData.autoTransfer !== ''
+ ...(formData.autoTransfer !== void 0 &&
+ formData.autoTransfer !== null &&
+ formData.autoTransfer !== ''
? { autoTransfer: Number(formData.autoTransfer) }
: {}),
...(formData.inAble !== void 0 && formData.inAble !== null && formData.inAble !== ''
@@ -288,7 +339,9 @@
export function createBasStationDialogModel(record = {}) {
return {
...createBasStationFormState(),
- ...(record.id !== void 0 && record.id !== null && record.id !== '' ? { id: Number(record.id) } : {}),
+ ...(record.id !== void 0 && record.id !== null && record.id !== ''
+ ? { id: Number(record.id) }
+ : {}),
stationName: normalizeText(record.stationName || ''),
stationId: normalizeText(record.stationId || ''),
type:
@@ -310,13 +363,18 @@
? Number(record.isWcs)
: 0,
wcsData: normalizeText(record.wcsData || ''),
- containerTypes: normalizePlainIdArray(record.containerTypes ?? record.containerType ?? record.containerTypes$ ?? []),
+ containerTypes: normalizePlainIdArray(
+ record.containerTypes ?? record.containerType ?? record.containerTypes$ ?? []
+ ),
barcode: normalizeText(record.barcode || ''),
autoTransfer:
record.autoTransfer !== void 0 && record.autoTransfer !== null && record.autoTransfer !== ''
? Number(record.autoTransfer)
: 0,
- inAble: record.inAble !== void 0 && record.inAble !== null && record.inAble !== '' ? Number(record.inAble) : 0,
+ inAble:
+ record.inAble !== void 0 && record.inAble !== null && record.inAble !== ''
+ ? Number(record.inAble)
+ : 0,
outAble:
record.outAble !== void 0 && record.outAble !== null && record.outAble !== ''
? Number(record.outAble)
@@ -327,6 +385,74 @@
}
export const buildBasStationDialogModel = createBasStationDialogModel
+
+export function buildBasStationInitModel(record = {}) {
+ const initialRows = Array.isArray(record.rows) ? record.rows : record.pairs
+ const rows =
+ Array.isArray(initialRows) && initialRows.length
+ ? initialRows
+ .map((item) => ({
+ stationName: normalizeText(item?.stationName || ''),
+ stationId: normalizeText(item?.stationId || '')
+ }))
+ .filter((item) => item.stationName || item.stationId)
+ : [createBasStationInitRow()]
+
+ return {
+ ...createBasStationInitState(),
+ type:
+ record.type !== void 0 && record.type !== null && record.type !== ''
+ ? Number(record.type)
+ : 0,
+ useStatus: normalizeText(record.useStatus || ''),
+ areaIds: normalizePlainIdArray(record.areaIds || []),
+ containerTypes: normalizePlainIdArray(record.containerTypes || []),
+ inAble:
+ record.inAble !== void 0 && record.inAble !== null && record.inAble !== ''
+ ? Number(record.inAble)
+ : 0,
+ outAble:
+ record.outAble !== void 0 && record.outAble !== null && record.outAble !== ''
+ ? Number(record.outAble)
+ : 0,
+ rows
+ }
+}
+
+export function buildBasStationInitPayloadList(formData = {}) {
+ const useStatus = normalizeText(formData.useStatus)
+ const basePayload = {
+ ...(formData.type !== void 0 && formData.type !== null && formData.type !== ''
+ ? { type: Number(formData.type) }
+ : {}),
+ ...(useStatus ? { useStatus } : {}),
+ ...(normalizePlainIdArray(formData.areaIds).length
+ ? { areaIds: normalizePlainIdArray(formData.areaIds) }
+ : {}),
+ ...(normalizePlainIdArray(formData.containerTypes).length
+ ? { containerTypes: normalizePlainIdArray(formData.containerTypes) }
+ : {}),
+ ...(formData.inAble !== void 0 && formData.inAble !== null && formData.inAble !== ''
+ ? { inAble: Number(formData.inAble) }
+ : {}),
+ ...(formData.outAble !== void 0 && formData.outAble !== null && formData.outAble !== ''
+ ? { outAble: Number(formData.outAble) }
+ : {})
+ }
+
+ const rows = Array.isArray(formData.rows) ? formData.rows : []
+ return rows
+ .map((item) => ({
+ stationName: normalizeText(item?.stationName || ''),
+ stationId: normalizeText(item?.stationId || '')
+ }))
+ .filter((item) => item.stationName && item.stationId)
+ .map((item) => ({
+ ...basePayload,
+ stationName: item.stationName,
+ stationId: item.stationId
+ }))
+}
export function resolveBasStationAreaOptions(records = []) {
if (!Array.isArray(records)) {
@@ -344,7 +470,9 @@
}
return {
value: Number(value),
- label: normalizeText(item.name || item.areaName || item.code || item.areaCode || `搴撳尯 ${value}`)
+ label: normalizeText(
+ item.name || item.areaName || item.code || item.areaCode || `搴撳尯 ${value}`
+ )
}
})
.filter(Boolean)
@@ -403,7 +531,7 @@
if (item === null || item === undefined || item === '') {
return ''
}
- const id = typeof item === 'object' ? item.id ?? item.areaId ?? item.value : item
+ const id = typeof item === 'object' ? (item.id ?? item.areaId ?? item.value) : item
if (typeof resolveLabel === 'function') {
const resolvedLabel = normalizeText(resolveLabel(id))
if (resolvedLabel) {
@@ -411,7 +539,9 @@
}
}
if (typeof item === 'object') {
- return normalizeText(item.name || item.areaName || item.label || item.code || item.areaCode || id)
+ return normalizeText(
+ item.name || item.areaName || item.label || item.code || item.areaCode || id
+ )
}
return normalizeText(`${fallbackPrefix} ${id}`)
})
@@ -425,7 +555,7 @@
}
return records
.map((item) => {
- const id = typeof item === 'object' ? item.id ?? item.value : item
+ const id = typeof item === 'object' ? (item.id ?? item.value) : item
if (typeof resolveLabel === 'function') {
const resolved = normalizeText(resolveLabel(id))
if (resolved) {
@@ -441,55 +571,108 @@
.join('銆�')
}
-export function normalizeBasStationDetailRecord(record = {}, resolveAreaLabel, resolveContainerTypeLabel) {
+export function normalizeBasStationDetailRecord(
+ record = {},
+ resolveAreaLabel,
+ resolveContainerTypeLabel
+) {
const areaIds = normalizeIdArray(record.areaIds ?? record.crossZoneArea ?? [])
- const containerTypes = normalizePlainIdArray(record.containerTypes ?? record.containerType ?? record.containerTypes$ ?? [])
+ const containerTypes = normalizePlainIdArray(
+ record.containerTypes ?? record.containerType ?? record.containerTypes$ ?? []
+ )
const statusMeta = getBasStationStatusMeta(record.statusBool ?? record.status)
const typeMeta = getBasStationTypeMeta(record.type)
const useStatusMeta = getBasStationUseStatusMeta(record.useStatus$ || record.useStatus)
+ const crossZoneAreaItems = areaIds
+ .map((item, index) => {
+ const label = normalizeText(
+ resolveAreaLabel?.(item.id) ||
+ item.name ||
+ item.areaName ||
+ item.label ||
+ item.code ||
+ item.areaCode ||
+ ''
+ )
+ return {
+ id: item.id,
+ sort: item.sort ?? index + 1,
+ label: label || `搴撳尯 ${item.id}`
+ }
+ })
+ .filter((item) => item.id !== void 0 && item.id !== null && item.id !== '')
+ const containerTypeItems = containerTypes
+ .map((item) => {
+ const label = normalizeText(resolveContainerTypeLabel?.(item) || '')
+ return {
+ value: item,
+ label: label || `瀹瑰櫒绫诲瀷 ${item}`
+ }
+ })
+ .filter((item) => item.value !== void 0 && item.value !== null && item.value !== '')
return {
...record,
stationName: normalizeText(record.stationName || ''),
stationId: normalizeText(record.stationId || ''),
- type: record.type !== void 0 && record.type !== null && record.type !== '' ? Number(record.type) : void 0,
+ type:
+ record.type !== void 0 && record.type !== null && record.type !== ''
+ ? Number(record.type)
+ : void 0,
typeText: typeMeta.text,
useStatus: normalizeText(record.useStatus || record.useStatus$ || ''),
useStatusText: useStatusMeta.text,
- area: record.area !== void 0 && record.area !== null && record.area !== '' ? Number(record.area) : void 0,
- areaText: normalizeText(resolveAreaLabel?.(record.area) || record.area$ || record.areaName || ''),
+ area:
+ record.area !== void 0 && record.area !== null && record.area !== ''
+ ? Number(record.area)
+ : void 0,
+ areaText: normalizeText(
+ resolveAreaLabel?.(record.area) || record.area$ || record.areaName || ''
+ ),
areaIds,
+ crossZoneAreaItems,
crossZoneAreaText: buildIdLabelText(areaIds, resolveAreaLabel, '搴撳尯'),
- isCrossZone: record.isCrossZone !== void 0 && record.isCrossZone !== null && record.isCrossZone !== ''
- ? Number(record.isCrossZone)
- : void 0,
+ isCrossZone:
+ record.isCrossZone !== void 0 && record.isCrossZone !== null && record.isCrossZone !== ''
+ ? Number(record.isCrossZone)
+ : void 0,
isCrossZoneText: getBasStationBooleanMeta(record.isCrossZone).text,
- isWcs: record.isWcs !== void 0 && record.isWcs !== null && record.isWcs !== ''
- ? Number(record.isWcs)
- : void 0,
+ isWcs:
+ record.isWcs !== void 0 && record.isWcs !== null && record.isWcs !== ''
+ ? Number(record.isWcs)
+ : void 0,
isWcsText: getBasStationBooleanMeta(record.isWcs).text,
wcsData: normalizeText(record.wcsData || ''),
containerTypes,
+ containerTypeItems,
containerTypesText: buildArrayText(containerTypes, resolveContainerTypeLabel, '瀹瑰櫒绫诲瀷'),
barcode: normalizeText(record.barcode || ''),
- autoTransfer: record.autoTransfer !== void 0 && record.autoTransfer !== null && record.autoTransfer !== ''
- ? Number(record.autoTransfer)
- : void 0,
+ autoTransfer:
+ record.autoTransfer !== void 0 && record.autoTransfer !== null && record.autoTransfer !== ''
+ ? Number(record.autoTransfer)
+ : void 0,
autoTransferText: getBasStationBooleanMeta(record.autoTransfer).text,
- inAble: record.inAble !== void 0 && record.inAble !== null && record.inAble !== ''
- ? Number(record.inAble)
- : void 0,
+ inAble:
+ record.inAble !== void 0 && record.inAble !== null && record.inAble !== ''
+ ? Number(record.inAble)
+ : void 0,
inAbleText: getBasStationBooleanMeta(record.inAble).text,
- outAble: record.outAble !== void 0 && record.outAble !== null && record.outAble !== ''
- ? Number(record.outAble)
- : void 0,
+ outAble:
+ record.outAble !== void 0 && record.outAble !== null && record.outAble !== ''
+ ? Number(record.outAble)
+ : void 0,
outAbleText: getBasStationBooleanMeta(record.outAble).text,
statusText: statusMeta.text,
statusType: statusMeta.type,
statusBool: record.statusBool !== void 0 ? Boolean(record.statusBool) : statusMeta.bool,
- stationAlias: Array.isArray(record.stationAlias) ? [...record.stationAlias] : record.stationAlias,
+ stationAlias: Array.isArray(record.stationAlias)
+ ? [...record.stationAlias]
+ : record.stationAlias,
stationAliasText: Array.isArray(record.stationAlias)
- ? record.stationAlias.map((item) => normalizeText(item)).filter(Boolean).join('銆�')
+ ? record.stationAlias
+ .map((item) => normalizeText(item))
+ .filter(Boolean)
+ .join('銆�')
: normalizeText(record.stationAlias || record.stationAlias$ || ''),
productionLineCode: normalizeText(record.productionLineCode || ''),
productionLineName: normalizeText(record.productionLineName || ''),
@@ -501,15 +684,25 @@
}
}
-export function normalizeBasStationListRow(record = {}, resolveAreaLabel, resolveContainerTypeLabel) {
+export function normalizeBasStationListRow(
+ record = {},
+ resolveAreaLabel,
+ resolveContainerTypeLabel
+) {
return normalizeBasStationDetailRecord(record, resolveAreaLabel, resolveContainerTypeLabel)
}
-export function buildBasStationPrintRows(records = [], resolveAreaLabel, resolveContainerTypeLabel) {
+export function buildBasStationPrintRows(
+ records = [],
+ resolveAreaLabel,
+ resolveContainerTypeLabel
+) {
if (!Array.isArray(records)) {
return []
}
- return records.map((record) => normalizeBasStationListRow(record, resolveAreaLabel, resolveContainerTypeLabel))
+ return records.map((record) =>
+ normalizeBasStationListRow(record, resolveAreaLabel, resolveContainerTypeLabel)
+ )
}
export function buildBasStationReportMeta({
diff --git a/rsf-design/src/views/basic-info/bas-station/basStationTable.columns.js b/rsf-design/src/views/basic-info/bas-station/basStationTable.columns.js
index 6a6c46f..0697d6a 100644
--- a/rsf-design/src/views/basic-info/bas-station/basStationTable.columns.js
+++ b/rsf-design/src/views/basic-info/bas-station/basStationTable.columns.js
@@ -2,6 +2,7 @@
import { ElTag } from 'element-plus'
import ArtButtonMore from '@/components/core/forms/art-button-more/index.vue'
import { $t } from '@/locales'
+import BasStationTagCell from './modules/bas-station-tag-cell.vue'
import {
getBasStationUseStatusMeta,
getBasStationStatusMeta,
@@ -10,6 +11,7 @@
export function createBasStationTableColumns({
handleView,
+ handleCopy,
handleEdit,
handleDelete,
canEdit = true,
@@ -17,12 +19,21 @@
} = {}) {
const operations = [{ key: 'view', label: $t('common.actions.detail'), icon: 'ri:eye-line' }]
+ if (handleCopy) {
+ operations.push({ key: 'copy', label: '澶嶅埗鍒濆鍖�', icon: 'ri:file-copy-line' })
+ }
+
if (canEdit && handleEdit) {
operations.push({ key: 'edit', label: $t('common.actions.edit'), icon: 'ri:pencil-line' })
}
if (canDelete && handleDelete) {
- operations.push({ key: 'delete', label: $t('common.actions.delete'), icon: 'ri:delete-bin-5-line', color: 'var(--art-error)' })
+ operations.push({
+ key: 'delete',
+ label: $t('common.actions.delete'),
+ icon: 'ri:delete-bin-5-line',
+ color: 'var(--art-error)'
+ })
}
return [
@@ -70,18 +81,24 @@
formatter: (row) => row.areaText || row.area$ || '--'
},
{
- prop: 'crossZoneAreaText',
+ prop: 'crossZoneAreaItems',
label: $t('pages.basicInfo.basStation.table.crossZoneArea'),
minWidth: 220,
- showOverflowTooltip: true,
- formatter: (row) => row.crossZoneAreaText || '--'
+ formatter: (row) =>
+ h(BasStationTagCell, {
+ items: row.crossZoneAreaItems || [],
+ title: '鍙法鍖哄簱鍖�'
+ })
},
{
- prop: 'containerTypesText',
+ prop: 'containerTypeItems',
label: $t('pages.basicInfo.basStation.table.containerTypes'),
minWidth: 200,
- showOverflowTooltip: true,
- formatter: (row) => row.containerTypesText || '--'
+ formatter: (row) =>
+ h(BasStationTagCell, {
+ items: row.containerTypeItems || [],
+ title: '鍙叆瀹瑰櫒绫诲瀷'
+ })
},
{
prop: 'barcode',
@@ -150,6 +167,20 @@
formatter: (row) => row.updateTimeText || row.updateTime$ || '--'
},
{
+ prop: 'createByText',
+ label: $t('table.createBy'),
+ minWidth: 120,
+ showOverflowTooltip: true,
+ formatter: (row) => row.createByText || row.createBy$ || '--'
+ },
+ {
+ prop: 'createTimeText',
+ label: $t('table.createTime'),
+ minWidth: 170,
+ showOverflowTooltip: true,
+ formatter: (row) => row.createTimeText || row.createTime$ || '--'
+ },
+ {
prop: 'memo',
label: $t('table.remark'),
minWidth: 180,
@@ -167,6 +198,7 @@
list: operations,
onClick: (item) => {
if (item.key === 'view') handleView?.(row)
+ if (item.key === 'copy') handleCopy?.(row)
if (item.key === 'edit') handleEdit?.(row)
if (item.key === 'delete') handleDelete?.(row)
}
diff --git a/rsf-design/src/views/basic-info/bas-station/index.vue b/rsf-design/src/views/basic-info/bas-station/index.vue
index a5dd00a..4995c25 100644
--- a/rsf-design/src/views/basic-info/bas-station/index.vue
+++ b/rsf-design/src/views/basic-info/bas-station/index.vue
@@ -13,6 +13,7 @@
<template #left>
<ElSpace wrap>
<ElButton v-auth="'add'" @click="showDialog('add')" v-ripple>鏂板绔欑偣</ElButton>
+ <ElButton v-auth="'add'" @click="openInitDialog()" v-ripple>绔欑偣鍒濆鍖�</ElButton>
<ElButton
v-auth="'delete'"
type="danger"
@@ -68,6 +69,15 @@
:resolve-area-label="resolveAreaLabel"
:resolve-container-type-label="resolveContainerTypeLabel"
/>
+
+ <BasStationInitDialog
+ v-model:visible="initDialogVisible"
+ :initial-data="currentInitData"
+ :area-options="areaOptions"
+ :container-type-options="containerTypeOptions"
+ :use-status-options="useStatusOptions"
+ @submit="handleInitSubmit"
+ />
</ElCard>
</div>
</template>
@@ -96,11 +106,14 @@
} from '@/api/bas-station'
import BasStationDialog from './modules/bas-station-dialog.vue'
import BasStationDetailDrawer from './modules/bas-station-detail-drawer.vue'
+ import BasStationInitDialog from './modules/bas-station-init-dialog.vue'
import { createBasStationTableColumns } from './basStationTable.columns'
import {
BAS_STATION_REPORT_STYLE,
BAS_STATION_REPORT_TITLE,
buildBasStationDialogModel,
+ buildBasStationInitModel,
+ buildBasStationInitPayloadList,
buildBasStationPageQueryParams,
buildBasStationPrintRows,
buildBasStationReportMeta,
@@ -129,6 +142,8 @@
const detailDrawerVisible = ref(false)
const detailLoading = ref(false)
const detailData = ref({})
+ const initDialogVisible = ref(false)
+ const currentInitData = ref(buildBasStationInitModel())
let handleDeleteAction = null
const reportTitle = BAS_STATION_REPORT_TITLE
@@ -207,6 +222,24 @@
}
},
{
+ label: '鍙叆',
+ key: 'inAble',
+ type: 'select',
+ props: {
+ clearable: true,
+ options: getBasStationBooleanOptions()
+ }
+ },
+ {
+ label: '鍙嚭',
+ key: 'outAble',
+ type: 'select',
+ props: {
+ clearable: true,
+ options: getBasStationBooleanOptions()
+ }
+ },
+ {
label: '鎵�灞炲簱鍖�',
key: 'area',
type: 'select',
@@ -226,12 +259,40 @@
}
},
{
+ label: '鍙法鍖哄簱鍖�',
+ key: 'crossZoneArea',
+ type: 'input',
+ props: {
+ clearable: true,
+ placeholder: '璇疯緭鍏ュ彲璺ㄥ尯搴撳尯'
+ }
+ },
+ {
label: '鏄惁WCS',
key: 'isWcs',
type: 'select',
props: {
clearable: true,
options: getBasStationBooleanOptions()
+ }
+ },
+ {
+ label: 'WCS鏁版嵁',
+ key: 'wcsData',
+ type: 'input',
+ props: {
+ clearable: true,
+ placeholder: '璇疯緭鍏CS鏁版嵁'
+ }
+ },
+ {
+ label: '瀹瑰櫒绫诲瀷',
+ key: 'containerType',
+ type: 'select',
+ props: {
+ clearable: true,
+ filterable: true,
+ options: containerTypeOptions.value
}
},
{
@@ -301,10 +362,18 @@
detailDrawerVisible.value = true
detailLoading.value = true
try {
- const detail = await guardRequestWithMessage(fetchGetBasStationDetail(row.id), {}, {
- timeoutMessage: '绔欑偣璇︽儏鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟'
- })
- detailData.value = normalizeBasStationDetailRecord(detail, resolveAreaLabel, resolveContainerTypeLabel)
+ const detail = await guardRequestWithMessage(
+ fetchGetBasStationDetail(row.id),
+ {},
+ {
+ timeoutMessage: '绔欑偣璇︽儏鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟'
+ }
+ )
+ detailData.value = normalizeBasStationDetailRecord(
+ detail,
+ resolveAreaLabel,
+ resolveContainerTypeLabel
+ )
} catch (error) {
detailDrawerVisible.value = false
detailData.value = {}
@@ -316,39 +385,86 @@
async function openEditDialog(row) {
try {
- const detail = await guardRequestWithMessage(fetchGetBasStationDetail(row.id), {}, {
- timeoutMessage: '绔欑偣璇︽儏鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟'
- })
+ const detail = await guardRequestWithMessage(
+ fetchGetBasStationDetail(row.id),
+ {},
+ {
+ timeoutMessage: '绔欑偣璇︽儏鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟'
+ }
+ )
showDialog('edit', detail)
} catch (error) {
ElMessage.error(error?.message || '鑾峰彇绔欑偣璇︽儏澶辫触')
}
}
- const { columns, columnChecks, data, loading, pagination, getData, replaceSearchParams, resetSearchParams, handleSizeChange, handleCurrentChange, refreshData, refreshCreate, refreshUpdate, refreshRemove } =
- useTable({
- core: {
- apiFn: fetchBasStationPage,
- apiParams: buildBasStationPageQueryParams(searchForm.value),
- paginationKey: getBasStationPaginationKey(),
- columnsFactory: () =>
- createBasStationTableColumns({
- handleView: openDetail,
- handleEdit: hasAuth('update') ? openEditDialog : null,
- handleDelete: hasAuth('delete') ? (row) => handleDeleteAction?.(row) : null,
- canEdit: hasAuth('update'),
- canDelete: hasAuth('delete')
- })
- },
- transform: {
- dataTransformer: (records) => {
- if (!Array.isArray(records)) {
- return []
- }
- return records.map((item) => normalizeBasStationListRow(item, resolveAreaLabel, resolveContainerTypeLabel))
+ function createInitRecordFromRow(row) {
+ return buildBasStationInitModel({
+ type: row.type ?? 0,
+ useStatus: row.useStatus || row.useStatusText || '',
+ areaIds: Array.isArray(row.areaIds)
+ ? row.areaIds.map((item) =>
+ typeof item === 'object' ? (item.id ?? item.areaId ?? item.value) : item
+ )
+ : [],
+ containerTypes: Array.isArray(row.containerTypes) ? [...row.containerTypes] : [],
+ inAble: row.inAble ?? 0,
+ outAble: row.outAble ?? 0,
+ rows: [
+ {
+ stationName: row.stationName || '',
+ stationId: row.stationId || ''
}
- }
+ ]
})
+ }
+
+ function openInitDialog(record = null) {
+ currentInitData.value = record ? createInitRecordFromRow(record) : buildBasStationInitModel()
+ initDialogVisible.value = true
+ }
+
+ const {
+ columns,
+ columnChecks,
+ data,
+ loading,
+ pagination,
+ getData,
+ replaceSearchParams,
+ resetSearchParams,
+ handleSizeChange,
+ handleCurrentChange,
+ refreshData,
+ refreshCreate,
+ refreshUpdate,
+ refreshRemove
+ } = useTable({
+ core: {
+ apiFn: fetchBasStationPage,
+ apiParams: buildBasStationPageQueryParams(searchForm.value),
+ paginationKey: getBasStationPaginationKey(),
+ columnsFactory: () =>
+ createBasStationTableColumns({
+ handleView: openDetail,
+ handleCopy: hasAuth('add') ? openInitDialog : null,
+ handleEdit: hasAuth('update') ? openEditDialog : null,
+ handleDelete: hasAuth('delete') ? (row) => handleDeleteAction?.(row) : null,
+ canEdit: hasAuth('update'),
+ canDelete: hasAuth('delete')
+ })
+ },
+ transform: {
+ dataTransformer: (records) => {
+ if (!Array.isArray(records)) {
+ return []
+ }
+ return records.map((item) =>
+ normalizeBasStationListRow(item, resolveAreaLabel, resolveContainerTypeLabel)
+ )
+ }
+ }
+ })
const {
dialogVisible,
@@ -394,30 +510,39 @@
await fetchBasStationPage({
...reportQueryParams.value,
current: 1,
- pageSize: Number(pagination.total) > 0 ? Number(pagination.total) : Number(payload?.pageSize) || 20
+ pageSize:
+ Number(pagination.total) > 0 ? Number(pagination.total) : Number(payload?.pageSize) || 20
})
).records
}
- const { previewVisible, previewRows, previewMeta, handlePreviewVisibleChange, handleExport, handlePrint } =
- usePrintExportPage({
- downloadFileName: 'bas-station.xlsx',
- requestExport: (payload) =>
- fetchExportBasStationReport(payload, {
- headers: {
- Authorization: userStore.accessToken || ''
- }
- }),
- resolvePrintRecords,
- buildPreviewRows: (records) => buildBasStationPrintRows(records, resolveAreaLabel, resolveContainerTypeLabel),
- buildPreviewMeta
- })
+ const {
+ previewVisible,
+ previewRows,
+ previewMeta,
+ handlePreviewVisibleChange,
+ handleExport,
+ handlePrint
+ } = usePrintExportPage({
+ downloadFileName: 'bas-station.xlsx',
+ requestExport: (payload) =>
+ fetchExportBasStationReport(payload, {
+ headers: {
+ Authorization: userStore.accessToken || ''
+ }
+ }),
+ resolvePrintRecords,
+ buildPreviewRows: (records) =>
+ buildBasStationPrintRows(records, resolveAreaLabel, resolveContainerTypeLabel),
+ buildPreviewMeta
+ })
const resolvedPreviewMeta = computed(() =>
buildBasStationReportMeta({
previewMeta: previewMeta.value,
count: previewRows.value.length,
- orientation: previewMeta.value?.reportStyle?.orientation || BAS_STATION_REPORT_STYLE.orientation
+ orientation:
+ previewMeta.value?.reportStyle?.orientation || BAS_STATION_REPORT_STYLE.orientation
})
)
@@ -429,6 +554,47 @@
function handleReset() {
Object.assign(searchForm.value, createBasStationSearchState())
resetSearchParams()
+ }
+
+ async function handleInitSubmit(formData) {
+ const payloads = buildBasStationInitPayloadList(formData)
+ if (!payloads.length) {
+ ElMessage.error('璇疯嚦灏戝~鍐欎竴缁勭珯鐐圭紪鐮佸拰绔欑偣鍚嶇О')
+ return
+ }
+
+ let successCount = 0
+ let failCount = 0
+ let firstErrorMessage = ''
+
+ for (const payload of payloads) {
+ try {
+ await fetchSaveBasStation(payload)
+ successCount += 1
+ } catch (error) {
+ failCount += 1
+ if (!firstErrorMessage) {
+ firstErrorMessage = error?.message || `${payload.stationName} 鍒濆鍖栧け璐
+ }
+ }
+ }
+
+ if (successCount > 0) {
+ ElMessage.success(
+ failCount > 0
+ ? `鎴愬姛淇濆瓨 ${successCount} 鏉★紝澶辫触 ${failCount} 鏉
+ : `鎴愬姛淇濆瓨 ${successCount} 鏉
+ )
+ initDialogVisible.value = false
+ currentInitData.value = buildBasStationInitModel()
+ await refreshCreate()
+ if (failCount > 0 && firstErrorMessage) {
+ ElMessage.warning(firstErrorMessage)
+ }
+ return
+ }
+
+ ElMessage.error(firstErrorMessage || '绔欑偣鍒濆鍖栧け璐�')
}
async function loadAreaOptions() {
@@ -449,7 +615,9 @@
{ records: [] },
{ timeoutMessage: '瀹瑰櫒绫诲瀷鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟' }
)
- containerTypeOptions.value = buildBasStationContainerTypeOptions(defaultResponseAdapter(response).records)
+ containerTypeOptions.value = buildBasStationContainerTypeOptions(
+ defaultResponseAdapter(response).records
+ )
}
async function loadUseStatusOptions() {
@@ -463,7 +631,9 @@
{ records: [] },
{ timeoutMessage: '浣跨敤鐘舵�佸姞杞借秴鏃讹紝宸插仠姝㈢瓑寰�' }
)
- useStatusOptions.value = buildBasStationUseStatusOptions(defaultResponseAdapter(response).records)
+ useStatusOptions.value = buildBasStationUseStatusOptions(
+ defaultResponseAdapter(response).records
+ )
}
onMounted(async () => {
diff --git a/rsf-design/src/views/basic-info/bas-station/modules/bas-station-init-dialog.vue b/rsf-design/src/views/basic-info/bas-station/modules/bas-station-init-dialog.vue
new file mode 100644
index 0000000..064fe0b
--- /dev/null
+++ b/rsf-design/src/views/basic-info/bas-station/modules/bas-station-init-dialog.vue
@@ -0,0 +1,239 @@
+<template>
+ <ElDialog
+ :title="dialogTitle"
+ :model-value="visible"
+ width="1080px"
+ align-center
+ destroy-on-close
+ @update:model-value="handleCancel"
+ @closed="handleClosed"
+ >
+ <ArtForm
+ ref="formRef"
+ v-model="form"
+ :items="formItems"
+ :rules="rules"
+ :span="12"
+ :gutter="20"
+ label-width="110px"
+ :show-reset="false"
+ :show-submit="false"
+ />
+
+ <div class="mt-4">
+ <div class="mb-3 flex items-center justify-between">
+ <div class="text-sm font-medium text-[var(--art-gray-900)]">绔欑偣琛屽垪琛�</div>
+ <ElButton size="small" @click="addRow">鏂板涓�琛�</ElButton>
+ </div>
+ <ElTable :data="form.rows" border>
+ <ElTableColumn label="绔欑偣缂栫爜" min-width="240">
+ <template #default="{ row }">
+ <ElInput v-model="row.stationName" clearable placeholder="璇疯緭鍏ョ珯鐐圭紪鐮�" />
+ </template>
+ </ElTableColumn>
+ <ElTableColumn label="绔欑偣鍚嶇О" min-width="240">
+ <template #default="{ row }">
+ <ElInput v-model="row.stationId" clearable placeholder="璇疯緭鍏ョ珯鐐瑰悕绉�" />
+ </template>
+ </ElTableColumn>
+ <ElTableColumn label="鎿嶄綔" width="100" align="center">
+ <template #default="{ $index }">
+ <ElButton
+ link
+ type="danger"
+ :disabled="form.rows.length <= 1"
+ @click="removeRow($index)"
+ >
+ 鍒犻櫎
+ </ElButton>
+ </template>
+ </ElTableColumn>
+ </ElTable>
+ <div class="mt-2 text-xs text-[var(--art-gray-500)]">
+ 姣忚琛ㄧず涓�缁勭珯鐐圭紪鐮佸拰绔欑偣鍚嶇О锛屾彁浜ゆ椂浼氭寜涓婃柟鍏叡閰嶇疆鎵归噺鍒濆鍖栥��
+ </div>
+ </div>
+
+ <template #footer>
+ <span class="dialog-footer">
+ <ElButton @click="handleCancel">鍙栨秷</ElButton>
+ <ElButton type="primary" @click="handleSubmit">纭畾</ElButton>
+ </span>
+ </template>
+ </ElDialog>
+</template>
+
+<script setup>
+ import { computed, nextTick, reactive, ref, watch } from 'vue'
+ import { ElMessage } from 'element-plus'
+ import ArtForm from '@/components/core/forms/art-form/index.vue'
+ import {
+ buildBasStationInitModel,
+ createBasStationInitRow,
+ createBasStationInitState,
+ getBasStationBooleanOptions,
+ getBasStationTypeOptions
+ } from '../basStationPage.helpers'
+
+ const props = defineProps({
+ visible: { type: Boolean, default: false },
+ initialData: { type: Object, default: () => ({}) },
+ areaOptions: { type: Array, default: () => [] },
+ containerTypeOptions: { type: Array, default: () => [] },
+ useStatusOptions: { type: Array, default: () => [] }
+ })
+
+ const emit = defineEmits(['update:visible', 'submit'])
+ const formRef = ref()
+ const form = reactive(createBasStationInitState())
+
+ const dialogTitle = computed(() => '绔欑偣鍒濆鍖�')
+
+ const rules = computed(() => ({
+ type: [{ required: true, message: '璇烽�夋嫨绔欑偣绫诲瀷', trigger: 'change' }],
+ areaIds: [{ type: 'array', required: true, message: '璇烽�夋嫨鍙法鍖哄簱鍖�', trigger: 'change' }],
+ containerTypes: [
+ { type: 'array', required: true, message: '璇烽�夋嫨鍙叆瀹瑰櫒绫诲瀷', trigger: 'change' }
+ ]
+ }))
+
+ const formItems = computed(() => [
+ {
+ label: '绔欑偣绫诲瀷',
+ key: 'type',
+ type: 'select',
+ props: {
+ placeholder: '璇烽�夋嫨绔欑偣绫诲瀷',
+ clearable: true,
+ options: getBasStationTypeOptions()
+ }
+ },
+ {
+ label: '浣跨敤鐘舵��',
+ key: 'useStatus',
+ type: 'select',
+ props: {
+ placeholder: '璇烽�夋嫨浣跨敤鐘舵��',
+ clearable: true,
+ filterable: true,
+ options: props.useStatusOptions || []
+ }
+ },
+ {
+ label: '鍙法鍖哄簱鍖�',
+ key: 'areaIds',
+ type: 'select',
+ span: 24,
+ props: {
+ placeholder: '璇烽�夋嫨鍙法鍖哄簱鍖�',
+ clearable: true,
+ multiple: true,
+ collapseTags: true,
+ filterable: true,
+ options: props.areaOptions || []
+ }
+ },
+ {
+ label: '鍙叆瀹瑰櫒绫诲瀷',
+ key: 'containerTypes',
+ type: 'select',
+ span: 24,
+ props: {
+ placeholder: '璇烽�夋嫨鍙叆瀹瑰櫒绫诲瀷',
+ clearable: true,
+ multiple: true,
+ collapseTags: true,
+ filterable: true,
+ options: props.containerTypeOptions || []
+ }
+ },
+ {
+ label: '鍙叆',
+ key: 'inAble',
+ type: 'select',
+ props: {
+ placeholder: '璇烽�夋嫨鍙叆',
+ clearable: true,
+ options: getBasStationBooleanOptions()
+ }
+ },
+ {
+ label: '鍙嚭',
+ key: 'outAble',
+ type: 'select',
+ props: {
+ placeholder: '璇烽�夋嫨鍙嚭',
+ clearable: true,
+ options: getBasStationBooleanOptions()
+ }
+ }
+ ])
+
+ function loadFormData() {
+ Object.assign(form, buildBasStationInitModel(props.initialData))
+ }
+
+ function resetForm() {
+ Object.assign(form, createBasStationInitState())
+ form.rows = [createBasStationInitRow()]
+ formRef.value?.clearValidate?.()
+ }
+
+ function addRow() {
+ form.rows.push(createBasStationInitRow())
+ }
+
+ function removeRow(index) {
+ if (form.rows.length <= 1) {
+ return
+ }
+ form.rows.splice(index, 1)
+ }
+
+ async function handleSubmit() {
+ if (!formRef.value) return
+ try {
+ await formRef.value.validate()
+ const validRows = (Array.isArray(form.rows) ? form.rows : []).filter(
+ (item) => String(item?.stationName || '').trim() && String(item?.stationId || '').trim()
+ )
+ if (!validRows.length) {
+ ElMessage.error('璇疯嚦灏戝~鍐欎竴缁勭珯鐐圭紪鐮佸拰绔欑偣鍚嶇О')
+ return
+ }
+ emit('submit', { ...form, rows: validRows })
+ } catch {
+ return
+ }
+ }
+
+ function handleCancel() {
+ emit('update:visible', false)
+ }
+
+ function handleClosed() {
+ resetForm()
+ }
+
+ watch(
+ () => props.visible,
+ async (visible) => {
+ if (visible) {
+ loadFormData()
+ await nextTick()
+ formRef.value?.clearValidate?.()
+ }
+ },
+ { immediate: true }
+ )
+
+ watch(
+ () => props.initialData,
+ () => {
+ if (props.visible) {
+ loadFormData()
+ }
+ },
+ { deep: true }
+ )
+</script>
diff --git a/rsf-design/src/views/basic-info/bas-station/modules/bas-station-tag-cell.vue b/rsf-design/src/views/basic-info/bas-station/modules/bas-station-tag-cell.vue
new file mode 100644
index 0000000..6efb4ac
--- /dev/null
+++ b/rsf-design/src/views/basic-info/bas-station/modules/bas-station-tag-cell.vue
@@ -0,0 +1,71 @@
+<template>
+ <div v-if="displayItems.length" class="tag-cell" @click="dialogVisible = true">
+ <ElTag v-for="item in previewItems" :key="item.key" effect="plain" size="small">
+ {{ item.label }}
+ </ElTag>
+ <ElTag v-if="hiddenCount > 0" effect="plain" size="small"> +{{ hiddenCount }} </ElTag>
+ </div>
+ <span v-else>{{ emptyText }}</span>
+
+ <ElDialog v-model="dialogVisible" :title="title" width="720px" append-to-body destroy-on-close>
+ <div v-if="displayItems.length" class="tag-cell__dialog-list">
+ <ElTag v-for="item in displayItems" :key="item.key" effect="plain">
+ {{ item.label }}
+ </ElTag>
+ </div>
+ <ElEmpty v-else :description="emptyText" :image-size="88" />
+ </ElDialog>
+</template>
+
+<script setup>
+ import { computed, ref } from 'vue'
+
+ const props = defineProps({
+ items: { type: Array, default: () => [] },
+ title: { type: String, default: '' },
+ emptyText: { type: String, default: '--' },
+ previewCount: { type: Number, default: 1 }
+ })
+
+ const dialogVisible = ref(false)
+
+ const displayItems = computed(() =>
+ (Array.isArray(props.items) ? props.items : [])
+ .map((item, index) => {
+ if (!item || typeof item !== 'object') {
+ return null
+ }
+ const rawLabel = item.label ?? item.name ?? item.text ?? item.value ?? item.id
+ const label = String(rawLabel ?? '').trim()
+ if (!label) {
+ return null
+ }
+ return {
+ key: item.key ?? item.id ?? item.value ?? `${label}-${index}`,
+ label
+ }
+ })
+ .filter(Boolean)
+ )
+
+ const previewItems = computed(() => displayItems.value.slice(0, props.previewCount))
+ const hiddenCount = computed(() =>
+ Math.max(displayItems.value.length - previewItems.value.length, 0)
+ )
+</script>
+
+<style scoped>
+ .tag-cell {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ gap: 6px;
+ cursor: pointer;
+ }
+
+ .tag-cell__dialog-list {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 8px;
+ }
+</style>
diff --git a/rsf-design/src/views/orders/asn-order-log/asnOrderLogPage.helpers.js b/rsf-design/src/views/orders/asn-order-log/asnOrderLogPage.helpers.js
index 3c8a7d5..81eed70 100644
--- a/rsf-design/src/views/orders/asn-order-log/asnOrderLogPage.helpers.js
+++ b/rsf-design/src/views/orders/asn-order-log/asnOrderLogPage.helpers.js
@@ -165,6 +165,13 @@
wkTypeText: normalizeTagText(record['wkType$'] || record.wkTypeText || record.wkType, {}),
anfme: record.anfme ?? '--',
qty: record.qty ?? '--',
+ purchaseOrgName: normalizeText(record.purchaseOrgName) || '--',
+ purchaseUserName: normalizeText(record.purchaseUserName) || '--',
+ purchaseDateText:
+ normalizeDateText(record.purchaseDate || record.businessTime || record['purchaseDate$']) ||
+ '--',
+ supplierId: normalizeText(record.supplierId) || '--',
+ supplierName: normalizeText(record.supplierName || record['supplierId$']) || '--',
logisNo: normalizeText(record.logisNo) || '--',
arrTime: record.arrTime ?? null,
arrTimeText: normalizeDateText(record['arrTime$'] || record.arrTime) || '--',
@@ -175,7 +182,10 @@
ntyStatusText: normalizeTagText(record['ntyStatus$'] || ntyStatusMeta.text, NTY_STATUS_META),
ntyStatusTagType: ntyStatusMeta.type,
exceStatus: record.exceStatus ?? '--',
- exceStatusText: normalizeTagText(record['exceStatus$'] || record.exceStatusText || record.exceStatus, {}),
+ exceStatusText: normalizeTagText(
+ record['exceStatus$'] || record.exceStatusText || record.exceStatus,
+ {}
+ ),
status: record.status ?? '--',
statusText: normalizeTagText(record['status$'] || statusMeta.text, STATUS_META),
statusType: statusMeta.type,
diff --git a/rsf-design/src/views/orders/asn-order-log/asnOrderLogTable.columns.js b/rsf-design/src/views/orders/asn-order-log/asnOrderLogTable.columns.js
index 9ecb695..c4666c7 100644
--- a/rsf-design/src/views/orders/asn-order-log/asnOrderLogTable.columns.js
+++ b/rsf-design/src/views/orders/asn-order-log/asnOrderLogTable.columns.js
@@ -69,6 +69,41 @@
formatter: (row) => row.qty ?? '--'
},
{
+ prop: 'purchaseOrgName',
+ label: '閲囪喘缁勭粐',
+ minWidth: 140,
+ showOverflowTooltip: true,
+ formatter: (row) => row.purchaseOrgName || '--'
+ },
+ {
+ prop: 'purchaseUserName',
+ label: '閲囪喘鍛�',
+ minWidth: 120,
+ showOverflowTooltip: true,
+ formatter: (row) => row.purchaseUserName || '--'
+ },
+ {
+ prop: 'purchaseDateText',
+ label: '閲囪喘鏃ユ湡',
+ minWidth: 160,
+ showOverflowTooltip: true,
+ formatter: (row) => row.purchaseDateText || '--'
+ },
+ {
+ prop: 'supplierId',
+ label: '渚涘簲鍟嗙紪鐮�',
+ minWidth: 140,
+ showOverflowTooltip: true,
+ formatter: (row) => row.supplierId || '--'
+ },
+ {
+ prop: 'supplierName',
+ label: '渚涘簲鍟�',
+ minWidth: 160,
+ showOverflowTooltip: true,
+ formatter: (row) => row.supplierName || '--'
+ },
+ {
prop: 'logisNo',
label: $t('pages.orders.asnOrderLog.table.logisNo'),
minWidth: 160,
@@ -125,6 +160,20 @@
formatter: (row) => row.updateTimeText || '--'
},
{
+ prop: 'createByText',
+ label: $t('table.createBy'),
+ minWidth: 120,
+ showOverflowTooltip: true,
+ formatter: (row) => row.createByText || '--'
+ },
+ {
+ prop: 'createTimeText',
+ label: $t('table.createTime'),
+ minWidth: 170,
+ showOverflowTooltip: true,
+ formatter: (row) => row.createTimeText || '--'
+ },
+ {
prop: 'memo',
label: $t('table.memo'),
minWidth: 180,
diff --git a/rsf-design/src/views/orders/asn-order-log/index.vue b/rsf-design/src/views/orders/asn-order-log/index.vue
index d1fc896..db2a6be 100644
--- a/rsf-design/src/views/orders/asn-order-log/index.vue
+++ b/rsf-design/src/views/orders/asn-order-log/index.vue
@@ -44,20 +44,14 @@
<AsnOrderLogDetailDrawer
v-model:visible="detailDrawerVisible"
:loading="detailLoading"
- :items-loading="detailItemsLoading"
:detail="detailData"
- :item-rows="detailItemRows"
- :item-columns="detailItemColumns"
- :pagination="detailPagination"
- @refresh="loadDetailResources"
- @size-change="handleDetailSizeChange"
- @current-change="handleDetailCurrentChange"
+ :log-id="activeLogId"
/>
</div>
</template>
<script setup>
- import { computed, onMounted, reactive, ref } from 'vue'
+ import { computed, onMounted, ref } from 'vue'
import { ElMessage } from 'element-plus'
import { useUserStore } from '@/store/modules/user'
import { useTableColumns } from '@/hooks/core/useTableColumns'
@@ -68,7 +62,6 @@
import { fetchDictDataPage } from '@/api/system-manage'
import {
DEFAULT_ASN_ORDER_LOG_PAGE_SIZE,
- buildAsnOrderLogDetailQueryParams,
buildAsnOrderLogPageQueryParams,
buildAsnOrderLogPrintRows,
buildAsnOrderLogReportMeta,
@@ -77,24 +70,19 @@
getAsnOrderLogNtyStatusOptions,
getAsnOrderLogRleStatusOptions,
getAsnOrderLogStatusOptions,
- normalizeAsnOrderItemLogRow,
normalizeAsnOrderLogRow,
resolveDictOptions,
ASN_ORDER_LOG_REPORT_STYLE,
ASN_ORDER_LOG_REPORT_TITLE
} from './asnOrderLogPage.helpers'
import {
- fetchAsnOrderItemLogPage,
fetchAsnOrderLogPage,
fetchExportAsnOrderLogReport,
fetchGetAsnOrderLogDetail,
fetchGetAsnOrderLogMany
} from '@/api/asn-order-log'
import AsnOrderLogDetailDrawer from './modules/asn-order-log-detail-drawer.vue'
- import {
- createAsnOrderItemLogColumns,
- createAsnOrderLogTableColumns
- } from './asnOrderLogTable.columns'
+ import { createAsnOrderLogTableColumns } from './asnOrderLogTable.columns'
defineOptions({ name: 'AsnOrderLog' })
@@ -106,24 +94,15 @@
const loading = ref(false)
const detailDrawerVisible = ref(false)
const detailLoading = ref(false)
- const detailItemsLoading = ref(false)
const detailData = ref({})
- const detailItemRows = ref([])
const activeLogId = ref(null)
const typeOptions = ref([])
const wkTypeOptions = ref([])
const exceStatusOptions = ref([])
- const detailItemColumns = createAsnOrderItemLogColumns()
const pageSize = ref(DEFAULT_ASN_ORDER_LOG_PAGE_SIZE)
const cursorHistory = ref([null])
const nextCursor = ref(null)
const hasNext = ref(false)
-
- const detailPagination = reactive({
- current: 1,
- size: 20,
- total: 0
- })
const reportQueryParams = computed(() => buildAsnOrderLogSearchParams(searchForm.value))
const mainPaginationOptions = {
@@ -132,7 +111,8 @@
}
const pagination = computed(() => {
const current = Math.max(1, cursorHistory.value.length || 1)
- const size = Number(pageSize.value) > 0 ? Number(pageSize.value) : DEFAULT_ASN_ORDER_LOG_PAGE_SIZE
+ const size =
+ Number(pageSize.value) > 0 ? Number(pageSize.value) : DEFAULT_ASN_ORDER_LOG_PAGE_SIZE
const recordCount = Math.max(0, Number(data.value.length) || 0)
const total = hasNext.value ? current * size + 1 : (current - 1) * size + recordCount
@@ -266,8 +246,7 @@
async function loadMainList({ history = cursorHistory.value } = {}) {
loading.value = true
try {
- const normalizedHistory =
- Array.isArray(history) && history.length > 0 ? [...history] : [null]
+ const normalizedHistory = Array.isArray(history) && history.length > 0 ? [...history] : [null]
const response = await guardRequestWithMessage(
fetchAsnOrderLogPage(
buildAsnOrderLogPageQueryParams({
@@ -303,11 +282,11 @@
}
}
- function openDetail(row) {
+ async function openDetail(row) {
activeLogId.value = row.id
- detailPagination.current = 1
+ detailData.value = normalizeAsnOrderLogRow(row || {})
detailDrawerVisible.value = true
- loadDetailResources()
+ await loadDetailResources()
}
async function loadDetailResources() {
@@ -316,47 +295,22 @@
}
detailLoading.value = true
- detailItemsLoading.value = true
try {
- const [detailResponse, itemResponse] = await Promise.all([
- guardRequestWithMessage(
- fetchGetAsnOrderLogDetail(activeLogId.value),
- {},
- {
- timeoutMessage: '鍘嗗彶閫氱煡鍗曡鎯呭姞杞借秴鏃讹紝宸插仠姝㈢瓑寰�'
- }
- ),
- guardRequestWithMessage(
- fetchAsnOrderItemLogPage(
- buildAsnOrderLogDetailQueryParams({
- logId: activeLogId.value,
- current: detailPagination.current,
- pageSize: detailPagination.size
- })
- ),
- { records: [], total: 0, current: detailPagination.current, size: detailPagination.size },
- {
- timeoutMessage: '鍘嗗彶閫氱煡鍗曟槑缁嗗姞杞借秴鏃讹紝宸插仠姝㈢瓑寰�'
- }
- )
- ])
+ const detailResponse = await guardRequestWithMessage(
+ fetchGetAsnOrderLogDetail(activeLogId.value),
+ {},
+ {
+ timeoutMessage: '鍘嗗彶閫氱煡鍗曡鎯呭姞杞借秴鏃讹紝宸插仠姝㈢瓑寰�'
+ }
+ )
detailData.value = normalizeAsnOrderLogRow(detailResponse || {})
- const itemResult = defaultResponseAdapter(itemResponse)
- detailItemRows.value = Array.isArray(itemResult.records)
- ? itemResult.records.map((item) => normalizeAsnOrderItemLogRow(item))
- : []
- detailPagination.total = Number(itemResult?.total || 0)
- detailPagination.current = Number(itemResult?.current || detailPagination.current || 1)
- detailPagination.size = Number(itemResult?.size || detailPagination.size || 20)
} catch (error) {
detailDrawerVisible.value = false
detailData.value = {}
- detailItemRows.value = []
ElMessage.error(error?.message || '鑾峰彇鍘嗗彶閫氱煡鍗曡鎯呭け璐�')
} finally {
detailLoading.value = false
- detailItemsLoading.value = false
}
}
@@ -416,17 +370,6 @@
await loadMainList()
}
- function handleDetailSizeChange(size) {
- detailPagination.size = size
- detailPagination.current = 1
- loadDetailResources()
- }
-
- function handleDetailCurrentChange(current) {
- detailPagination.current = current
- loadDetailResources()
- }
-
function buildPreviewDialogMeta(rows) {
const now = new Date()
return {
@@ -461,7 +404,11 @@
records.push(...pageRecords)
- if (!response?.hasNext || response?.nextCursor === null || response?.nextCursor === undefined) {
+ if (
+ !response?.hasNext ||
+ response?.nextCursor === null ||
+ response?.nextCursor === undefined
+ ) {
break
}
cursor = response.nextCursor
diff --git a/rsf-design/src/views/orders/asn-order-log/modules/asn-order-item-log-panel.vue b/rsf-design/src/views/orders/asn-order-log/modules/asn-order-item-log-panel.vue
new file mode 100644
index 0000000..b144931
--- /dev/null
+++ b/rsf-design/src/views/orders/asn-order-log/modules/asn-order-item-log-panel.vue
@@ -0,0 +1,447 @@
+<template>
+ <div class="flex min-h-0 flex-1 flex-col gap-3">
+ <ArtSearchBar
+ v-model="searchForm"
+ :items="searchItems"
+ :showExpand="true"
+ @search="handleSearch"
+ @reset="handleReset"
+ />
+
+ <ElCard class="art-table-card min-h-0 flex-1">
+ <ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="refreshData">
+ <template #left>
+ <ListExportPrint
+ class="inline-flex"
+ :preview-visible="previewVisible"
+ @update:previewVisible="handlePreviewVisibleChange"
+ :report-title="reportTitle"
+ :selected-rows="selectedRows"
+ :query-params="reportQueryParams"
+ :columns="reportColumns"
+ :preview-rows="previewRows"
+ :preview-meta="resolvedPreviewMeta"
+ :total="pagination.total"
+ :disabled="loading"
+ @export="handleExport"
+ @print="handlePrint"
+ />
+ </template>
+ </ArtTableHeader>
+
+ <ArtTable
+ :loading="loading"
+ :data="data"
+ :columns="columns"
+ :pagination="pagination"
+ @pagination:size-change="handleSizeChange"
+ @pagination:current-change="handleCurrentChange"
+ />
+ </ElCard>
+ </div>
+</template>
+
+<script setup>
+ import { computed, ref, watch } from 'vue'
+ import { useI18n } from 'vue-i18n'
+ import { useUserStore } from '@/store/modules/user'
+ import { useTable } from '@/hooks/core/useTable'
+ import { defaultResponseAdapter } from '@/utils/table/tableUtils'
+ import { usePrintExportPage } from '@/views/system/common/usePrintExportPage'
+ import ListExportPrint from '@/components/biz/list-export-print/index.vue'
+ import {
+ fetchAsnOrderItemLogPage,
+ fetchExportAsnOrderItemLogReport,
+ fetchGetAsnOrderItemLogMany
+ } from '@/api/asn-order-log'
+ import { createAsnOrderItemLogColumns } from '../asnOrderLogTable.columns'
+ import { normalizeAsnOrderItemLogRow } from '../asnOrderLogPage.helpers'
+ import {
+ ASN_ORDER_ITEM_LOG_REPORT_STYLE,
+ ASN_ORDER_ITEM_LOG_REPORT_TITLE,
+ buildAsnOrderItemLogPageQueryParams,
+ buildAsnOrderItemLogPrintRows,
+ buildAsnOrderItemLogReportMeta,
+ buildAsnOrderItemLogSearchParams,
+ createAsnOrderItemLogSearchState,
+ getAsnOrderItemLogPaginationKey,
+ getAsnOrderItemLogReportColumns
+ } from '../../asn-order-item-log/asnOrderItemLogPage.helpers'
+
+ defineOptions({ name: 'AsnOrderItemLogPanel' })
+
+ const props = defineProps({
+ logId: {
+ type: [Number, String],
+ default: ''
+ }
+ })
+
+ const { t } = useI18n()
+ const userStore = useUserStore()
+ const selectedRows = ref([])
+ const searchForm = ref(createSeededSearchState())
+ const reportTitle = ASN_ORDER_ITEM_LOG_REPORT_TITLE
+ const reportColumns = getAsnOrderItemLogReportColumns()
+
+ const searchItems = computed(() => [
+ {
+ label: t('pages.orders.asnOrderItemLog.search.condition'),
+ key: 'condition',
+ type: 'input',
+ props: {
+ clearable: true,
+ placeholder: t('pages.orders.asnOrderItemLog.search.conditionPlaceholder')
+ }
+ },
+ {
+ label: t('pages.orders.asnOrderItemLog.table.asnCode'),
+ key: 'orderCode',
+ type: 'input',
+ props: {
+ clearable: true,
+ placeholder: t('pages.orders.asnOrderItemLog.search.asnCodePlaceholder')
+ }
+ },
+ {
+ label: t('pages.orders.asnOrderItemLog.table.platItemId'),
+ key: 'platItemId',
+ type: 'input',
+ props: {
+ clearable: true,
+ placeholder: t('pages.orders.asnOrderItemLog.table.platItemId')
+ }
+ },
+ {
+ label: t('pages.orders.asnOrderItemLog.table.poDetlId'),
+ key: 'poDetlId',
+ type: 'inputNumber',
+ props: {
+ clearable: true,
+ controlsPosition: 'right',
+ placeholder: t('pages.orders.asnOrderItemLog.table.poDetlId')
+ }
+ },
+ {
+ label: t('pages.orders.asnOrderItemLog.table.poCode'),
+ key: 'poCode',
+ type: 'input',
+ props: {
+ clearable: true,
+ placeholder: t('pages.orders.asnOrderItemLog.search.poCodePlaceholder')
+ }
+ },
+ {
+ label: t('pages.orders.asnOrderItemLog.table.fieldsIndex'),
+ key: 'fieldsIndex',
+ type: 'input',
+ props: {
+ clearable: true,
+ placeholder: t('pages.orders.asnOrderItemLog.table.fieldsIndex')
+ }
+ },
+ {
+ label: t('pages.orders.asnOrderItemLog.table.matnrCode'),
+ key: 'matnrCode',
+ type: 'input',
+ props: {
+ clearable: true,
+ placeholder: t('pages.orders.asnOrderItemLog.search.matnrCodePlaceholder')
+ }
+ },
+ {
+ label: t('pages.orders.asnOrderItemLog.table.maktx'),
+ key: 'maktx',
+ type: 'input',
+ props: {
+ clearable: true,
+ placeholder: t('pages.orders.asnOrderItemLog.search.maktxPlaceholder')
+ }
+ },
+ {
+ label: t('pages.orders.asnOrderItemLog.table.anfme'),
+ key: 'anfme',
+ type: 'inputNumber',
+ props: {
+ clearable: true,
+ controlsPosition: 'right',
+ placeholder: t('pages.orders.asnOrderItemLog.table.anfme')
+ }
+ },
+ {
+ label: t('pages.orders.asnOrderItemLog.table.stockUnit'),
+ key: 'stockUnit',
+ type: 'input',
+ props: {
+ clearable: true,
+ placeholder: t('pages.orders.asnOrderItemLog.table.stockUnit')
+ }
+ },
+ {
+ label: t('pages.orders.asnOrderItemLog.table.purQty'),
+ key: 'purQty',
+ type: 'inputNumber',
+ props: {
+ clearable: true,
+ controlsPosition: 'right',
+ placeholder: t('pages.orders.asnOrderItemLog.table.purQty')
+ }
+ },
+ {
+ label: t('pages.orders.asnOrderItemLog.table.purUnit'),
+ key: 'purUnit',
+ type: 'input',
+ props: {
+ clearable: true,
+ placeholder: t('pages.orders.asnOrderItemLog.table.purUnit')
+ }
+ },
+ {
+ label: t('pages.orders.asnOrderItemLog.table.qty'),
+ key: 'qty',
+ type: 'inputNumber',
+ props: {
+ clearable: true,
+ controlsPosition: 'right',
+ placeholder: t('pages.orders.asnOrderItemLog.table.qty')
+ }
+ },
+ {
+ label: t('pages.orders.asnOrderItemLog.table.splrCode'),
+ key: 'splrCode',
+ type: 'input',
+ props: {
+ clearable: true,
+ placeholder: t('pages.orders.asnOrderItemLog.table.splrCode')
+ }
+ },
+ {
+ label: t('pages.orders.asnOrderItemLog.table.splrBatch'),
+ key: 'splrBatch',
+ type: 'input',
+ props: {
+ clearable: true,
+ placeholder: t('pages.orders.asnOrderItemLog.search.splrBatchPlaceholder')
+ }
+ },
+ {
+ label: t('pages.orders.asnOrderItemLog.table.splrName'),
+ key: 'splrName',
+ type: 'input',
+ props: {
+ clearable: true,
+ placeholder: t('pages.orders.asnOrderItemLog.table.splrName')
+ }
+ },
+ {
+ label: t('pages.orders.asnOrderItemLog.table.qrcode'),
+ key: 'qrcode',
+ type: 'input',
+ props: {
+ clearable: true,
+ placeholder: t('pages.orders.asnOrderItemLog.table.qrcode')
+ }
+ },
+ {
+ label: t('pages.orders.asnOrderItemLog.table.trackCode'),
+ key: 'trackCode',
+ type: 'input',
+ props: {
+ clearable: true,
+ placeholder: t('pages.orders.asnOrderItemLog.table.trackCode')
+ }
+ },
+ {
+ label: t('pages.orders.asnOrderItemLog.table.barcode'),
+ key: 'barcode',
+ type: 'input',
+ props: {
+ clearable: true,
+ placeholder: t('pages.orders.asnOrderItemLog.table.barcode')
+ }
+ },
+ {
+ label: t('pages.orders.asnOrderItemLog.table.packName'),
+ key: 'packName',
+ type: 'input',
+ props: {
+ clearable: true,
+ placeholder: t('pages.orders.asnOrderItemLog.table.packName')
+ }
+ },
+ {
+ label: t('pages.orders.asnOrderItemLog.table.ntyStatus'),
+ key: 'ntyStatus',
+ type: 'select',
+ props: {
+ clearable: true,
+ options: [
+ { label: t('pages.orders.asnOrderItemLog.status.notReported'), value: 0 },
+ { label: t('pages.orders.asnOrderItemLog.status.reported'), value: 1 },
+ { label: t('pages.orders.asnOrderItemLog.status.partialReported'), value: 2 }
+ ]
+ }
+ },
+ {
+ label: t('table.memo'),
+ key: 'memo',
+ type: 'input',
+ props: {
+ clearable: true,
+ placeholder: t('table.memo')
+ }
+ },
+ {
+ label: t('table.status'),
+ key: 'status',
+ type: 'select',
+ props: {
+ clearable: true,
+ options: [
+ { label: t('common.status.normal'), value: 1 },
+ { label: t('common.status.frozen'), value: 0 }
+ ]
+ }
+ }
+ ])
+
+ const reportQueryParams = computed(() =>
+ buildAsnOrderItemLogSearchParams({
+ ...searchForm.value,
+ logId: normalizeLogId(props.logId)
+ })
+ )
+
+ const {
+ columns,
+ columnChecks,
+ data,
+ loading,
+ pagination,
+ getData,
+ replaceSearchParams,
+ handleSizeChange,
+ handleCurrentChange,
+ refreshData
+ } = useTable({
+ core: {
+ apiFn: fetchAsnOrderItemLogPage,
+ apiParams: buildAsnOrderItemLogPageQueryParams(searchForm.value),
+ paginationKey: getAsnOrderItemLogPaginationKey(),
+ immediate: false,
+ columnsFactory: () => createAsnOrderItemLogColumns()
+ },
+ transform: {
+ dataTransformer: (records) =>
+ Array.isArray(records) ? records.map((item) => normalizeAsnOrderItemLogRow(item)) : []
+ }
+ })
+
+ watch(
+ () => props.logId,
+ (value) => {
+ if (normalizeLogId(value) === '') {
+ return
+ }
+ searchForm.value = createSeededSearchState()
+ replaceSearchParams(buildAsnOrderItemLogPageQueryParams(searchForm.value))
+ getData()
+ },
+ { immediate: true }
+ )
+
+ function normalizeLogId(value) {
+ if (value === '' || value === null || value === undefined) {
+ return ''
+ }
+ const parsed = Number(value)
+ return Number.isFinite(parsed) ? parsed : ''
+ }
+
+ function createSeededSearchState() {
+ return createAsnOrderItemLogSearchState({
+ logId: normalizeLogId(props.logId)
+ })
+ }
+
+ function buildQueryParams(params = {}) {
+ return buildAsnOrderItemLogPageQueryParams({
+ ...params,
+ logId: normalizeLogId(props.logId)
+ })
+ }
+
+ function handleSearch(params) {
+ searchForm.value = {
+ ...searchForm.value,
+ ...params,
+ logId: normalizeLogId(props.logId)
+ }
+ replaceSearchParams(buildQueryParams(searchForm.value))
+ getData()
+ }
+
+ function handleReset() {
+ searchForm.value = createSeededSearchState()
+ replaceSearchParams(buildQueryParams(searchForm.value))
+ getData()
+ }
+
+ const resolvePrintRecords = async (payload) => {
+ if (Array.isArray(payload?.ids) && payload.ids.length > 0) {
+ return defaultResponseAdapter(await fetchGetAsnOrderItemLogMany(payload.ids)).records
+ }
+
+ return defaultResponseAdapter(
+ await fetchAsnOrderItemLogPage({
+ ...reportQueryParams.value,
+ logId: normalizeLogId(props.logId),
+ current: 1,
+ pageSize: Number(pagination.total) > 0 ? Number(pagination.total) : 20
+ })
+ ).records
+ }
+
+ const {
+ previewVisible,
+ previewRows,
+ previewMeta,
+ handlePreviewVisibleChange,
+ handleExport,
+ handlePrint
+ } = usePrintExportPage({
+ downloadFileName: 'asn-order-item-log.xlsx',
+ requestExport: () =>
+ fetchExportAsnOrderItemLogReport(
+ {
+ logId: normalizeLogId(props.logId)
+ },
+ {
+ headers: {
+ Authorization: userStore.accessToken || ''
+ }
+ }
+ ),
+ resolvePrintRecords,
+ buildPreviewRows: (records) => buildAsnOrderItemLogPrintRows(records),
+ buildPreviewMeta: (rows) => ({
+ reportTitle,
+ reportDate: new Date().toLocaleDateString(),
+ printedAt: new Date().toLocaleString([], { hour12: false }),
+ operator: userStore.getUserInfo?.name || userStore.getUserInfo?.username || '',
+ count: rows.length,
+ reportStyle: {
+ ...ASN_ORDER_ITEM_LOG_REPORT_STYLE
+ }
+ })
+ })
+
+ const resolvedPreviewMeta = computed(() =>
+ buildAsnOrderItemLogReportMeta({
+ previewMeta: previewMeta.value,
+ count: previewRows.value.length,
+ orientation:
+ previewMeta.value?.reportStyle?.orientation || ASN_ORDER_ITEM_LOG_REPORT_STYLE.orientation
+ })
+ )
+</script>
diff --git a/rsf-design/src/views/orders/asn-order-log/modules/asn-order-log-detail-drawer.vue b/rsf-design/src/views/orders/asn-order-log/modules/asn-order-log-detail-drawer.vue
index 529bd8b..03792cb 100644
--- a/rsf-design/src/views/orders/asn-order-log/modules/asn-order-log-detail-drawer.vue
+++ b/rsf-design/src/views/orders/asn-order-log/modules/asn-order-log-detail-drawer.vue
@@ -14,57 +14,73 @@
<ElDescriptionsItem label="PO鍗旾D">{{ detail.poId ?? '--' }}</ElDescriptionsItem>
<ElDescriptionsItem label="鍗曟嵁绫诲瀷">{{ detail.typeText || '--' }}</ElDescriptionsItem>
<ElDescriptionsItem label="涓氬姟绫诲瀷">{{ detail.wkTypeText || '--' }}</ElDescriptionsItem>
+ <ElDescriptionsItem label="閲囪喘缁勭粐">{{
+ detail.purchaseOrgName || '--'
+ }}</ElDescriptionsItem>
+ <ElDescriptionsItem label="閲囪喘鍛�">{{
+ detail.purchaseUserName || '--'
+ }}</ElDescriptionsItem>
+ <ElDescriptionsItem label="閲囪喘鏃ユ湡">{{
+ detail.purchaseDateText || '--'
+ }}</ElDescriptionsItem>
+ <ElDescriptionsItem label="渚涘簲鍟嗙紪鐮�">{{
+ detail.supplierId || '--'
+ }}</ElDescriptionsItem>
+ <ElDescriptionsItem label="渚涘簲鍟�">{{ detail.supplierName || '--' }}</ElDescriptionsItem>
<ElDescriptionsItem label="閫佽揣鏁伴噺">{{ detail.anfme ?? '--' }}</ElDescriptionsItem>
<ElDescriptionsItem label="宸叉敹鏁伴噺">{{ detail.qty ?? '--' }}</ElDescriptionsItem>
<ElDescriptionsItem label="鐗╂祦鍗曞彿">{{ detail.logisNo || '--' }}</ElDescriptionsItem>
- <ElDescriptionsItem label="棰勮鍒拌揪鏃堕棿">{{ detail.arrTimeText || '--' }}</ElDescriptionsItem>
- <ElDescriptionsItem label="閲婃斁鐘舵��">{{ detail.rleStatusText || '--' }}</ElDescriptionsItem>
- <ElDescriptionsItem label="涓婃姤鐘舵��">{{ detail.ntyStatusText || '--' }}</ElDescriptionsItem>
- <ElDescriptionsItem label="鎵ц鐘舵��">{{ detail.exceStatusText || '--' }}</ElDescriptionsItem>
+ <ElDescriptionsItem label="棰勮鍒拌揪鏃堕棿">{{
+ detail.arrTimeText || '--'
+ }}</ElDescriptionsItem>
+ <ElDescriptionsItem label="閲婃斁鐘舵��">{{
+ detail.rleStatusText || '--'
+ }}</ElDescriptionsItem>
+ <ElDescriptionsItem label="涓婃姤鐘舵��">{{
+ detail.ntyStatusText || '--'
+ }}</ElDescriptionsItem>
+ <ElDescriptionsItem label="鎵ц鐘舵��">{{
+ detail.exceStatusText || '--'
+ }}</ElDescriptionsItem>
<ElDescriptionsItem label="鐘舵��">
- <ElTag :type="detail.statusType || 'info'" effect="light">{{ detail.statusText || '--' }}</ElTag>
+ <ElTag :type="detail.statusType || 'info'" effect="light">{{
+ detail.statusText || '--'
+ }}</ElTag>
</ElDescriptionsItem>
<ElDescriptionsItem label="鍒涘缓浜�">{{ detail.createByText || '--' }}</ElDescriptionsItem>
- <ElDescriptionsItem label="鍒涘缓鏃堕棿">{{ detail.createTimeText || '--' }}</ElDescriptionsItem>
+ <ElDescriptionsItem label="鍒涘缓鏃堕棿">{{
+ detail.createTimeText || '--'
+ }}</ElDescriptionsItem>
<ElDescriptionsItem label="鏇存柊浜�">{{ detail.updateByText || '--' }}</ElDescriptionsItem>
- <ElDescriptionsItem label="鏇存柊鏃堕棿">{{ detail.updateTimeText || '--' }}</ElDescriptionsItem>
+ <ElDescriptionsItem label="鏇存柊鏃堕棿">{{
+ detail.updateTimeText || '--'
+ }}</ElDescriptionsItem>
<ElDescriptionsItem label="澶囨敞" :span="4">{{ detail.memo || '--' }}</ElDescriptionsItem>
</ElDescriptions>
<div class="flex items-center justify-between">
<div class="text-sm text-[var(--art-gray-600)]">鍘嗗彶鏄庣粏</div>
- <ElButton :loading="loading || itemsLoading" @click="$emit('refresh')">鍒锋柊</ElButton>
</div>
- <ElCard shadow="never" class="border border-[var(--art-border-color)]">
- <ArtTable
- :loading="itemsLoading"
- :data="itemRows"
- :columns="itemColumns"
- :pagination="pagination"
- @pagination:size-change="$emit('size-change', $event)"
- @pagination:current-change="$emit('current-change', $event)"
- />
- </ElCard>
+ <AsnOrderItemLogPanel :log-id="logId" />
</div>
</ElScrollbar>
</ElDrawer>
</template>
<script setup>
+ import AsnOrderItemLogPanel from './asn-order-item-log-panel.vue'
+
defineOptions({ name: 'AsnOrderLogDetailDrawer' })
defineProps({
visible: { type: Boolean, default: false },
loading: { type: Boolean, default: false },
- itemsLoading: { type: Boolean, default: false },
detail: { type: Object, default: () => ({}) },
- itemRows: { type: Array, default: () => [] },
- itemColumns: { type: Array, default: () => [] },
- pagination: { type: Object, default: () => ({ current: 1, size: 20, total: 0 }) }
+ logId: { type: [Number, String], default: '' }
})
- const emit = defineEmits(['update:visible', 'refresh', 'size-change', 'current-change'])
+ const emit = defineEmits(['update:visible'])
function handleVisibleChange(visible) {
emit('update:visible', visible)
diff --git a/rsf-design/src/views/orders/asn-order/asnOrderPage.helpers.js b/rsf-design/src/views/orders/asn-order/asnOrderPage.helpers.js
index 1e3063d..d878b78 100644
--- a/rsf-design/src/views/orders/asn-order/asnOrderPage.helpers.js
+++ b/rsf-design/src/views/orders/asn-order/asnOrderPage.helpers.js
@@ -15,6 +15,10 @@
return String(value ?? '').trim()
}
+function normalizeDateValue(value) {
+ return value ? String(value) : ''
+}
+
function normalizeNumber(value) {
if (value === '' || value === null || value === undefined) {
return 0
@@ -60,7 +64,14 @@
condition: '',
code: '',
poCode: '',
+ poId: '',
+ type: '',
wkType: '',
+ anfme: '',
+ qty: '',
+ logisNo: '',
+ arrTime: '',
+ memo: '',
exceStatus: '',
supplierName: '',
purchaseUserName: ''
@@ -78,10 +89,27 @@
export function buildAsnOrderSearchParams(params = {}) {
const result = {}
- ;['condition', 'code', 'poCode', 'wkType', 'supplierName', 'purchaseUserName'].forEach((key) => {
+ ;[
+ 'condition',
+ 'code',
+ 'poCode',
+ 'type',
+ 'wkType',
+ 'logisNo',
+ 'arrTime',
+ 'memo',
+ 'supplierName',
+ 'purchaseUserName'
+ ].forEach((key) => {
const value = normalizeText(params[key])
if (value) {
result[key] = value
+ }
+ })
+ ;['poId', 'anfme', 'qty'].forEach((key) => {
+ const value = normalizeText(params[key])
+ if (value) {
+ result[key] = Number(value)
}
})
@@ -96,6 +124,7 @@
return {
current: params.current || 1,
pageSize: params.pageSize || params.size || 20,
+ orderBy: params.orderBy || 'create_time desc',
...buildAsnOrderSearchParams(params)
}
}
@@ -129,11 +158,13 @@
export function normalizeAsnOrderRow(record = {}, t = $t) {
const statusConfig = getStatusConfig(record.exceStatus, record['exceStatus$'], t)
+ const exceStatus = Number(record.exceStatus)
return {
...record,
id: record.id ?? null,
code: record.code || '-',
poCode: record.poCode || '-',
+ poId: record.poId ?? '-',
wkTypeLabel: record['wkType$'] || record.wkType || '-',
orderTypeLabel: record['type$'] || record.type || '-',
exceStatusText: statusConfig.label,
@@ -149,8 +180,12 @@
updateTimeText: record['updateTime$'] || record.updateTime || '-',
createByText: record['createBy$'] || '-',
createTimeText: record['createTime$'] || record.createTime || '-',
+ logisNo: record.logisNo || '-',
+ arrTimeText: record['arrTime$'] || record.arrTime || '-',
memo: record.memo || '-',
- canComplete: Number(record.exceStatus) === 1
+ canEdit: exceStatus === 0 || exceStatus === 1,
+ canDelete: exceStatus === 0,
+ canComplete: exceStatus === 1
}
}
@@ -173,6 +208,200 @@
memo: record.memo || '-',
prodTimeText: record['prodTime$'] || record.prodTime || '-'
}
+}
+
+let tempItemSeed = 0
+
+function createTempRowKey(prefix = 'asn-item') {
+ tempItemSeed += 1
+ return `${prefix}-${Date.now()}-${tempItemSeed}`
+}
+
+export function createAsnOrderFormState() {
+ return {
+ id: undefined,
+ version: undefined,
+ code: '',
+ type: '',
+ wkType: '',
+ poCode: '',
+ logisNo: '',
+ arrTime: '',
+ memo: ''
+ }
+}
+
+export function buildAsnOrderDialogModel(record = {}) {
+ return {
+ ...createAsnOrderFormState(),
+ ...record,
+ poCode: normalizeText(record.poCode),
+ logisNo: normalizeText(record.logisNo),
+ arrTime: normalizeDateValue(record.arrTime || record['arrTime$']),
+ memo: normalizeText(record.memo)
+ }
+}
+
+export function createAsnOrderMaterialSearchState() {
+ return {
+ name: '',
+ code: '',
+ groupId: undefined
+ }
+}
+
+export function buildAsnOrderMaterialSearchParams(params = {}) {
+ const result = {}
+ ;['name', 'code'].forEach((key) => {
+ const value = normalizeText(params[key])
+ if (value) {
+ result[key] = value
+ }
+ })
+
+ const groupId = params.groupId
+ if (groupId !== undefined && groupId !== null && groupId !== '') {
+ result.groupId = groupId
+ }
+
+ return result
+}
+
+export function buildAsnOrderEditableItem(record = {}, fieldDefinitions = []) {
+ const dynamicFields = Object.fromEntries(
+ fieldDefinitions.map((item) => [
+ item.fields,
+ record[item.fields] ?? record.extendFields?.[item.fields] ?? ''
+ ])
+ )
+
+ return {
+ ...record,
+ ...dynamicFields,
+ __rowKey: record.__rowKey || (record.id ? `existing-${record.id}` : createTempRowKey()),
+ matnrId: record.matnrId ?? null,
+ matnrCode: record.matnrCode || '-',
+ maktx: record.maktx || record.matnrName || '-',
+ stockUnit: record.stockUnit || record.unit || '-',
+ purUnit: record.purUnit || record.stockUnit || record.unit || '-',
+ platItemId: record.platItemId || '',
+ splrBatch: record.splrBatch || '',
+ splrCode: record.splrCode || '',
+ splrName: record.splrName || record.supplierName || '',
+ memo: record.memo || '',
+ anfme: normalizeNumber(record.anfme),
+ qty: normalizeNumber(record.qty)
+ }
+}
+
+export function createAsnOrderEditableItemFromMaterial(record = {}, fieldDefinitions = []) {
+ const dynamicFields = Object.fromEntries(
+ fieldDefinitions.map((item) => [
+ item.fields,
+ record[item.fields] ?? record.extendFields?.[item.fields] ?? ''
+ ])
+ )
+
+ return {
+ ...record,
+ ...dynamicFields,
+ __rowKey: createTempRowKey('asn-material'),
+ id: undefined,
+ matnrId: record.id ?? record.matnrId ?? null,
+ matnrCode: record.code || record.matnrCode || '',
+ maktx: record.name || record.maktx || '',
+ stockUnit: record.stockUnit || record.unit || '',
+ purUnit: record.purUnit || record.unit || '',
+ platItemId: '',
+ splrBatch: '',
+ splrCode: '',
+ splrName: '',
+ memo: '',
+ anfme: 0,
+ qty: 0
+ }
+}
+
+export function buildAsnOrderSavePayload({
+ formData = {},
+ itemRows = [],
+ fieldDefinitions = []
+} = {}) {
+ const orders = {
+ ...formData,
+ type: normalizeText(formData.type),
+ wkType: normalizeText(formData.wkType),
+ poCode: normalizeText(formData.poCode),
+ logisNo: normalizeText(formData.logisNo),
+ arrTime: normalizeDateValue(formData.arrTime),
+ memo: normalizeText(formData.memo)
+ }
+
+ const items = itemRows.map((row) => {
+ const payload = { ...row }
+ delete payload.__rowKey
+ fieldDefinitions.forEach((item) => {
+ const value = row[item.fields] ?? row.extendFields?.[item.fields]
+ if (value !== undefined && value !== null && value !== '') {
+ payload[item.fields] = value
+ }
+ })
+ return payload
+ })
+
+ return {
+ orders,
+ items
+ }
+}
+
+export function resolveDictOptions(records = [], options = {}) {
+ const { group } = options
+ if (!Array.isArray(records)) {
+ return []
+ }
+
+ return records
+ .filter((item) => {
+ if (!item || typeof item !== 'object') {
+ return false
+ }
+ if (group === undefined) {
+ return true
+ }
+ return normalizeText(item.group) === normalizeText(group)
+ })
+ .map((item) => {
+ const value = item.value ?? item.id ?? item.dictValue
+ if (value === undefined || value === null || value === '') {
+ return null
+ }
+ return {
+ value: normalizeText(value),
+ label: normalizeText(item.label || item.name || item.dictLabel || value)
+ }
+ })
+ .filter(Boolean)
+}
+
+export function normalizeTreeOptions(records = [], level = 0) {
+ if (!Array.isArray(records)) {
+ return []
+ }
+
+ return records
+ .map((item) => {
+ if (!item || typeof item !== 'object') {
+ return null
+ }
+ const children = normalizeTreeOptions(item.children || item.childrens || [], level + 1)
+ return {
+ value: item.id,
+ label: `${' '.repeat(level * 2)}${item.name || item.label || item.code || item.id}`,
+ children
+ }
+ })
+ .filter(Boolean)
}
export function normalizePurchaseRow(record = {}) {
@@ -243,9 +472,9 @@
}))
}
-export function getAsnOrderActionList(row = {}) {
+export function getAsnOrderActionList(row = {}, options = {}) {
const normalizedRow = normalizeAsnOrderRow(row)
- return [
+ const actionList = [
{
key: 'view',
label: $t('pages.orders.asnOrder.actions.view'),
@@ -269,4 +498,25 @@
disabled: !normalizedRow.canComplete
}
]
+
+ if (options.canEdit) {
+ actionList.splice(2, 0, {
+ key: 'edit',
+ label: $t('pages.orders.asnOrder.actions.edit'),
+ icon: 'ri:edit-line',
+ disabled: !normalizedRow.canEdit
+ })
+ }
+
+ if (options.canDelete) {
+ actionList.push({
+ key: 'delete',
+ label: $t('pages.orders.asnOrder.actions.delete'),
+ icon: 'ri:delete-bin-line',
+ color: 'var(--el-color-danger)',
+ disabled: !normalizedRow.canDelete
+ })
+ }
+
+ return actionList
}
diff --git a/rsf-design/src/views/orders/asn-order/asnOrderTable.columns.js b/rsf-design/src/views/orders/asn-order/asnOrderTable.columns.js
index d3fb242..2955a14 100644
--- a/rsf-design/src/views/orders/asn-order/asnOrderTable.columns.js
+++ b/rsf-design/src/views/orders/asn-order/asnOrderTable.columns.js
@@ -5,7 +5,11 @@
import ArtButtonTable from '@/components/core/forms/art-button-table/index.vue'
import { getAsnOrderActionList } from './asnOrderPage.helpers'
-export function createAsnOrderTableColumns({ handleActionClick }) {
+export function createAsnOrderTableColumns({
+ handleActionClick,
+ canEdit = false,
+ canDelete = false
+}) {
return [
{
type: 'selection',
@@ -29,6 +33,13 @@
label: $t('pages.orders.asnOrder.search.poCode'),
minWidth: 170,
showOverflowTooltip: true
+ },
+ {
+ prop: 'poId',
+ label: $t('pages.orders.asnOrder.search.poId'),
+ width: 110,
+ align: 'right',
+ visible: false
},
{
prop: 'wkTypeLabel',
@@ -61,6 +72,27 @@
showOverflowTooltip: true
},
{
+ prop: 'purchaseOrgName',
+ label: $t('pages.orders.asnOrder.table.purchaseOrgName'),
+ minWidth: 140,
+ showOverflowTooltip: true,
+ visible: false
+ },
+ {
+ prop: 'businessTimeText',
+ label: $t('pages.orders.asnOrder.table.businessTime'),
+ minWidth: 170,
+ showOverflowTooltip: true,
+ visible: false
+ },
+ {
+ prop: 'supplierId',
+ label: $t('pages.orders.asnOrder.table.supplierId'),
+ minWidth: 140,
+ showOverflowTooltip: true,
+ visible: false
+ },
+ {
prop: 'exceStatusText',
label: $t('pages.orders.asnOrder.search.exceStatus'),
width: 120,
@@ -72,20 +104,48 @@
)
},
{
+ prop: 'updateByText',
+ label: $t('table.updateBy'),
+ minWidth: 120,
+ showOverflowTooltip: true,
+ visible: false
+ },
+ {
prop: 'updateTimeText',
label: $t('pages.orders.asnOrder.detail.updateTime'),
minWidth: 170,
showOverflowTooltip: true
},
{
+ prop: 'createByText',
+ label: $t('table.createBy'),
+ minWidth: 120,
+ showOverflowTooltip: true,
+ visible: false
+ },
+ {
+ prop: 'createTimeText',
+ label: $t('pages.orders.asnOrder.detail.createTime'),
+ minWidth: 170,
+ showOverflowTooltip: true,
+ visible: false
+ },
+ {
+ prop: 'memo',
+ label: $t('table.remark'),
+ minWidth: 180,
+ showOverflowTooltip: true,
+ visible: false
+ },
+ {
prop: 'operation',
label: $t('table.operation'),
- width: 120,
+ width: 130,
align: 'center',
fixed: 'right',
formatter: (row) =>
h(ArtButtonMore, {
- list: getAsnOrderActionList(row),
+ list: getAsnOrderActionList(row, { canEdit, canDelete }),
onClick: (item) => handleActionClick(item, row)
})
}
diff --git a/rsf-design/src/views/orders/asn-order/index.vue b/rsf-design/src/views/orders/asn-order/index.vue
index 1a23773..275ed97 100644
--- a/rsf-design/src/views/orders/asn-order/index.vue
+++ b/rsf-design/src/views/orders/asn-order/index.vue
@@ -12,8 +12,35 @@
<ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="refreshData">
<template #left>
<ElSpace wrap>
- <ElButton type="primary" @click="poDialogVisible = true">{{ t('pages.orders.asnOrder.buttons.createByPo') }}</ElButton>
+ <ElButton v-auth="'add'" type="primary" @click="showDialog('add')">
+ {{ t('pages.orders.asnOrder.buttons.create') }}
+ </ElButton>
+ <ElButton v-auth="'add'" @click="poDialogVisible = true">
+ {{ t('pages.orders.asnOrder.buttons.createByPo') }}
+ </ElButton>
+ <ElUpload
+ v-auth="'update'"
+ :auto-upload="false"
+ :show-file-list="false"
+ accept=".xlsx,.xls"
+ @change="handleImportFileChange"
+ >
+ <ElButton :loading="importing">
+ {{ t('pages.orders.asnOrder.buttons.import') }}
+ </ElButton>
+ </ElUpload>
+ <ElButton
+ v-auth="'update'"
+ :loading="templateDownloading"
+ @click="handleDownloadTemplate"
+ >
+ {{ t('pages.orders.asnOrder.buttons.downloadTemplate') }}
+ </ElButton>
+ <ElButton :disabled="selectedRows.length === 0" @click="handleInspectSelected">
+ {{ t('pages.orders.asnOrder.buttons.inspection') }}
+ </ElButton>
<ListExportPrint
+ class="inline-flex"
:preview-visible="previewVisible"
@update:previewVisible="handlePreviewVisibleChange"
:report-title="reportTitle"
@@ -42,6 +69,16 @@
/>
</ElCard>
+ <AsnOrderDialog
+ v-model:visible="dialogVisible"
+ :dialog-type="dialogType"
+ :order-data="currentOrderData"
+ :type-options="typeOptions"
+ :wk-type-options="wkTypeOptions"
+ :field-definitions="fieldDefinitions"
+ @submit="handleDialogSubmit"
+ />
+
<AsnOrderDetailDrawer
v-model:visible="detailDrawerVisible"
:loading="detailLoading"
@@ -55,17 +92,20 @@
/>
<AsnOrderCreateByPoDialog v-model:visible="poDialogVisible" @success="handlePoCreateSuccess" />
+
+ <AsnOrderItemDialog v-model:visible="itemDialogVisible" :order="currentItemOrder" />
</div>
</template>
<script setup>
- import { computed, reactive, ref } from 'vue'
- import { useRouter } from 'vue-router'
+ import { computed, onMounted, reactive, ref } from 'vue'
import { useI18n } from 'vue-i18n'
import { ElMessage, ElMessageBox } from 'element-plus'
import { useUserStore } from '@/store/modules/user'
+ import { useAuth } from '@/hooks/core/useAuth'
import { useTable } from '@/hooks/core/useTable'
import { usePrintExportPage } from '@/views/system/common/usePrintExportPage'
+ import { fetchDictDataPage } from '@/api/system-manage'
import ListExportPrint from '@/components/biz/list-export-print/index.vue'
import { defaultResponseAdapter } from '@/utils/table/tableUtils'
import { guardRequestWithMessage } from '@/utils/sys/requestGuard'
@@ -73,11 +113,21 @@
fetchAsnOrderItemPage,
fetchAsnOrderPage,
fetchCompleteAsnOrder,
+ fetchDeleteAsnOrder,
+ fetchDownloadAsnOrderTemplate,
+ fetchEnabledAsnOrderFields,
fetchExportAsnOrderReport,
- fetchGetAsnOrderMany
+ fetchGetAsnOrderDetail,
+ fetchGetAsnOrderMany,
+ fetchImportAsnOrder,
+ fetchInspectAsnOrder,
+ fetchSaveAsnOrderWithItems,
+ fetchUpdateAsnOrderWithItems
} from '@/api/asn-order'
+ import AsnOrderDialog from './modules/asn-order-dialog.vue'
import AsnOrderDetailDrawer from './modules/asn-order-detail-drawer.vue'
import AsnOrderCreateByPoDialog from './modules/asn-order-create-by-po-dialog.vue'
+ import AsnOrderItemDialog from './modules/asn-order-item-dialog.vue'
import {
createAsnOrderDetailItemColumns,
createAsnOrderTableColumns
@@ -85,22 +135,27 @@
import {
ASN_ORDER_REPORT_STYLE,
buildAsnOrderDetailQueryParams,
+ buildAsnOrderDialogModel,
buildAsnOrderPageQueryParams,
buildAsnOrderPrintRows,
buildAsnOrderReportMeta,
+ buildAsnOrderSavePayload,
buildAsnOrderSearchParams,
+ createAsnOrderFormState,
createAsnOrderSearchState,
getAsnOrderReportTitle,
getAsnOrderStatusOptions,
normalizeAsnOrderItemRow,
- normalizeAsnOrderRow
+ normalizeAsnOrderRow,
+ resolveDictOptions
} from './asnOrderPage.helpers'
defineOptions({ name: 'AsnOrder' })
const userStore = useUserStore()
- const router = useRouter()
+ const { hasAuth } = useAuth()
const { t } = useI18n()
+
const reportTitle = computed(() => getAsnOrderReportTitle(t))
const searchForm = ref(createAsnOrderSearchState())
const selectedRows = ref([])
@@ -111,6 +166,19 @@
const activeOrderId = ref(null)
const activeOrderRow = ref(null)
const poDialogVisible = ref(false)
+ const dialogVisible = ref(false)
+ const itemDialogVisible = ref(false)
+ const dialogType = ref('add')
+ const currentOrderData = ref({
+ order: createAsnOrderFormState(),
+ items: []
+ })
+ const currentItemOrder = ref({})
+ const typeOptions = ref([])
+ const wkTypeOptions = ref([])
+ const fieldDefinitions = ref([])
+ const importing = ref(false)
+ const templateDownloading = ref(false)
const detailPagination = reactive({
current: 1,
@@ -149,12 +217,81 @@
}
},
{
- label: t('pages.orders.asnOrder.search.wkType'),
- key: 'wkType',
+ label: t('pages.orders.asnOrder.search.poId'),
+ key: 'poId',
type: 'input',
props: {
clearable: true,
+ placeholder: t('pages.orders.asnOrder.placeholder.poId')
+ }
+ },
+ {
+ label: t('pages.orders.asnOrder.search.type'),
+ key: 'type',
+ type: 'select',
+ props: {
+ clearable: true,
+ filterable: true,
+ options: typeOptions.value,
+ placeholder: t('pages.orders.asnOrder.placeholder.type')
+ }
+ },
+ {
+ label: t('pages.orders.asnOrder.search.wkType'),
+ key: 'wkType',
+ type: 'select',
+ props: {
+ clearable: true,
+ filterable: true,
+ options: wkTypeOptions.value,
placeholder: t('pages.orders.asnOrder.placeholder.wkType')
+ }
+ },
+ {
+ label: t('pages.orders.asnOrder.search.anfme'),
+ key: 'anfme',
+ type: 'input',
+ props: {
+ clearable: true,
+ placeholder: t('pages.orders.asnOrder.placeholder.anfme')
+ }
+ },
+ {
+ label: t('pages.orders.asnOrder.search.qty'),
+ key: 'qty',
+ type: 'input',
+ props: {
+ clearable: true,
+ placeholder: t('pages.orders.asnOrder.placeholder.qty')
+ }
+ },
+ {
+ label: t('pages.orders.asnOrder.search.logisNo'),
+ key: 'logisNo',
+ type: 'input',
+ props: {
+ clearable: true,
+ placeholder: t('pages.orders.asnOrder.placeholder.logisNo')
+ }
+ },
+ {
+ label: t('pages.orders.asnOrder.search.arrTime'),
+ key: 'arrTime',
+ type: 'date',
+ props: {
+ clearable: true,
+ valueFormat: 'YYYY-MM-DD HH:mm:ss',
+ format: 'YYYY-MM-DD HH:mm:ss',
+ placeholder: t('pages.orders.asnOrder.placeholder.arrTime')
+ }
+ },
+ {
+ label: t('pages.orders.asnOrder.search.memo'),
+ key: 'memo',
+ type: 'input',
+ props: {
+ clearable: true,
+ placeholder: t('pages.orders.asnOrder.placeholder.memo')
}
},
{
@@ -186,6 +323,63 @@
}
])
+ function updatePaginationState(target, response, fallbackCurrent, fallbackSize) {
+ target.total = Number(response?.total || 0)
+ target.current = Number(response?.current || fallbackCurrent || 1)
+ target.size = Number(response?.size || fallbackSize || target.size || 20)
+ }
+
+ function createEmptyDialogData() {
+ return {
+ order: buildAsnOrderDialogModel(),
+ items: []
+ }
+ }
+
+ async function fetchAllOrderItems(orderId) {
+ const firstPage = await guardRequestWithMessage(
+ fetchAsnOrderItemPage({
+ orderId,
+ current: 1,
+ pageSize: 200
+ }),
+ {
+ records: [],
+ total: 0,
+ current: 1,
+ size: 200
+ },
+ {
+ timeoutMessage: t('pages.orders.asnOrder.messages.detailTimeout')
+ }
+ )
+
+ const firstRecords = Array.isArray(firstPage?.records) ? firstPage.records : []
+ const total = Number(firstPage?.total || firstRecords.length)
+
+ if (total > firstRecords.length) {
+ const fullPage = await guardRequestWithMessage(
+ fetchAsnOrderItemPage({
+ orderId,
+ current: 1,
+ pageSize: total
+ }),
+ {
+ records: firstRecords,
+ total,
+ current: 1,
+ size: total
+ },
+ {
+ timeoutMessage: t('pages.orders.asnOrder.messages.detailTimeout')
+ }
+ )
+ return Array.isArray(fullPage?.records) ? fullPage.records : firstRecords
+ }
+
+ return firstRecords
+ }
+
async function openDetail(row) {
activeOrderId.value = row.id
activeOrderRow.value = row
@@ -193,6 +387,50 @@
detailPagination.current = 1
detailDrawerVisible.value = true
await loadDetailResources()
+ }
+
+ function showDialog(type, payload = createEmptyDialogData()) {
+ dialogType.value = type
+ currentOrderData.value = payload
+ dialogVisible.value = true
+ }
+
+ async function openEditDialog(row) {
+ try {
+ const [detail, items] = await Promise.all([
+ guardRequestWithMessage(
+ fetchGetAsnOrderDetail(row.id),
+ {},
+ {
+ timeoutMessage: t('pages.orders.asnOrder.messages.detailTimeout')
+ }
+ ),
+ fetchAllOrderItems(row.id)
+ ])
+
+ showDialog('edit', {
+ order: buildAsnOrderDialogModel(detail),
+ items
+ })
+ } catch (error) {
+ ElMessage.error(error?.message || t('pages.orders.asnOrder.messages.detailFailed'))
+ }
+ }
+
+ async function handleDelete(row) {
+ await ElMessageBox.confirm(
+ t('pages.orders.asnOrder.messages.deleteConfirm', { code: row.code || '' }),
+ t('pages.orders.asnOrder.messages.deleteTitle'),
+ {
+ confirmButtonText: t('common.confirm'),
+ cancelButtonText: t('common.cancel'),
+ type: 'warning'
+ }
+ )
+
+ await fetchDeleteAsnOrder(row.id)
+ ElMessage.success(t('pages.orders.asnOrder.messages.deleteSuccess'))
+ await refreshData()
}
async function handleActionClick(action, row) {
@@ -206,18 +444,24 @@
return
}
+ if (action.key === 'edit') {
+ await openEditDialog(row)
+ return
+ }
+
if (action.key === 'print') {
await handlePrint({ ids: [row.id], pageSize: 1 })
return
}
if (action.key === 'items') {
- router.push({
- path: '/orders/asn-order-item',
- query: {
- orderId: String(row.id)
- }
- })
+ currentItemOrder.value = row
+ itemDialogVisible.value = true
+ return
+ }
+
+ if (action.key === 'delete') {
+ await handleDelete(row)
return
}
@@ -265,7 +509,9 @@
apiParams: buildAsnOrderPageQueryParams(searchForm.value),
columnsFactory: () =>
createAsnOrderTableColumns({
- handleActionClick
+ handleActionClick,
+ canEdit: hasAuth('update'),
+ canDelete: hasAuth('delete')
})
},
transform: {
@@ -273,12 +519,6 @@
Array.isArray(records) ? records.map((item) => normalizeAsnOrderRow(item, t)) : []
}
})
-
- function updatePaginationState(target, response, fallbackCurrent, fallbackSize) {
- target.total = Number(response?.total || 0)
- target.current = Number(response?.current || fallbackCurrent || 1)
- target.size = Number(response?.size || fallbackSize || target.size || 20)
- }
async function loadDetailResources() {
if (!activeOrderId.value) {
@@ -354,6 +594,157 @@
await refreshSoft()
}
+ async function handleDialogSubmit(payload) {
+ try {
+ const requestPayload = buildAsnOrderSavePayload({
+ formData: payload.order,
+ itemRows: payload.items,
+ fieldDefinitions: fieldDefinitions.value
+ })
+
+ if (dialogType.value === 'edit') {
+ await fetchUpdateAsnOrderWithItems(requestPayload)
+ ElMessage.success(t('pages.orders.asnOrder.messages.updateSuccess'))
+ } else {
+ await fetchSaveAsnOrderWithItems(requestPayload)
+ ElMessage.success(t('pages.orders.asnOrder.messages.createSuccess'))
+ }
+
+ dialogVisible.value = false
+ currentOrderData.value = createEmptyDialogData()
+ await refreshData()
+ } catch (error) {
+ ElMessage.error(
+ error?.message ||
+ (dialogType.value === 'edit'
+ ? t('pages.orders.asnOrder.messages.updateFailed')
+ : t('pages.orders.asnOrder.messages.createFailed'))
+ )
+ }
+ }
+
+ async function handleInspectSelected() {
+ if (!selectedRows.value.length) {
+ ElMessage.warning(t('pages.orders.asnOrder.messages.inspectionSelectRequired'))
+ return
+ }
+
+ try {
+ await ElMessageBox.confirm(
+ t('pages.orders.asnOrder.messages.inspectionConfirm', { count: selectedRows.value.length }),
+ t('pages.orders.asnOrder.messages.inspectionTitle'),
+ {
+ confirmButtonText: t('common.confirm'),
+ cancelButtonText: t('common.cancel'),
+ type: 'warning'
+ }
+ )
+ await fetchInspectAsnOrder(selectedRows.value.map((row) => ({ id: row.id })))
+ ElMessage.success(t('pages.orders.asnOrder.messages.inspectionSuccess'))
+ await refreshData()
+ } catch (error) {
+ if (error === 'cancel' || error?.message === 'cancel') {
+ return
+ }
+ ElMessage.error(error?.message || t('pages.orders.asnOrder.messages.inspectionFailed'))
+ }
+ }
+
+ async function handleImportFileChange(uploadFile) {
+ if (!uploadFile?.raw) {
+ return
+ }
+ importing.value = true
+ try {
+ await fetchImportAsnOrder(uploadFile.raw)
+ ElMessage.success(t('pages.orders.asnOrder.messages.importSuccess'))
+ await refreshData()
+ } catch (error) {
+ ElMessage.error(error?.message || t('pages.orders.asnOrder.messages.importFailed'))
+ } finally {
+ importing.value = false
+ }
+ }
+
+ async function downloadFile(response, fallbackName) {
+ const blob = await response.blob()
+ if (!blob || !blob.size) {
+ throw new Error(t('pages.orders.asnOrder.messages.templateDownloadFailed'))
+ }
+ const disposition = response.headers.get('Content-Disposition') || ''
+ const matchedName =
+ disposition.match(/filename\*=UTF-8''([^;]+)/i)?.[1] ||
+ disposition.match(/filename="?([^";]+)"?/i)?.[1]
+ const fileName = matchedName ? decodeURIComponent(matchedName) : fallbackName
+ const url = URL.createObjectURL(blob)
+ const anchor = document.createElement('a')
+ anchor.href = url
+ anchor.download = fileName
+ document.body.appendChild(anchor)
+ anchor.click()
+ anchor.remove()
+ URL.revokeObjectURL(url)
+ }
+
+ async function handleDownloadTemplate() {
+ templateDownloading.value = true
+ try {
+ const response = await fetchDownloadAsnOrderTemplate(
+ {},
+ {
+ headers: {
+ Authorization: userStore.accessToken || ''
+ }
+ }
+ )
+ await downloadFile(response, 'asn-order-template.xlsx')
+ ElMessage.success(t('pages.orders.asnOrder.messages.templateDownloadSuccess'))
+ } catch (error) {
+ ElMessage.error(error?.message || t('pages.orders.asnOrder.messages.templateDownloadFailed'))
+ } finally {
+ templateDownloading.value = false
+ }
+ }
+
+ async function loadTypeOptions() {
+ const response = await guardRequestWithMessage(
+ fetchDictDataPage({
+ current: 1,
+ pageSize: 200,
+ dictTypeCode: 'sys_order_type',
+ group: '1',
+ status: 1
+ }),
+ { records: [] },
+ { timeoutMessage: t('pages.orders.asnOrder.messages.typeOptionsTimeout') }
+ )
+ typeOptions.value = resolveDictOptions(defaultResponseAdapter(response).records, { group: '1' })
+ }
+
+ async function loadWkTypeOptions() {
+ const response = await guardRequestWithMessage(
+ fetchDictDataPage({
+ current: 1,
+ pageSize: 200,
+ dictTypeCode: 'sys_business_type',
+ group: '1',
+ status: 1
+ }),
+ { records: [] },
+ { timeoutMessage: t('pages.orders.asnOrder.messages.wkTypeOptionsTimeout') }
+ )
+ wkTypeOptions.value = resolveDictOptions(defaultResponseAdapter(response).records, {
+ group: '1'
+ })
+ }
+
+ async function loadFieldDefinitions() {
+ const records = await guardRequestWithMessage(fetchEnabledAsnOrderFields(), [], {
+ timeoutMessage: t('pages.orders.asnOrder.messages.fieldOptionsTimeout')
+ })
+ fieldDefinitions.value = Array.isArray(records) ? records : []
+ }
+
const {
previewVisible,
previewRows,
@@ -404,8 +795,13 @@
buildAsnOrderReportMeta({
previewMeta: previewMeta.value,
count: previewRows.value.length,
- orientation: previewMeta.value?.reportStyle?.orientation || ASN_ORDER_REPORT_STYLE.orientation,
+ orientation:
+ previewMeta.value?.reportStyle?.orientation || ASN_ORDER_REPORT_STYLE.orientation,
t
})
)
+
+ onMounted(async () => {
+ await Promise.allSettled([loadTypeOptions(), loadWkTypeOptions(), loadFieldDefinitions()])
+ })
</script>
diff --git a/rsf-design/src/views/orders/asn-order/modules/asn-order-create-by-po-dialog.vue b/rsf-design/src/views/orders/asn-order/modules/asn-order-create-by-po-dialog.vue
index 578c02c..5eb3b79 100644
--- a/rsf-design/src/views/orders/asn-order/modules/asn-order-create-by-po-dialog.vue
+++ b/rsf-design/src/views/orders/asn-order/modules/asn-order-create-by-po-dialog.vue
@@ -20,8 +20,12 @@
<ElCard class="art-table-card" shadow="never">
<template #header>
<div class="flex items-center justify-between gap-3">
- <div class="text-sm font-medium">{{ t('pages.orders.asnOrder.createByPoDialog.purchaseList') }}</div>
- <ElButton :loading="purchaseLoading" @click="refreshPurchaseData">{{ t('common.actions.refresh') }}</ElButton>
+ <div class="text-sm font-medium">{{
+ t('pages.orders.asnOrder.createByPoDialog.purchaseList')
+ }}</div>
+ <ElButton :loading="purchaseLoading" @click="refreshPurchaseData">{{
+ t('common.actions.refresh')
+ }}</ElButton>
</div>
</template>
@@ -39,11 +43,15 @@
<template #header>
<div class="flex items-center justify-between gap-3">
<div class="flex flex-col">
- <span class="text-sm font-medium">{{ t('pages.orders.asnOrder.createByPoDialog.purchasePreview') }}</span>
+ <span class="text-sm font-medium">{{
+ t('pages.orders.asnOrder.createByPoDialog.purchasePreview')
+ }}</span>
<span class="text-xs text-[var(--art-gray-600)]">
{{
selectedPurchase.code
- ? t('pages.orders.asnOrder.createByPoDialog.purchaseSelected', { code: selectedPurchase.code })
+ ? t('pages.orders.asnOrder.createByPoDialog.purchaseSelected', {
+ code: selectedPurchase.code
+ })
: t('pages.orders.asnOrder.createByPoDialog.purchaseEmpty')
}}
</span>
@@ -68,7 +76,10 @@
<div class="text-xs text-[var(--art-gray-600)]">
{{
selectedPurchase.id
- ? t('pages.orders.asnOrder.createByPoDialog.purchaseGenerateHint', { code: selectedPurchase.code, count: purchaseItems.length })
+ ? t('pages.orders.asnOrder.createByPoDialog.purchaseGenerateHint', {
+ code: selectedPurchase.code,
+ count: purchaseItems.length
+ })
: t('pages.orders.asnOrder.createByPoDialog.purchaseGenerateEmpty')
}}
</div>
@@ -252,7 +263,7 @@
current: 1,
size: 200
},
- {
+ {
timeoutMessage: t('pages.orders.asnOrder.createByPoDialog.messages.purchaseItemsTimeout')
}
)
@@ -274,7 +285,9 @@
size: total
},
{
- timeoutMessage: t('pages.orders.asnOrder.createByPoDialog.messages.purchaseItemsAllTimeout')
+ timeoutMessage: t(
+ 'pages.orders.asnOrder.createByPoDialog.messages.purchaseItemsAllTimeout'
+ )
}
)
purchaseItems.value = Array.isArray(fullPage?.records)
@@ -333,7 +346,9 @@
emit('success')
handleVisibleChange(false)
} catch (error) {
- ElMessage.error(error?.message || t('pages.orders.asnOrder.createByPoDialog.messages.createByPoFailed'))
+ ElMessage.error(
+ error?.message || t('pages.orders.asnOrder.createByPoDialog.messages.createByPoFailed')
+ )
} finally {
submitLoading.value = false
}
diff --git a/rsf-design/src/views/orders/asn-order/modules/asn-order-detail-drawer.vue b/rsf-design/src/views/orders/asn-order/modules/asn-order-detail-drawer.vue
index 903e6d1..097d55b 100644
--- a/rsf-design/src/views/orders/asn-order/modules/asn-order-detail-drawer.vue
+++ b/rsf-design/src/views/orders/asn-order/modules/asn-order-detail-drawer.vue
@@ -9,27 +9,59 @@
<ElScrollbar class="h-[calc(100vh-180px)] pr-1">
<div class="space-y-4">
<ElDescriptions :title="t('pages.orders.asnOrder.detail.baseInfo')" :column="4" border>
- <ElDescriptionsItem :label="t('pages.orders.asnOrder.detail.asnCode')">{{ detail.code || '--' }}</ElDescriptionsItem>
- <ElDescriptionsItem :label="t('pages.orders.asnOrder.detail.poCode')">{{ detail.poCode || '--' }}</ElDescriptionsItem>
- <ElDescriptionsItem :label="t('pages.orders.asnOrder.detail.wkType')">{{ detail.wkTypeLabel || '--' }}</ElDescriptionsItem>
- <ElDescriptionsItem :label="t('pages.orders.asnOrder.detail.orderType')">{{ detail.orderTypeLabel || '--' }}</ElDescriptionsItem>
- <ElDescriptionsItem :label="t('pages.orders.asnOrder.detail.status')">{{ detail.exceStatusText || '--' }}</ElDescriptionsItem>
- <ElDescriptionsItem :label="t('pages.orders.asnOrder.detail.purchaseOrg')">{{ detail.purchaseOrgName || '--' }}</ElDescriptionsItem>
- <ElDescriptionsItem :label="t('pages.orders.asnOrder.detail.purchaseUser')">{{ detail.purchaseUserName || '--' }}</ElDescriptionsItem>
- <ElDescriptionsItem :label="t('pages.orders.asnOrder.detail.supplier')">{{ detail.supplierName || '--' }}</ElDescriptionsItem>
- <ElDescriptionsItem :label="t('pages.orders.asnOrder.detail.anfme')">{{ detail.anfme ?? 0 }}</ElDescriptionsItem>
- <ElDescriptionsItem :label="t('pages.orders.asnOrder.detail.qty')">{{ detail.qty ?? 0 }}</ElDescriptionsItem>
- <ElDescriptionsItem :label="t('pages.orders.asnOrder.detail.updateTime')">{{ detail.updateTimeText || '--' }}</ElDescriptionsItem>
- <ElDescriptionsItem :label="t('pages.orders.asnOrder.detail.createTime')">{{ detail.createTimeText || '--' }}</ElDescriptionsItem>
- <ElDescriptionsItem :label="t('pages.orders.asnOrder.detail.memo')" :span="4">{{ detail.memo || '--' }}</ElDescriptionsItem>
+ <ElDescriptionsItem :label="t('pages.orders.asnOrder.detail.asnCode')">{{
+ detail.code || '--'
+ }}</ElDescriptionsItem>
+ <ElDescriptionsItem :label="t('pages.orders.asnOrder.detail.poCode')">{{
+ detail.poCode || '--'
+ }}</ElDescriptionsItem>
+ <ElDescriptionsItem :label="t('pages.orders.asnOrder.detail.wkType')">{{
+ detail.wkTypeLabel || '--'
+ }}</ElDescriptionsItem>
+ <ElDescriptionsItem :label="t('pages.orders.asnOrder.detail.orderType')">{{
+ detail.orderTypeLabel || '--'
+ }}</ElDescriptionsItem>
+ <ElDescriptionsItem :label="t('pages.orders.asnOrder.detail.status')">{{
+ detail.exceStatusText || '--'
+ }}</ElDescriptionsItem>
+ <ElDescriptionsItem :label="t('pages.orders.asnOrder.detail.purchaseOrg')">{{
+ detail.purchaseOrgName || '--'
+ }}</ElDescriptionsItem>
+ <ElDescriptionsItem :label="t('pages.orders.asnOrder.detail.purchaseUser')">{{
+ detail.purchaseUserName || '--'
+ }}</ElDescriptionsItem>
+ <ElDescriptionsItem :label="t('pages.orders.asnOrder.detail.supplier')">{{
+ detail.supplierName || '--'
+ }}</ElDescriptionsItem>
+ <ElDescriptionsItem :label="t('pages.orders.asnOrder.detail.anfme')">{{
+ detail.anfme ?? 0
+ }}</ElDescriptionsItem>
+ <ElDescriptionsItem :label="t('pages.orders.asnOrder.detail.qty')">{{
+ detail.qty ?? 0
+ }}</ElDescriptionsItem>
+ <ElDescriptionsItem :label="t('pages.orders.asnOrder.detail.updateTime')">{{
+ detail.updateTimeText || '--'
+ }}</ElDescriptionsItem>
+ <ElDescriptionsItem :label="t('pages.orders.asnOrder.detail.createTime')">{{
+ detail.createTimeText || '--'
+ }}</ElDescriptionsItem>
+ <ElDescriptionsItem :label="t('pages.orders.asnOrder.detail.memo')" :span="4">{{
+ detail.memo || '--'
+ }}</ElDescriptionsItem>
</ElDescriptions>
<div class="space-y-3">
<div class="flex items-center justify-between">
- <div class="text-sm font-medium text-[var(--art-gray-900)]">{{ t('pages.orders.asnOrder.detail.items') }}</div>
+ <div class="text-sm font-medium text-[var(--art-gray-900)]">{{
+ t('pages.orders.asnOrder.detail.items')
+ }}</div>
<div class="flex items-center gap-3">
- <ElTag effect="plain">{{ t('pages.orders.asnOrder.detail.count', { count: data.length }) }}</ElTag>
- <ElButton :loading="loading" @click="$emit('refresh')">{{ t('common.actions.refresh') }}</ElButton>
+ <ElTag effect="plain">{{
+ t('pages.orders.asnOrder.detail.count', { count: data.length })
+ }}</ElTag>
+ <ElButton :loading="loading" @click="$emit('refresh')">{{
+ t('common.actions.refresh')
+ }}</ElButton>
</div>
</div>
diff --git a/rsf-design/src/views/orders/asn-order/modules/asn-order-dialog.vue b/rsf-design/src/views/orders/asn-order/modules/asn-order-dialog.vue
new file mode 100644
index 0000000..4c31363
--- /dev/null
+++ b/rsf-design/src/views/orders/asn-order/modules/asn-order-dialog.vue
@@ -0,0 +1,370 @@
+<template>
+ <ElDialog
+ :model-value="visible"
+ :title="dialogTitle"
+ width="92%"
+ top="4vh"
+ destroy-on-close
+ @update:model-value="handleVisibleChange"
+ @closed="handleClosed"
+ >
+ <div class="flex flex-col gap-4">
+ <ArtForm
+ ref="formRef"
+ v-model="form"
+ :items="formItems"
+ :rules="rules"
+ :span="8"
+ :gutter="20"
+ label-width="110px"
+ :show-reset="false"
+ :show-submit="false"
+ />
+
+ <div class="flex flex-wrap items-center justify-between gap-3">
+ <ElSpace wrap>
+ <ElButton type="primary" @click="materialDialogVisible = true">
+ {{ t('pages.orders.asnOrder.dialog.addMaterial') }}
+ </ElButton>
+ <ElButton
+ type="danger"
+ plain
+ :disabled="selectedItemKeys.length === 0"
+ @click="handleBatchRemove"
+ >
+ {{ t('pages.orders.asnOrder.dialog.deleteSelected') }}
+ </ElButton>
+ </ElSpace>
+ <div class="text-xs text-[var(--art-gray-600)]">
+ {{ t('pages.orders.asnOrder.dialog.itemCount', { count: itemRows.length }) }}
+ </div>
+ </div>
+
+ <ElTable
+ :data="itemRows"
+ row-key="__rowKey"
+ border
+ size="small"
+ max-height="420"
+ @selection-change="handleItemSelectionChange"
+ >
+ <ElTableColumn type="selection" width="48" align="center" />
+ <ElTableColumn type="index" :label="t('table.index')" width="72" align="center" />
+ <ElTableColumn
+ prop="matnrCode"
+ :label="t('table.materialCode')"
+ min-width="140"
+ show-overflow-tooltip
+ />
+ <ElTableColumn
+ prop="maktx"
+ :label="t('table.materialName')"
+ min-width="220"
+ show-overflow-tooltip
+ />
+ <ElTableColumn
+ :label="t('pages.orders.asnOrder.table.expectedQty')"
+ width="140"
+ align="right"
+ >
+ <template #default="{ row }">
+ <ElInputNumber
+ v-model="row.anfme"
+ :min="0"
+ :precision="2"
+ controls-position="right"
+ class="w-full"
+ />
+ </template>
+ </ElTableColumn>
+ <ElTableColumn :label="t('pages.orders.asnOrder.table.poItemId')" min-width="120">
+ <template #default="{ row }">
+ <ElInput v-model="row.platItemId" clearable />
+ </template>
+ </ElTableColumn>
+ <ElTableColumn :label="t('table.supplierBatch')" min-width="140">
+ <template #default="{ row }">
+ <ElInput v-model="row.splrBatch" clearable />
+ </template>
+ </ElTableColumn>
+ <ElTableColumn prop="stockUnit" :label="t('table.unit')" width="100" align="center" />
+ <ElTableColumn
+ v-for="field in fieldDefinitions"
+ :key="field.fields"
+ :label="field.fieldsAlise || field.fields"
+ min-width="140"
+ >
+ <template #default="{ row }">
+ <ElInput v-model="row[field.fields]" clearable />
+ </template>
+ </ElTableColumn>
+ <ElTableColumn :label="t('table.operation')" fixed="right" width="88" align="center">
+ <template #default="{ row }">
+ <ElButton link type="danger" @click="handleRemoveRow(row)">
+ {{ t('pages.orders.asnOrder.actions.delete') }}
+ </ElButton>
+ </template>
+ </ElTableColumn>
+ </ElTable>
+ </div>
+
+ <template #footer>
+ <ElSpace>
+ <ElButton @click="handleVisibleChange(false)">{{ t('common.cancel') }}</ElButton>
+ <ElButton type="primary" @click="handleSubmit">{{ t('common.confirm') }}</ElButton>
+ </ElSpace>
+ </template>
+
+ <AsnOrderMaterialDialog
+ v-model:visible="materialDialogVisible"
+ :field-definitions="fieldDefinitions"
+ :selected-matnr-ids="selectedMatnrIds"
+ @confirm="handleMaterialConfirm"
+ />
+ </ElDialog>
+</template>
+
+<script setup>
+ import { computed, nextTick, reactive, ref, watch } from 'vue'
+ import { ElMessage } from 'element-plus'
+ import { useI18n } from 'vue-i18n'
+ import ArtForm from '@/components/core/forms/art-form/index.vue'
+ import {
+ buildAsnOrderDialogModel,
+ buildAsnOrderEditableItem,
+ createAsnOrderEditableItemFromMaterial,
+ createAsnOrderFormState
+ } from '../asnOrderPage.helpers'
+ import AsnOrderMaterialDialog from './asn-order-material-dialog.vue'
+
+ const props = defineProps({
+ visible: { type: Boolean, default: false },
+ dialogType: { type: String, default: 'add' },
+ orderData: {
+ type: Object,
+ default: () => ({
+ order: {},
+ items: []
+ })
+ },
+ typeOptions: { type: Array, default: () => [] },
+ wkTypeOptions: { type: Array, default: () => [] },
+ fieldDefinitions: { type: Array, default: () => [] }
+ })
+
+ const emit = defineEmits(['update:visible', 'submit'])
+ const { t } = useI18n()
+
+ const formRef = ref()
+ const form = reactive(createAsnOrderFormState())
+ const itemRows = ref([])
+ const selectedItemKeys = ref([])
+ const materialDialogVisible = ref(false)
+
+ const isEdit = computed(() => props.dialogType === 'edit')
+ const dialogTitle = computed(() =>
+ isEdit.value
+ ? t('pages.orders.asnOrder.dialog.editTitle')
+ : t('pages.orders.asnOrder.dialog.createTitle')
+ )
+ const selectedMatnrIds = computed(() =>
+ itemRows.value.map((item) => item.matnrId).filter((item) => item !== undefined && item !== null)
+ )
+
+ const rules = computed(() => ({
+ type: [
+ {
+ required: true,
+ message: t('pages.orders.asnOrder.dialog.validation.type'),
+ trigger: 'change'
+ }
+ ],
+ wkType: [
+ {
+ required: true,
+ message: t('pages.orders.asnOrder.dialog.validation.wkType'),
+ trigger: 'change'
+ }
+ ]
+ }))
+
+ const formItems = computed(() => [
+ {
+ label: t('pages.orders.asnOrder.detail.orderType'),
+ key: 'type',
+ type: 'select',
+ props: {
+ clearable: true,
+ filterable: true,
+ placeholder: t('pages.orders.asnOrder.dialog.placeholder.type'),
+ options: props.typeOptions
+ }
+ },
+ {
+ label: t('pages.orders.asnOrder.detail.wkType'),
+ key: 'wkType',
+ type: 'select',
+ props: {
+ clearable: true,
+ filterable: true,
+ placeholder: t('pages.orders.asnOrder.dialog.placeholder.wkType'),
+ options: props.wkTypeOptions
+ }
+ },
+ {
+ label: t('pages.orders.asnOrder.detail.poCode'),
+ key: 'poCode',
+ type: 'input',
+ props: {
+ clearable: true,
+ placeholder: t('pages.orders.asnOrder.dialog.placeholder.poCode')
+ }
+ },
+ {
+ label: t('pages.orders.asnOrder.search.logisNo'),
+ key: 'logisNo',
+ type: 'input',
+ props: {
+ clearable: true,
+ placeholder: t('pages.orders.asnOrder.dialog.placeholder.logisNo')
+ }
+ },
+ {
+ label: t('pages.orders.asnOrder.search.arrTime'),
+ key: 'arrTime',
+ type: 'datetime',
+ props: {
+ clearable: true,
+ valueFormat: 'YYYY-MM-DD HH:mm:ss',
+ format: 'YYYY-MM-DD HH:mm:ss',
+ placeholder: t('pages.orders.asnOrder.dialog.placeholder.arrTime')
+ }
+ },
+ {
+ label: t('pages.orders.asnOrder.detail.memo'),
+ key: 'memo',
+ type: 'input',
+ span: 24,
+ props: {
+ type: 'textarea',
+ rows: 3,
+ clearable: true,
+ placeholder: t('pages.orders.asnOrder.dialog.placeholder.memo')
+ }
+ }
+ ])
+
+ function loadDialogData() {
+ Object.assign(form, buildAsnOrderDialogModel(props.orderData?.order || {}))
+ itemRows.value = Array.isArray(props.orderData?.items)
+ ? props.orderData.items.map((item) => buildAsnOrderEditableItem(item, props.fieldDefinitions))
+ : []
+ selectedItemKeys.value = []
+ }
+
+ function resetDialogData() {
+ Object.assign(form, createAsnOrderFormState())
+ itemRows.value = []
+ selectedItemKeys.value = []
+ formRef.value?.clearValidate?.()
+ }
+
+ function handleItemSelectionChange(rows) {
+ selectedItemKeys.value = Array.isArray(rows) ? rows.map((item) => item.__rowKey) : []
+ }
+
+ function handleRemoveRow(row) {
+ itemRows.value = itemRows.value.filter((item) => item.__rowKey !== row.__rowKey)
+ selectedItemKeys.value = selectedItemKeys.value.filter((item) => item !== row.__rowKey)
+ }
+
+ function handleBatchRemove() {
+ if (!selectedItemKeys.value.length) {
+ return
+ }
+ const selectedKeySet = new Set(selectedItemKeys.value)
+ itemRows.value = itemRows.value.filter((item) => !selectedKeySet.has(item.__rowKey))
+ selectedItemKeys.value = []
+ }
+
+ function handleMaterialConfirm(rows) {
+ const existingMatnrIds = new Set(selectedMatnrIds.value)
+ const nextRows = rows
+ .map((item) => createAsnOrderEditableItemFromMaterial(item, props.fieldDefinitions))
+ .filter((item) => !existingMatnrIds.has(item.matnrId))
+
+ if (!nextRows.length) {
+ ElMessage.warning(t('pages.orders.asnOrder.dialog.materialDuplicate'))
+ return
+ }
+
+ itemRows.value = [...itemRows.value, ...nextRows]
+ }
+
+ function validateItems() {
+ if (!itemRows.value.length) {
+ ElMessage.warning(t('pages.orders.asnOrder.dialog.validation.items'))
+ return false
+ }
+
+ const invalidRow = itemRows.value.find((item) => Number(item.anfme) <= 0)
+ if (invalidRow) {
+ ElMessage.warning(t('pages.orders.asnOrder.dialog.validation.anfme'))
+ return false
+ }
+
+ return true
+ }
+
+ async function handleSubmit() {
+ if (!formRef.value) {
+ return
+ }
+
+ try {
+ await formRef.value.validate()
+ } catch {
+ return
+ }
+
+ if (!validateItems()) {
+ return
+ }
+
+ emit('submit', {
+ order: { ...form },
+ items: itemRows.value.map((item) => ({ ...item }))
+ })
+ }
+
+ function handleVisibleChange(visible) {
+ emit('update:visible', visible)
+ }
+
+ function handleClosed() {
+ resetDialogData()
+ }
+
+ watch(
+ () => props.visible,
+ (visible) => {
+ if (visible) {
+ loadDialogData()
+ nextTick(() => {
+ formRef.value?.clearValidate?.()
+ })
+ }
+ },
+ { immediate: true }
+ )
+
+ watch(
+ () => props.orderData,
+ () => {
+ if (props.visible) {
+ loadDialogData()
+ }
+ },
+ { deep: true }
+ )
+</script>
diff --git a/rsf-design/src/views/orders/asn-order/modules/asn-order-item-dialog.vue b/rsf-design/src/views/orders/asn-order/modules/asn-order-item-dialog.vue
new file mode 100644
index 0000000..db63b16
--- /dev/null
+++ b/rsf-design/src/views/orders/asn-order/modules/asn-order-item-dialog.vue
@@ -0,0 +1,342 @@
+<template>
+ <ElDialog
+ :model-value="visible"
+ :title="t('menus.orders.asnOrderItem')"
+ width="92vw"
+ top="4vh"
+ destroy-on-close
+ append-to-body
+ @update:model-value="handleVisibleChange"
+ >
+ <div class="flex h-[78vh] min-h-0 flex-col gap-3">
+ <ElCard shadow="never" class="shrink-0">
+ <ElDescriptions :column="4" border>
+ <ElDescriptionsItem :label="t('pages.orders.asnOrder.detail.asnCode')">
+ {{ order.code || t('common.placeholder.empty') }}
+ </ElDescriptionsItem>
+ <ElDescriptionsItem :label="t('pages.orders.asnOrder.detail.poCode')">
+ {{ order.poCode || t('common.placeholder.empty') }}
+ </ElDescriptionsItem>
+ <ElDescriptionsItem :label="t('pages.orders.asnOrder.detail.wkType')">
+ {{ order.wkTypeLabel || t('common.placeholder.empty') }}
+ </ElDescriptionsItem>
+ <ElDescriptionsItem :label="t('pages.orders.asnOrder.detail.status')">
+ {{ order.exceStatusText || t('common.placeholder.empty') }}
+ </ElDescriptionsItem>
+ </ElDescriptions>
+ </ElCard>
+
+ <ArtSearchBar
+ v-model="searchForm"
+ class="shrink-0"
+ :items="searchItems"
+ :showExpand="true"
+ @search="handleSearch"
+ @reset="handleReset"
+ />
+
+ <ElCard class="art-table-card min-h-0 flex-1">
+ <ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="refreshData" />
+
+ <ArtTable
+ :loading="loading"
+ :data="data"
+ :columns="columns"
+ :pagination="pagination"
+ @pagination:size-change="handleSizeChange"
+ @pagination:current-change="handleCurrentChange"
+ />
+ </ElCard>
+ </div>
+
+ <template #footer>
+ <ElButton @click="handleVisibleChange(false)">{{ t('common.cancel') }}</ElButton>
+ </template>
+
+ <AsnOrderItemDetailDrawer
+ v-model:visible="detailDrawerVisible"
+ :loading="detailLoading"
+ :detail="detailData"
+ />
+ </ElDialog>
+</template>
+
+<script setup>
+ import { ref, watch } from 'vue'
+ import { useI18n } from 'vue-i18n'
+ import { ElMessage } from 'element-plus'
+ import { useTable } from '@/hooks/core/useTable'
+ import { guardRequestWithMessage } from '@/utils/sys/requestGuard'
+ import { fetchAsnOrderItemFullPage, fetchGetAsnOrderItemDetail } from '@/api/asn-order-item'
+ import AsnOrderItemDetailDrawer from '../../asn-order-item/modules/asn-order-item-detail-drawer.vue'
+ import { createAsnOrderItemTableColumns } from '../../asn-order-item/asnOrderItemTable.columns'
+ import {
+ buildAsnOrderItemPageQueryParams,
+ createAsnOrderItemSearchState,
+ normalizeAsnOrderItemDetail,
+ normalizeAsnOrderItemRow
+ } from '../../asn-order-item/asnOrderItemPage.helpers'
+
+ defineOptions({ name: 'AsnOrderItemDialog' })
+
+ const DEFAULT_PAGE_SIZE = 20
+
+ const props = defineProps({
+ visible: { type: Boolean, default: false },
+ order: { type: Object, default: () => ({}) }
+ })
+
+ const emit = defineEmits(['update:visible'])
+ const { t } = useI18n()
+
+ const searchForm = ref(createDialogSearchState())
+ const detailDrawerVisible = ref(false)
+ const detailLoading = ref(false)
+ const detailData = ref({})
+ const activeItemId = ref(null)
+
+ const searchItems = computed(() => [
+ {
+ label: t('table.keyword'),
+ key: 'condition',
+ type: 'input',
+ props: {
+ clearable: true,
+ placeholder: t('pages.orders.asnOrderItem.search.conditionPlaceholder')
+ }
+ },
+ {
+ label: t('pages.orders.asnOrderItem.search.poCode'),
+ key: 'poCode',
+ type: 'input',
+ props: {
+ clearable: true,
+ placeholder: t('pages.orders.asnOrderItem.search.poCodePlaceholder')
+ }
+ },
+ {
+ label: t('pages.orders.asnOrderItem.search.platWorkCode'),
+ key: 'platWorkCode',
+ type: 'input',
+ props: {
+ clearable: true,
+ placeholder: t('pages.orders.asnOrderItem.search.platWorkCodePlaceholder')
+ }
+ },
+ {
+ label: t('pages.orders.asnOrderItem.search.platItemId'),
+ key: 'platItemId',
+ type: 'input',
+ props: {
+ clearable: true,
+ placeholder: t('pages.orders.asnOrderItem.search.platItemIdPlaceholder')
+ }
+ },
+ {
+ label: t('table.materialCode'),
+ key: 'matnrCode',
+ type: 'input',
+ props: {
+ clearable: true,
+ placeholder: t('pages.orders.asnOrderItem.search.matnrCodePlaceholder')
+ }
+ },
+ {
+ label: t('table.materialName'),
+ key: 'maktx',
+ type: 'input',
+ props: {
+ clearable: true,
+ placeholder: t('pages.orders.asnOrderItem.search.maktxPlaceholder')
+ }
+ },
+ {
+ label: t('table.supplierBatch'),
+ key: 'splrBatch',
+ type: 'input',
+ props: {
+ clearable: true,
+ placeholder: t('pages.orders.asnOrderItem.search.splrBatchPlaceholder')
+ }
+ },
+ {
+ label: t('pages.orders.asnOrderItem.search.stockUnit'),
+ key: 'stockUnit',
+ type: 'input',
+ props: {
+ clearable: true,
+ placeholder: t('pages.orders.asnOrderItem.search.stockUnitPlaceholder')
+ }
+ },
+ {
+ label: t('table.status'),
+ key: 'status',
+ type: 'select',
+ props: {
+ clearable: true,
+ options: [
+ { label: t('common.status.normal'), value: 1 },
+ { label: t('common.status.frozen'), value: 0 }
+ ]
+ }
+ },
+ {
+ label: t('pages.orders.asnOrderItem.search.ntyStatus'),
+ key: 'ntyStatus',
+ type: 'select',
+ props: {
+ clearable: true,
+ options: [
+ { label: t('pages.orders.asnOrderItem.ntyStatus.notReported'), value: 0 },
+ { label: t('pages.orders.asnOrderItem.ntyStatus.reported'), value: 1 }
+ ]
+ }
+ },
+ {
+ label: t('pages.orders.asnOrderItem.search.createTimeRange'),
+ key: 'createTimeRange',
+ type: 'datetimerange',
+ props: {
+ clearable: true,
+ startPlaceholder: t('pages.orders.asnOrderItem.search.startTime'),
+ endPlaceholder: t('pages.orders.asnOrderItem.search.endTime'),
+ rangeSeparator: t('pages.orders.asnOrderItem.search.rangeSeparator')
+ }
+ },
+ {
+ label: t('pages.orders.asnOrderItem.search.updateTimeRange'),
+ key: 'updateTimeRange',
+ type: 'datetimerange',
+ props: {
+ clearable: true,
+ startPlaceholder: t('pages.orders.asnOrderItem.search.startTime'),
+ endPlaceholder: t('pages.orders.asnOrderItem.search.endTime'),
+ rangeSeparator: t('pages.orders.asnOrderItem.search.rangeSeparator')
+ }
+ }
+ ])
+
+ const {
+ columns,
+ columnChecks,
+ data,
+ loading,
+ pagination,
+ replaceSearchParams,
+ handleSizeChange,
+ handleCurrentChange,
+ refreshData,
+ getData
+ } = useTable({
+ core: {
+ apiFn: fetchAsnOrderItemFullPage,
+ apiParams: buildQueryParams({
+ ...searchForm.value,
+ pageSize: DEFAULT_PAGE_SIZE
+ }),
+ immediate: false,
+ columnsFactory: () =>
+ createAsnOrderItemTableColumns({
+ handleView: openDetail,
+ t
+ })
+ },
+ transform: {
+ dataTransformer: (records) =>
+ Array.isArray(records) ? records.map((item) => normalizeAsnOrderItemRow(item, t)) : []
+ }
+ })
+
+ watch(
+ () => [props.visible, props.order?.id],
+ ([visible, orderId]) => {
+ if (!visible || !orderId) {
+ return
+ }
+ resetDialogState()
+ replaceSearchParams(buildQueryParams(searchForm.value))
+ getData()
+ },
+ { immediate: true }
+ )
+
+ function createDialogSearchState() {
+ return {
+ ...createAsnOrderItemSearchState(),
+ orderId: props.order?.id ? String(props.order.id) : ''
+ }
+ }
+
+ function buildQueryParams(params = {}) {
+ return buildAsnOrderItemPageQueryParams({
+ ...params,
+ orderId: props.order?.id ? String(props.order.id) : '',
+ orderBy: 'id desc'
+ })
+ }
+
+ function resetDialogState() {
+ searchForm.value = createDialogSearchState()
+ detailDrawerVisible.value = false
+ detailData.value = {}
+ activeItemId.value = null
+ }
+
+ function handleVisibleChange(value) {
+ emit('update:visible', value)
+ }
+
+ function handleSearch(params) {
+ searchForm.value = {
+ ...searchForm.value,
+ ...params,
+ orderId: props.order?.id ? String(props.order.id) : ''
+ }
+ replaceSearchParams(buildQueryParams(searchForm.value))
+ getData()
+ }
+
+ function handleReset() {
+ resetDialogState()
+ replaceSearchParams(buildQueryParams(searchForm.value))
+ getData()
+ }
+
+ async function openDetail(row) {
+ activeItemId.value = row.id
+ detailData.value = normalizeAsnOrderItemDetail(row, t)
+ detailDrawerVisible.value = true
+ await loadDetailResource()
+ }
+
+ async function loadDetailResource() {
+ if (!activeItemId.value) {
+ return
+ }
+
+ detailLoading.value = true
+ try {
+ const detailResponse = await guardRequestWithMessage(
+ fetchGetAsnOrderItemDetail(activeItemId.value),
+ {},
+ {
+ timeoutMessage: t('pages.orders.asnOrderItem.messages.detailTimeout')
+ }
+ )
+
+ detailData.value = normalizeAsnOrderItemDetail(
+ {
+ ...detailData.value,
+ ...detailResponse
+ },
+ t
+ )
+ } catch (error) {
+ detailDrawerVisible.value = false
+ detailData.value = {}
+ ElMessage.error(error?.message || t('pages.orders.asnOrderItem.messages.detailFailed'))
+ } finally {
+ detailLoading.value = false
+ }
+ }
+</script>
diff --git a/rsf-design/src/views/orders/asn-order/modules/asn-order-material-dialog.vue b/rsf-design/src/views/orders/asn-order/modules/asn-order-material-dialog.vue
new file mode 100644
index 0000000..babcf2e
--- /dev/null
+++ b/rsf-design/src/views/orders/asn-order/modules/asn-order-material-dialog.vue
@@ -0,0 +1,269 @@
+<template>
+ <ElDialog
+ :model-value="visible"
+ :title="t('pages.orders.asnOrder.materialDialog.title')"
+ width="88%"
+ top="5vh"
+ destroy-on-close
+ @update:model-value="handleVisibleChange"
+ >
+ <div class="flex flex-col gap-4">
+ <ArtSearchBar
+ v-model="searchForm"
+ :items="searchItems"
+ :showExpand="false"
+ @search="handleSearch"
+ @reset="handleReset"
+ />
+
+ <ArtTable
+ :loading="loading"
+ :data="data"
+ :columns="columns"
+ :pagination="pagination"
+ @selection-change="handleSelectionChange"
+ @pagination:size-change="handleSizeChange"
+ @pagination:current-change="handleCurrentChange"
+ />
+ </div>
+
+ <template #footer>
+ <ElSpace>
+ <ElButton @click="handleVisibleChange(false)">{{ t('common.cancel') }}</ElButton>
+ <ElButton type="primary" @click="handleConfirm">{{ t('common.confirm') }}</ElButton>
+ </ElSpace>
+ </template>
+ </ElDialog>
+</template>
+
+<script setup>
+ import { computed, h, ref, watch } from 'vue'
+ import { ElTag } from 'element-plus'
+ import { useI18n } from 'vue-i18n'
+ import { useTable } from '@/hooks/core/useTable'
+ import { defaultResponseAdapter } from '@/utils/table/tableUtils'
+ import { guardRequestWithMessage } from '@/utils/sys/requestGuard'
+ import { fetchMatnrGroupTree, fetchMatnrPage } from '@/api/wh-mat'
+ import {
+ buildAsnOrderMaterialSearchParams,
+ createAsnOrderMaterialSearchState,
+ normalizeTreeOptions
+ } from '../asnOrderPage.helpers'
+
+ const props = defineProps({
+ visible: { type: Boolean, default: false },
+ fieldDefinitions: { type: Array, default: () => [] },
+ selectedMatnrIds: { type: Array, default: () => [] }
+ })
+
+ const emit = defineEmits(['update:visible', 'confirm'])
+ const { t } = useI18n()
+
+ const searchForm = ref(createAsnOrderMaterialSearchState())
+ const selectedRows = ref([])
+ const groupTreeOptions = ref([])
+
+ const searchItems = computed(() => [
+ {
+ label: t('pages.orders.asnOrder.materialDialog.search.name'),
+ key: 'name',
+ type: 'input',
+ props: {
+ clearable: true,
+ placeholder: t('pages.orders.asnOrder.materialDialog.placeholder.name')
+ }
+ },
+ {
+ label: t('pages.orders.asnOrder.materialDialog.search.code'),
+ key: 'code',
+ type: 'input',
+ props: {
+ clearable: true,
+ placeholder: t('pages.orders.asnOrder.materialDialog.placeholder.code')
+ }
+ },
+ {
+ label: t('pages.orders.asnOrder.materialDialog.search.groupId'),
+ key: 'groupId',
+ type: 'treeselect',
+ props: {
+ clearable: true,
+ checkStrictly: true,
+ renderAfterExpand: false,
+ showCheckbox: false,
+ data: groupTreeOptions.value,
+ placeholder: t('pages.orders.asnOrder.materialDialog.placeholder.groupId')
+ }
+ }
+ ])
+
+ const createColumns = () => {
+ const baseColumns = [
+ { type: 'selection', width: 48, align: 'center' },
+ { type: 'globalIndex', label: t('table.index'), width: 72, align: 'center' },
+ {
+ prop: 'code',
+ label: t('pages.orders.asnOrder.materialDialog.table.code'),
+ minWidth: 170,
+ showOverflowTooltip: true
+ },
+ {
+ prop: 'name',
+ label: t('pages.orders.asnOrder.materialDialog.table.name'),
+ minWidth: 220,
+ showOverflowTooltip: true
+ },
+ {
+ prop: 'groupName',
+ label: t('pages.orders.asnOrder.materialDialog.table.group'),
+ minWidth: 140,
+ showOverflowTooltip: true
+ },
+ {
+ prop: 'spec',
+ label: t('pages.orders.asnOrder.materialDialog.table.spec'),
+ minWidth: 120,
+ showOverflowTooltip: true,
+ visible: false
+ },
+ {
+ prop: 'model',
+ label: t('pages.orders.asnOrder.materialDialog.table.model'),
+ minWidth: 120,
+ showOverflowTooltip: true,
+ visible: false
+ },
+ {
+ prop: 'unit',
+ label: t('table.unit'),
+ width: 100,
+ align: 'center'
+ },
+ {
+ prop: 'selected',
+ label: t('pages.orders.asnOrder.materialDialog.table.status'),
+ width: 110,
+ formatter: (row) =>
+ h(
+ ElTag,
+ {
+ type: props.selectedMatnrIds.includes(row.id) ? 'warning' : 'info',
+ effect: 'light'
+ },
+ () =>
+ props.selectedMatnrIds.includes(row.id)
+ ? t('pages.orders.asnOrder.materialDialog.selected')
+ : t('pages.orders.asnOrder.materialDialog.unselected')
+ )
+ }
+ ]
+
+ return [
+ ...baseColumns,
+ ...props.fieldDefinitions.map((item) => ({
+ prop: item.fields,
+ label: item.fieldsAlise || item.fields,
+ minWidth: 140,
+ showOverflowTooltip: true,
+ visible: false
+ }))
+ ]
+ }
+
+ const {
+ data,
+ loading,
+ pagination,
+ columns,
+ replaceSearchParams,
+ handleSizeChange,
+ handleCurrentChange,
+ getData,
+ resetColumns
+ } = useTable({
+ core: {
+ apiFn: fetchMatnrPage,
+ apiParams: buildAsnOrderMaterialSearchParams(searchForm.value),
+ immediate: false,
+ columnsFactory: createColumns
+ },
+ transform: {
+ dataTransformer: (records) =>
+ Array.isArray(records)
+ ? records.map((item) => ({
+ ...item,
+ groupName: item['groupId$'] || item.groupName || '-',
+ unit: item.unit || item.stockUnit || '-',
+ ...Object.fromEntries(
+ props.fieldDefinitions.map((field) => [
+ field.fields,
+ item[field.fields] ?? item.extendFields?.[field.fields] ?? ''
+ ])
+ )
+ }))
+ : []
+ }
+ })
+
+ function handleSelectionChange(rows) {
+ selectedRows.value = Array.isArray(rows) ? rows : []
+ }
+
+ function handleSearch(params) {
+ searchForm.value = {
+ ...searchForm.value,
+ ...params
+ }
+ replaceSearchParams(buildAsnOrderMaterialSearchParams(searchForm.value))
+ getData()
+ }
+
+ function handleReset() {
+ searchForm.value = createAsnOrderMaterialSearchState()
+ replaceSearchParams(buildAsnOrderMaterialSearchParams(searchForm.value))
+ getData()
+ }
+
+ function handleConfirm() {
+ emit(
+ 'confirm',
+ selectedRows.value.map((item) => ({ ...item }))
+ )
+ handleVisibleChange(false)
+ }
+
+ function handleVisibleChange(visible) {
+ emit('update:visible', visible)
+ }
+
+ async function loadGroupTree() {
+ const response = await guardRequestWithMessage(fetchMatnrGroupTree({}), [], {
+ timeoutMessage: t('pages.orders.asnOrder.materialDialog.messages.groupTimeout')
+ })
+ const records = Array.isArray(response) ? response : defaultResponseAdapter(response).records
+ groupTreeOptions.value = normalizeTreeOptions(records)
+ }
+
+ watch(
+ () => props.visible,
+ async (visible) => {
+ if (!visible) {
+ searchForm.value = createAsnOrderMaterialSearchState()
+ selectedRows.value = []
+ return
+ }
+
+ searchForm.value = createAsnOrderMaterialSearchState()
+ replaceSearchParams(buildAsnOrderMaterialSearchParams(searchForm.value))
+ await Promise.allSettled([loadGroupTree(), getData()])
+ }
+ )
+
+ watch(
+ () => props.fieldDefinitions,
+ () => {
+ resetColumns?.()
+ },
+ { deep: true }
+ )
+</script>
diff --git a/rsf-design/src/views/orders/delivery-item/deliveryItemPage.helpers.js b/rsf-design/src/views/orders/delivery-item/deliveryItemPage.helpers.js
index db0887c..fbb4491 100644
--- a/rsf-design/src/views/orders/delivery-item/deliveryItemPage.helpers.js
+++ b/rsf-design/src/views/orders/delivery-item/deliveryItemPage.helpers.js
@@ -50,21 +50,39 @@
platItemId: '',
matnrCode: '',
maktx: '',
+ fieldsIndex: '',
splrName: '',
+ splrCode: '',
+ status: '',
+ timeStart: '',
+ timeEnd: '',
+ memo: '',
+ orderBy: 'create_time desc',
splrBatch: ''
}
}
export function buildDeliveryItemSearchParams(params = {}) {
const result = {}
- ;['condition', 'deliveryCode', 'platItemId', 'matnrCode', 'maktx', 'splrName', 'splrCode', 'splrBatch', 'memo'].forEach(
- (key) => {
- const value = normalizeText(params[key])
- if (value) {
- result[key] = value
- }
+ ;[
+ 'condition',
+ 'deliveryCode',
+ 'platItemId',
+ 'matnrCode',
+ 'maktx',
+ 'fieldsIndex',
+ 'splrName',
+ 'splrCode',
+ 'splrBatch',
+ 'timeStart',
+ 'timeEnd',
+ 'memo'
+ ].forEach((key) => {
+ const value = normalizeText(params[key])
+ if (value) {
+ result[key] = value
}
- )
+ })
if (params.deliveryId !== '' && params.deliveryId !== undefined && params.deliveryId !== null) {
result.deliveryId = normalizeNumber(params.deliveryId)
@@ -81,6 +99,7 @@
return {
current: params.current || 1,
pageSize: params.pageSize || params.size || 20,
+ orderBy: normalizeText(params.orderBy) || 'create_time desc',
...buildDeliveryItemSearchParams(params)
}
}
@@ -117,18 +136,103 @@
batch: normalizeText(record.batch) || '--',
trackCode: normalizeText(record.trackCode) || '--',
packName: normalizeText(record.packName) || '--',
- prodTimeText: normalizeText(record['prodTime$'] || record.prodTimeText || record.prodTime) || '--',
+ prodTimeText:
+ normalizeText(record['prodTime$'] || record.prodTimeText || record.prodTime) || '--',
statusText: statusMeta.text,
statusType: statusMeta.type,
statusBool: record.statusBool !== void 0 ? Boolean(record.statusBool) : statusMeta.bool,
createByText: normalizeText(record['createBy$'] || record.createByText) || '--',
- createTimeText: normalizeText(record['createTime$'] || record.createTimeText || record.createTime) || '--',
+ createTimeText:
+ normalizeText(record['createTime$'] || record.createTimeText || record.createTime) || '--',
updateByText: normalizeText(record['updateBy$'] || record.updateByText) || '--',
- updateTimeText: normalizeText(record['updateTime$'] || record.updateTimeText || record.updateTime) || '--',
+ updateTimeText:
+ normalizeText(record['updateTime$'] || record.updateTimeText || record.updateTime) || '--',
memo: normalizeText(record.memo) || '--'
}
}
+export function createDeliveryItemFormState() {
+ return {
+ id: undefined,
+ deliveryId: undefined,
+ platItemId: '',
+ matnrId: undefined,
+ matnrCode: '',
+ maktx: '',
+ fieldsIndex: '',
+ unit: '',
+ anfme: undefined,
+ qty: undefined,
+ printQty: undefined,
+ splrName: '',
+ splrCode: '',
+ splrBatch: '',
+ status: 1,
+ memo: ''
+ }
+}
+
+export function buildDeliveryItemDialogModel(record = {}) {
+ return {
+ ...createDeliveryItemFormState(),
+ ...record,
+ id: record.id ?? undefined,
+ deliveryId: record.deliveryId ?? undefined,
+ platItemId: normalizeText(record.platItemId),
+ matnrId: record.matnrId ?? undefined,
+ matnrCode: normalizeText(record.matnrCode),
+ maktx: normalizeText(record.maktx || record.matnrName),
+ fieldsIndex: normalizeText(record.fieldsIndex),
+ unit: normalizeText(record.unit),
+ anfme: normalizeNumber(record.anfme, undefined),
+ qty: normalizeNumber(record.qty, undefined),
+ printQty: normalizeNumber(record.printQty, undefined),
+ splrName: normalizeText(record.splrName),
+ splrCode: normalizeText(record.splrCode),
+ splrBatch: normalizeText(record.splrBatch),
+ status: normalizeNumber(record.statusBool ?? record.status, 1),
+ memo: normalizeText(record.memo)
+ }
+}
+
+export function buildDeliveryItemSavePayload(formData = {}) {
+ return {
+ ...(formData.id !== undefined && formData.id !== null ? { id: Number(formData.id) } : {}),
+ ...(formData.deliveryId !== undefined && formData.deliveryId !== null
+ ? { deliveryId: Number(formData.deliveryId) }
+ : {}),
+ ...(formData.matnrId !== undefined && formData.matnrId !== null
+ ? { matnrId: Number(formData.matnrId) }
+ : {}),
+ platItemId: normalizeText(formData.platItemId),
+ matnrCode: normalizeText(formData.matnrCode),
+ maktx: normalizeText(formData.maktx),
+ fieldsIndex: normalizeText(formData.fieldsIndex),
+ unit: normalizeText(formData.unit),
+ ...(formData.anfme !== undefined && formData.anfme !== null && formData.anfme !== ''
+ ? { anfme: Number(formData.anfme) }
+ : {}),
+ ...(formData.qty !== undefined && formData.qty !== null && formData.qty !== ''
+ ? { qty: Number(formData.qty) }
+ : {}),
+ ...(formData.printQty !== undefined && formData.printQty !== null && formData.printQty !== ''
+ ? { printQty: Number(formData.printQty) }
+ : {}),
+ splrName: normalizeText(formData.splrName),
+ splrCode: normalizeText(formData.splrCode),
+ splrBatch: normalizeText(formData.splrBatch),
+ status: normalizeNumber(formData.status, 1),
+ memo: normalizeText(formData.memo)
+ }
+}
+
+export function getDeliveryItemStatusOptions(t = $t) {
+ return [
+ { value: 1, label: t('common.status.normal') },
+ { value: 0, label: t('common.status.frozen') }
+ ]
+}
+
export function buildDeliveryItemPrintRows(records = [], t = $t) {
if (!Array.isArray(records)) {
return []
diff --git a/rsf-design/src/views/orders/delivery-item/deliveryItemTable.columns.js b/rsf-design/src/views/orders/delivery-item/deliveryItemTable.columns.js
index 23275c6..3e0c3ce 100644
--- a/rsf-design/src/views/orders/delivery-item/deliveryItemTable.columns.js
+++ b/rsf-design/src/views/orders/delivery-item/deliveryItemTable.columns.js
@@ -1,12 +1,54 @@
import { h } from 'vue'
import { ElTag } from 'element-plus'
-import ArtButtonTable from '@/components/core/forms/art-button-table/index.vue'
+import ArtButtonMore from '@/components/core/forms/art-button-more/index.vue'
import { $t } from '@/locales'
-export function createDeliveryItemTableColumns({ handleActionClick, t = $t } = {}) {
+function createActionList({ canEdit = true, canDelete = true, t = $t } = {}) {
+ const actions = [
+ {
+ key: 'view',
+ label: t('pages.orders.delivery.actions.view'),
+ icon: 'ri:eye-line'
+ }
+ ]
+
+ if (canEdit) {
+ actions.push({
+ key: 'edit',
+ label: t('common.actions.edit'),
+ icon: 'ri:edit-line'
+ })
+ }
+
+ if (canDelete) {
+ actions.push({
+ key: 'delete',
+ label: t('common.actions.delete'),
+ icon: 'ri:delete-bin-5-line',
+ color: 'var(--art-error)'
+ })
+ }
+
+ return actions
+}
+
+export function createDeliveryItemTableColumns({
+ handleActionClick,
+ canEdit = true,
+ canDelete = true,
+ t = $t
+} = {}) {
return [
{ type: 'selection', width: 48, align: 'center' },
{ type: 'globalIndex', label: t('table.index'), width: 72, align: 'center' },
+ {
+ prop: 'id',
+ label: t('table.id'),
+ width: 100,
+ align: 'right',
+ visible: false,
+ formatter: (row) => row.id ?? '--'
+ },
{
prop: 'deliveryCode',
label: t('pages.orders.deliveryItem.table.deliveryCode'),
@@ -121,6 +163,38 @@
h(ElTag, { type: row.statusType || 'info', effect: 'light' }, () => row.statusText || '--')
},
{
+ prop: 'updateByText',
+ label: t('table.updateBy'),
+ minWidth: 130,
+ visible: false,
+ showOverflowTooltip: true,
+ formatter: (row) => row.updateByText || '--'
+ },
+ {
+ prop: 'updateTimeText',
+ label: t('table.updateTime'),
+ minWidth: 170,
+ visible: false,
+ showOverflowTooltip: true,
+ formatter: (row) => row.updateTimeText || '--'
+ },
+ {
+ prop: 'createByText',
+ label: t('table.createBy'),
+ minWidth: 130,
+ visible: false,
+ showOverflowTooltip: true,
+ formatter: (row) => row.createByText || '--'
+ },
+ {
+ prop: 'createTimeText',
+ label: t('table.createTime'),
+ minWidth: 170,
+ visible: false,
+ showOverflowTooltip: true,
+ formatter: (row) => row.createTimeText || '--'
+ },
+ {
prop: 'memo',
label: t('table.memo'),
minWidth: 180,
@@ -130,12 +204,12 @@
{
prop: 'operation',
label: t('table.operation'),
- width: 92,
+ width: 120,
fixed: 'right',
formatter: (row) =>
- h(ArtButtonTable, {
- type: 'view',
- onClick: () => handleActionClick?.(row)
+ h(ArtButtonMore, {
+ list: createActionList({ canEdit, canDelete, t }),
+ onClick: (item) => handleActionClick?.(item, row)
})
}
]
diff --git a/rsf-design/src/views/orders/delivery-item/index.vue b/rsf-design/src/views/orders/delivery-item/index.vue
index 0c0f089..312980f 100644
--- a/rsf-design/src/views/orders/delivery-item/index.vue
+++ b/rsf-design/src/views/orders/delivery-item/index.vue
@@ -3,38 +3,24 @@
<ElCard v-if="activeSourceSummary" class="mb-3">
<div class="flex items-center justify-between gap-3">
<div class="flex items-center gap-2 text-sm text-[var(--art-text-gray-600)]">
- <span class="font-medium text-[var(--art-text-gray-900)]">{{ t('pages.orders.deliveryItem.sourceTitle') }}</span>
- <span>{{ t('pages.orders.deliveryItem.sourceLabel', { id: activeSourceSummary.deliveryId }) }}</span>
+ <span class="font-medium text-[var(--art-text-gray-900)]">{{
+ t('pages.orders.deliveryItem.sourceTitle')
+ }}</span>
+ <span>{{
+ t('pages.orders.deliveryItem.sourceLabel', { id: activeSourceSummary.deliveryId })
+ }}</span>
</div>
- <ElButton link type="primary" @click="handleClearSourceFilter">{{ t('common.actions.viewAll') }}</ElButton>
+ <ElButton link type="primary" @click="handleClearSourceFilter">{{
+ t('common.actions.viewAll')
+ }}</ElButton>
</div>
</ElCard>
- <ArtSearchBar
- v-model="searchForm"
- :items="searchItems"
- :showExpand="true"
- @search="handleSearch"
- @reset="handleReset"
- />
-
- <ElCard class="art-table-card">
- <ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="refreshData" />
-
- <ArtTable
- :loading="loading"
- :data="data"
- :columns="columns"
- :pagination="pagination"
- @pagination:size-change="handleSizeChange"
- @pagination:current-change="handleCurrentChange"
- />
- </ElCard>
-
- <DeliveryItemDetailDrawer
- v-model:visible="detailDrawerVisible"
- :loading="detailLoading"
- :detail="detailData"
+ <DeliveryItemManagePanel
+ :delivery-id="searchForm.deliveryId"
+ :can-add="hasAuth('add')"
+ :can-edit="hasAuth('update')"
+ :can-delete="hasAuth('delete')"
/>
</div>
</template>
@@ -42,164 +28,32 @@
<script setup>
import { computed, onMounted, ref, watch } from 'vue'
import { useI18n } from 'vue-i18n'
- import { ElButton, ElMessage } from 'element-plus'
+ import { ElButton } from 'element-plus'
import { useRoute, useRouter } from 'vue-router'
- import { useTable } from '@/hooks/core/useTable'
- import { guardRequestWithMessage } from '@/utils/sys/requestGuard'
- import { fetchDeliveryItemPage, fetchGetDeliveryItemDetail } from '@/api/delivery'
- import DeliveryItemDetailDrawer from './modules/delivery-item-detail-drawer.vue'
- import { createDeliveryItemTableColumns } from './deliveryItemTable.columns.js'
- import {
- buildDeliveryItemPageQueryParams,
- createDeliveryItemSearchState,
- normalizeDeliveryItemRow
- } from './deliveryItemPage.helpers.js'
+ import { useAuth } from '@/hooks/core/useAuth'
+ import DeliveryItemManagePanel from './modules/delivery-item-manage-panel.vue'
+ import { createDeliveryItemSearchState } from './deliveryItemPage.helpers.js'
defineOptions({ name: 'DeliveryItem' })
const { t } = useI18n()
+ const { hasAuth } = useAuth()
const route = useRoute()
const router = useRouter()
const searchForm = ref(createDeliveryItemSearchState())
- const detailDrawerVisible = ref(false)
- const detailLoading = ref(false)
- const detailData = ref({})
const activeSourceSummary = computed(() => {
- if (searchForm.value.deliveryId === '' || searchForm.value.deliveryId === undefined || searchForm.value.deliveryId === null) {
+ if (
+ searchForm.value.deliveryId === '' ||
+ searchForm.value.deliveryId === undefined ||
+ searchForm.value.deliveryId === null
+ ) {
return null
}
return {
deliveryId: searchForm.value.deliveryId
}
})
-
- const searchItems = computed(() => [
- {
- label: t('table.keyword'),
- key: 'condition',
- type: 'input',
- props: {
- clearable: true,
- placeholder: t('pages.orders.deliveryItem.search.conditionPlaceholder')
- }
- },
- {
- label: t('pages.orders.deliveryItem.search.deliveryCode'),
- key: 'deliveryCode',
- type: 'input',
- props: {
- clearable: true,
- placeholder: t('pages.orders.deliveryItem.search.deliveryCodePlaceholder')
- }
- },
- {
- label: t('pages.orders.deliveryItem.search.platItemId'),
- key: 'platItemId',
- type: 'input',
- props: {
- clearable: true,
- placeholder: t('pages.orders.deliveryItem.search.platItemIdPlaceholder')
- }
- },
- {
- label: t('table.materialCode'),
- key: 'matnrCode',
- type: 'input',
- props: {
- clearable: true,
- placeholder: t('pages.orders.deliveryItem.search.matnrCodePlaceholder')
- }
- },
- {
- label: t('table.materialName'),
- key: 'maktx',
- type: 'input',
- props: {
- clearable: true,
- placeholder: t('pages.orders.deliveryItem.search.maktxPlaceholder')
- }
- },
- {
- label: t('pages.orders.deliveryItem.search.supplierName'),
- key: 'splrName',
- type: 'input',
- props: {
- clearable: true,
- placeholder: t('pages.orders.deliveryItem.search.supplierNamePlaceholder')
- }
- },
- {
- label: t('table.supplierBatch'),
- key: 'splrBatch',
- type: 'input',
- props: {
- clearable: true,
- placeholder: t('pages.orders.deliveryItem.search.supplierBatchPlaceholder')
- }
- }
- ])
-
- function openDetail(row) {
- detailDrawerVisible.value = true
- detailLoading.value = true
- guardRequestWithMessage(fetchGetDeliveryItemDetail(row.id), {}, {
- timeoutMessage: t('pages.orders.deliveryItem.messages.detailTimeout')
- })
- .then((detail) => {
- detailData.value = normalizeDeliveryItemRow(detail, t)
- })
- .catch((error) => {
- detailDrawerVisible.value = false
- detailData.value = {}
- ElMessage.error(error?.message || t('pages.orders.deliveryItem.messages.detailFailed'))
- })
- .finally(() => {
- detailLoading.value = false
- })
- }
-
- const {
- columns,
- columnChecks,
- data,
- loading,
- pagination,
- replaceSearchParams,
- resetSearchParams,
- handleSizeChange,
- handleCurrentChange,
- refreshData,
- getData
- } = useTable({
- core: {
- apiFn: fetchDeliveryItemPage,
- apiParams: buildDeliveryItemPageQueryParams({
- ...searchForm.value,
- pageSize: 20
- }),
- immediate: false,
- columnsFactory: () => createDeliveryItemTableColumns({ handleActionClick: openDetail, t })
- },
- transform: {
- dataTransformer: (records) =>
- Array.isArray(records) ? records.map((item) => normalizeDeliveryItemRow(item, t)) : []
- }
- })
-
- function handleSearch(params) {
- searchForm.value = {
- ...searchForm.value,
- ...params
- }
- replaceSearchParams(buildDeliveryItemPageQueryParams(searchForm.value))
- getData()
- }
-
- function handleReset() {
- Object.assign(searchForm.value, createDeliveryItemSearchState())
- resetSearchParams()
- }
function applyRouteSearch() {
const deliveryId = route.query.deliveryId
@@ -220,8 +74,6 @@
deliveryId: undefined
}
})
- replaceSearchParams(buildDeliveryItemPageQueryParams(searchForm.value))
- getData()
}
watch(
@@ -231,14 +83,10 @@
return
}
applyRouteSearch()
- replaceSearchParams(buildDeliveryItemPageQueryParams(searchForm.value))
- getData()
}
)
onMounted(() => {
applyRouteSearch()
- replaceSearchParams(buildDeliveryItemPageQueryParams(searchForm.value))
- getData()
})
</script>
diff --git a/rsf-design/src/views/orders/delivery-item/modules/delivery-item-dialog.vue b/rsf-design/src/views/orders/delivery-item/modules/delivery-item-dialog.vue
new file mode 100644
index 0000000..8217c47
--- /dev/null
+++ b/rsf-design/src/views/orders/delivery-item/modules/delivery-item-dialog.vue
@@ -0,0 +1,271 @@
+<template>
+ <ElDialog
+ :model-value="visible"
+ :title="dialogTitle"
+ width="860px"
+ destroy-on-close
+ @update:model-value="handleVisibleChange"
+ @closed="handleClosed"
+ >
+ <ArtForm
+ ref="formRef"
+ v-model="form"
+ :items="formItems"
+ :rules="rules"
+ :span="12"
+ :gutter="20"
+ label-width="110px"
+ :show-reset="false"
+ :show-submit="false"
+ />
+
+ <template #footer>
+ <ElSpace>
+ <ElButton @click="handleVisibleChange(false)">{{ t('common.cancel') }}</ElButton>
+ <ElButton type="primary" :loading="submitLoading" @click="handleSubmit">
+ {{ t('common.confirm') }}
+ </ElButton>
+ </ElSpace>
+ </template>
+ </ElDialog>
+</template>
+
+<script setup>
+ import { computed, nextTick, reactive, ref, watch } from 'vue'
+ import { useI18n } from 'vue-i18n'
+ import ArtForm from '@/components/core/forms/art-form/index.vue'
+ import {
+ buildDeliveryItemDialogModel,
+ createDeliveryItemFormState,
+ getDeliveryItemStatusOptions
+ } from '../deliveryItemPage.helpers.js'
+
+ const props = defineProps({
+ visible: { type: Boolean, default: false },
+ dialogType: { type: String, default: 'add' },
+ itemData: { type: Object, default: () => ({}) },
+ deliveryId: { type: [Number, String], default: undefined },
+ submitLoading: { type: Boolean, default: false }
+ })
+
+ const emit = defineEmits(['update:visible', 'submit'])
+ const { t } = useI18n()
+
+ const formRef = ref()
+ const form = reactive(createDeliveryItemFormState())
+
+ const isEdit = computed(() => props.dialogType === 'edit')
+ const dialogTitle = computed(() =>
+ isEdit.value
+ ? t('pages.orders.deliveryItem.dialog.titleEdit')
+ : t('pages.orders.deliveryItem.dialog.titleAdd')
+ )
+
+ const rules = computed(() => ({
+ anfme: [
+ {
+ required: true,
+ message: t('pages.orders.deliveryItem.dialog.validation.anfme'),
+ trigger: 'blur'
+ }
+ ]
+ }))
+
+ const formItems = computed(() => [
+ {
+ label: t('pages.orders.deliveryItem.dialog.deliveryId'),
+ key: 'deliveryId',
+ type: 'input',
+ hidden: true,
+ props: {
+ disabled: true
+ }
+ },
+ {
+ label: t('pages.orders.deliveryItem.dialog.platItemId'),
+ key: 'platItemId',
+ type: 'input',
+ props: {
+ clearable: true,
+ placeholder: t('pages.orders.deliveryItem.dialog.placeholder.platItemId')
+ }
+ },
+ {
+ label: t('pages.orders.deliveryItem.dialog.matnrCode'),
+ key: 'matnrCode',
+ type: 'input',
+ props: {
+ clearable: true,
+ placeholder: t('pages.orders.deliveryItem.dialog.placeholder.matnrCode')
+ }
+ },
+ {
+ label: t('pages.orders.deliveryItem.dialog.maktx'),
+ key: 'maktx',
+ type: 'input',
+ props: {
+ clearable: true,
+ placeholder: t('pages.orders.deliveryItem.dialog.placeholder.maktx')
+ }
+ },
+ {
+ label: t('pages.orders.deliveryItem.dialog.fieldsIndex'),
+ key: 'fieldsIndex',
+ type: 'input',
+ props: {
+ clearable: true,
+ placeholder: t('pages.orders.deliveryItem.dialog.placeholder.fieldsIndex')
+ }
+ },
+ {
+ label: t('pages.orders.deliveryItem.dialog.unit'),
+ key: 'unit',
+ type: 'input',
+ props: {
+ clearable: true,
+ placeholder: t('pages.orders.deliveryItem.dialog.placeholder.unit')
+ }
+ },
+ {
+ label: t('pages.orders.deliveryItem.dialog.anfme'),
+ key: 'anfme',
+ type: 'number',
+ props: {
+ min: 0,
+ precision: 2,
+ controlsPosition: 'right',
+ placeholder: t('pages.orders.deliveryItem.dialog.placeholder.anfme')
+ }
+ },
+ {
+ label: t('pages.orders.deliveryItem.dialog.qty'),
+ key: 'qty',
+ type: 'number',
+ props: {
+ min: 0,
+ precision: 2,
+ controlsPosition: 'right',
+ placeholder: t('pages.orders.deliveryItem.dialog.placeholder.qty')
+ }
+ },
+ {
+ label: t('pages.orders.deliveryItem.dialog.printQty'),
+ key: 'printQty',
+ type: 'number',
+ props: {
+ min: 0,
+ precision: 2,
+ controlsPosition: 'right',
+ placeholder: t('pages.orders.deliveryItem.dialog.placeholder.printQty')
+ }
+ },
+ {
+ label: t('pages.orders.deliveryItem.dialog.splrName'),
+ key: 'splrName',
+ type: 'input',
+ props: {
+ clearable: true,
+ placeholder: t('pages.orders.deliveryItem.dialog.placeholder.splrName')
+ }
+ },
+ {
+ label: t('pages.orders.deliveryItem.dialog.splrCode'),
+ key: 'splrCode',
+ type: 'input',
+ props: {
+ clearable: true,
+ placeholder: t('pages.orders.deliveryItem.dialog.placeholder.splrCode')
+ }
+ },
+ {
+ label: t('pages.orders.deliveryItem.dialog.splrBatch'),
+ key: 'splrBatch',
+ type: 'input',
+ props: {
+ clearable: true,
+ placeholder: t('pages.orders.deliveryItem.dialog.placeholder.splrBatch')
+ }
+ },
+ {
+ label: t('pages.orders.deliveryItem.dialog.status'),
+ key: 'status',
+ type: 'select',
+ props: {
+ clearable: true,
+ options: getDeliveryItemStatusOptions(t),
+ placeholder: t('pages.orders.deliveryItem.dialog.placeholder.status')
+ }
+ },
+ {
+ label: t('pages.orders.deliveryItem.dialog.memo'),
+ key: 'memo',
+ type: 'input',
+ span: 24,
+ props: {
+ type: 'textarea',
+ rows: 3,
+ clearable: true,
+ placeholder: t('pages.orders.deliveryItem.dialog.placeholder.memo')
+ }
+ }
+ ])
+
+ function loadFormData() {
+ const nextForm = buildDeliveryItemDialogModel({
+ ...props.itemData,
+ deliveryId: props.itemData?.deliveryId ?? props.deliveryId
+ })
+ Object.assign(form, nextForm)
+ }
+
+ function resetForm() {
+ Object.assign(form, createDeliveryItemFormState(), {
+ deliveryId: props.deliveryId
+ })
+ formRef.value?.clearValidate?.()
+ }
+
+ async function handleSubmit() {
+ if (!formRef.value) {
+ return
+ }
+ try {
+ await formRef.value.validate()
+ emit('submit', { ...form })
+ } catch {
+ return
+ }
+ }
+
+ function handleVisibleChange(value) {
+ emit('update:visible', value)
+ }
+
+ function handleClosed() {
+ resetForm()
+ }
+
+ watch(
+ () => props.visible,
+ (visible) => {
+ if (!visible) {
+ return
+ }
+ loadFormData()
+ nextTick(() => {
+ formRef.value?.clearValidate?.()
+ })
+ },
+ { immediate: true }
+ )
+
+ watch(
+ () => props.itemData,
+ () => {
+ if (props.visible) {
+ loadFormData()
+ }
+ },
+ { deep: true }
+ )
+</script>
diff --git a/rsf-design/src/views/orders/delivery-item/modules/delivery-item-manage-panel.vue b/rsf-design/src/views/orders/delivery-item/modules/delivery-item-manage-panel.vue
new file mode 100644
index 0000000..4129c92
--- /dev/null
+++ b/rsf-design/src/views/orders/delivery-item/modules/delivery-item-manage-panel.vue
@@ -0,0 +1,441 @@
+<template>
+ <div class="flex flex-col gap-4">
+ <ArtSearchBar
+ v-model="searchForm"
+ :items="searchItems"
+ :showExpand="true"
+ @search="handleSearch"
+ @reset="handleReset"
+ />
+
+ <ElCard class="art-table-card">
+ <ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="refreshData">
+ <template #left>
+ <ElSpace wrap>
+ <ElButton type="primary" :disabled="!canCreateItem" @click="openCreateDialog">
+ {{ t('pages.orders.deliveryItem.actions.add') }}
+ </ElButton>
+ <ElButton
+ type="danger"
+ plain
+ :disabled="!selectedRows.length || !canDelete"
+ @click="handleBatchDelete"
+ >
+ {{ t('common.actions.batchDelete') }}
+ </ElButton>
+ </ElSpace>
+ </template>
+ </ArtTableHeader>
+
+ <ArtTable
+ :loading="loading"
+ :data="data"
+ :columns="columns"
+ :pagination="pagination"
+ @selection-change="handleSelectionChange"
+ @pagination:size-change="handleSizeChange"
+ @pagination:current-change="handleCurrentChange"
+ />
+ </ElCard>
+
+ <DeliveryItemDetailDrawer
+ v-model:visible="detailDrawerVisible"
+ :loading="detailLoading"
+ :detail="detailData"
+ />
+
+ <DeliveryItemDialog
+ v-model:visible="dialogVisible"
+ :dialog-type="dialogType"
+ :item-data="currentItemData"
+ :delivery-id="resolvedDeliveryId"
+ :submit-loading="submitLoading"
+ @submit="handleDialogSubmit"
+ />
+ </div>
+</template>
+
+<script setup>
+ import { computed, ref, watch } from 'vue'
+ import { ElMessage, ElMessageBox } from 'element-plus'
+ import { useI18n } from 'vue-i18n'
+ import { useTable } from '@/hooks/core/useTable'
+ import { guardRequestWithMessage } from '@/utils/sys/requestGuard'
+ import {
+ fetchDeleteDeliveryItem,
+ fetchDeleteDeliveryItemMany,
+ fetchDeliveryItemPage,
+ fetchGetDeliveryItemDetail,
+ fetchSaveDeliveryItem,
+ fetchUpdateDeliveryItem
+ } from '@/api/delivery'
+ import DeliveryItemDetailDrawer from './delivery-item-detail-drawer.vue'
+ import DeliveryItemDialog from './delivery-item-dialog.vue'
+ import { createDeliveryItemTableColumns } from '../deliveryItemTable.columns.js'
+ import {
+ buildDeliveryItemDialogModel,
+ buildDeliveryItemPageQueryParams,
+ buildDeliveryItemSavePayload,
+ createDeliveryItemFormState,
+ createDeliveryItemSearchState,
+ getDeliveryItemStatusOptions,
+ normalizeDeliveryItemRow
+ } from '../deliveryItemPage.helpers.js'
+
+ const props = defineProps({
+ deliveryId: { type: [Number, String], default: undefined },
+ canEdit: { type: Boolean, default: true },
+ canDelete: { type: Boolean, default: true },
+ canAdd: { type: Boolean, default: true }
+ })
+
+ const emit = defineEmits(['changed'])
+ const { t } = useI18n()
+
+ const searchForm = ref(createDeliveryItemSearchState())
+ const selectedRows = ref([])
+ const detailDrawerVisible = ref(false)
+ const detailLoading = ref(false)
+ const detailData = ref({})
+ const dialogVisible = ref(false)
+ const dialogType = ref('add')
+ const currentItemData = ref(createDeliveryItemFormState())
+ const submitLoading = ref(false)
+
+ const resolvedDeliveryId = computed(() => {
+ if (props.deliveryId === '' || props.deliveryId === undefined || props.deliveryId === null) {
+ return undefined
+ }
+ const numericValue = Number(props.deliveryId)
+ return Number.isFinite(numericValue) ? numericValue : undefined
+ })
+
+ const canCreateItem = computed(() => props.canAdd && resolvedDeliveryId.value !== undefined)
+
+ const searchItems = computed(() => [
+ {
+ label: t('table.keyword'),
+ key: 'condition',
+ type: 'input',
+ props: {
+ clearable: true,
+ placeholder: t('pages.orders.deliveryItem.search.conditionPlaceholder')
+ }
+ },
+ {
+ label: t('pages.orders.deliveryItem.search.deliveryCode'),
+ key: 'deliveryCode',
+ type: 'input',
+ props: {
+ clearable: true,
+ placeholder: t('pages.orders.deliveryItem.search.deliveryCodePlaceholder')
+ }
+ },
+ {
+ label: t('pages.orders.deliveryItem.search.platItemId'),
+ key: 'platItemId',
+ type: 'input',
+ props: {
+ clearable: true,
+ placeholder: t('pages.orders.deliveryItem.search.platItemIdPlaceholder')
+ }
+ },
+ {
+ label: t('table.materialCode'),
+ key: 'matnrCode',
+ type: 'input',
+ props: {
+ clearable: true,
+ placeholder: t('pages.orders.deliveryItem.search.matnrCodePlaceholder')
+ }
+ },
+ {
+ label: t('table.materialName'),
+ key: 'maktx',
+ type: 'input',
+ props: {
+ clearable: true,
+ placeholder: t('pages.orders.deliveryItem.search.maktxPlaceholder')
+ }
+ },
+ {
+ label: t('pages.orders.deliveryItem.table.fieldsIndex'),
+ key: 'fieldsIndex',
+ type: 'input',
+ props: {
+ clearable: true,
+ placeholder: t('pages.orders.deliveryItem.search.fieldsIndexPlaceholder')
+ }
+ },
+ {
+ label: t('pages.orders.deliveryItem.search.supplierName'),
+ key: 'splrName',
+ type: 'input',
+ props: {
+ clearable: true,
+ placeholder: t('pages.orders.deliveryItem.search.supplierNamePlaceholder')
+ }
+ },
+ {
+ label: t('pages.orders.deliveryItem.table.supplierCode'),
+ key: 'splrCode',
+ type: 'input',
+ props: {
+ clearable: true,
+ placeholder: t('pages.orders.deliveryItem.search.supplierCodePlaceholder')
+ }
+ },
+ {
+ label: t('table.supplierBatch'),
+ key: 'splrBatch',
+ type: 'input',
+ props: {
+ clearable: true,
+ placeholder: t('pages.orders.deliveryItem.search.supplierBatchPlaceholder')
+ }
+ },
+ {
+ label: t('table.status'),
+ key: 'status',
+ type: 'select',
+ props: {
+ clearable: true,
+ options: getDeliveryItemStatusOptions(t),
+ placeholder: t('pages.orders.deliveryItem.search.statusPlaceholder')
+ }
+ },
+ {
+ label: t('pages.orders.deliveryItem.search.timeStart'),
+ key: 'timeStart',
+ type: 'date',
+ props: {
+ clearable: true,
+ valueFormat: 'YYYY-MM-DD',
+ placeholder: t('pages.orders.deliveryItem.search.timeStartPlaceholder')
+ }
+ },
+ {
+ label: t('pages.orders.deliveryItem.search.timeEnd'),
+ key: 'timeEnd',
+ type: 'date',
+ props: {
+ clearable: true,
+ valueFormat: 'YYYY-MM-DD',
+ placeholder: t('pages.orders.deliveryItem.search.timeEndPlaceholder')
+ }
+ },
+ {
+ label: t('table.memo'),
+ key: 'memo',
+ type: 'input',
+ props: {
+ clearable: true,
+ placeholder: t('pages.orders.deliveryItem.search.memoPlaceholder')
+ }
+ }
+ ])
+
+ const {
+ columns,
+ columnChecks,
+ data,
+ loading,
+ pagination,
+ replaceSearchParams,
+ handleSizeChange,
+ handleCurrentChange,
+ refreshData,
+ getData
+ } = useTable({
+ core: {
+ apiFn: fetchDeliveryItemPage,
+ apiParams: buildDeliveryItemPageQueryParams(searchForm.value),
+ immediate: false,
+ columnsFactory: () =>
+ createDeliveryItemTableColumns({
+ handleActionClick: handleActionClick,
+ canEdit: props.canEdit,
+ canDelete: props.canDelete,
+ t
+ })
+ },
+ transform: {
+ dataTransformer: (records) =>
+ Array.isArray(records) ? records.map((item) => normalizeDeliveryItemRow(item, t)) : []
+ }
+ })
+
+ function syncDeliveryFilter() {
+ searchForm.value.deliveryId = resolvedDeliveryId.value ?? ''
+ }
+
+ function handleSelectionChange(rows) {
+ selectedRows.value = Array.isArray(rows) ? rows : []
+ }
+
+ function handleSearch(params) {
+ searchForm.value = {
+ ...searchForm.value,
+ ...params,
+ deliveryId: resolvedDeliveryId.value ?? searchForm.value.deliveryId
+ }
+ replaceSearchParams(buildDeliveryItemPageQueryParams(searchForm.value))
+ getData()
+ }
+
+ function handleReset() {
+ searchForm.value = createDeliveryItemSearchState()
+ syncDeliveryFilter()
+ replaceSearchParams(buildDeliveryItemPageQueryParams(searchForm.value))
+ getData()
+ }
+
+ async function openDetail(row) {
+ detailDrawerVisible.value = true
+ detailLoading.value = true
+ try {
+ const detail = await guardRequestWithMessage(
+ fetchGetDeliveryItemDetail(row.id),
+ {},
+ {
+ timeoutMessage: t('pages.orders.deliveryItem.messages.detailTimeout')
+ }
+ )
+ detailData.value = normalizeDeliveryItemRow(detail, t)
+ } catch (error) {
+ detailDrawerVisible.value = false
+ detailData.value = {}
+ ElMessage.error(error?.message || t('pages.orders.deliveryItem.messages.detailFailed'))
+ } finally {
+ detailLoading.value = false
+ }
+ }
+
+ async function openEditDialog(row) {
+ dialogType.value = 'edit'
+ currentItemData.value = buildDeliveryItemDialogModel(row)
+ dialogVisible.value = true
+ }
+
+ function openCreateDialog() {
+ if (!canCreateItem.value) {
+ return
+ }
+ dialogType.value = 'add'
+ currentItemData.value = buildDeliveryItemDialogModel({
+ deliveryId: resolvedDeliveryId.value
+ })
+ dialogVisible.value = true
+ }
+
+ async function handleDelete(row) {
+ await ElMessageBox.confirm(
+ t('pages.orders.deliveryItem.messages.deleteConfirm', {
+ code: row.platItemId || row.id || ''
+ }),
+ t('crud.confirm.deleteTitle'),
+ {
+ confirmButtonText: t('common.confirm'),
+ cancelButtonText: t('common.cancel'),
+ type: 'warning'
+ }
+ )
+ await fetchDeleteDeliveryItem(row.id)
+ ElMessage.success(t('crud.messages.deleteSuccess'))
+ await refreshData()
+ emit('changed')
+ }
+
+ async function handleBatchDelete() {
+ if (!selectedRows.value.length) {
+ return
+ }
+ await ElMessageBox.confirm(
+ t('crud.confirm.batchDeleteMessage', {
+ count: selectedRows.value.length,
+ entity: t('menu.deliveryItem')
+ }),
+ t('crud.confirm.batchDeleteTitle'),
+ {
+ confirmButtonText: t('common.confirm'),
+ cancelButtonText: t('common.cancel'),
+ type: 'warning'
+ }
+ )
+ await fetchDeleteDeliveryItemMany(selectedRows.value.map((item) => item.id))
+ ElMessage.success(t('crud.messages.batchDeleteSuccess'))
+ selectedRows.value = []
+ await refreshData()
+ emit('changed')
+ }
+
+ async function handleActionClick(action, row) {
+ try {
+ if (action.key === 'view') {
+ await openDetail(row)
+ return
+ }
+ if (action.key === 'edit') {
+ await openEditDialog(row)
+ return
+ }
+ if (action.key === 'delete') {
+ await handleDelete(row)
+ }
+ } catch (error) {
+ if (error === 'cancel' || error?.message === 'cancel') {
+ return
+ }
+ ElMessage.error(error?.message || t('pages.orders.deliveryItem.messages.actionFailed'))
+ }
+ }
+
+ async function handleDialogSubmit(payload) {
+ submitLoading.value = true
+ try {
+ const requestPayload = buildDeliveryItemSavePayload({
+ ...payload,
+ deliveryId: payload.deliveryId ?? resolvedDeliveryId.value
+ })
+ if (dialogType.value === 'edit') {
+ await fetchUpdateDeliveryItem(requestPayload)
+ ElMessage.success(t('pages.orders.deliveryItem.messages.updateSuccess'))
+ } else {
+ await fetchSaveDeliveryItem(requestPayload)
+ ElMessage.success(t('pages.orders.deliveryItem.messages.createSuccess'))
+ }
+ dialogVisible.value = false
+ await refreshData()
+ emit('changed')
+ } catch (error) {
+ ElMessage.error(
+ error?.message ||
+ (dialogType.value === 'edit'
+ ? t('pages.orders.deliveryItem.messages.updateFailed')
+ : t('pages.orders.deliveryItem.messages.createFailed'))
+ )
+ } finally {
+ submitLoading.value = false
+ }
+ }
+
+ async function reload() {
+ syncDeliveryFilter()
+ replaceSearchParams(buildDeliveryItemPageQueryParams(searchForm.value))
+ await getData()
+ }
+
+ watch(
+ () => props.deliveryId,
+ () => {
+ syncDeliveryFilter()
+ replaceSearchParams(buildDeliveryItemPageQueryParams(searchForm.value))
+ getData()
+ },
+ { immediate: true }
+ )
+
+ defineExpose({
+ reload
+ })
+</script>
diff --git a/rsf-design/src/views/orders/delivery/deliveryPage.helpers.js b/rsf-design/src/views/orders/delivery/deliveryPage.helpers.js
index 9b61115..2c99bcb 100644
--- a/rsf-design/src/views/orders/delivery/deliveryPage.helpers.js
+++ b/rsf-design/src/views/orders/delivery/deliveryPage.helpers.js
@@ -60,23 +60,51 @@
}
return fallback
}
- return deliveryExceStatusMeta[Number(exceStatus)] || {
- text: normalizeText(exceStatus) || '--',
- type: 'info'
- }
+ return (
+ deliveryExceStatusMeta[Number(exceStatus)] || {
+ text: normalizeText(exceStatus) || '--',
+ type: 'info'
+ }
+ )
}
export function createDeliverySearchState() {
return {
condition: '',
+ timeStart: '',
+ timeEnd: '',
code: '',
platId: '',
type: '',
wkType: '',
source: '',
+ anfme: '',
+ qty: '',
+ workQty: '',
+ platCode: '',
+ startTime: '',
+ endTime: '',
+ status: '',
exceStatus: '',
- memo: ''
+ memo: '',
+ orderBy: 'create_time desc'
}
+}
+
+export function getDeliveryStatusOptions(t = $t) {
+ return [
+ { value: 1, label: t('pages.orders.delivery.status.normal') },
+ { value: 0, label: t('pages.orders.delivery.status.disabled') }
+ ]
+}
+
+export function getDeliveryExceStatusOptions(t = $t) {
+ return [
+ { value: 0, label: t('pages.orders.delivery.status.pending') },
+ { value: 1, label: t('pages.orders.delivery.status.running') },
+ { value: 2, label: t('pages.orders.delivery.status.partial') },
+ { value: 3, label: t('pages.orders.delivery.status.completed') }
+ ]
}
export function getDeliveryPaginationKey() {
@@ -88,10 +116,17 @@
export function buildDeliverySearchParams(params = {}) {
const result = {}
- ;['condition', 'code', 'platId', 'type', 'wkType', 'source', 'memo'].forEach((key) => {
- const value = normalizeText(params[key])
- if (value) {
- result[key] = value
+ ;['condition', 'code', 'platId', 'type', 'wkType', 'source', 'platCode', 'memo'].forEach(
+ (key) => {
+ const value = normalizeText(params[key])
+ if (value) {
+ result[key] = value
+ }
+ }
+ )
+ ;['anfme', 'qty', 'workQty'].forEach((key) => {
+ if (params[key] !== '' && params[key] !== undefined && params[key] !== null) {
+ result[key] = normalizeNumber(params[key])
}
})
@@ -111,6 +146,14 @@
result.timeEnd = normalizeText(params.timeEnd)
}
+ if (params.startTime !== '' && params.startTime !== undefined && params.startTime !== null) {
+ result.startTime = normalizeText(params.startTime)
+ }
+
+ if (params.endTime !== '' && params.endTime !== undefined && params.endTime !== null) {
+ result.endTime = normalizeText(params.endTime)
+ }
+
return result
}
@@ -118,6 +161,7 @@
return {
current: params.current || 1,
pageSize: params.pageSize || params.size || 20,
+ orderBy: normalizeText(params.orderBy) || 'create_time desc',
...buildDeliverySearchParams(params)
}
}
@@ -132,7 +176,11 @@
export function normalizeDeliveryRow(record = {}, t = $t) {
const statusMeta = normalizeStatusMeta(record.statusBool ?? record.status, t)
- const exceStatusMeta = normalizeExceStatusMeta(record.exceStatus, record['exceStatus$'] || record.exceStatusText, t)
+ const exceStatusMeta = normalizeExceStatusMeta(
+ record.exceStatus,
+ record['exceStatus$'] || record.exceStatusText,
+ t
+ )
return {
...record,
id: record.id ?? null,
@@ -145,17 +193,21 @@
statusText: statusMeta.text,
statusType: statusMeta.type,
statusBool: record.statusBool !== void 0 ? Boolean(record.statusBool) : statusMeta.bool,
- exceStatusText: normalizeText(record['exceStatus$'] || record.exceStatusText) || exceStatusMeta.text,
+ exceStatusText:
+ normalizeText(record['exceStatus$'] || record.exceStatusText) || exceStatusMeta.text,
exceStatusTagType: exceStatusMeta.type,
anfme: record.anfme ?? '--',
qty: record.qty ?? '--',
workQty: record.workQty ?? '--',
- startTimeText: normalizeText(record['startTime$'] || record.startTimeText || record.startTime) || '--',
+ startTimeText:
+ normalizeText(record['startTime$'] || record.startTimeText || record.startTime) || '--',
endTimeText: normalizeText(record['endTime$'] || record.endTimeText || record.endTime) || '--',
createByText: normalizeText(record['createBy$'] || record.createByText) || '--',
- createTimeText: normalizeText(record['createTime$'] || record.createTimeText || record.createTime) || '--',
+ createTimeText:
+ normalizeText(record['createTime$'] || record.createTimeText || record.createTime) || '--',
updateByText: normalizeText(record['updateBy$'] || record.updateByText) || '--',
- updateTimeText: normalizeText(record['updateTime$'] || record.updateTimeText || record.updateTime) || '--',
+ updateTimeText:
+ normalizeText(record['updateTime$'] || record.updateTimeText || record.updateTime) || '--',
memo: normalizeText(record.memo) || '--'
}
}
@@ -187,17 +239,17 @@
batch: normalizeText(record.batch) || '--',
trackCode: normalizeText(record.trackCode) || '--',
packName: normalizeText(record.packName) || '--',
- prodTimeText: normalizeText(record['prodTime$'] || record.prodTimeText || record.prodTime) || '--',
+ prodTimeText:
+ normalizeText(record['prodTime$'] || record.prodTimeText || record.prodTime) || '--',
statusText: statusMeta.text,
statusType: statusMeta.type,
- statusBool:
- record.statusBool !== void 0
- ? Boolean(record.statusBool)
- : statusMeta.bool,
+ statusBool: record.statusBool !== void 0 ? Boolean(record.statusBool) : statusMeta.bool,
createByText: normalizeText(record['createBy$'] || record.createByText) || '--',
- createTimeText: normalizeText(record['createTime$'] || record.createTimeText || record.createTime) || '--',
+ createTimeText:
+ normalizeText(record['createTime$'] || record.createTimeText || record.createTime) || '--',
updateByText: normalizeText(record['updateBy$'] || record.updateByText) || '--',
- updateTimeText: normalizeText(record['updateTime$'] || record.updateTimeText || record.updateTime) || '--',
+ updateTimeText:
+ normalizeText(record['updateTime$'] || record.updateTimeText || record.updateTime) || '--',
memo: normalizeText(record.memo) || '--'
}
}
@@ -254,22 +306,32 @@
}
}
-export function getDeliveryActionList(row = {}) {
+export function getDeliveryActionList(row = {}, options = {}) {
const normalizedRow = normalizeDeliveryRow(row)
+ const { canEdit = true, canDelete = true } = options
const actions = [
{
key: 'view',
label: $t('pages.orders.delivery.actions.view'),
icon: 'ri:eye-line'
- },
- {
- key: 'items',
- label: $t('pages.orders.delivery.actions.items'),
- icon: 'ri:list-check-3'
}
]
- if (Number(normalizedRow.exceStatus) === 0) {
+ if (canEdit) {
+ actions.push({
+ key: 'edit',
+ label: $t('pages.orders.delivery.actions.edit'),
+ icon: 'ri:edit-line'
+ })
+ }
+
+ actions.push({
+ key: 'items',
+ label: $t('pages.orders.delivery.actions.items'),
+ icon: 'ri:list-check-3'
+ })
+
+ if (canDelete && Number(normalizedRow.exceStatus) === 0) {
actions.push({
key: 'delete',
label: $t('pages.orders.delivery.actions.delete'),
diff --git a/rsf-design/src/views/orders/delivery/deliveryTable.columns.js b/rsf-design/src/views/orders/delivery/deliveryTable.columns.js
index 7a07112..7cae065 100644
--- a/rsf-design/src/views/orders/delivery/deliveryTable.columns.js
+++ b/rsf-design/src/views/orders/delivery/deliveryTable.columns.js
@@ -4,10 +4,22 @@
import ArtButtonMore from '@/components/core/forms/art-button-more/index.vue'
import { getDeliveryActionList } from './deliveryPage.helpers.js'
-export function createDeliveryTableColumns({ handleActionClick } = {}) {
+export function createDeliveryTableColumns({
+ handleActionClick,
+ canEdit = true,
+ canDelete = true
+} = {}) {
return [
{ type: 'selection', width: 48, align: 'center' },
{ type: 'globalIndex', label: $t('table.index'), width: 72, align: 'center' },
+ {
+ prop: 'id',
+ label: $t('table.id'),
+ width: 100,
+ align: 'right',
+ visible: false,
+ formatter: (row) => row.id ?? '--'
+ },
{
prop: 'code',
label: $t('pages.orders.delivery.search.code'),
@@ -73,7 +85,7 @@
},
{
prop: 'status',
- label: $t('pages.orders.transfer.search.status'),
+ label: $t('table.status'),
width: 96,
align: 'center',
formatter: (row) =>
@@ -85,7 +97,11 @@
minWidth: 120,
showOverflowTooltip: true,
formatter: (row) =>
- h(ElTag, { type: row.exceStatusTagType || 'info', effect: 'light' }, () => row.exceStatusText || '--')
+ h(
+ ElTag,
+ { type: row.exceStatusTagType || 'info', effect: 'light' },
+ () => row.exceStatusText || '--'
+ )
},
{
prop: 'startTimeText',
@@ -102,6 +118,38 @@
formatter: (row) => row.endTimeText || '--'
},
{
+ prop: 'updateByText',
+ label: $t('table.updateBy'),
+ minWidth: 130,
+ visible: false,
+ showOverflowTooltip: true,
+ formatter: (row) => row.updateByText || '--'
+ },
+ {
+ prop: 'updateTimeText',
+ label: $t('table.updateTime'),
+ minWidth: 170,
+ visible: false,
+ showOverflowTooltip: true,
+ formatter: (row) => row.updateTimeText || '--'
+ },
+ {
+ prop: 'createByText',
+ label: $t('table.createBy'),
+ minWidth: 130,
+ visible: false,
+ showOverflowTooltip: true,
+ formatter: (row) => row.createByText || '--'
+ },
+ {
+ prop: 'createTimeText',
+ label: $t('table.createTime'),
+ minWidth: 170,
+ visible: false,
+ showOverflowTooltip: true,
+ formatter: (row) => row.createTimeText || '--'
+ },
+ {
prop: 'memo',
label: $t('pages.orders.delivery.search.memo'),
minWidth: 180,
@@ -116,7 +164,7 @@
fixed: 'right',
formatter: (row) =>
h(ArtButtonMore, {
- list: getDeliveryActionList(row),
+ list: getDeliveryActionList(row, { canEdit, canDelete }),
onClick: (item) => handleActionClick?.(item, row)
})
}
diff --git a/rsf-design/src/views/orders/delivery/index.vue b/rsf-design/src/views/orders/delivery/index.vue
index f679705..3086179 100644
--- a/rsf-design/src/views/orders/delivery/index.vue
+++ b/rsf-design/src/views/orders/delivery/index.vue
@@ -11,21 +11,50 @@
<ElCard class="art-table-card">
<ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="refreshData">
<template #left>
- <ListExportPrint
- class="inline-flex"
- :preview-visible="previewVisible"
- @update:previewVisible="handlePreviewVisibleChange"
- :report-title="reportTitle"
- :selected-rows="selectedRows"
- :query-params="reportQueryParams"
- :columns="columns"
- :preview-rows="previewRows"
- :preview-meta="resolvedPreviewMeta"
- :total="pagination.total"
- :disabled="loading"
- @export="handleExport"
- @print="handlePrint"
- />
+ <ElSpace wrap>
+ <ElUpload
+ v-auth="'update'"
+ :auto-upload="false"
+ :show-file-list="false"
+ accept=".xlsx,.xls"
+ @change="handleImportFileChange"
+ >
+ <ElButton :loading="importing">{{
+ t('pages.orders.delivery.buttons.import')
+ }}</ElButton>
+ </ElUpload>
+ <ElButton
+ v-auth="'update'"
+ :loading="templateDownloading"
+ @click="handleDownloadTemplate"
+ >
+ {{ t('pages.orders.delivery.buttons.downloadTemplate') }}
+ </ElButton>
+ <ElButton
+ v-auth="'delete'"
+ type="danger"
+ plain
+ :disabled="selectedRows.length === 0"
+ @click="handleBatchDelete"
+ >
+ {{ t('common.actions.batchDelete') }}
+ </ElButton>
+ <ListExportPrint
+ class="inline-flex"
+ :preview-visible="previewVisible"
+ @update:previewVisible="handlePreviewVisibleChange"
+ :report-title="reportTitle"
+ :selected-rows="selectedRows"
+ :query-params="reportQueryParams"
+ :columns="columns"
+ :preview-rows="previewRows"
+ :preview-meta="resolvedPreviewMeta"
+ :total="pagination.total"
+ :disabled="loading"
+ @export="handleExport"
+ @print="handlePrint"
+ />
+ </ElSpace>
</template>
</ArtTableHeader>
@@ -50,15 +79,24 @@
@size-change="handleDetailSizeChange"
@current-change="handleDetailCurrentChange"
/>
+
+ <DeliveryManageDialog
+ v-model:visible="manageDialogVisible"
+ :delivery="manageDeliveryData"
+ :can-add="hasAuth('update')"
+ :can-edit="hasAuth('update')"
+ :can-delete="hasAuth('delete')"
+ @changed="handleManageChanged"
+ />
</div>
</template>
<script setup>
import { computed, reactive, ref } from 'vue'
- import { useRouter } from 'vue-router'
import { useI18n } from 'vue-i18n'
import { ElMessage, ElMessageBox } from 'element-plus'
import { useUserStore } from '@/store/modules/user'
+ import { useAuth } from '@/hooks/core/useAuth'
import { useTable } from '@/hooks/core/useTable'
import { usePrintExportPage } from '@/views/system/common/usePrintExportPage'
import ListExportPrint from '@/components/biz/list-export-print/index.vue'
@@ -73,24 +111,29 @@
createDeliverySearchState,
getDeliveryReportTitle,
getDeliveryPaginationKey,
+ getDeliveryStatusOptions,
+ getDeliveryExceStatusOptions,
normalizeDeliveryItemRow,
normalizeDeliveryRow
} from './deliveryPage.helpers.js'
import {
- fetchDeleteDelivery,
+ fetchDeleteDeliveryMany,
fetchDeliveryItemPage,
fetchDeliveryPage,
+ fetchDownloadDeliveryTemplate,
fetchExportDeliveryReport,
fetchGetDeliveryDetail,
fetchGetDeliveryMany
} from '@/api/delivery'
+ import { fetchImportDelivery } from '@/api/delivery'
import DeliveryDetailDrawer from './modules/delivery-detail-drawer.vue'
+ import DeliveryManageDialog from './modules/delivery-manage-dialog.vue'
import { createDeliveryTableColumns } from './deliveryTable.columns.js'
defineOptions({ name: 'Delivery' })
const userStore = useUserStore()
- const router = useRouter()
+ const { hasAuth } = useAuth()
const { t } = useI18n()
const reportTitle = computed(() => getDeliveryReportTitle(t))
const searchForm = ref(createDeliverySearchState())
@@ -101,6 +144,10 @@
const detailData = ref({})
const detailItemRows = ref([])
const activeDeliveryId = ref(null)
+ const importing = ref(false)
+ const templateDownloading = ref(false)
+ const manageDialogVisible = ref(false)
+ const manageDeliveryData = ref({})
const detailItemPagination = reactive({
current: 1,
size: 20,
@@ -116,6 +163,26 @@
props: {
clearable: true,
placeholder: t('pages.orders.delivery.placeholder.condition')
+ }
+ },
+ {
+ label: t('pages.orders.delivery.search.timeStart'),
+ key: 'timeStart',
+ type: 'date',
+ props: {
+ clearable: true,
+ valueFormat: 'YYYY-MM-DD',
+ placeholder: t('pages.orders.delivery.placeholder.timeStart')
+ }
+ },
+ {
+ label: t('pages.orders.delivery.search.timeEnd'),
+ key: 'timeEnd',
+ type: 'date',
+ props: {
+ clearable: true,
+ valueFormat: 'YYYY-MM-DD',
+ placeholder: t('pages.orders.delivery.placeholder.timeEnd')
}
},
{
@@ -164,11 +231,84 @@
}
},
{
- label: t('pages.orders.delivery.search.exceStatus'),
- key: 'exceStatus',
+ label: t('pages.orders.delivery.search.anfme'),
+ key: 'anfme',
+ type: 'number',
+ props: {
+ min: 0,
+ precision: 2,
+ controlsPosition: 'right',
+ placeholder: t('pages.orders.delivery.placeholder.anfme')
+ }
+ },
+ {
+ label: t('pages.orders.delivery.search.qty'),
+ key: 'qty',
+ type: 'number',
+ props: {
+ min: 0,
+ precision: 2,
+ controlsPosition: 'right',
+ placeholder: t('pages.orders.delivery.placeholder.qty')
+ }
+ },
+ {
+ label: t('pages.orders.delivery.search.workQty'),
+ key: 'workQty',
+ type: 'number',
+ props: {
+ min: 0,
+ precision: 2,
+ controlsPosition: 'right',
+ placeholder: t('pages.orders.delivery.placeholder.workQty')
+ }
+ },
+ {
+ label: t('pages.orders.delivery.search.platCode'),
+ key: 'platCode',
type: 'input',
props: {
clearable: true,
+ placeholder: t('pages.orders.delivery.placeholder.platCode')
+ }
+ },
+ {
+ label: t('pages.orders.delivery.search.startTime'),
+ key: 'startTime',
+ type: 'date',
+ props: {
+ clearable: true,
+ valueFormat: 'YYYY-MM-DD',
+ placeholder: t('pages.orders.delivery.placeholder.startTime')
+ }
+ },
+ {
+ label: t('pages.orders.delivery.search.endTime'),
+ key: 'endTime',
+ type: 'date',
+ props: {
+ clearable: true,
+ valueFormat: 'YYYY-MM-DD',
+ placeholder: t('pages.orders.delivery.placeholder.endTime')
+ }
+ },
+ {
+ label: t('table.status'),
+ key: 'status',
+ type: 'select',
+ props: {
+ clearable: true,
+ options: getDeliveryStatusOptions(t),
+ placeholder: t('pages.orders.delivery.placeholder.status')
+ }
+ },
+ {
+ label: t('pages.orders.delivery.search.exceStatus'),
+ key: 'exceStatus',
+ type: 'select',
+ props: {
+ clearable: true,
+ options: getDeliveryExceStatusOptions(t),
placeholder: t('pages.orders.delivery.placeholder.exceStatus')
}
},
@@ -208,9 +348,13 @@
{ timeoutMessage: t('pages.orders.delivery.messages.itemsTimeout') }
)
const normalizedResponse = defaultResponseAdapter(response)
- detailItemRows.value = normalizedResponse.records.map((item) => normalizeDeliveryItemRow(item, t))
+ detailItemRows.value = normalizedResponse.records.map((item) =>
+ normalizeDeliveryItemRow(item, t)
+ )
detailItemPagination.total = Number(normalizedResponse.total || 0)
- detailItemPagination.current = Number(normalizedResponse.current || detailItemPagination.current || 1)
+ detailItemPagination.current = Number(
+ normalizedResponse.current || detailItemPagination.current || 1
+ )
detailItemPagination.size = Number(normalizedResponse.size || detailItemPagination.size || 20)
} finally {
detailItemsLoading.value = false
@@ -248,51 +392,75 @@
}
async function handleDelete(row) {
- try {
- await ElMessageBox.confirm(
- t('crud.confirm.deleteMessage', {
- entity: t('pages.orders.delivery.entity'),
- label: row.code || row.id
- }),
- t('crud.confirm.deleteTitle'),
- {
- confirmButtonText: t('common.confirm'),
- cancelButtonText: t('common.cancel'),
- type: 'warning'
- }
- )
- await fetchDeleteDelivery(row.id)
- ElMessage.success(t('crud.messages.deleteSuccess'))
- await refreshRemove()
- } catch (error) {
- if (error !== 'cancel') {
- ElMessage.error(error?.message || t('crud.messages.deleteFailed'))
+ await ElMessageBox.confirm(
+ t('crud.confirm.deleteMessage', {
+ entity: t('pages.orders.delivery.entity'),
+ label: row.code || row.id
+ }),
+ t('crud.confirm.deleteTitle'),
+ {
+ confirmButtonText: t('common.confirm'),
+ cancelButtonText: t('common.cancel'),
+ type: 'warning'
}
- }
+ )
+ await fetchDeleteDeliveryMany([row.id])
+ ElMessage.success(t('crud.messages.deleteSuccess'))
+ await refreshRemove()
}
- function handleTableActionClick(action, row) {
+ function openManageDialog(row) {
+ manageDeliveryData.value = normalizeDeliveryRow(row, t)
+ manageDialogVisible.value = true
+ }
+
+ async function handleBatchDelete() {
+ if (!selectedRows.value.length) {
+ return
+ }
+ await ElMessageBox.confirm(
+ t('crud.confirm.batchDeleteMessage', {
+ count: selectedRows.value.length,
+ entity: t('pages.orders.delivery.entity')
+ }),
+ t('crud.confirm.batchDeleteTitle'),
+ {
+ confirmButtonText: t('common.confirm'),
+ cancelButtonText: t('common.cancel'),
+ type: 'warning'
+ }
+ )
+ await fetchDeleteDeliveryMany(selectedRows.value.map((item) => item.id))
+ ElMessage.success(t('crud.messages.batchDeleteSuccess'))
+ selectedRows.value = []
+ await refreshData()
+ }
+
+ async function handleTableActionClick(action, row) {
if (!action) {
return
}
if (action.key === 'view') {
- openDetail(row)
+ await openDetail(row)
+ return
+ }
+ if (action.key === 'edit') {
+ openManageDialog(row)
return
}
if (action.key === 'items') {
- if (!row?.id) {
- return
- }
- router.push({
- path: '/orders/delivery-item',
- query: {
- deliveryId: String(row.id)
- }
- })
+ openManageDialog(row)
return
}
if (action.key === 'delete') {
- handleDelete(row)
+ try {
+ await handleDelete(row)
+ } catch (error) {
+ if (error === 'cancel' || error?.message === 'cancel') {
+ return
+ }
+ ElMessage.error(error?.message || t('crud.messages.deleteFailed'))
+ }
}
}
@@ -317,10 +485,16 @@
pageSize: 20
}),
paginationKey: getDeliveryPaginationKey(),
- columnsFactory: () => createDeliveryTableColumns({ handleActionClick: handleTableActionClick })
+ columnsFactory: () =>
+ createDeliveryTableColumns({
+ handleActionClick: handleTableActionClick,
+ canEdit: hasAuth('update'),
+ canDelete: hasAuth('delete')
+ })
},
transform: {
- dataTransformer: (records) => (Array.isArray(records) ? records.map((item) => normalizeDeliveryRow(item, t)) : [])
+ dataTransformer: (records) =>
+ Array.isArray(records) ? records.map((item) => normalizeDeliveryRow(item, t)) : []
}
})
@@ -338,6 +512,69 @@
resetSearchParams()
}
+ async function handleManageChanged() {
+ await refreshData()
+ if (detailDrawerVisible.value && activeDeliveryId.value) {
+ await loadDetailItems(activeDeliveryId.value)
+ }
+ }
+
+ async function handleImportFileChange(uploadFile) {
+ if (!uploadFile?.raw) {
+ return
+ }
+ importing.value = true
+ try {
+ await fetchImportDelivery(uploadFile.raw)
+ ElMessage.success(t('pages.orders.delivery.messages.importSuccess'))
+ await refreshData()
+ } catch (error) {
+ ElMessage.error(error?.message || t('pages.orders.delivery.messages.importFailed'))
+ } finally {
+ importing.value = false
+ }
+ }
+
+ async function downloadFile(response, fallbackName) {
+ const blob = await response.blob()
+ if (!blob || !blob.size) {
+ throw new Error(t('pages.orders.delivery.messages.templateDownloadFailed'))
+ }
+ const disposition = response.headers.get('Content-Disposition') || ''
+ const matchedName =
+ disposition.match(/filename\*=UTF-8''([^;]+)/i)?.[1] ||
+ disposition.match(/filename="?([^";]+)"?/i)?.[1]
+ const fileName = matchedName ? decodeURIComponent(matchedName) : fallbackName
+ const url = URL.createObjectURL(blob)
+ const anchor = document.createElement('a')
+ anchor.href = url
+ anchor.download = fileName
+ document.body.appendChild(anchor)
+ anchor.click()
+ anchor.remove()
+ URL.revokeObjectURL(url)
+ }
+
+ async function handleDownloadTemplate() {
+ templateDownloading.value = true
+ try {
+ const response = await fetchDownloadDeliveryTemplate(
+ {},
+ {
+ headers: {
+ Authorization: userStore.accessToken || ''
+ }
+ }
+ )
+ await downloadFile(response, 'delivery-template.xlsx')
+ ElMessage.success(t('pages.orders.delivery.messages.templateDownloadSuccess'))
+ } catch (error) {
+ ElMessage.error(error?.message || t('pages.orders.delivery.messages.templateDownloadFailed'))
+ } finally {
+ templateDownloading.value = false
+ }
+ }
+
const resolvePrintRecords = async (payload) => {
if (Array.isArray(payload?.ids) && payload.ids.length > 0) {
return defaultResponseAdapter(await fetchGetDeliveryMany(payload.ids)).records
@@ -346,7 +583,8 @@
await fetchDeliveryPage({
...reportQueryParams.value,
current: 1,
- pageSize: Number(pagination.total) > 0 ? Number(pagination.total) : Number(payload?.pageSize) || 20
+ pageSize:
+ Number(pagination.total) > 0 ? Number(pagination.total) : Number(payload?.pageSize) || 20
})
).records
}
diff --git a/rsf-design/src/views/orders/delivery/modules/delivery-manage-dialog.vue b/rsf-design/src/views/orders/delivery/modules/delivery-manage-dialog.vue
new file mode 100644
index 0000000..e76c6a0
--- /dev/null
+++ b/rsf-design/src/views/orders/delivery/modules/delivery-manage-dialog.vue
@@ -0,0 +1,86 @@
+<template>
+ <ElDialog
+ :model-value="visible"
+ :title="t('pages.orders.delivery.manage.title')"
+ width="94%"
+ top="4vh"
+ destroy-on-close
+ @update:model-value="handleVisibleChange"
+ >
+ <div class="flex flex-col gap-4">
+ <ElDescriptions
+ :title="t('pages.orders.delivery.manage.baseInfo')"
+ :column="3"
+ border
+ size="small"
+ >
+ <ElDescriptionsItem :label="t('pages.orders.delivery.detail.code')">{{
+ delivery.code || '--'
+ }}</ElDescriptionsItem>
+ <ElDescriptionsItem :label="t('pages.orders.delivery.detail.platId')">{{
+ delivery.platId || '--'
+ }}</ElDescriptionsItem>
+ <ElDescriptionsItem :label="t('pages.orders.delivery.detail.platCode')">{{
+ delivery.platCode || '--'
+ }}</ElDescriptionsItem>
+ <ElDescriptionsItem :label="t('pages.orders.delivery.detail.type')">{{
+ delivery.typeLabel || '--'
+ }}</ElDescriptionsItem>
+ <ElDescriptionsItem :label="t('pages.orders.delivery.detail.wkType')">{{
+ delivery.wkTypeLabel || '--'
+ }}</ElDescriptionsItem>
+ <ElDescriptionsItem :label="t('pages.orders.delivery.detail.source')">{{
+ delivery.source || '--'
+ }}</ElDescriptionsItem>
+ <ElDescriptionsItem :label="t('pages.orders.delivery.detail.anfme')">{{
+ delivery.anfme ?? '--'
+ }}</ElDescriptionsItem>
+ <ElDescriptionsItem :label="t('pages.orders.delivery.detail.qty')">{{
+ delivery.qty ?? '--'
+ }}</ElDescriptionsItem>
+ <ElDescriptionsItem :label="t('pages.orders.delivery.detail.workQty')">{{
+ delivery.workQty ?? '--'
+ }}</ElDescriptionsItem>
+ <ElDescriptionsItem :label="t('pages.orders.delivery.detail.startTime')">{{
+ delivery.startTimeText || '--'
+ }}</ElDescriptionsItem>
+ <ElDescriptionsItem :label="t('pages.orders.delivery.detail.endTime')">{{
+ delivery.endTimeText || '--'
+ }}</ElDescriptionsItem>
+ <ElDescriptionsItem :label="t('pages.orders.delivery.detail.memo')">{{
+ delivery.memo || '--'
+ }}</ElDescriptionsItem>
+ </ElDescriptions>
+
+ <DeliveryItemManagePanel
+ :delivery-id="delivery.id"
+ :can-add="canAdd"
+ :can-edit="canEdit"
+ :can-delete="canDelete"
+ @changed="emit('changed')"
+ />
+ </div>
+ </ElDialog>
+</template>
+
+<script setup>
+ import { useI18n } from 'vue-i18n'
+ import DeliveryItemManagePanel from '@/views/orders/delivery-item/modules/delivery-item-manage-panel.vue'
+
+ defineOptions({ name: 'DeliveryManageDialog' })
+
+ defineProps({
+ visible: { type: Boolean, default: false },
+ delivery: { type: Object, default: () => ({}) },
+ canAdd: { type: Boolean, default: true },
+ canEdit: { type: Boolean, default: true },
+ canDelete: { type: Boolean, default: true }
+ })
+
+ const emit = defineEmits(['update:visible', 'changed'])
+ const { t } = useI18n()
+
+ function handleVisibleChange(value) {
+ emit('update:visible', value)
+ }
+</script>
diff --git a/rsf-design/src/views/orders/preparation-item/index.vue b/rsf-design/src/views/orders/preparation-item/index.vue
index 2631793..750f3f2 100644
--- a/rsf-design/src/views/orders/preparation-item/index.vue
+++ b/rsf-design/src/views/orders/preparation-item/index.vue
@@ -21,21 +21,34 @@
<ElCard class="art-table-card">
<ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="refreshData">
<template #left>
- <ListExportPrint
- class="inline-flex"
- :preview-visible="previewVisible"
- @update:previewVisible="handlePreviewVisibleChange"
- :report-title="reportTitle"
- :selected-rows="selectedRows"
- :query-params="reportQueryParams"
- :columns="reportColumns"
- :preview-rows="previewRows"
- :preview-meta="resolvedPreviewMeta"
- :total="pagination.total"
- :disabled="loading"
- @export="handleExport"
- @print="handlePrint"
- />
+ <ElSpace wrap>
+ <ElButton type="primary" :disabled="!canCreateItem" @click="openCreateDialog">
+ 鏂板鏄庣粏
+ </ElButton>
+ <ElButton
+ type="danger"
+ plain
+ :disabled="!selectedRows.length || !canDelete"
+ @click="handleBatchDelete"
+ >
+ 鎵归噺鍒犻櫎
+ </ElButton>
+ <ListExportPrint
+ class="inline-flex"
+ :preview-visible="previewVisible"
+ @update:previewVisible="handlePreviewVisibleChange"
+ :report-title="reportTitle"
+ :selected-rows="selectedRows"
+ :query-params="reportQueryParams"
+ :columns="reportColumns"
+ :preview-rows="previewRows"
+ :preview-meta="resolvedPreviewMeta"
+ :total="pagination.total"
+ :disabled="loading"
+ @export="handleExport"
+ @print="handlePrint"
+ />
+ </ElSpace>
</template>
</ArtTableHeader>
@@ -55,13 +68,23 @@
:loading="detailLoading"
:detail="detailData"
/>
+
+ <PreparationItemDialog
+ v-model:visible="dialogVisible"
+ :dialog-type="dialogType"
+ :item-data="currentItemData"
+ :order-id="resolvedOrderId"
+ :submit-loading="submitLoading"
+ @submit="handleDialogSubmit"
+ />
</div>
</template>
<script setup>
import { computed, onMounted, ref, watch } from 'vue'
import { useRoute, useRouter } from 'vue-router'
- import { ElButton, ElMessage } from 'element-plus'
+ import { ElButton, ElMessage, ElMessageBox, ElSpace } from 'element-plus'
+ import { useAuth } from '@/hooks/core/useAuth'
import { useUserStore } from '@/store/modules/user'
import { useTable } from '@/hooks/core/useTable'
import { defaultResponseAdapter } from '@/utils/table/tableUtils'
@@ -69,20 +92,27 @@
import { usePrintExportPage } from '@/views/system/common/usePrintExportPage'
import ListExportPrint from '@/components/biz/list-export-print/index.vue'
import {
+ fetchDeletePreparationItem,
fetchExportPreparationItemReport,
fetchGetPreparationItemDetail,
fetchGetPreparationItemMany,
- fetchPreparationItemPage
+ fetchPreparationItemPage,
+ fetchSavePreparationItem,
+ fetchUpdatePreparationItem
} from '@/api/preparation-item'
- import { createOutStockItemTableColumns } from '../out-stock-item/outStockItemTable.columns.js'
import OutStockItemDetailDrawer from '../out-stock-item/modules/out-stock-item-detail-drawer.vue'
+ import PreparationItemDialog from './modules/preparation-item-dialog.vue'
+ import { createPreparationItemTableColumns } from './preparationItemTable.columns.js'
import {
PREPARATION_ITEM_REPORT_STYLE,
PREPARATION_ITEM_REPORT_TITLE,
+ buildPreparationItemDialogModel,
buildPreparationItemPageQueryParams,
buildPreparationItemPrintRows,
buildPreparationItemReportMeta,
+ buildPreparationItemSavePayload,
buildPreparationItemSearchParams,
+ createPreparationItemFormState,
createPreparationItemSearchState,
getPreparationItemPaginationKey,
getPreparationItemReportColumns,
@@ -93,6 +123,7 @@
const route = useRoute()
const router = useRouter()
+ const { hasAuth } = useAuth()
const userStore = useUserStore()
const initialOrderId = route.query.orderId || route.query.id
const searchForm = ref(
@@ -104,126 +135,163 @@
const detailLoading = ref(false)
const detailData = ref({})
const selectedRows = ref([])
+ const dialogVisible = ref(false)
+ const dialogType = ref('add')
+ const currentItemData = ref(createPreparationItemFormState())
+ const submitLoading = ref(false)
const reportTitle = PREPARATION_ITEM_REPORT_TITLE
const reportColumns = getPreparationItemReportColumns()
const reportQueryParams = computed(() => buildPreparationItemSearchParams(searchForm.value))
- const activeSourceSummary = computed(() => {
+ const resolvedOrderId = computed(() => {
if (
searchForm.value.orderId === '' ||
searchForm.value.orderId === undefined ||
searchForm.value.orderId === null
) {
- return null
+ return undefined
}
- return {
- orderId: searchForm.value.orderId
- }
+ const numericValue = Number(searchForm.value.orderId)
+ return Number.isFinite(numericValue) ? numericValue : undefined
})
+ const canUpdate = computed(() => hasAuth('update'))
+ const canDelete = computed(() => hasAuth('delete'))
+ const canCreateItem = computed(() => hasAuth('add') && resolvedOrderId.value !== undefined)
+ const activeSourceSummary = computed(() =>
+ resolvedOrderId.value === undefined
+ ? null
+ : {
+ orderId: resolvedOrderId.value
+ }
+ )
const searchItems = computed(() => [
{
label: '鍏抽敭瀛�',
key: 'condition',
type: 'input',
- props: {
- clearable: true,
- placeholder: '璇疯緭鍏ュ鏂欏崟鍙�/鐗╂枡缂栫爜/鐗╂枡鍚嶇О'
- }
+ props: { clearable: true, placeholder: '璇疯緭鍏ュ鏂欏崟鍙�/鐗╂枡缂栫爜/鐗╂枡鍚嶇О' }
},
{
label: '澶囨枡鍗旾D',
key: 'orderId',
type: 'inputNumber',
- props: {
- clearable: true,
- controlsPosition: 'right',
- placeholder: '璇疯緭鍏ュ鏂欏崟ID'
- }
+ props: { clearable: true, controlsPosition: 'right', placeholder: '璇疯緭鍏ュ鏂欏崟ID' }
},
{
label: '澶囨枡鍗曞彿',
key: 'orderCode',
type: 'input',
- props: {
- clearable: true,
- placeholder: '璇疯緭鍏ュ鏂欏崟鍙�'
- }
+ props: { clearable: true, placeholder: '璇疯緭鍏ュ鏂欏崟鍙�' }
},
{
- label: 'PO鍗曞彿',
- key: 'poCode',
+ label: 'PO鏄庣粏ID',
+ key: 'poDetlId',
type: 'input',
- props: {
- clearable: true,
- placeholder: '璇疯緭鍏O鍗曞彿'
- }
+ props: { clearable: true, placeholder: '璇疯緭鍏� PO 鏄庣粏ID' }
},
{
- label: '鐗╂枡缂栫爜',
- key: 'matnrCode',
+ label: '鐗╂枡ID',
+ key: 'matnrId',
type: 'input',
- props: {
- clearable: true,
- placeholder: '璇疯緭鍏ョ墿鏂欑紪鐮�'
- }
+ props: { clearable: true, placeholder: '璇疯緭鍏ョ墿鏂橧D' }
},
{
label: '鐗╂枡鍚嶇О',
key: 'maktx',
type: 'input',
- props: {
- clearable: true,
- placeholder: '璇疯緭鍏ョ墿鏂欏悕绉�'
- }
+ props: { clearable: true, placeholder: '璇疯緭鍏ョ墿鏂欏悕绉�' }
+ },
+ {
+ label: '鐗╂枡缂栫爜',
+ key: 'matnrCode',
+ type: 'input',
+ props: { clearable: true, placeholder: '璇疯緭鍏ョ墿鏂欑紪鐮�' }
+ },
+ {
+ label: '璁″垝鏁伴噺',
+ key: 'anfme',
+ type: 'inputNumber',
+ props: { clearable: true, controlsPosition: 'right', placeholder: '璇疯緭鍏ヨ鍒掓暟閲�' }
+ },
+ {
+ label: '搴撳瓨鍗曚綅',
+ key: 'stockUnit',
+ type: 'input',
+ props: { clearable: true, placeholder: '璇疯緭鍏ュ簱瀛樺崟浣�' }
+ },
+ {
+ label: '閲囪喘鏁伴噺',
+ key: 'purQty',
+ type: 'inputNumber',
+ props: { clearable: true, controlsPosition: 'right', placeholder: '璇疯緭鍏ラ噰璐暟閲�' }
+ },
+ {
+ label: '閲囪喘鍗曚綅',
+ key: 'purUnit',
+ type: 'input',
+ props: { clearable: true, placeholder: '璇疯緭鍏ラ噰璐崟浣�' }
+ },
+ {
+ label: '宸插嚭鏁伴噺',
+ key: 'qty',
+ type: 'inputNumber',
+ props: { clearable: true, controlsPosition: 'right', placeholder: '璇疯緭鍏ュ凡鍑烘暟閲�' }
+ },
+ {
+ label: '渚涘簲鍟嗙紪鐮�',
+ key: 'splrCode',
+ type: 'input',
+ props: { clearable: true, placeholder: '璇疯緭鍏ヤ緵搴斿晢缂栫爜' }
+ },
+ {
+ label: '渚涘簲鍟�',
+ key: 'splrName',
+ type: 'input',
+ props: { clearable: true, placeholder: '璇疯緭鍏ヤ緵搴斿晢' }
+ },
+ {
+ label: '浜岀淮鐮�',
+ key: 'qrcode',
+ type: 'input',
+ props: { clearable: true, placeholder: '璇疯緭鍏ヤ簩缁寸爜' }
+ },
+ {
+ label: '鏉$爜',
+ key: 'trackCode',
+ type: 'input',
+ props: { clearable: true, placeholder: '璇疯緭鍏ユ潯鐮�' }
+ },
+ {
+ label: '鍖呰',
+ key: 'packName',
+ type: 'input',
+ props: { clearable: true, placeholder: '璇疯緭鍏ュ寘瑁�' }
},
{
label: '鎵规',
key: 'batch',
type: 'input',
- props: {
- clearable: true,
- placeholder: '璇疯緭鍏ユ壒娆�'
- }
+ props: { clearable: true, placeholder: '璇疯緭鍏ユ壒娆�' }
},
{
label: '渚涘簲鍟嗘壒娆�',
key: 'splrBatch',
type: 'input',
- props: {
- clearable: true,
- placeholder: '璇疯緭鍏ヤ緵搴斿晢鎵规'
- }
+ props: { clearable: true, placeholder: '璇疯緭鍏ヤ緵搴斿晢鎵规' }
},
{
label: '瀛楁绱㈠紩',
key: 'fieldsIndex',
type: 'input',
- props: {
- clearable: true,
- placeholder: '璇疯緭鍏ュ瓧娈电储寮�'
- }
+ props: { clearable: true, placeholder: '璇疯緭鍏ュ瓧娈电储寮�' }
+ },
+ {
+ label: '澶囨敞',
+ key: 'memo',
+ type: 'input',
+ props: { clearable: true, placeholder: '璇疯緭鍏ュ娉�' }
}
])
-
- async function openDetail(row) {
- detailDrawerVisible.value = true
- detailLoading.value = true
- try {
- const detail = await guardRequestWithMessage(fetchGetPreparationItemDetail(row.id), {}, {
- timeoutMessage: '澶囨枡鍗曟槑缁嗚鎯呭姞杞借秴鏃讹紝宸插仠姝㈢瓑寰�'
- })
- detailData.value = normalizePreparationItemRow({
- ...row,
- ...(detail || {})
- })
- } catch (error) {
- detailDrawerVisible.value = false
- detailData.value = {}
- ElMessage.error(error?.message || '鑾峰彇澶囨枡鍗曟槑缁嗚鎯呭け璐�')
- } finally {
- detailLoading.value = false
- }
- }
const {
columns,
@@ -243,7 +311,12 @@
apiParams: buildPreparationItemPageQueryParams(searchForm.value),
immediate: false,
paginationKey: getPreparationItemPaginationKey(),
- columnsFactory: () => createOutStockItemTableColumns({ handleActionClick: openDetail })
+ columnsFactory: () =>
+ createPreparationItemTableColumns({
+ handleActionClick,
+ canEdit: canUpdate.value,
+ canDelete: canDelete.value
+ })
},
transform: {
dataTransformer: (records) =>
@@ -265,10 +338,132 @@
}
function handleReset() {
- const resetSeed =
- initialOrderId !== undefined ? { orderId: Number(initialOrderId) || '' } : {}
+ const resetSeed = initialOrderId !== undefined ? { orderId: Number(initialOrderId) || '' } : {}
Object.assign(searchForm.value, createPreparationItemSearchState(resetSeed))
- resetSearchParams(buildPreparationItemPageQueryParams(createPreparationItemSearchState(resetSeed)))
+ resetSearchParams(
+ buildPreparationItemPageQueryParams(createPreparationItemSearchState(resetSeed))
+ )
+ }
+
+ async function openDetail(row) {
+ detailDrawerVisible.value = true
+ detailLoading.value = true
+ try {
+ const detail = await guardRequestWithMessage(
+ fetchGetPreparationItemDetail(row.id),
+ {},
+ {
+ timeoutMessage: '澶囨枡鍗曟槑缁嗚鎯呭姞杞借秴鏃讹紝宸插仠姝㈢瓑寰�'
+ }
+ )
+ detailData.value = normalizePreparationItemRow({
+ ...row,
+ ...(detail || {})
+ })
+ } catch (error) {
+ detailDrawerVisible.value = false
+ detailData.value = {}
+ ElMessage.error(error?.message || '鑾峰彇澶囨枡鍗曟槑缁嗚鎯呭け璐�')
+ } finally {
+ detailLoading.value = false
+ }
+ }
+
+ function openCreateDialog() {
+ if (!canCreateItem.value) {
+ return
+ }
+ dialogType.value = 'add'
+ currentItemData.value = buildPreparationItemDialogModel({
+ orderId: resolvedOrderId.value
+ })
+ dialogVisible.value = true
+ }
+
+ function openEditDialog(row) {
+ dialogType.value = 'edit'
+ currentItemData.value = buildPreparationItemDialogModel(row)
+ dialogVisible.value = true
+ }
+
+ async function handleDelete(row) {
+ await ElMessageBox.confirm(
+ `纭畾鍒犻櫎澶囨枡鏄庣粏 ${row.matnrCode || row.id || ''} 鍚楋紵`,
+ '鍒犻櫎纭',
+ {
+ confirmButtonText: '纭畾',
+ cancelButtonText: '鍙栨秷',
+ type: 'warning'
+ }
+ )
+ await fetchDeletePreparationItem(row.id)
+ ElMessage.success('澶囨枡鏄庣粏宸插垹闄�')
+ await refreshData()
+ }
+
+ async function handleBatchDelete() {
+ if (!selectedRows.value.length) {
+ return
+ }
+ await ElMessageBox.confirm(
+ `纭畾鍒犻櫎閫変腑鐨� ${selectedRows.value.length} 鏉″鏂欐槑缁嗗悧锛焋,
+ '鎵归噺鍒犻櫎纭',
+ {
+ confirmButtonText: '纭畾',
+ cancelButtonText: '鍙栨秷',
+ type: 'warning'
+ }
+ )
+ await fetchDeletePreparationItem(selectedRows.value.map((item) => item.id))
+ ElMessage.success('澶囨枡鏄庣粏宸叉壒閲忓垹闄�')
+ selectedRows.value = []
+ await refreshData()
+ }
+
+ async function handleActionClick(action, row) {
+ try {
+ if (action.key === 'view') {
+ await openDetail(row)
+ return
+ }
+ if (action.key === 'edit') {
+ openEditDialog(row)
+ return
+ }
+ if (action.key === 'delete') {
+ await handleDelete(row)
+ }
+ } catch (error) {
+ if (error === 'cancel' || error?.message === 'cancel') {
+ return
+ }
+ ElMessage.error(error?.message || '澶囨枡鏄庣粏鎿嶄綔澶辫触')
+ }
+ }
+
+ async function handleDialogSubmit(payload) {
+ submitLoading.value = true
+ try {
+ const requestPayload = buildPreparationItemSavePayload({
+ ...payload,
+ orderId: payload.orderId ?? resolvedOrderId.value
+ })
+ if (dialogType.value === 'edit') {
+ await fetchUpdatePreparationItem(requestPayload)
+ ElMessage.success('澶囨枡鏄庣粏宸叉洿鏂�')
+ } else {
+ await fetchSavePreparationItem(requestPayload)
+ ElMessage.success('澶囨枡鏄庣粏宸叉柊澧�')
+ }
+ dialogVisible.value = false
+ await refreshData()
+ } catch (error) {
+ ElMessage.error(
+ error?.message || (dialogType.value === 'edit' ? '澶囨枡鏄庣粏鏇存柊澶辫触' : '澶囨枡鏄庣粏鏂板澶辫触')
+ )
+ } finally {
+ submitLoading.value = false
+ }
}
function applyRouteSearch() {
diff --git a/rsf-design/src/views/orders/preparation-item/modules/preparation-item-dialog.vue b/rsf-design/src/views/orders/preparation-item/modules/preparation-item-dialog.vue
new file mode 100644
index 0000000..c475541
--- /dev/null
+++ b/rsf-design/src/views/orders/preparation-item/modules/preparation-item-dialog.vue
@@ -0,0 +1,238 @@
+<template>
+ <ElDialog
+ :model-value="visible"
+ :title="dialogTitle"
+ width="900px"
+ destroy-on-close
+ @update:model-value="handleVisibleChange"
+ @closed="handleClosed"
+ >
+ <ArtForm
+ ref="formRef"
+ v-model="form"
+ :items="formItems"
+ :rules="rules"
+ :span="12"
+ :gutter="20"
+ label-width="110px"
+ :show-reset="false"
+ :show-submit="false"
+ />
+
+ <template #footer>
+ <ElSpace>
+ <ElButton @click="handleVisibleChange(false)">鍙栨秷</ElButton>
+ <ElButton type="primary" :loading="submitLoading" @click="handleSubmit">纭畾</ElButton>
+ </ElSpace>
+ </template>
+ </ElDialog>
+</template>
+
+<script setup>
+ import { computed, nextTick, reactive, ref, watch } from 'vue'
+ import ArtForm from '@/components/core/forms/art-form/index.vue'
+ import {
+ buildPreparationItemDialogModel,
+ createPreparationItemFormState,
+ getPreparationItemStatusOptions
+ } from '../preparationItemPage.helpers.js'
+
+ const props = defineProps({
+ visible: { type: Boolean, default: false },
+ dialogType: { type: String, default: 'add' },
+ itemData: { type: Object, default: () => ({}) },
+ orderId: { type: [Number, String], default: undefined },
+ submitLoading: { type: Boolean, default: false }
+ })
+
+ const emit = defineEmits(['update:visible', 'submit'])
+
+ const formRef = ref()
+ const form = reactive(createPreparationItemFormState())
+
+ const isEdit = computed(() => props.dialogType === 'edit')
+ const dialogTitle = computed(() => (isEdit.value ? '缂栬緫澶囨枡鏄庣粏' : '鏂板澶囨枡鏄庣粏'))
+
+ const rules = computed(() => ({
+ anfme: [{ required: true, message: '璇疯緭鍏ヨ鍒掓暟閲�', trigger: 'blur' }]
+ }))
+
+ const formItems = computed(() => [
+ {
+ label: '澶囨枡鍗旾D',
+ key: 'orderId',
+ type: 'input',
+ hidden: true,
+ props: { disabled: true }
+ },
+ {
+ label: '澶囨枡鍗曞彿',
+ key: 'orderCode',
+ type: 'input',
+ props: { clearable: true, placeholder: '璇疯緭鍏ュ鏂欏崟鍙�' }
+ },
+ {
+ label: 'PO鏄庣粏ID',
+ key: 'poDetlId',
+ type: 'input',
+ props: { clearable: true, placeholder: '璇疯緭鍏� PO 鏄庣粏ID' }
+ },
+ {
+ label: '鐗╂枡ID',
+ key: 'matnrId',
+ type: 'input',
+ props: { clearable: true, placeholder: '璇疯緭鍏ョ墿鏂橧D' }
+ },
+ {
+ label: '鐗╂枡缂栫爜',
+ key: 'matnrCode',
+ type: 'input',
+ props: { clearable: true, placeholder: '璇疯緭鍏ョ墿鏂欑紪鐮�' }
+ },
+ {
+ label: '鐗╂枡鍚嶇О',
+ key: 'maktx',
+ type: 'input',
+ props: { clearable: true, placeholder: '璇疯緭鍏ョ墿鏂欏悕绉�' }
+ },
+ {
+ label: '璁″垝鏁伴噺',
+ key: 'anfme',
+ type: 'number',
+ props: { min: 0, precision: 2, controlsPosition: 'right', placeholder: '璇疯緭鍏ヨ鍒掓暟閲�' }
+ },
+ {
+ label: '搴撳瓨鍗曚綅',
+ key: 'stockUnit',
+ type: 'input',
+ props: { clearable: true, placeholder: '璇疯緭鍏ュ簱瀛樺崟浣�' }
+ },
+ {
+ label: '閲囪喘鏁伴噺',
+ key: 'purQty',
+ type: 'number',
+ props: { min: 0, precision: 2, controlsPosition: 'right', placeholder: '璇疯緭鍏ラ噰璐暟閲�' }
+ },
+ {
+ label: '閲囪喘鍗曚綅',
+ key: 'purUnit',
+ type: 'input',
+ props: { clearable: true, placeholder: '璇疯緭鍏ラ噰璐崟浣�' }
+ },
+ {
+ label: '宸插嚭鏁伴噺',
+ key: 'qty',
+ type: 'number',
+ props: { min: 0, precision: 2, controlsPosition: 'right', placeholder: '璇疯緭鍏ュ凡鍑烘暟閲�' }
+ },
+ {
+ label: '渚涘簲鍟嗙紪鐮�',
+ key: 'splrCode',
+ type: 'input',
+ props: { clearable: true, placeholder: '璇疯緭鍏ヤ緵搴斿晢缂栫爜' }
+ },
+ {
+ label: '渚涘簲鍟�',
+ key: 'splrName',
+ type: 'input',
+ props: { clearable: true, placeholder: '璇疯緭鍏ヤ緵搴斿晢' }
+ },
+ {
+ label: '渚涘簲鍟嗘壒娆�',
+ key: 'splrBatch',
+ type: 'input',
+ props: { clearable: true, placeholder: '璇疯緭鍏ヤ緵搴斿晢鎵规' }
+ },
+ {
+ label: '浜岀淮鐮�',
+ key: 'qrcode',
+ type: 'input',
+ props: { clearable: true, placeholder: '璇疯緭鍏ヤ簩缁寸爜' }
+ },
+ {
+ label: '鏉$爜',
+ key: 'trackCode',
+ type: 'input',
+ props: { clearable: true, placeholder: '璇疯緭鍏ユ潯鐮�' }
+ },
+ {
+ label: '鍖呰',
+ key: 'packName',
+ type: 'input',
+ props: { clearable: true, placeholder: '璇疯緭鍏ュ寘瑁�' }
+ },
+ {
+ label: '鐘舵��',
+ key: 'status',
+ type: 'select',
+ props: {
+ clearable: true,
+ options: getPreparationItemStatusOptions(),
+ placeholder: '璇烽�夋嫨鐘舵��'
+ }
+ },
+ {
+ label: '澶囨敞',
+ key: 'memo',
+ type: 'input',
+ span: 24,
+ props: { type: 'textarea', rows: 3, clearable: true, placeholder: '璇疯緭鍏ュ娉�' }
+ }
+ ])
+
+ function loadFormData() {
+ Object.assign(
+ form,
+ buildPreparationItemDialogModel({
+ ...props.itemData,
+ orderId: props.itemData?.orderId ?? props.orderId
+ })
+ )
+ }
+
+ function resetForm() {
+ Object.assign(form, createPreparationItemFormState(), {
+ orderId: props.orderId
+ })
+ formRef.value?.clearValidate?.()
+ }
+
+ async function handleSubmit() {
+ try {
+ await formRef.value?.validate?.()
+ emit('submit', { ...form })
+ } catch {
+ return
+ }
+ }
+
+ function handleVisibleChange(value) {
+ emit('update:visible', value)
+ }
+
+ function handleClosed() {
+ resetForm()
+ }
+
+ watch(
+ () => props.visible,
+ (visible) => {
+ if (!visible) {
+ return
+ }
+ loadFormData()
+ nextTick(() => formRef.value?.clearValidate?.())
+ },
+ { immediate: true }
+ )
+
+ watch(
+ () => props.itemData,
+ () => {
+ if (props.visible) {
+ loadFormData()
+ }
+ },
+ { deep: true }
+ )
+</script>
diff --git a/rsf-design/src/views/orders/preparation-item/preparationItemPage.helpers.js b/rsf-design/src/views/orders/preparation-item/preparationItemPage.helpers.js
index 93afb51..b026138 100644
--- a/rsf-design/src/views/orders/preparation-item/preparationItemPage.helpers.js
+++ b/rsf-design/src/views/orders/preparation-item/preparationItemPage.helpers.js
@@ -18,14 +18,26 @@
orderId: '',
orderCode: '',
poCode: '',
+ poDetlId: '',
+ matnrId: '',
platItemId: '',
matnrCode: '',
maktx: '',
+ anfme: '',
+ stockUnit: '',
+ purQty: '',
+ purUnit: '',
+ qty: '',
+ splrCode: '',
+ splrName: '',
batch: '',
splrBatch: '',
trackCode: '',
+ qrcode: '',
+ packName: '',
barcode: '',
fieldsIndex: '',
+ memo: '',
status: '',
...seed
}
@@ -42,6 +54,31 @@
const result = {
...buildOutStockItemSearchParams(params)
}
+
+ ;[
+ 'poDetlId',
+ 'matnrId',
+ 'stockUnit',
+ 'purUnit',
+ 'splrCode',
+ 'splrName',
+ 'qrcode',
+ 'packName',
+ 'memo'
+ ].forEach((key) => {
+ const value = params[key]
+ if (value !== '' && value !== undefined && value !== null) {
+ result[key] = value
+ }
+ })
+ ;['anfme', 'purQty', 'qty'].forEach((key) => {
+ if (params[key] !== '' && params[key] !== undefined && params[key] !== null) {
+ const numericValue = Number(params[key])
+ if (!Number.isNaN(numericValue)) {
+ result[key] = numericValue
+ }
+ }
+ })
if (params.orderId !== '' && params.orderId !== undefined && params.orderId !== null) {
const numericValue = Number(params.orderId)
@@ -65,6 +102,81 @@
return normalizeOutStockItemRow(record)
}
+export function getPreparationItemStatusOptions() {
+ return [
+ { label: '姝e父', value: 1 },
+ { label: '鍐荤粨', value: 0 }
+ ]
+}
+
+export function createPreparationItemFormState() {
+ return {
+ id: undefined,
+ orderId: undefined,
+ orderCode: '',
+ poDetlId: '',
+ matnrId: '',
+ matnrCode: '',
+ maktx: '',
+ anfme: undefined,
+ stockUnit: '',
+ purQty: undefined,
+ purUnit: '',
+ qty: undefined,
+ splrCode: '',
+ splrName: '',
+ splrBatch: '',
+ qrcode: '',
+ trackCode: '',
+ packName: '',
+ memo: '',
+ status: 1
+ }
+}
+
+export function buildPreparationItemDialogModel(record = {}) {
+ return {
+ ...createPreparationItemFormState(),
+ ...record
+ }
+}
+
+export function buildPreparationItemSavePayload(formData = {}) {
+ const payload = {}
+ ;[
+ 'id',
+ 'orderId',
+ 'orderCode',
+ 'poDetlId',
+ 'matnrId',
+ 'matnrCode',
+ 'maktx',
+ 'stockUnit',
+ 'purUnit',
+ 'splrCode',
+ 'splrName',
+ 'splrBatch',
+ 'qrcode',
+ 'trackCode',
+ 'packName',
+ 'memo'
+ ].forEach((key) => {
+ if (formData[key] !== '' && formData[key] !== undefined && formData[key] !== null) {
+ payload[key] = formData[key]
+ }
+ })
+ ;['anfme', 'purQty', 'qty', 'status'].forEach((key) => {
+ if (formData[key] !== '' && formData[key] !== undefined && formData[key] !== null) {
+ const numericValue = Number(formData[key])
+ if (!Number.isNaN(numericValue)) {
+ payload[key] = numericValue
+ }
+ }
+ })
+
+ return payload
+}
+
export function getPreparationItemReportColumns() {
return [
{ key: 'orderCode', label: '澶囨枡鍗曞彿' },
diff --git a/rsf-design/src/views/orders/preparation-item/preparationItemTable.columns.js b/rsf-design/src/views/orders/preparation-item/preparationItemTable.columns.js
new file mode 100644
index 0000000..ac37f8b
--- /dev/null
+++ b/rsf-design/src/views/orders/preparation-item/preparationItemTable.columns.js
@@ -0,0 +1,66 @@
+import { h } from 'vue'
+import { ElTag } from 'element-plus'
+import ArtButtonMore from '@/components/core/forms/art-button-more/index.vue'
+
+function buildStatusTag(row) {
+ return h(
+ ElTag,
+ { type: row.statusTagType || 'info', effect: 'light' },
+ () => row.statusText || '--'
+ )
+}
+
+export function createPreparationItemTableColumns({
+ handleActionClick,
+ canEdit = true,
+ canDelete = true
+} = {}) {
+ return [
+ { type: 'selection', width: 48, align: 'center' },
+ { type: 'globalIndex', label: '搴忓彿', width: 72, align: 'center' },
+ { prop: 'orderCode', label: '澶囨枡鍗曞彿', minWidth: 150, showOverflowTooltip: true },
+ { prop: 'poDetlId', label: 'PO鏄庣粏ID', minWidth: 120, showOverflowTooltip: true },
+ { prop: 'matnrId', label: '鐗╂枡ID', minWidth: 120, showOverflowTooltip: true },
+ { prop: 'matnrCode', label: '鐗╂枡缂栫爜', minWidth: 150, showOverflowTooltip: true },
+ { prop: 'maktx', label: '鐗╂枡鍚嶇О', minWidth: 220, showOverflowTooltip: true },
+ { prop: 'anfme', label: '璁″垝鏁伴噺', width: 100, align: 'right' },
+ { prop: 'stockUnit', label: '搴撳瓨鍗曚綅', width: 100, align: 'center' },
+ { prop: 'purQty', label: '閲囪喘鏁伴噺', width: 100, align: 'right' },
+ { prop: 'purUnit', label: '閲囪喘鍗曚綅', width: 100, align: 'center' },
+ { prop: 'qty', label: '宸插嚭鏁伴噺', width: 100, align: 'right' },
+ { prop: 'splrCode', label: '渚涘簲鍟嗙紪鐮�', minWidth: 120, showOverflowTooltip: true },
+ { prop: 'splrName', label: '渚涘簲鍟�', minWidth: 150, showOverflowTooltip: true },
+ { prop: 'qrcode', label: '浜岀淮鐮�', minWidth: 140, showOverflowTooltip: true },
+ { prop: 'trackCode', label: '鏉$爜', minWidth: 140, showOverflowTooltip: true },
+ { prop: 'packName', label: '鍖呰', minWidth: 120, showOverflowTooltip: true },
+ {
+ prop: 'statusText',
+ label: '鐘舵��',
+ width: 96,
+ align: 'center',
+ formatter: (row) => buildStatusTag(row)
+ },
+ { prop: 'memo', label: '澶囨敞', minWidth: 150, showOverflowTooltip: true },
+ {
+ prop: 'operation',
+ label: '鎿嶄綔',
+ width: 120,
+ fixed: 'right',
+ formatter: (row) =>
+ h(ArtButtonMore, {
+ list: [
+ { key: 'view', label: '鏌ョ湅', icon: 'ri:eye-line' },
+ { key: 'edit', label: '缂栬緫', icon: 'ri:edit-line', disabled: !canEdit },
+ {
+ key: 'delete',
+ label: '鍒犻櫎',
+ icon: 'ri:delete-bin-6-line',
+ color: 'var(--el-color-danger)',
+ disabled: !canDelete
+ }
+ ],
+ onClick: (item) => handleActionClick?.(item, row)
+ })
+ }
+ ]
+}
diff --git a/rsf-design/src/views/orders/preparation/index.vue b/rsf-design/src/views/orders/preparation/index.vue
index 1e162a0..525d8a4 100644
--- a/rsf-design/src/views/orders/preparation/index.vue
+++ b/rsf-design/src/views/orders/preparation/index.vue
@@ -12,6 +12,16 @@
<ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="refreshData">
<template #left>
<ElSpace wrap>
+ <ElButton v-if="canCreate" type="primary" @click="generateDialogVisible = true">
+ 鎵嬪姩寤哄崟
+ </ElButton>
+ <ElButton
+ v-if="canUpdate"
+ :disabled="loading || selectedRows.length === 0"
+ @click="waveDialogVisible = true"
+ >
+ 鐢熸垚娉㈡
+ </ElButton>
<ListExportPrint
:preview-visible="previewVisible"
@update:previewVisible="handlePreviewVisibleChange"
@@ -52,13 +62,31 @@
@size-change="handleDetailSizeChange"
@current-change="handleDetailCurrentChange"
/>
+
+ <PreparationGenerateDialog
+ v-model:visible="generateDialogVisible"
+ @created="handlePreparationCreated"
+ />
+
+ <PreparationWaveDialog
+ v-model:visible="waveDialogVisible"
+ :submitting="waveSubmitting"
+ @submit="handleGenerateWave"
+ />
+
+ <PreparationTaskDialog
+ v-model:visible="taskDialogVisible"
+ :row="activeTaskRow"
+ @submitted="handleTaskSubmitted"
+ />
</div>
</template>
<script setup>
import { computed, reactive, ref } from 'vue'
- import { ElMessage, ElMessageBox } from 'element-plus'
+ import { ElButton, ElMessage, ElMessageBox, ElSpace } from 'element-plus'
import { useRouter } from 'vue-router'
+ import { useAuth } from '@/hooks/core/useAuth'
import { useUserStore } from '@/store/modules/user'
import { useTable } from '@/hooks/core/useTable'
import { usePrintExportPage } from '@/views/system/common/usePrintExportPage'
@@ -70,15 +98,20 @@
fetchCompletePreparation,
fetchDeletePreparation,
fetchExportPreparationReport,
+ fetchGeneratePreparationWave,
fetchGetPreparationDetail,
fetchGetPreparationMany,
fetchPreparationItemPage,
fetchPreparationPage
} from '@/api/preparation'
import PreparationDetailDrawer from './modules/preparation-detail-drawer.vue'
+ import PreparationGenerateDialog from './modules/preparation-generate-dialog.vue'
+ import PreparationTaskDialog from './modules/preparation-task-dialog.vue'
+ import PreparationWaveDialog from './modules/preparation-wave-dialog.vue'
import { createPreparationTableColumns } from './preparationTable.columns'
import {
buildPreparationDetailQueryParams,
+ buildPreparationGenerateWavePayload,
buildPreparationPageQueryParams,
buildPreparationPrintRows,
buildPreparationReportMeta,
@@ -93,6 +126,7 @@
defineOptions({ name: 'Preparation' })
+ const { hasAuth } = useAuth()
const userStore = useUserStore()
const router = useRouter()
const reportTitle = PREPARATION_REPORT_TITLE
@@ -103,6 +137,11 @@
const detailData = ref({})
const detailTableData = ref([])
const activePreparationId = ref(null)
+ const generateDialogVisible = ref(false)
+ const waveDialogVisible = ref(false)
+ const waveSubmitting = ref(false)
+ const taskDialogVisible = ref(false)
+ const activeTaskRow = ref({})
const detailPagination = reactive({
current: 1,
@@ -110,6 +149,8 @@
total: 0
})
+ const canCreate = computed(() => hasAuth('add'))
+ const canUpdate = computed(() => hasAuth('update'))
const reportQueryParams = computed(() => buildPreparationSearchParams(searchForm.value))
const detailColumns = computed(() => createPreparationDetailItemColumns())
const searchItems = computed(() => [
@@ -132,6 +173,12 @@
props: { clearable: true, placeholder: '璇疯緭鍏� PO 鍗曞彿' }
},
{
+ label: 'PO ID',
+ key: 'poId',
+ type: 'inputNumber',
+ props: { clearable: true, controlsPosition: 'right', placeholder: '璇疯緭鍏� PO ID' }
+ },
+ {
label: '涓氬姟绫诲瀷',
key: 'wkType',
type: 'input',
@@ -140,20 +187,56 @@
{
label: '鍗曟嵁鐘舵��',
key: 'exceStatus',
- type: 'input',
- props: { clearable: true, placeholder: '璇疯緭鍏ョ姸鎬�' }
+ type: 'inputNumber',
+ props: { clearable: true, controlsPosition: 'right', placeholder: '璇疯緭鍏ョ姸鎬�' }
},
{
label: '閲婃斁鐘舵��',
key: 'rleStatus',
+ type: 'inputNumber',
+ props: { clearable: true, controlsPosition: 'right', placeholder: '璇疯緭鍏ラ噴鏀剧姸鎬�' }
+ },
+ {
+ label: '璁″垝鏁伴噺',
+ key: 'anfme',
+ type: 'inputNumber',
+ props: { clearable: true, controlsPosition: 'right', placeholder: '璇疯緭鍏ヨ鍒掓暟閲�' }
+ },
+ {
+ label: '宸插嚭鏁伴噺',
+ key: 'qty',
+ type: 'inputNumber',
+ props: { clearable: true, controlsPosition: 'right', placeholder: '璇疯緭鍏ュ凡鍑烘暟閲�' }
+ },
+ {
+ label: '鐗╂祦鍗曞彿',
+ key: 'logisNo',
type: 'input',
- props: { clearable: true, placeholder: '璇疯緭鍏ラ噴鏀剧姸鎬�' }
+ props: { clearable: true, placeholder: '璇疯緭鍏ョ墿娴佸崟鍙�' }
+ },
+ {
+ label: '鍒拌揣鏃ユ湡',
+ key: 'arrTime',
+ type: 'date',
+ props: { clearable: true, valueFormat: 'YYYY-MM-DD', placeholder: '璇烽�夋嫨鍒拌揣鏃ユ湡' }
},
{
label: '瀹㈡埛',
key: 'customerName',
type: 'input',
props: { clearable: true, placeholder: '璇疯緭鍏ュ鎴�' }
+ },
+ {
+ label: '閿�鍞粍缁�',
+ key: 'saleOrgName',
+ type: 'input',
+ props: { clearable: true, placeholder: '璇疯緭鍏ラ攢鍞粍缁�' }
+ },
+ {
+ label: '澶囨敞',
+ key: 'memo',
+ type: 'input',
+ props: { clearable: true, placeholder: '璇疯緭鍏ュ娉�' }
}
])
@@ -178,9 +261,13 @@
detailLoading.value = true
try {
const [detailResponse, itemResponse] = await Promise.all([
- guardRequestWithMessage(fetchGetPreparationDetail(activePreparationId.value), {}, {
- timeoutMessage: '澶囨枡鍗曡鎯呭姞杞借秴鏃讹紝宸插仠姝㈢瓑寰�'
- }),
+ guardRequestWithMessage(
+ fetchGetPreparationDetail(activePreparationId.value),
+ {},
+ {
+ timeoutMessage: '澶囨枡鍗曡鎯呭姞杞借秴鏃讹紝宸插仠姝㈢瓑寰�'
+ }
+ ),
guardRequestWithMessage(
fetchPreparationItemPage(
buildPreparationDetailQueryParams({
@@ -239,6 +326,12 @@
orderId: String(row.id)
}
})
+ return
+ }
+
+ if (action.key === 'public') {
+ activeTaskRow.value = row
+ taskDialogVisible.value = true
return
}
@@ -329,6 +422,34 @@
resetSearchParams()
}
+ async function handleGenerateWave(waveRuleId) {
+ waveSubmitting.value = true
+ try {
+ await fetchGeneratePreparationWave(
+ buildPreparationGenerateWavePayload(selectedRows.value, waveRuleId)
+ )
+ ElMessage.success('娉㈡鐢熸垚鎴愬姛')
+ waveDialogVisible.value = false
+ selectedRows.value = []
+ await refreshData()
+ } catch (error) {
+ ElMessage.error(error?.message || '娉㈡鐢熸垚澶辫触')
+ } finally {
+ waveSubmitting.value = false
+ }
+ }
+
+ async function handlePreparationCreated() {
+ await refreshData()
+ }
+
+ async function handleTaskSubmitted() {
+ await refreshData()
+ if (detailDrawerVisible.value && activePreparationId.value === activeTaskRow.value?.id) {
+ await loadDetailResources()
+ }
+ }
+
function handleDetailSizeChange(size) {
detailPagination.size = size
detailPagination.current = 1
diff --git a/rsf-design/src/views/orders/preparation/modules/preparation-generate-dialog.vue b/rsf-design/src/views/orders/preparation/modules/preparation-generate-dialog.vue
new file mode 100644
index 0000000..0320f04
--- /dev/null
+++ b/rsf-design/src/views/orders/preparation/modules/preparation-generate-dialog.vue
@@ -0,0 +1,301 @@
+<template>
+ <ElDialog
+ :model-value="visible"
+ title="鐢熸垚澶囨枡鍗�"
+ width="92%"
+ top="4vh"
+ destroy-on-close
+ @update:model-value="handleVisibleChange"
+ >
+ <div class="flex flex-col gap-4">
+ <ArtSearchBar
+ v-model="searchForm"
+ :items="searchItems"
+ :show-expand="true"
+ @search="handleSearch"
+ @reset="handleReset"
+ />
+
+ <ElCard class="art-table-card">
+ <template #header>
+ <div class="flex items-center justify-between gap-3">
+ <span class="font-medium">鍙�夊彂璐ф槑缁�</span>
+ <ElSpace>
+ <ElButton @click="reloadData">鍒锋柊</ElButton>
+ <ElButton type="primary" :disabled="!selectedRows.length" @click="openPreview">
+ 棰勮鐢熸垚
+ </ElButton>
+ </ElSpace>
+ </div>
+ </template>
+
+ <ArtTable
+ :loading="loading"
+ :data="tableData"
+ :columns="tableColumns"
+ :pagination="pagination"
+ @selection-change="handleSelectionChange"
+ @pagination:size-change="handleSizeChange"
+ @pagination:current-change="handleCurrentChange"
+ />
+ </ElCard>
+ </div>
+
+ <ElDialog v-model="previewVisible" title="鐢熸垚棰勮" width="88%" append-to-body destroy-on-close>
+ <div class="flex flex-col gap-4">
+ <div class="text-sm text-[var(--art-text-gray-600)]">
+ 宸查�夋嫨 {{ previewRows.length }} 鏉″彂璐ф槑缁嗭紝鍙湪涓嬫柟璋冩暣鏈澶囨枡鏁伴噺銆�
+ </div>
+
+ <ElTable :data="previewRows" border height="56vh">
+ <ElTableColumn type="index" label="搴忓彿" width="72" align="center" />
+ <ElTableColumn
+ prop="deliveryCode"
+ label="鍙戣揣鍗曞彿"
+ min-width="160"
+ show-overflow-tooltip
+ />
+ <ElTableColumn prop="matnrCode" label="鐗╂枡缂栫爜" min-width="150" show-overflow-tooltip />
+ <ElTableColumn prop="maktx" label="鐗╂枡鍚嶇О" min-width="220" show-overflow-tooltip />
+ <ElTableColumn prop="splrName" label="渚涘簲鍟�" min-width="150" show-overflow-tooltip />
+ <ElTableColumn prop="unit" label="鍗曚綅" width="90" align="center" />
+ <ElTableColumn label="鍓╀綑鏁伴噺" width="110" align="right">
+ <template #default="{ row }">
+ {{ row.remainingQty }}
+ </template>
+ </ElTableColumn>
+ <ElTableColumn label="鏈澶囨枡鏁伴噺" width="160" align="center">
+ <template #default="{ row }">
+ <ElInputNumber
+ v-model="row.anfme"
+ :min="0"
+ :max="row.remainingQty"
+ :precision="2"
+ controls-position="right"
+ />
+ </template>
+ </ElTableColumn>
+ <ElTableColumn
+ prop="splrBatch"
+ label="渚涘簲鍟嗘壒娆�"
+ min-width="140"
+ show-overflow-tooltip
+ />
+ <ElTableColumn prop="updateTime" label="鏇存柊鏃堕棿" min-width="170" show-overflow-tooltip />
+ </ElTable>
+ </div>
+
+ <template #footer>
+ <ElSpace>
+ <ElButton @click="previewVisible = false">鍙栨秷</ElButton>
+ <ElButton type="primary" :loading="submitting" @click="handleConfirmGenerate">
+ 鐢熸垚澶囨枡鍗�
+ </ElButton>
+ </ElSpace>
+ </template>
+ </ElDialog>
+
+ <template #footer>
+ <ElButton @click="handleVisibleChange(false)">鍏抽棴</ElButton>
+ </template>
+ </ElDialog>
+</template>
+
+<script setup>
+ import { computed, reactive, ref, watch } from 'vue'
+ import { ElMessage } from 'element-plus'
+ import { defaultResponseAdapter } from '@/utils/table/tableUtils'
+ import { fetchPreparationDialogPage, fetchGeneratePreparationOrders } from '@/api/preparation'
+
+ const props = defineProps({
+ visible: { type: Boolean, default: false }
+ })
+
+ const emit = defineEmits(['update:visible', 'created'])
+
+ const loading = ref(false)
+ const submitting = ref(false)
+ const tableData = ref([])
+ const selectedRows = ref([])
+ const previewVisible = ref(false)
+ const previewRows = ref([])
+ const searchForm = ref(createSearchState())
+ const pagination = reactive({
+ current: 1,
+ size: 20,
+ total: 0
+ })
+
+ const searchItems = computed(() => [
+ {
+ label: '鍏抽敭瀛�',
+ key: 'condition',
+ type: 'input',
+ props: { clearable: true, placeholder: '璇疯緭鍏ュ彂璐у崟鍙�/鐗╂枡缂栫爜/鐗╂枡鍚嶇О' }
+ },
+ {
+ label: '鍙戣揣鍗曞彿',
+ key: 'deliveryCode',
+ type: 'input',
+ props: { clearable: true, placeholder: '璇疯緭鍏ュ彂璐у崟鍙�' }
+ },
+ {
+ label: '鐗╂枡鍚嶇О',
+ key: 'maktx',
+ type: 'input',
+ props: { clearable: true, placeholder: '璇疯緭鍏ョ墿鏂欏悕绉�' }
+ },
+ {
+ label: '鐗╂枡缂栫爜',
+ key: 'matnrCode',
+ type: 'input',
+ props: { clearable: true, placeholder: '璇疯緭鍏ョ墿鏂欑紪鐮�' }
+ },
+ {
+ label: '渚涘簲鍟�',
+ key: 'splrName',
+ type: 'input',
+ props: { clearable: true, placeholder: '璇疯緭鍏ヤ緵搴斿晢' }
+ }
+ ])
+
+ const tableColumns = [
+ { type: 'selection', width: 48, align: 'center' },
+ { type: 'globalIndex', label: '搴忓彿', width: 72, align: 'center' },
+ { prop: 'deliveryCode', label: '鍙戣揣鍗曞彿', minWidth: 160, showOverflowTooltip: true },
+ { prop: 'matnrCode', label: '鐗╂枡缂栫爜', minWidth: 150, showOverflowTooltip: true },
+ { prop: 'maktx', label: '鐗╂枡鍚嶇О', minWidth: 220, showOverflowTooltip: true },
+ { prop: 'unit', label: '鍗曚綅', width: 90, align: 'center' },
+ { prop: 'anfme', label: '璁″垝鏁伴噺', width: 100, align: 'right' },
+ { prop: 'workQty', label: '鎵ц鏁伴噺', width: 100, align: 'right' },
+ { prop: 'qty', label: '宸插嚭鏁伴噺', width: 100, align: 'right' },
+ { prop: 'splrName', label: '渚涘簲鍟�', minWidth: 150, showOverflowTooltip: true },
+ { prop: 'splrBatch', label: '渚涘簲鍟嗘壒娆�', minWidth: 140, showOverflowTooltip: true },
+ { prop: 'updateTime', label: '鏇存柊鏃堕棿', minWidth: 170, showOverflowTooltip: true }
+ ]
+
+ function createSearchState() {
+ return {
+ condition: '',
+ deliveryCode: '',
+ maktx: '',
+ matnrCode: '',
+ splrName: ''
+ }
+ }
+
+ function buildQueryParams() {
+ return {
+ current: pagination.current,
+ pageSize: pagination.size,
+ ...Object.fromEntries(
+ Object.entries(searchForm.value).filter(([, value]) => value !== '' && value !== undefined)
+ )
+ }
+ }
+
+ function normalizeRow(row = {}) {
+ const anfme = Number(row.anfme || 0)
+ const workQty = Number(row.workQty || 0)
+ const qty = Number(row.qty || 0)
+ const remainingQty = Math.max(anfme - workQty - qty, 0)
+
+ return {
+ ...row,
+ remainingQty,
+ anfme: remainingQty
+ }
+ }
+
+ async function loadData() {
+ loading.value = true
+ try {
+ const response = await fetchPreparationDialogPage(buildQueryParams())
+ const normalized = defaultResponseAdapter(response)
+ tableData.value = Array.isArray(normalized.records) ? normalized.records : []
+ pagination.total = Number(normalized.total || 0)
+ pagination.current = Number(normalized.current || pagination.current || 1)
+ pagination.size = Number(normalized.size || pagination.size || 20)
+ } catch (error) {
+ tableData.value = []
+ pagination.total = 0
+ ElMessage.error(error?.message || '鍙戣揣鏄庣粏鍔犺浇澶辫触')
+ } finally {
+ loading.value = false
+ }
+ }
+
+ function reloadData() {
+ loadData()
+ }
+
+ function handleSelectionChange(rows) {
+ selectedRows.value = Array.isArray(rows) ? rows : []
+ }
+
+ function handleSearch(params) {
+ searchForm.value = { ...searchForm.value, ...params }
+ pagination.current = 1
+ loadData()
+ }
+
+ function handleReset() {
+ searchForm.value = createSearchState()
+ pagination.current = 1
+ loadData()
+ }
+
+ function handleSizeChange(size) {
+ pagination.size = size
+ pagination.current = 1
+ loadData()
+ }
+
+ function handleCurrentChange(current) {
+ pagination.current = current
+ loadData()
+ }
+
+ function openPreview() {
+ previewRows.value = selectedRows.value.map((row) => normalizeRow({ ...row }))
+ previewVisible.value = true
+ }
+
+ async function handleConfirmGenerate() {
+ const rows = previewRows.value.filter((row) => Number(row.anfme) > 0)
+ if (!rows.length) {
+ ElMessage.warning('鑷冲皯淇濈暀涓�鏉℃暟閲忓ぇ浜� 0 鐨勫彂璐ф槑缁�')
+ return
+ }
+
+ submitting.value = true
+ try {
+ await fetchGeneratePreparationOrders({ ids: rows })
+ ElMessage.success('澶囨枡鍗曠敓鎴愭垚鍔�')
+ previewVisible.value = false
+ emit('created')
+ handleVisibleChange(false)
+ } catch (error) {
+ ElMessage.error(error?.message || '澶囨枡鍗曠敓鎴愬け璐�')
+ } finally {
+ submitting.value = false
+ }
+ }
+
+ function handleVisibleChange(value) {
+ emit('update:visible', value)
+ }
+
+ watch(
+ () => props.visible,
+ (visible) => {
+ if (!visible) {
+ selectedRows.value = []
+ previewRows.value = []
+ previewVisible.value = false
+ return
+ }
+ loadData()
+ }
+ )
+</script>
diff --git a/rsf-design/src/views/orders/preparation/modules/preparation-task-dialog.vue b/rsf-design/src/views/orders/preparation/modules/preparation-task-dialog.vue
new file mode 100644
index 0000000..0398528
--- /dev/null
+++ b/rsf-design/src/views/orders/preparation/modules/preparation-task-dialog.vue
@@ -0,0 +1,259 @@
+<template>
+ <ElDialog
+ :model-value="visible"
+ title="涓嬪彂鎵ц"
+ width="94%"
+ top="4vh"
+ destroy-on-close
+ @update:model-value="handleVisibleChange"
+ >
+ <div class="flex flex-col gap-4">
+ <ElCard shadow="never">
+ <div class="grid gap-4 md:grid-cols-[240px_240px_auto]">
+ <div>
+ <div class="mb-2 text-sm text-[var(--art-text-gray-600)]">娉㈡绛栫暐</div>
+ <ElSelect
+ v-model="waveRuleId"
+ class="w-full"
+ filterable
+ clearable
+ placeholder="璇烽�夋嫨娉㈡绛栫暐"
+ :loading="metaLoading"
+ @change="handleWaveRuleChange"
+ >
+ <ElOption
+ v-for="item in waveRuleOptions"
+ :key="item.value"
+ :label="item.label"
+ :value="item.value"
+ />
+ </ElSelect>
+ </div>
+
+ <div>
+ <div class="mb-2 text-sm text-[var(--art-text-gray-600)]">鎵归噺璁剧疆绔欑偣</div>
+ <ElSelect
+ v-model="batchSiteNo"
+ class="w-full"
+ filterable
+ clearable
+ placeholder="璇烽�夋嫨绔欑偣"
+ >
+ <ElOption
+ v-for="site in siteOptions"
+ :key="site.value"
+ :label="site.label"
+ :value="site.value"
+ />
+ </ElSelect>
+ </div>
+
+ <div class="flex items-end gap-2">
+ <ElButton :loading="previewLoading" @click="loadTaskPreview">鍒锋柊棰勮</ElButton>
+ <ElButton :disabled="!selectedRowIds.length || !batchSiteNo" @click="applyBatchSite">
+ 搴旂敤鍒伴�変腑琛�
+ </ElButton>
+ </div>
+ </div>
+ </ElCard>
+
+ <ElTable :data="taskRows" border height="58vh" @selection-change="handleSelectionChange">
+ <ElTableColumn type="selection" width="48" align="center" />
+ <ElTableColumn type="index" label="搴忓彿" width="72" align="center" />
+ <ElTableColumn prop="locCode" label="搴撲綅" min-width="120" show-overflow-tooltip />
+ <ElTableColumn prop="barcode" label="瀹瑰櫒" min-width="120" show-overflow-tooltip />
+ <ElTableColumn prop="matnrCode" label="鐗╂枡缂栫爜" min-width="150" show-overflow-tooltip />
+ <ElTableColumn prop="batch" label="鎵规" min-width="120" show-overflow-tooltip />
+ <ElTableColumn prop="unit" label="鍗曚綅" width="90" align="center" />
+ <ElTableColumn prop="outQty" label="鍑哄簱鏁伴噺" width="110" align="right" />
+ <ElTableColumn prop="anfme" label="搴撳瓨鏁伴噺" width="110" align="right" />
+ <ElTableColumn label="鍑哄簱鍙�" width="180">
+ <template #default="{ row }">
+ <ElSelect
+ v-model="row.siteNo"
+ class="w-full"
+ filterable
+ clearable
+ placeholder="璇烽�夋嫨绔欑偣"
+ >
+ <ElOption
+ v-for="site in resolveSiteOptions(row)"
+ :key="site.value"
+ :label="site.label"
+ :value="site.value"
+ />
+ </ElSelect>
+ </template>
+ </ElTableColumn>
+ </ElTable>
+ </div>
+
+ <template #footer>
+ <ElSpace>
+ <ElButton @click="handleVisibleChange(false)">鍏抽棴</ElButton>
+ <ElButton type="primary" :loading="submitting" @click="handleSubmit"> 鐢熸垚浠诲姟 </ElButton>
+ </ElSpace>
+ </template>
+ </ElDialog>
+</template>
+
+<script setup>
+ import { ref, watch } from 'vue'
+ import { ElMessage } from 'element-plus'
+ import { fetchWaveRulePage } from '@/api/system-manage'
+ import { defaultResponseAdapter } from '@/utils/table/tableUtils'
+ import {
+ fetchGeneratePreparationTasks,
+ fetchPreparationTaskPreview,
+ fetchPreparationTaskSites
+ } from '@/api/preparation'
+
+ const props = defineProps({
+ visible: { type: Boolean, default: false },
+ row: { type: Object, default: () => ({}) }
+ })
+
+ const emit = defineEmits(['update:visible', 'submitted'])
+
+ const metaLoading = ref(false)
+ const previewLoading = ref(false)
+ const submitting = ref(false)
+ const waveRuleId = ref()
+ const waveRuleOptions = ref([])
+ const siteOptions = ref([])
+ const batchSiteNo = ref('')
+ const selectedRowIds = ref([])
+ const taskRows = ref([])
+
+ function handleVisibleChange(value) {
+ emit('update:visible', value)
+ }
+
+ function normalizeSiteOptions(records = []) {
+ return (Array.isArray(records) ? records : []).map((item) => ({
+ label: item.site || item.staNo || item.name || '--',
+ value: item.site || item.staNo || item.name || '',
+ raw: item
+ }))
+ }
+
+ async function loadMeta() {
+ metaLoading.value = true
+ try {
+ const [waveResponse, siteResponse] = await Promise.all([
+ fetchWaveRulePage({ current: 1, pageSize: 200, status: 1 }),
+ fetchPreparationTaskSites()
+ ])
+ const waveRecords = defaultResponseAdapter(waveResponse).records
+ waveRuleOptions.value = waveRecords.map((item) => ({
+ label: item.name || item.code || `娉㈡绛栫暐${item.id}`,
+ value: Number(item.id)
+ }))
+ if (!waveRuleId.value && waveRuleOptions.value.length) {
+ waveRuleId.value = waveRuleOptions.value[0].value
+ }
+ siteOptions.value = normalizeSiteOptions(siteResponse)
+ } catch (error) {
+ waveRuleOptions.value = []
+ siteOptions.value = []
+ ElMessage.error(error?.message || '涓嬪彂鎵ц鍒濆鍖栧け璐�')
+ } finally {
+ metaLoading.value = false
+ }
+ }
+
+ function normalizeTaskRow(row = {}) {
+ return {
+ ...row,
+ siteNo: row.siteNo || row.site || ''
+ }
+ }
+
+ async function loadTaskPreview() {
+ if (!props.row?.id) {
+ return
+ }
+ if (!waveRuleId.value) {
+ ElMessage.warning('璇烽�夋嫨娉㈡绛栫暐')
+ return
+ }
+
+ previewLoading.value = true
+ try {
+ const response = await fetchPreparationTaskPreview({
+ orderId: Number(props.row.id),
+ waveId: Number(waveRuleId.value)
+ })
+ taskRows.value = Array.isArray(response) ? response.map((item) => normalizeTaskRow(item)) : []
+ selectedRowIds.value = []
+ } catch (error) {
+ taskRows.value = []
+ ElMessage.error(error?.message || '浠诲姟棰勮鍔犺浇澶辫触')
+ } finally {
+ previewLoading.value = false
+ }
+ }
+
+ function handleWaveRuleChange() {
+ if (props.visible) {
+ loadTaskPreview()
+ }
+ }
+
+ function handleSelectionChange(rows) {
+ selectedRowIds.value = Array.isArray(rows) ? rows.map((item) => item.id) : []
+ }
+
+ function applyBatchSite() {
+ taskRows.value = taskRows.value.map((item) =>
+ selectedRowIds.value.includes(item.id) ? { ...item, siteNo: batchSiteNo.value } : item
+ )
+ }
+
+ function resolveSiteOptions(row) {
+ if (Array.isArray(row?.staNos) && row.staNos.length) {
+ return row.staNos.map((item) => ({
+ label: item.staNo || item.site || '--',
+ value: item.staNo || item.site || ''
+ }))
+ }
+ return siteOptions.value
+ }
+
+ async function handleSubmit() {
+ const items = taskRows.value.filter((item) => item.locCode && item.siteNo)
+ if (!items.length) {
+ ElMessage.warning('璇疯嚦灏戜负涓�鏉℃湁搴撲綅鐨勮褰曟寚瀹氱珯鐐�')
+ return
+ }
+
+ submitting.value = true
+ try {
+ await fetchGeneratePreparationTasks({
+ outId: Number(props.row.id),
+ items
+ })
+ ElMessage.success('浠诲姟鐢熸垚鎴愬姛')
+ emit('submitted')
+ handleVisibleChange(false)
+ } catch (error) {
+ ElMessage.error(error?.message || '浠诲姟鐢熸垚澶辫触')
+ } finally {
+ submitting.value = false
+ }
+ }
+
+ watch(
+ () => props.visible,
+ async (visible) => {
+ if (!visible) {
+ taskRows.value = []
+ selectedRowIds.value = []
+ batchSiteNo.value = ''
+ return
+ }
+ await loadMeta()
+ await loadTaskPreview()
+ }
+ )
+</script>
diff --git a/rsf-design/src/views/orders/preparation/modules/preparation-wave-dialog.vue b/rsf-design/src/views/orders/preparation/modules/preparation-wave-dialog.vue
new file mode 100644
index 0000000..8deaa9a
--- /dev/null
+++ b/rsf-design/src/views/orders/preparation/modules/preparation-wave-dialog.vue
@@ -0,0 +1,97 @@
+<template>
+ <ElDialog
+ :model-value="visible"
+ title="鐢熸垚娉㈡"
+ width="520px"
+ destroy-on-close
+ @update:model-value="handleVisibleChange"
+ >
+ <ElForm label-width="110px">
+ <ElFormItem label="娉㈡绛栫暐" required>
+ <ElSelect
+ v-model="waveRuleId"
+ class="w-full"
+ filterable
+ clearable
+ placeholder="璇烽�夋嫨娉㈡绛栫暐"
+ :loading="loading"
+ >
+ <ElOption
+ v-for="item in waveRuleOptions"
+ :key="item.value"
+ :label="item.label"
+ :value="item.value"
+ />
+ </ElSelect>
+ </ElFormItem>
+ </ElForm>
+
+ <template #footer>
+ <ElSpace>
+ <ElButton @click="handleVisibleChange(false)">鍙栨秷</ElButton>
+ <ElButton type="primary" :loading="submitting" @click="handleSubmit">纭畾</ElButton>
+ </ElSpace>
+ </template>
+ </ElDialog>
+</template>
+
+<script setup>
+ import { ref, watch } from 'vue'
+ import { ElMessage } from 'element-plus'
+ import { fetchWaveRulePage } from '@/api/system-manage'
+ import { defaultResponseAdapter } from '@/utils/table/tableUtils'
+
+ const props = defineProps({
+ visible: { type: Boolean, default: false },
+ submitting: { type: Boolean, default: false }
+ })
+
+ const emit = defineEmits(['update:visible', 'submit'])
+
+ const loading = ref(false)
+ const waveRuleId = ref()
+ const waveRuleOptions = ref([])
+
+ async function loadWaveRuleOptions() {
+ loading.value = true
+ try {
+ const response = await fetchWaveRulePage({ current: 1, pageSize: 200, status: 1 })
+ const normalized = defaultResponseAdapter(response)
+ const records = Array.isArray(normalized.records) ? normalized.records : []
+ waveRuleOptions.value = records.map((item) => ({
+ label: item.name || item.code || `娉㈡绛栫暐${item.id}`,
+ value: Number(item.id)
+ }))
+ if (!waveRuleId.value && waveRuleOptions.value.length) {
+ waveRuleId.value = waveRuleOptions.value[0].value
+ }
+ } catch (error) {
+ waveRuleOptions.value = []
+ ElMessage.error(error?.message || '娉㈡绛栫暐鍔犺浇澶辫触')
+ } finally {
+ loading.value = false
+ }
+ }
+
+ function handleVisibleChange(value) {
+ emit('update:visible', value)
+ }
+
+ function handleSubmit() {
+ if (!waveRuleId.value) {
+ ElMessage.warning('璇烽�夋嫨娉㈡绛栫暐')
+ return
+ }
+ emit('submit', waveRuleId.value)
+ }
+
+ watch(
+ () => props.visible,
+ (visible) => {
+ if (!visible) {
+ return
+ }
+ loadWaveRuleOptions()
+ }
+ )
+</script>
diff --git a/rsf-design/src/views/orders/preparation/preparationPage.helpers.js b/rsf-design/src/views/orders/preparation/preparationPage.helpers.js
index 53bdd34..d04f094 100644
--- a/rsf-design/src/views/orders/preparation/preparationPage.helpers.js
+++ b/rsf-design/src/views/orders/preparation/preparationPage.helpers.js
@@ -37,30 +37,38 @@
function normalizeStatusMeta(exceStatus, exceStatusText) {
if (exceStatusText) {
- return PREPARATION_STATUS_META[Number(exceStatus)] || {
- text: exceStatusText,
- type: 'info'
- }
+ return (
+ PREPARATION_STATUS_META[Number(exceStatus)] || {
+ text: exceStatusText,
+ type: 'info'
+ }
+ )
}
- return PREPARATION_STATUS_META[Number(exceStatus)] || {
- text: normalizeText(exceStatus) || '--',
- type: 'info'
- }
+ return (
+ PREPARATION_STATUS_META[Number(exceStatus)] || {
+ text: normalizeText(exceStatus) || '--',
+ type: 'info'
+ }
+ )
}
function normalizeRleStatusMeta(rleStatus, rleStatusText) {
if (rleStatusText) {
- return PREPARATION_RLE_STATUS_META[Number(rleStatus)] || {
- text: rleStatusText,
- type: 'info'
- }
+ return (
+ PREPARATION_RLE_STATUS_META[Number(rleStatus)] || {
+ text: rleStatusText,
+ type: 'info'
+ }
+ )
}
- return PREPARATION_RLE_STATUS_META[Number(rleStatus)] || {
- text: normalizeText(rleStatus) || '--',
- type: 'info'
- }
+ return (
+ PREPARATION_RLE_STATUS_META[Number(rleStatus)] || {
+ text: normalizeText(rleStatus) || '--',
+ type: 'info'
+ }
+ )
}
export function createPreparationSearchState() {
@@ -68,10 +76,14 @@
condition: '',
code: '',
poCode: '',
+ poId: '',
wkType: '',
exceStatus: '',
rleStatus: '',
+ anfme: '',
+ qty: '',
logisNo: '',
+ arrTime: '',
customerName: '',
saleOrgName: '',
memo: ''
@@ -87,6 +99,7 @@
'poCode',
'wkType',
'logisNo',
+ 'arrTime',
'customerName',
'saleOrgName',
'memo'
@@ -104,6 +117,12 @@
if (params.rleStatus !== '' && params.rleStatus !== undefined && params.rleStatus !== null) {
result.rleStatus = normalizeNumber(params.rleStatus)
}
+
+ ;['poId', 'anfme', 'qty'].forEach((key) => {
+ if (params[key] !== '' && params[key] !== undefined && params[key] !== null) {
+ result[key] = normalizeNumber(params[key])
+ }
+ })
return result
}
@@ -166,7 +185,8 @@
memo: normalizeText(record.memo) || '--',
canComplete: Number(record.exceStatus) !== 15,
canCancel: Number(record.exceStatus) === 10,
- canDelete: Number(record.exceStatus) !== 15
+ canDelete: Number(record.exceStatus) !== 15,
+ canPublic: Number(record.workQty || 0) < Number(record.anfme || 0)
}
}
@@ -189,6 +209,13 @@
return [
{ key: 'view', label: $t('common.actions.detail'), icon: 'ri:eye-line' },
{ key: 'items', label: $t('common.actions.items'), icon: 'ri:list-check-3' },
+ {
+ key: 'public',
+ label: '涓嬪彂鎵ц',
+ icon: 'ri:send-plane-line',
+ color: 'var(--el-color-primary)',
+ disabled: !normalizedRow.canPublic
+ },
{ key: 'print', label: $t('common.actions.print'), icon: 'ri:printer-line' },
{
key: 'complete',
@@ -251,3 +278,12 @@
}
}
}
+
+export function buildPreparationGenerateWavePayload(rows = [], waveRuleId) {
+ return {
+ ids: Array.isArray(rows)
+ ? rows.map((row) => Number(row?.id)).filter((id) => Number.isFinite(id))
+ : [],
+ waveRuleId: normalizeNumber(waveRuleId)
+ }
+}
diff --git a/rsf-design/src/views/orders/preparation/preparationTable.columns.js b/rsf-design/src/views/orders/preparation/preparationTable.columns.js
index 95116a5..9dd6e1a 100644
--- a/rsf-design/src/views/orders/preparation/preparationTable.columns.js
+++ b/rsf-design/src/views/orders/preparation/preparationTable.columns.js
@@ -7,12 +7,17 @@
return [
{ type: 'selection', width: 48, align: 'center' },
{ type: 'globalIndex', label: '搴忓彿', width: 72, align: 'center' },
+ { prop: 'id', label: 'ID', width: 90, align: 'center' },
{ prop: 'code', label: '澶囨枡鍗曞彿', minWidth: 170, showOverflowTooltip: true },
{ prop: 'poCode', label: 'PO鍗曞彿', minWidth: 150, showOverflowTooltip: true },
{ prop: 'typeLabel', label: '鍗曟嵁绫诲瀷', minWidth: 120, showOverflowTooltip: true },
{ prop: 'wkTypeLabel', label: '涓氬姟绫诲瀷', minWidth: 130, showOverflowTooltip: true },
+ { prop: 'saleUserName', label: '閿�鍞憳', minWidth: 120, showOverflowTooltip: true },
+ { prop: 'businessTimeText', label: '鍑哄簱鏃ユ湡', minWidth: 150, showOverflowTooltip: true },
+ { prop: 'customerId', label: '瀹㈡埛缂栫爜', minWidth: 140, showOverflowTooltip: true },
{ prop: 'customerName', label: '瀹㈡埛', minWidth: 160, showOverflowTooltip: true },
{ prop: 'saleOrgName', label: '閿�鍞粍缁�', minWidth: 150, showOverflowTooltip: true },
+ { prop: 'stockOrgName', label: '搴撳瓨缁勭粐', minWidth: 150, showOverflowTooltip: true },
{ prop: 'anfme', label: '搴斿嚭鏁伴噺', width: 100, align: 'right' },
{ prop: 'workQty', label: '鎵ц鏁伴噺', width: 100, align: 'right' },
{ prop: 'qty', label: '宸插嚭鏁伴噺', width: 100, align: 'right' },
@@ -39,11 +44,15 @@
() => row.exceStatusText || '--'
)
},
+ { prop: 'updateByText', label: '鏇存柊浜�', minWidth: 120, showOverflowTooltip: true },
{ prop: 'updateTimeText', label: '鏇存柊鏃堕棿', minWidth: 170, showOverflowTooltip: true },
+ { prop: 'createByText', label: '鍒涘缓浜�', minWidth: 120, showOverflowTooltip: true },
+ { prop: 'createTimeText', label: '鍒涘缓鏃堕棿', minWidth: 170, showOverflowTooltip: true },
+ { prop: 'memo', label: '澶囨敞', minWidth: 150, showOverflowTooltip: true },
{
prop: 'operation',
label: '鎿嶄綔',
- width: 120,
+ width: 140,
align: 'center',
fixed: 'right',
formatter: (row) =>
--
Gitblit v1.9.1