| | |
| | | /log.path_IS_UNDEFINED/*.log |
| | | /log.path_IS_UNDEFINED/*/*.log |
| | | .worktrees/ |
| | | .playwright-cli/ |
| | |
| | | return { |
| | | current: params.current || 1, |
| | | pageSize: params.pageSize || params.size || 20, |
| | | orderBy: params.orderBy || 'create_time desc', |
| | | ...filterParams(params, ['current', 'pageSize', 'size']) |
| | | } |
| | | } |
| | |
| | | }) |
| | | } |
| | | |
| | | 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', |
| | |
| | | }) |
| | | } |
| | | |
| | | 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', |
| | |
| | | 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' |
| | | } |
| | | }) |
| | | } |
| | |
| | | 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']) |
| | | } |
| | | } |
| | | |
| | |
| | | ? 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) |
| | |
| | | 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: |
| | |
| | | } |
| | | |
| | | 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 |
| | | ) |
| | | ) |
| | | } |
| | | |
| | |
| | | ? { 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 |
| | |
| | | : {}), |
| | | 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 !== '' |
| | |
| | | |
| | | 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) |
| | |
| | | return { |
| | | current: params.current || 1, |
| | | pageSize: params.pageSize || params.size || 20, |
| | | orderBy: normalizeText(params.orderBy) || 'create_time desc', |
| | | ...buildDeliverySearchParams(params) |
| | | } |
| | | } |
| | |
| | | 'splrBatch', |
| | | 'timeStart', |
| | | 'timeEnd', |
| | | 'memo' |
| | | 'memo', |
| | | 'fieldsIndex' |
| | | ].forEach((key) => { |
| | | const value = normalizeText(params[key]) |
| | | if (value) result[key] = value |
| | |
| | | return { |
| | | current: params.current || 1, |
| | | pageSize: params.pageSize || params.size || 20, |
| | | orderBy: normalizeText(params.orderBy) || 'create_time desc', |
| | | ...buildDeliveryItemSearchParams(params) |
| | | } |
| | | } |
| | |
| | | }) |
| | | } |
| | | |
| | | export function fetchDeleteDeliveryMany(ids) { |
| | | return request.post({ |
| | | url: `/delivery/remove/${normalizeIds(ids)}` |
| | | }) |
| | | } |
| | | |
| | | export function fetchSaveDelivery(payload = {}) { |
| | | return request.post({ |
| | | url: '/delivery/save', |
| | |
| | | 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' |
| | | } |
| | | }) |
| | | } |
| | | |
| | |
| | | }) |
| | | } |
| | | |
| | | export function fetchDeleteDeliveryItemMany(ids) { |
| | | return request.post({ |
| | | url: `/deliveryItem/remove/${normalizeIds(ids)}` |
| | | }) |
| | | } |
| | | |
| | | export function fetchSaveDeliveryItem(payload = {}) { |
| | | return request.post({ |
| | | url: '/deliveryItem/save', |
| | |
| | | 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) |
| | | }) |
| | | } |
| | |
| | | }) |
| | | } |
| | | |
| | | 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', |
| | |
| | | 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', |
| | |
| | | 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', |
| | |
| | | "reportTitle": "ASN Report", |
| | | "entity": "ASN", |
| | | "buttons": { |
| | | "create": "New ASN", |
| | | "import": "Import", |
| | | "downloadTemplate": "Download Template", |
| | | "inspection": "Batch Inspection", |
| | | "createByPo": "Create by PO" |
| | | }, |
| | | "search": { |
| | |
| | | "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", |
| | |
| | | "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" |
| | | }, |
| | |
| | | "actions": { |
| | | "view": "View Detail", |
| | | "items": "Receiving Items", |
| | | "edit": "Edit", |
| | | "delete": "Delete", |
| | | "print": "Print", |
| | | "complete": "Complete" |
| | | }, |
| | |
| | | "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", |
| | |
| | | } |
| | | }, |
| | | "table": { |
| | | "purchaseOrgName": "Purchasing Org", |
| | | "businessTime": "Purchase Date", |
| | | "supplierId": "Supplier Code", |
| | | "poItemId": "PO Line No.", |
| | | "expectedQty": "Expected Qty", |
| | | "receivedQty": "Received Qty", |
| | |
| | | "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", |
| | |
| | | "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", |
| | |
| | | }, |
| | | "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", |
| | |
| | | }, |
| | | "actions": { |
| | | "view": "View Detail", |
| | | "edit": "Edit", |
| | | "items": "Items", |
| | | "delete": "Delete" |
| | | }, |
| | | "manage": { |
| | | "title": "Edit DO", |
| | | "baseInfo": "Order Information" |
| | | }, |
| | | "detail": { |
| | | "title": "Handover Order Detail", |
| | |
| | | "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": { |
| | |
| | | "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", |
| | |
| | | }, |
| | | "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": { |
| | |
| | | "reportTitle": "入库通知单报表", |
| | | "entity": "入库通知单", |
| | | "buttons": { |
| | | "create": "新建入库通知单", |
| | | "import": "导入", |
| | | "downloadTemplate": "下载模板", |
| | | "inspection": "批量报检", |
| | | "createByPo": "按PO建单" |
| | | }, |
| | | "search": { |
| | |
| | | "codePlaceholder": "请输入 ASN 单号", |
| | | "poCode": "PO单号", |
| | | "poCodePlaceholder": "请输入 PO 单号", |
| | | "poId": "PO单ID", |
| | | "type": "单据类型", |
| | | "wkType": "业务类型", |
| | | "wkTypePlaceholder": "请输入业务类型", |
| | | "anfme": "送货数量", |
| | | "qty": "已收数量", |
| | | "logisNo": "物流单号", |
| | | "arrTime": "预计到达时间", |
| | | "memo": "备注", |
| | | "exceStatus": "单据状态", |
| | | "supplierName": "供应商", |
| | | "supplierPlaceholder": "请输入供应商", |
| | |
| | | "condition": "请输入 ASN 单号/PO 单号/供应商", |
| | | "code": "请输入 ASN 单号", |
| | | "poCode": "请输入 PO 单号", |
| | | "wkType": "请输入业务类型", |
| | | "poId": "请输入 PO 单号 ID", |
| | | "type": "请选择单据类型", |
| | | "wkType": "请选择业务类型", |
| | | "anfme": "请输入送货数量", |
| | | "qty": "请输入已收数量", |
| | | "logisNo": "请输入物流单号", |
| | | "arrTime": "请选择预计到达时间", |
| | | "memo": "请输入备注", |
| | | "supplierName": "请输入供应商", |
| | | "purchaseUserName": "请输入采购员" |
| | | }, |
| | |
| | | "actions": { |
| | | "view": "查看详情", |
| | | "items": "收货明细", |
| | | "edit": "编辑", |
| | | "delete": "删除", |
| | | "print": "打印", |
| | | "complete": "完成" |
| | | }, |
| | |
| | | "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": "按PO建单", |
| | |
| | | } |
| | | }, |
| | | "table": { |
| | | "purchaseOrgName": "采购组织", |
| | | "businessTime": "采购日期", |
| | | "supplierId": "供应商编码", |
| | | "poItemId": "PO行号", |
| | | "expectedQty": "应收数量", |
| | | "receivedQty": "已收数量", |
| | |
| | | "search": { |
| | | "condition": "关键字", |
| | | "conditionPlaceholder": "请输入单号/ERP主单标识/平台单号", |
| | | "timeStart": "创建开始时间", |
| | | "timeEnd": "创建结束时间", |
| | | "code": "单号", |
| | | "codePlaceholder": "请输入单号", |
| | | "platId": "ERP主单标识", |
| | |
| | | "wkTypePlaceholder": "请输入业务类型", |
| | | "source": "单据来源", |
| | | "sourcePlaceholder": "请输入单据来源", |
| | | "anfme": "出库数量", |
| | | "qty": "已出库数量", |
| | | "workQty": "执行中数量", |
| | | "platCode": "平台单号", |
| | | "startTime": "计划出库时间", |
| | | "endTime": "计划出库结束时间", |
| | | "exceStatus": "执行状态", |
| | | "exceStatusPlaceholder": "请输入执行状态", |
| | | "memo": "备注", |
| | |
| | | }, |
| | | "placeholder": { |
| | | "condition": "请输入单号/ERP主单标识/平台单号", |
| | | "timeStart": "请选择创建开始时间", |
| | | "timeEnd": "请选择创建结束时间", |
| | | "code": "请输入单号", |
| | | "platId": "请输入ERP主单标识", |
| | | "type": "请输入单据类型", |
| | | "wkType": "请输入业务类型", |
| | | "source": "请输入单据来源", |
| | | "anfme": "请输入出库数量", |
| | | "qty": "请输入已出库数量", |
| | | "workQty": "请输入执行中数量", |
| | | "platCode": "请输入平台单号", |
| | | "startTime": "请选择计划出库时间", |
| | | "endTime": "请选择计划出库结束时间", |
| | | "status": "请选择状态", |
| | | "exceStatus": "请输入执行状态", |
| | | "memo": "请输入备注" |
| | | }, |
| | | "buttons": { |
| | | "import": "导入", |
| | | "downloadTemplate": "下载模板" |
| | | }, |
| | | "status": { |
| | | "normal": "正常", |
| | |
| | | }, |
| | | "actions": { |
| | | "view": "查看详情", |
| | | "edit": "编辑", |
| | | "items": "明细", |
| | | "delete": "删除" |
| | | }, |
| | | "manage": { |
| | | "title": "编辑DO单", |
| | | "baseInfo": "主单信息" |
| | | }, |
| | | "detail": { |
| | | "title": "交接单详情", |
| | |
| | | "messages": { |
| | | "itemsTimeout": "DO单明细加载超时,已停止等待", |
| | | "detailTimeout": "DO单详情加载超时,已停止等待", |
| | | "detailLoadFailed": "DO单详情加载失败" |
| | | "detailLoadFailed": "DO单详情加载失败", |
| | | "importSuccess": "DO单导入成功", |
| | | "importFailed": "DO单导入失败", |
| | | "templateDownloadSuccess": "模板下载成功", |
| | | "templateDownloadFailed": "模板下载失败" |
| | | } |
| | | }, |
| | | "deliveryItem": { |
| | |
| | | "deliveryCodePlaceholder": "请输入DO单号", |
| | | "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单ID", |
| | | "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单ID", |
| | |
| | | }, |
| | | "messages": { |
| | | "detailTimeout": "DO单明细详情加载超时,已停止等待", |
| | | "detailFailed": "获取DO单明细详情失败" |
| | | "detailFailed": "获取DO单明细详情失败", |
| | | "createSuccess": "DO单明细新增成功", |
| | | "createFailed": "DO单明细新增失败", |
| | | "updateSuccess": "DO单明细修改成功", |
| | | "updateFailed": "DO单明细修改失败", |
| | | "deleteConfirm": "确定删除DO单明细 {code} 吗?", |
| | | "actionFailed": "DO单明细操作失败" |
| | | } |
| | | }, |
| | | "transfer": { |
| | |
| | | <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="请选择可入库区" |
| | |
| | | :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> |
| | |
| | | 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))) |
| | |
| | | } |
| | | 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 |
| | | ) |
| | | } |
| | | } |
| | | |
| | |
| | | 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> |
| | |
| | | stationId: '', |
| | | type: '', |
| | | useStatus: '', |
| | | inAble: '', |
| | | outAble: '', |
| | | area: '', |
| | | isCrossZone: '', |
| | | crossZoneArea: '', |
| | | isWcs: '', |
| | | wcsData: '', |
| | | containerType: '', |
| | | barcode: '', |
| | | autoTransfer: '', |
| | | status: '', |
| | |
| | | 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: '' |
| | | } |
| | | } |
| | | |
| | |
| | | ? 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) |
| | |
| | | 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: |
| | |
| | | } |
| | | |
| | | 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 |
| | | ) |
| | | ) |
| | | } |
| | | |
| | |
| | | return { |
| | | current: params.current || 1, |
| | | pageSize: params.pageSize || params.size || 20, |
| | | orderBy: normalizeText(params.orderBy) || 'create_time desc', |
| | | ...buildBasStationSearchParams(params) |
| | | } |
| | | } |
| | |
| | | ? { 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) } |
| | | : {}), |
| | |
| | | ? { 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 !== '' |
| | |
| | | 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: |
| | |
| | | ? 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) |
| | |
| | | } |
| | | |
| | | 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)) { |
| | |
| | | } |
| | | 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) |
| | |
| | | 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) { |
| | |
| | | } |
| | | } |
| | | 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}`) |
| | | }) |
| | |
| | | } |
| | | 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) { |
| | |
| | | .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 || ''), |
| | |
| | | } |
| | | } |
| | | |
| | | 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({ |
| | |
| | | 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, |
| | |
| | | |
| | | export function createBasStationTableColumns({ |
| | | handleView, |
| | | handleCopy, |
| | | handleEdit, |
| | | handleDelete, |
| | | canEdit = true, |
| | |
| | | } = {}) { |
| | | 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 [ |
| | |
| | | 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', |
| | |
| | | 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, |
| | |
| | | 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) |
| | | } |
| | |
| | | <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" |
| | |
| | | :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> |
| | |
| | | } 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, |
| | |
| | | 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 |
| | |
| | | } |
| | | }, |
| | | { |
| | | 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', |
| | |
| | | } |
| | | }, |
| | | { |
| | | 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: '请输入WCS数据' |
| | | } |
| | | }, |
| | | { |
| | | label: '容器类型', |
| | | key: 'containerType', |
| | | type: 'select', |
| | | props: { |
| | | clearable: true, |
| | | filterable: true, |
| | | options: containerTypeOptions.value |
| | | } |
| | | }, |
| | | { |
| | |
| | | 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 = {} |
| | |
| | | |
| | | 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, |
| | |
| | | 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 |
| | | }) |
| | | ) |
| | | |
| | |
| | | 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() { |
| | |
| | | { records: [] }, |
| | | { timeoutMessage: '容器类型加载超时,已停止等待' } |
| | | ) |
| | | containerTypeOptions.value = buildBasStationContainerTypeOptions(defaultResponseAdapter(response).records) |
| | | containerTypeOptions.value = buildBasStationContainerTypeOptions( |
| | | defaultResponseAdapter(response).records |
| | | ) |
| | | } |
| | | |
| | | async function loadUseStatusOptions() { |
| | |
| | | { records: [] }, |
| | | { timeoutMessage: '使用状态加载超时,已停止等待' } |
| | | ) |
| | | useStatusOptions.value = buildBasStationUseStatusOptions(defaultResponseAdapter(response).records) |
| | | useStatusOptions.value = buildBasStationUseStatusOptions( |
| | | defaultResponseAdapter(response).records |
| | | ) |
| | | } |
| | | |
| | | onMounted(async () => { |
| New file |
| | |
| | | <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> |
| New file |
| | |
| | | <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> |
| | |
| | | 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) || '--', |
| | |
| | | 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, |
| | |
| | | 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, |
| | |
| | | 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, |
| | |
| | | <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' |
| | |
| | | import { fetchDictDataPage } from '@/api/system-manage' |
| | | import { |
| | | DEFAULT_ASN_ORDER_LOG_PAGE_SIZE, |
| | | buildAsnOrderLogDetailQueryParams, |
| | | buildAsnOrderLogPageQueryParams, |
| | | buildAsnOrderLogPrintRows, |
| | | buildAsnOrderLogReportMeta, |
| | |
| | | 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' }) |
| | | |
| | |
| | | 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 = { |
| | |
| | | } |
| | | 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 |
| | | |
| | |
| | | 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({ |
| | |
| | | } |
| | | } |
| | | |
| | | 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() { |
| | |
| | | } |
| | | |
| | | 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 |
| | | } |
| | | } |
| | | |
| | |
| | | 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 { |
| | |
| | | |
| | | 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 |
| New file |
| | |
| | | <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> |
| | |
| | | <ElDescriptionsItem label="PO单ID">{{ 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) |
| | |
| | | return String(value ?? '').trim() |
| | | } |
| | | |
| | | function normalizeDateValue(value) { |
| | | return value ? String(value) : '' |
| | | } |
| | | |
| | | function normalizeNumber(value) { |
| | | if (value === '' || value === null || value === undefined) { |
| | | return 0 |
| | |
| | | condition: '', |
| | | code: '', |
| | | poCode: '', |
| | | poId: '', |
| | | type: '', |
| | | wkType: '', |
| | | anfme: '', |
| | | qty: '', |
| | | logisNo: '', |
| | | arrTime: '', |
| | | memo: '', |
| | | exceStatus: '', |
| | | supplierName: '', |
| | | purchaseUserName: '' |
| | |
| | | |
| | | 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) |
| | | } |
| | | }) |
| | | |
| | |
| | | return { |
| | | current: params.current || 1, |
| | | pageSize: params.pageSize || params.size || 20, |
| | | orderBy: params.orderBy || 'create_time desc', |
| | | ...buildAsnOrderSearchParams(params) |
| | | } |
| | | } |
| | |
| | | |
| | | 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, |
| | |
| | | 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 |
| | | } |
| | | } |
| | | |
| | |
| | | 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 = {}) { |
| | |
| | | })) |
| | | } |
| | | |
| | | 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'), |
| | |
| | | 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 |
| | | } |
| | |
| | | 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', |
| | |
| | | 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', |
| | |
| | | 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, |
| | |
| | | ) |
| | | }, |
| | | { |
| | | 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) |
| | | }) |
| | | } |
| | |
| | | <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" |
| | |
| | | /> |
| | | </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" |
| | |
| | | /> |
| | | |
| | | <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' |
| | |
| | | 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 |
| | |
| | | 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([]) |
| | |
| | | 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, |
| | |
| | | } |
| | | }, |
| | | { |
| | | 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') |
| | | } |
| | | }, |
| | | { |
| | |
| | | } |
| | | ]) |
| | | |
| | | 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 |
| | |
| | | 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) { |
| | |
| | | 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 |
| | | } |
| | | |
| | |
| | | apiParams: buildAsnOrderPageQueryParams(searchForm.value), |
| | | columnsFactory: () => |
| | | createAsnOrderTableColumns({ |
| | | handleActionClick |
| | | handleActionClick, |
| | | canEdit: hasAuth('update'), |
| | | canDelete: hasAuth('delete') |
| | | }) |
| | | }, |
| | | transform: { |
| | |
| | | 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) { |
| | |
| | | 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, |
| | |
| | | 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> |
| | |
| | | <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> |
| | | |
| | |
| | | <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> |
| | |
| | | <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> |
| | |
| | | current: 1, |
| | | size: 200 |
| | | }, |
| | | { |
| | | { |
| | | timeoutMessage: t('pages.orders.asnOrder.createByPoDialog.messages.purchaseItemsTimeout') |
| | | } |
| | | ) |
| | |
| | | size: total |
| | | }, |
| | | { |
| | | timeoutMessage: t('pages.orders.asnOrder.createByPoDialog.messages.purchaseItemsAllTimeout') |
| | | timeoutMessage: t( |
| | | 'pages.orders.asnOrder.createByPoDialog.messages.purchaseItemsAllTimeout' |
| | | ) |
| | | } |
| | | ) |
| | | purchaseItems.value = Array.isArray(fullPage?.records) |
| | |
| | | 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 |
| | | } |
| | |
| | | <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> |
| | | |
| New file |
| | |
| | | <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> |
| New file |
| | |
| | | <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> |
| New file |
| | |
| | | <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> |
| | |
| | | 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) |
| | |
| | | return { |
| | | current: params.current || 1, |
| | | pageSize: params.pageSize || params.size || 20, |
| | | orderBy: normalizeText(params.orderBy) || 'create_time desc', |
| | | ...buildDeliveryItemSearchParams(params) |
| | | } |
| | | } |
| | |
| | | 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 [] |
| | |
| | | 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'), |
| | |
| | | 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, |
| | |
| | | { |
| | | 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) |
| | | }) |
| | | } |
| | | ] |
| | |
| | | <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> |
| | |
| | | <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 |
| | |
| | | deliveryId: undefined |
| | | } |
| | | }) |
| | | replaceSearchParams(buildDeliveryItemPageQueryParams(searchForm.value)) |
| | | getData() |
| | | } |
| | | |
| | | watch( |
| | |
| | | return |
| | | } |
| | | applyRouteSearch() |
| | | replaceSearchParams(buildDeliveryItemPageQueryParams(searchForm.value)) |
| | | getData() |
| | | } |
| | | ) |
| | | |
| | | onMounted(() => { |
| | | applyRouteSearch() |
| | | replaceSearchParams(buildDeliveryItemPageQueryParams(searchForm.value)) |
| | | getData() |
| | | }) |
| | | </script> |
| New file |
| | |
| | | <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> |
| New file |
| | |
| | | <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> |
| | |
| | | } |
| | | 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() { |
| | |
| | | |
| | | 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]) |
| | | } |
| | | }) |
| | | |
| | |
| | | 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 |
| | | } |
| | | |
| | |
| | | return { |
| | | current: params.current || 1, |
| | | pageSize: params.pageSize || params.size || 20, |
| | | orderBy: normalizeText(params.orderBy) || 'create_time desc', |
| | | ...buildDeliverySearchParams(params) |
| | | } |
| | | } |
| | |
| | | |
| | | 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, |
| | |
| | | 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) || '--' |
| | | } |
| | | } |
| | |
| | | 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) || '--' |
| | | } |
| | | } |
| | |
| | | } |
| | | } |
| | | |
| | | 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'), |
| | |
| | | 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'), |
| | |
| | | }, |
| | | { |
| | | prop: 'status', |
| | | label: $t('pages.orders.transfer.search.status'), |
| | | label: $t('table.status'), |
| | | width: 96, |
| | | align: 'center', |
| | | formatter: (row) => |
| | |
| | | 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', |
| | |
| | | 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, |
| | |
| | | fixed: 'right', |
| | | formatter: (row) => |
| | | h(ArtButtonMore, { |
| | | list: getDeliveryActionList(row), |
| | | list: getDeliveryActionList(row, { canEdit, canDelete }), |
| | | onClick: (item) => handleActionClick?.(item, row) |
| | | }) |
| | | } |
| | |
| | | <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> |
| | | |
| | |
| | | @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' |
| | |
| | | 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()) |
| | |
| | | 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, |
| | |
| | | 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') |
| | | } |
| | | }, |
| | | { |
| | |
| | | } |
| | | }, |
| | | { |
| | | 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') |
| | | } |
| | | }, |
| | |
| | | { 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 |
| | |
| | | } |
| | | |
| | | 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')) |
| | | } |
| | | } |
| | | } |
| | | |
| | |
| | | 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)) : [] |
| | | } |
| | | }) |
| | | |
| | |
| | | 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 |
| | |
| | | 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 |
| | | } |
| New file |
| | |
| | | <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> |
| | |
| | | <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> |
| | | |
| | |
| | | :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' |
| | |
| | | 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, |
| | |
| | | |
| | | const route = useRoute() |
| | | const router = useRouter() |
| | | const { hasAuth } = useAuth() |
| | | const userStore = useUserStore() |
| | | const initialOrderId = route.query.orderId || route.query.id |
| | | const searchForm = ref( |
| | |
| | | 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: '备料单ID', |
| | | 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: '请输入PO单号' |
| | | } |
| | | props: { clearable: true, placeholder: '请输入 PO 明细ID' } |
| | | }, |
| | | { |
| | | label: '物料编码', |
| | | key: 'matnrCode', |
| | | label: '物料ID', |
| | | key: 'matnrId', |
| | | type: 'input', |
| | | props: { |
| | | clearable: true, |
| | | placeholder: '请输入物料编码' |
| | | } |
| | | props: { clearable: true, placeholder: '请输入物料ID' } |
| | | }, |
| | | { |
| | | 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, |
| | |
| | | apiParams: buildPreparationItemPageQueryParams(searchForm.value), |
| | | immediate: false, |
| | | paginationKey: getPreparationItemPaginationKey(), |
| | | columnsFactory: () => createOutStockItemTableColumns({ handleActionClick: openDetail }) |
| | | columnsFactory: () => |
| | | createPreparationItemTableColumns({ |
| | | handleActionClick, |
| | | canEdit: canUpdate.value, |
| | | canDelete: canDelete.value |
| | | }) |
| | | }, |
| | | transform: { |
| | | dataTransformer: (records) => |
| | |
| | | } |
| | | |
| | | 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() { |
| New file |
| | |
| | | <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: '备料单ID', |
| | | 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: '请输入物料ID' } |
| | | }, |
| | | { |
| | | 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> |
| | |
| | | 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 |
| | | } |
| | |
| | | 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) |
| | |
| | | return normalizeOutStockItemRow(record) |
| | | } |
| | | |
| | | export function getPreparationItemStatusOptions() { |
| | | return [ |
| | | { label: '正常', 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: '备料单号' }, |
| New file |
| | |
| | | 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) |
| | | }) |
| | | } |
| | | ] |
| | | } |
| | |
| | | <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" |
| | |
| | | @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' |
| | |
| | | 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, |
| | |
| | | |
| | | defineOptions({ name: 'Preparation' }) |
| | | |
| | | const { hasAuth } = useAuth() |
| | | const userStore = useUserStore() |
| | | const router = useRouter() |
| | | const reportTitle = PREPARATION_REPORT_TITLE |
| | |
| | | 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, |
| | |
| | | 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(() => [ |
| | |
| | | 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', |
| | |
| | | { |
| | | 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: '请输入备注' } |
| | | } |
| | | ]) |
| | | |
| | |
| | | detailLoading.value = true |
| | | try { |
| | | const [detailResponse, itemResponse] = await Promise.all([ |
| | | guardRequestWithMessage(fetchGetPreparationDetail(activePreparationId.value), {}, { |
| | | timeoutMessage: '备料单详情加载超时,已停止等待' |
| | | }), |
| | | guardRequestWithMessage( |
| | | fetchGetPreparationDetail(activePreparationId.value), |
| | | {}, |
| | | { |
| | | timeoutMessage: '备料单详情加载超时,已停止等待' |
| | | } |
| | | ), |
| | | guardRequestWithMessage( |
| | | fetchPreparationItemPage( |
| | | buildPreparationDetailQueryParams({ |
| | |
| | | orderId: String(row.id) |
| | | } |
| | | }) |
| | | return |
| | | } |
| | | |
| | | if (action.key === 'public') { |
| | | activeTaskRow.value = row |
| | | taskDialogVisible.value = true |
| | | return |
| | | } |
| | | |
| | |
| | | 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 |
| New file |
| | |
| | | <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> |
| New file |
| | |
| | | <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> |
| New file |
| | |
| | | <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> |
| | |
| | | |
| | | 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() { |
| | |
| | | condition: '', |
| | | code: '', |
| | | poCode: '', |
| | | poId: '', |
| | | wkType: '', |
| | | exceStatus: '', |
| | | rleStatus: '', |
| | | anfme: '', |
| | | qty: '', |
| | | logisNo: '', |
| | | arrTime: '', |
| | | customerName: '', |
| | | saleOrgName: '', |
| | | memo: '' |
| | |
| | | 'poCode', |
| | | 'wkType', |
| | | 'logisNo', |
| | | 'arrTime', |
| | | 'customerName', |
| | | 'saleOrgName', |
| | | 'memo' |
| | |
| | | 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 |
| | | } |
| | |
| | | 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) |
| | | } |
| | | } |
| | | |
| | |
| | | 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', |
| | |
| | | } |
| | | } |
| | | } |
| | | |
| | | export function buildPreparationGenerateWavePayload(rows = [], waveRuleId) { |
| | | return { |
| | | ids: Array.isArray(rows) |
| | | ? rows.map((row) => Number(row?.id)).filter((id) => Number.isFinite(id)) |
| | | : [], |
| | | waveRuleId: normalizeNumber(waveRuleId) |
| | | } |
| | | } |
| | |
| | | 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' }, |
| | |
| | | () => 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) => |