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('确认后将同时生成运单、应收应付明细、计结算单,是否继续?', '提示');