From 2be6a559e7782bb43be05453e24de4d5935e08e3 Mon Sep 17 00:00:00 2001 From: weicw <594098497@qq.com> Date: Fri, 6 Nov 2026 07:30:04 +0800 Subject: [PATCH] =?UTF-8?q?:sparkles:=20=E8=BF=90=E5=8D=95=E5=AF=BC?= =?UTF-8?q?=E5=85=A5=E6=96=B0=E5=A2=9E=E4=B8=8E=E5=B7=B2=E6=9C=89=E8=BF=90?= =?UTF-8?q?=E5=8D=95=E9=87=8D=E5=A4=8D=E9=A1=B5=E7=AD=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/business/waybill-manage.js | 2 + .../components/waybill-import-dialog.vue | 83 ++++++++++++++----- 2 files changed, 64 insertions(+), 21 deletions(-) diff --git a/src/api/business/waybill-manage.js b/src/api/business/waybill-manage.js index e90982c..3778653 100644 --- a/src/api/business/waybill-manage.js +++ b/src/api/business/waybill-manage.js @@ -95,3 +95,5 @@ export const exportImportFailures = data => }); export const confirmImport = data => request({ url: `${baseUrl}/import-batch/confirm`, method: 'post', data, timeout: 120000 }); +export const checkImportDuplicates = data => + request({ url: `${baseUrl}/import-batch/duplicates`, method: 'post', data, timeout: 120000 }); diff --git a/src/views/business/components/waybill-import-dialog.vue b/src/views/business/components/waybill-import-dialog.vue index e5db21c..33339d6 100644 --- a/src/views/business/components/waybill-import-dialog.vue +++ b/src/views/business/components/waybill-import-dialog.vue @@ -484,20 +484,24 @@ 保存取消 rows.value.filter(row => row._duplicate)); -const previewRows = computed(() => - previewTab.value === 'duplicate' ? duplicateRows.value : rows.value -); +const historyDuplicateRows = computed(() => rows.value.filter(row => row._historyWaybillNo)); +const previewRows = computed(() => { + if (previewTab.value === 'duplicate') return duplicateRows.value; + if (previewTab.value === 'history') return historyDuplicateRows.value; + return rows.value; +}); const carrierContractsLoaded = ref(false), hasCarrierContracts = ref(null); const carrierTypes = ['承运商', '自运']; @@ -795,6 +800,16 @@ const detailColumns = [ { prop: 'remark', label: '备注', editor: 'textarea', minWidth: 200 }, { prop: 'waybillIdentifier', label: '同一运单标识号', editor: 'input', minWidth: 150 }, ]; +// 与已有运单重复页签的列与明细一致,只在运单批次号后多一列已有运单号。 +const previewColumns = computed(() => + previewTab.value === 'history' + ? [ + detailColumns[0], + { prop: '_historyWaybillNo', label: '已有运单号', minWidth: 220 }, + ...detailColumns.slice(1), + ] + : detailColumns +); const requiredDetailFields = [ { prop: 'vehicleNo', label: '车牌号/船班号/船次号/班次号' }, { prop: 'transportType', label: '运输方式' }, @@ -1187,6 +1202,7 @@ const loadBatchRows = async batchId => { batchNo: item.batchNo || form.batchNo, })); refreshDuplicateFlags(); + await refreshHistoryDuplicates(); rowSelection.value = []; previewTab.value = 'all'; }; @@ -1509,6 +1525,8 @@ const fileChange = async (file, list) => { if (isValid) { // 校验通过,填充表格数据 rows.value = parsedRows; + // 传 rows.value(响应式代理),否则后续写入不触发重新渲染 + await refreshHistoryDuplicates(rows.value); } else { // 校验失败,清空数据 rows.value = []; @@ -1526,16 +1544,12 @@ const IMPORT_BATCH_SIZE = 100; const importFieldText = value => String(value ?? '').trim(); -/** 同一运单批次号下,全部导入字段一致时标记为疑似重复。已在线确认的行不再标为重复。 */ +/** 同一运单批次号下,全部导入字段一致时标记为 Excel 内重复。 */ const refreshDuplicateFlags = (list = rows.value) => { const groups = new Map(); list.forEach(row => { const signature = detailColumns.map(column => importFieldText(row[column.prop])).join('\u0001'); const batchNo = importFieldText(row.batchNo || form.batchNo); - if (row._duplicateConfirmed && row._duplicateSignature !== signature) { - row._duplicateConfirmed = false; - } - row._duplicateSignature = signature; const groupKey = `${batchNo}\u0002${signature}`; const group = groups.get(groupKey) || []; group.push(row); @@ -1544,19 +1558,39 @@ const refreshDuplicateFlags = (list = rows.value) => { groups.forEach(group => { const duplicated = group.length > 1; group.forEach(row => { - row._duplicate = duplicated && !row._duplicateConfirmed; + row._duplicate = duplicated; }); }); }; -const confirmDuplicateRow = row => { - row._duplicateConfirmed = true; - refreshDuplicateFlags(); - ElMessage.success('已确认,该行将按正常数据导入'); +/** 逐行提交给后端与项目下已有运单比对,命中的行带上已有运单号。比对失败不影响导入。 */ +const refreshHistoryDuplicates = async (list = rows.value) => { + if (!list.length || !form.projectId) { + list.forEach(row => { + row._historyWaybillNo = ''; + }); + return; + } + let duplicates = []; + try { + const res = await api.checkImportDuplicates(buildImportPayload(form.status, list)); + const data = res?.data?.data ?? res?.data ?? res; + duplicates = Array.isArray(data) ? data : []; + } catch (error) { + console.error('与已有运单重复校验失败:', error); + duplicates = []; + } + const numbersByRow = new Map( + duplicates.map(item => [item.rowIndex, (item.waybillNos || []).filter(Boolean)]) + ); + list.forEach((row, index) => { + row._historyWaybillNo = (numbersByRow.get(index) || []).join('、'); + }); }; const stripInternalRowFields = row => { - const { _key, _duplicate, _duplicateKey, _duplicateConfirmed, _duplicateSignature, _editing, _editSnapshot, ...data } = row; + const { _key, _duplicate, _duplicateKey, _editing, _editSnapshot, _historyWaybillNo, ...data } = + row; return data; }; @@ -1771,6 +1805,7 @@ const saveRow = row => { row._editing = false; delete row._editSnapshot; refreshDuplicateFlags(); + refreshHistoryDuplicates(); }; const cancelRow = row => { if (row._editSnapshot) Object.assign(row, row._editSnapshot); @@ -1780,12 +1815,14 @@ const cancelRow = row => { const deleteRow = row => { rows.value = rows.value.filter(item => item !== row); refreshDuplicateFlags(); + refreshHistoryDuplicates(); }; const removeSelectedRows = () => { const keys = new Set(rowSelection.value.map(row => row._key)); rows.value = rows.value.filter(row => !keys.has(row._key)); rowSelection.value = []; refreshDuplicateFlags(); + refreshHistoryDuplicates(); }; const normalizeOptionalPlanId = value => { if (value === undefined || value === null || value === '') return null; @@ -1849,7 +1886,11 @@ const confirmImport = async () => { await formRef.value?.validate(); if (!validateImportRows()) return; const count = rows.value.filter(row => row._duplicate).length; - if (count) await ElMessageBox.confirm(`当前导入有${count}条疑似重复数据,是否确认导入?`, '提示'); + if (count) + await ElMessageBox.confirm(`当前导入有${count}条Excel内重复数据,是否确认导入?`, '提示'); + const historyCount = rows.value.filter(row => row._historyWaybillNo).length; + if (historyCount) + await ElMessageBox.confirm(`当前导入有${historyCount}条与已有运单重复,是否确认导入?`, '提示'); if (form.importType === 'settlement' && form.status === 'completed') await ElMessageBox.confirm('确认后将同时生成运单、应收应付明细、计结算单,是否继续?', '提示');