🐛 修复车务非必填-1展示,并完善总单调度与运单表单体验
调度页限制上一段到达后才可调度;司机候选项点击展开;车务多模块-1显示为空;同步配载/总单编辑与导入相关优化。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1268,63 +1268,116 @@ const fileRemove = () => {
|
||||
form.file = null;
|
||||
};
|
||||
|
||||
const performValidation = async (parsedRows) => {
|
||||
/** 运单批量导入每次请求最多提交行数,避免网关 body 超限。 */
|
||||
const IMPORT_BATCH_SIZE = 100;
|
||||
|
||||
const stripInternalRowFields = row => {
|
||||
const { _key, _duplicate, _duplicateKey, _editing, _editSnapshot, ...data } = row;
|
||||
return data;
|
||||
};
|
||||
|
||||
/**
|
||||
* 按配载标识号 / 同一运单标识号尽量同组,再按 IMPORT_BATCH_SIZE 切分。
|
||||
* 返回 [{ rows, globalIndexes }],globalIndexes 与 rows 一一对应。
|
||||
*/
|
||||
const chunkImportRows = sourceRows => {
|
||||
if (!sourceRows?.length) return [];
|
||||
const groups = [];
|
||||
const groupIndexByKey = new Map();
|
||||
sourceRows.forEach((row, globalIndex) => {
|
||||
const loadingIdentifier = String(row.loadingIdentifier || '').trim();
|
||||
const waybillIdentifier = String(row.waybillIdentifier || '').trim();
|
||||
let groupKey = '';
|
||||
if (loadingIdentifier) groupKey = `L:${loadingIdentifier}`;
|
||||
else if (waybillIdentifier) groupKey = `W:${waybillIdentifier}`;
|
||||
else groupKey = `R:${globalIndex}`;
|
||||
let groupPos = groupIndexByKey.get(groupKey);
|
||||
if (groupPos == null) {
|
||||
groupPos = groups.length;
|
||||
groupIndexByKey.set(groupKey, groupPos);
|
||||
groups.push({ rows: [], globalIndexes: [] });
|
||||
}
|
||||
groups[groupPos].rows.push(row);
|
||||
groups[groupPos].globalIndexes.push(globalIndex);
|
||||
});
|
||||
|
||||
const chunks = [];
|
||||
let current = { rows: [], globalIndexes: [] };
|
||||
const flush = () => {
|
||||
if (!current.rows.length) return;
|
||||
chunks.push(current);
|
||||
current = { rows: [], globalIndexes: [] };
|
||||
};
|
||||
groups.forEach(group => {
|
||||
// 单组超过上限时整组单独成批,保证跨行一致性校验与多货物合并完整。
|
||||
if (group.rows.length > IMPORT_BATCH_SIZE) {
|
||||
flush();
|
||||
chunks.push(group);
|
||||
return;
|
||||
}
|
||||
if (current.rows.length + group.rows.length > IMPORT_BATCH_SIZE) {
|
||||
flush();
|
||||
}
|
||||
current.rows.push(...group.rows);
|
||||
current.globalIndexes.push(...group.globalIndexes);
|
||||
});
|
||||
flush();
|
||||
return chunks;
|
||||
};
|
||||
|
||||
const downloadImportFailures = async failureRows => {
|
||||
if (!failureRows.length) return;
|
||||
const response = await api.exportImportFailures({
|
||||
rows: failureRows.map(row => stripInternalRowFields(row)),
|
||||
});
|
||||
const blob = response.data || response;
|
||||
downloadXls(blob, `运单导入失败明细_${dayjs().format('YYYYMMDDHHmmss')}.xlsx`);
|
||||
};
|
||||
|
||||
const performValidation = async parsedRows => {
|
||||
if (!parsedRows || !parsedRows.length) return false;
|
||||
|
||||
try {
|
||||
// 构建校验请求参数
|
||||
const payload = {
|
||||
...form,
|
||||
rows: parsedRows.map(row => {
|
||||
const { _key, _duplicate, _duplicateKey, _editing, _editSnapshot, ...data } = row;
|
||||
return data;
|
||||
})
|
||||
};
|
||||
const chunks = chunkImportRows(parsedRows);
|
||||
const errorMap = new Map();
|
||||
|
||||
// 调用后端校验接口
|
||||
const response = await api.validateImport(payload);
|
||||
|
||||
// 检查响应类型
|
||||
const blob = response.data || response;
|
||||
|
||||
// 如果是 Blob,需要检查其 MIME 类型
|
||||
if (blob instanceof Blob) {
|
||||
// Excel 文件的 MIME 类型
|
||||
if (blob.type.includes('application/vnd.ms-excel') ||
|
||||
blob.type.includes('application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')) {
|
||||
// 校验失败,下载错误明细
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.download = `运单导入失败明细_${dayjs().format('YYYYMMDDHHmmss')}.xlsx`;
|
||||
link.click();
|
||||
window.URL.revokeObjectURL(url);
|
||||
ElMessage.warning('数据校验失败,已自动下载错误明细表,请修正后重新上传');
|
||||
return false;
|
||||
} else if (blob.type.includes('application/json')) {
|
||||
// 可能是 JSON 响应被当作 Blob,需要解析
|
||||
const text = await blob.text();
|
||||
const json = JSON.parse(text);
|
||||
if (json.success) {
|
||||
ElMessage.success('数据校验通过');
|
||||
return true;
|
||||
} else {
|
||||
ElMessage.error(json.msg || '校验失败');
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// 其他类型,默认认为是校验通过
|
||||
ElMessage.success('数据校验通过');
|
||||
return true;
|
||||
for (const chunk of chunks) {
|
||||
const payload = {
|
||||
...form,
|
||||
rows: chunk.rows.map(stripInternalRowFields),
|
||||
};
|
||||
const response = await api.validateImport(payload);
|
||||
const result = response?.data?.data ?? response?.data ?? response;
|
||||
if (result?.success === false || (Array.isArray(result?.errors) && result.errors.length)) {
|
||||
(result.errors || []).forEach(error => {
|
||||
const localIndex = Number(error.rowIndex);
|
||||
if (Number.isNaN(localIndex) || localIndex < 0 || localIndex >= chunk.globalIndexes.length) {
|
||||
return;
|
||||
}
|
||||
const globalIndex = chunk.globalIndexes[localIndex];
|
||||
errorMap.set(globalIndex, error.errorMessage || '校验失败');
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// 不是 Blob,直接判断
|
||||
ElMessage.success('数据校验通过');
|
||||
return true;
|
||||
}
|
||||
|
||||
if (errorMap.size) {
|
||||
const failureRows = [];
|
||||
errorMap.forEach((errorMessage, globalIndex) => {
|
||||
failureRows.push({
|
||||
...stripInternalRowFields(parsedRows[globalIndex]),
|
||||
errorMessage,
|
||||
});
|
||||
});
|
||||
await downloadImportFailures(failureRows);
|
||||
ElMessage.warning('数据校验失败,已自动下载错误明细表,请修正后重新上传');
|
||||
return false;
|
||||
}
|
||||
|
||||
ElMessage.success('数据校验通过');
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('校验失败:', error);
|
||||
ElMessage.error('校验接口调用失败');
|
||||
ElMessage.error(error?.message || '校验接口调用失败');
|
||||
return false;
|
||||
}
|
||||
};
|
||||
@@ -1442,9 +1495,14 @@ const normalizeImportRow = row => ({
|
||||
row['*运输方式']
|
||||
),
|
||||
});
|
||||
const buildImportPayload = (status = form.status) => {
|
||||
const buildImportPayload = (status = form.status, rowList = rows.value, extra = {}) => {
|
||||
const { file, ...batch } = form;
|
||||
return { ...batch, status, rows: rows.value.map(normalizeImportRow) };
|
||||
return {
|
||||
...batch,
|
||||
status,
|
||||
rows: rowList.map(normalizeImportRow).map(stripInternalRowFields),
|
||||
...extra,
|
||||
};
|
||||
};
|
||||
// 独立页面下返回批次列表路由,弹窗形态下关闭新建面板并刷新列表。
|
||||
const backToList = () => {
|
||||
@@ -1454,7 +1512,24 @@ const backToList = () => {
|
||||
// 保存草稿:批次与已解析明细一并入库,成功后返回批次列表。
|
||||
const saveDraft = async () => {
|
||||
await formRef.value?.validateField(draftRequiredFields);
|
||||
await api.saveImportDraft(buildImportPayload('draft'));
|
||||
const chunks = chunkImportRows(rows.value);
|
||||
if (!chunks.length) {
|
||||
ElMessage.warning('请上传至少一条运单明细');
|
||||
return;
|
||||
}
|
||||
// 草稿编辑时首批带原 id 覆盖;后续批 append,避免整包超限。
|
||||
let batchId = form.id || '';
|
||||
for (let i = 0; i < chunks.length; i++) {
|
||||
const append = i > 0;
|
||||
const payload = buildImportPayload('draft', chunks[i].rows, {
|
||||
id: append ? batchId : batchId || undefined,
|
||||
append,
|
||||
});
|
||||
const response = await api.saveImportDraft(payload);
|
||||
const batch = response?.data?.data ?? response?.data ?? response;
|
||||
batchId = batch?.id || batchId;
|
||||
}
|
||||
if (batchId) form.id = batchId;
|
||||
ElMessage.success('保存草稿成功');
|
||||
backToList();
|
||||
};
|
||||
@@ -1467,9 +1542,32 @@ const confirmImport = async () => {
|
||||
if (count) await ElMessageBox.confirm(`当前导入有${count}条重复数据,是否确认导入?`, '提示');
|
||||
if (form.importType === 'settlement')
|
||||
await ElMessageBox.confirm('确认后将同时生成运单、应收应付明细、计结算单,是否继续?', '提示');
|
||||
await api.confirmImport(buildImportPayload());
|
||||
ElMessage.success('导入成功');
|
||||
backToList();
|
||||
|
||||
const chunks = chunkImportRows(rows.value);
|
||||
// 仅草稿转确认时复用原批次 id;过程中不回写 form.id,避免失败重试误覆盖已完成批次。
|
||||
const draftBatchId = form.id || '';
|
||||
let batchId = '';
|
||||
try {
|
||||
for (let i = 0; i < chunks.length; i++) {
|
||||
const append = i > 0;
|
||||
const payload = buildImportPayload(form.status, chunks[i].rows, {
|
||||
id: append ? batchId : draftBatchId || undefined,
|
||||
append,
|
||||
});
|
||||
const response = await api.confirmImport(payload);
|
||||
const batch = response?.data?.data ?? response?.data ?? response;
|
||||
batchId = batch?.id || batchId;
|
||||
}
|
||||
ElMessage.success('导入成功');
|
||||
backToList();
|
||||
} catch (error) {
|
||||
console.error('确认导入失败:', error);
|
||||
ElMessage.error(
|
||||
batchId
|
||||
? `部分数据可能已导入(批次已创建),请到批次详情核对后重试:${error?.message || '导入失败'}`
|
||||
: error?.message || '导入失败'
|
||||
);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user