Merge remote-tracking branch 'origin/master'

This commit is contained in:
2026-09-09 12:45:50 +08:00
12 changed files with 257 additions and 19 deletions
+24
View File
@@ -35,3 +35,27 @@ export const importTransportPlan = ({
timeout: 60000, timeout: 60000,
}); });
}; };
export const validateTransportPlan = ({
file,
projectId,
projectName,
customerName,
contractId,
contractName,
}) => {
const data = new FormData();
data.append('file', file);
data.append('projectId', projectId || '');
data.append('projectName', projectName || '');
data.append('customerName', customerName || '');
data.append('contractId', contractId || '');
data.append('contractName', contractName || '');
return request({
url: `${baseUrl}/validate-transport-plan`,
method: 'post',
data,
responseType: 'blob',
timeout: 60000,
});
};
+7
View File
@@ -16,6 +16,13 @@ export const replaceFolder = (voucherId, plateNo, file) => {
data.append('file', file); data.append('file', file);
return request({ url: `${baseUrl}/folder-replace`, method: 'post', data }); return request({ url: `${baseUrl}/folder-replace`, method: 'post', data });
}; };
export const replaceFolderByObject = (voucherId, plateNo, fileInfo) =>
request({
url: `${baseUrl}/folder-replace-object`,
method: 'post',
params: { voucherId, plateNo, ...fileInfo },
timeout: 120000,
});
export const removeFolder = (voucherId, plateNo) => export const removeFolder = (voucherId, plateNo) =>
request({ url: `${baseUrl}/folder-remove`, method: 'post', params: { voucherId, plateNo } }); request({ url: `${baseUrl}/folder-remove`, method: 'post', params: { voucherId, plateNo } });
export const submit = data => request({ url: `${baseUrl}/submit`, method: 'post', data }); export const submit = data => request({ url: `${baseUrl}/submit`, method: 'post', data });
+1
View File
@@ -41,4 +41,5 @@ export const removeImportBatches = ids => request({ url: `${baseUrl}/import-batc
export const getImportOptions = () => request({ url: `${baseUrl}/import-batch/options`, method: 'get' }); export const getImportOptions = () => request({ url: `${baseUrl}/import-batch/options`, method: 'get' });
export const exportImportTemplate = () => request({ url: `${baseUrl}/import-batch/export-template`, method: 'get', responseType: 'blob' }); export const exportImportTemplate = () => request({ url: `${baseUrl}/import-batch/export-template`, method: 'get', responseType: 'blob' });
export const saveImportDraft = data => request({ url: `${baseUrl}/import-batch/draft`, method: 'post', data }); export const saveImportDraft = data => request({ url: `${baseUrl}/import-batch/draft`, method: 'post', data });
export const validateImport = data => request({ url: `${baseUrl}/import-batch/validate`, method: 'post', data, responseType: 'blob' });
export const confirmImport = data => request({ url: `${baseUrl}/import-batch/confirm`, method: 'post', data }); export const confirmImport = data => request({ url: `${baseUrl}/import-batch/confirm`, method: 'post', data });
@@ -23,8 +23,8 @@ export const manualMatch = data =>
export const unmatch = internalId => export const unmatch = internalId =>
request({ url: `${baseUrl}/unmatch`, method: 'post', params: { internalId } }); request({ url: `${baseUrl}/unmatch`, method: 'post', params: { internalId } });
export const adjust = data => request({ url: `${baseUrl}/adjust`, method: 'post', data }); export const adjust = data => request({ url: `${baseUrl}/adjust`, method: 'post', data });
export const updateByMatch = id => export const updateByMatch = (id, data) =>
request({ url: `${baseUrl}/update-by-match`, method: 'post', params: { id } }); request({ url: `${baseUrl}/update-by-match`, method: 'post', params: { id }, data });
export const complete = id => export const complete = id =>
request({ url: `${baseUrl}/complete`, method: 'post', params: { id } }); request({ url: `${baseUrl}/complete`, method: 'post', params: { id } });
export const completeWithData = data => export const completeWithData = data =>
@@ -7234,6 +7234,7 @@ export default {
.then(res => { .then(res => {
const detail = res?.data?.data || res?.data || row; const detail = res?.data?.data || res?.data || row;
this.detailRow = detail; this.detailRow = detail;
this.loadWaybillVoucherImages();
this.ensureTransportPlanDetailTransportTypeName(detail); this.ensureTransportPlanDetailTransportTypeName(detail);
if (this.isWaybillDetailLayout && detail.contractId) { if (this.isWaybillDetailLayout && detail.contractId) {
return getContractDetail(detail.contractId) return getContractDetail(detail.contractId)
@@ -7296,7 +7297,8 @@ export default {
if (!config?.id) return []; if (!config?.id) return [];
return getProcessConfigDetail(config.id, this.detailRow.id).then(detailRes => { return getProcessConfigDetail(config.id, this.detailRow.id).then(detailRes => {
const detail = detailRes?.data?.data || detailRes?.data || config; const detail = detailRes?.data?.data || detailRes?.data || config;
this.waybillHasRelatedVoucher = Boolean(detail.hasRelatedVoucher); this.waybillHasRelatedVoucher =
this.waybillHasRelatedVoucher || Boolean(detail.hasRelatedVoucher);
return this.getProcessConfigEnabledNodes(detail); return this.getProcessConfigEnabledNodes(detail);
}); });
}) })
@@ -7320,6 +7322,7 @@ export default {
getProcessConfigVoucherImages(this.detailRow.id) getProcessConfigVoucherImages(this.detailRow.id)
.then(res => { .then(res => {
this.waybillVoucherImages = extractRecords(res); this.waybillVoucherImages = extractRecords(res);
if (this.waybillVoucherImages.length) this.waybillHasRelatedVoucher = true;
this.waybillVoucherImagesLoaded = true; this.waybillVoucherImagesLoaded = true;
}) })
.finally(() => { .finally(() => {
@@ -1095,7 +1095,8 @@ const fileChange = async (file, list) => {
return text.slice(0, 10); return text.slice(0, 10);
}; };
const count = new Map(); const count = new Map();
rows.value = source.map((item, index) => { // 先解析数据,但不填充到 rows.value
const parsedRows = source.map((item, index) => {
const row = { _key: `${Date.now()}-${index}`, ...item, batchNo: form.batchNo }; const row = { _key: `${Date.now()}-${index}`, ...item, batchNo: form.batchNo };
Object.entries(keyMap).forEach(([key, labels]) => { Object.entries(keyMap).forEach(([key, labels]) => {
row[key] = row[key] =
@@ -1112,14 +1113,88 @@ const fileChange = async (file, list) => {
row._duplicateKey = duplicateKey; row._duplicateKey = duplicateKey;
return row; return row;
}); });
rows.value.forEach(row => { parsedRows.forEach(row => {
row._duplicate = count.get(row._duplicateKey) > 1; row._duplicate = count.get(row._duplicateKey) > 1;
}); });
// 调用后端校验接口,校验通过才填充表格
const isValid = await performValidation(parsedRows);
if (isValid) {
// 校验通过,填充表格数据
rows.value = parsedRows;
} else {
// 校验失败,清空数据
rows.value = [];
files.value = [];
form.file = null;
}
}; };
const fileRemove = () => { const fileRemove = () => {
files.value = []; files.value = [];
form.file = null; form.file = null;
}; };
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 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;
}
} else {
// 不是 Blob,直接判断
ElMessage.success('数据校验通过');
return true;
}
} catch (error) {
console.error('校验失败:', error);
ElMessage.error('校验接口调用失败');
return false;
}
};
const formatCell = (row, column) => row[column.prop] || ''; const formatCell = (row, column) => row[column.prop] || '';
const updateEditorValue = (column, row, value) => { const updateEditorValue = (column, row, value) => {
if (column.editor === 'number') { if (column.editor === 'number') {
@@ -4127,6 +4127,7 @@ export default {
.then(res => { .then(res => {
const detail = res?.data?.data || res?.data || row; const detail = res?.data?.data || res?.data || row;
this.detailRow = detail; this.detailRow = detail;
this.loadWaybillVoucherImages();
if (detail.contractId) { if (detail.contractId) {
return getContractDetail(detail.contractId) return getContractDetail(detail.contractId)
.then(contractRes => { .then(contractRes => {
@@ -4196,7 +4197,8 @@ export default {
if (!config?.id) return []; if (!config?.id) return [];
return getProcessConfigDetail(config.id, this.detailRow.id).then(detailRes => { return getProcessConfigDetail(config.id, this.detailRow.id).then(detailRes => {
const detail = detailRes?.data?.data || detailRes?.data || config; const detail = detailRes?.data?.data || detailRes?.data || config;
this.waybillHasRelatedVoucher = Boolean(detail.hasRelatedVoucher); this.waybillHasRelatedVoucher =
this.waybillHasRelatedVoucher || Boolean(detail.hasRelatedVoucher);
return this.getProcessConfigEnabledNodes(detail); return this.getProcessConfigEnabledNodes(detail);
}); });
}) })
@@ -4220,6 +4222,7 @@ export default {
getProcessConfigVoucherImages(this.detailRow.id) getProcessConfigVoucherImages(this.detailRow.id)
.then(res => { .then(res => {
this.waybillVoucherImages = extractRecords(res); this.waybillVoucherImages = extractRecords(res);
if (this.waybillVoucherImages.length) this.waybillHasRelatedVoucher = true;
this.waybillVoucherImagesLoaded = true; this.waybillVoucherImagesLoaded = true;
}) })
.finally(() => { .finally(() => {
+62 -1
View File
@@ -163,9 +163,10 @@
import { exportBlob } from '@/api/common'; import { exportBlob } from '@/api/common';
import { getList as getContractList } from '@/api/business/contract-manage'; import { getList as getContractList } from '@/api/business/contract-manage';
import { getList as getProjectList } from '@/api/business/project-apply'; import { getList as getProjectList } from '@/api/business/project-apply';
import { importTransportPlan } from '@/api/business/transport-plan'; import { importTransportPlan, validateTransportPlan } from '@/api/business/transport-plan';
import { config } from '@/option/business/transport-plan'; import { config } from '@/option/business/transport-plan';
import { downloadXls } from '@/utils/util'; import { downloadXls } from '@/utils/util';
import dayjs from 'dayjs';
const importColumns = [ const importColumns = [
['计划名称', 'planName'], ['计划名称', 'planName'],
@@ -362,6 +363,9 @@ export default {
this.markDuplicates(); this.markDuplicates();
this.$refs.importFormRef?.validateField('file'); this.$refs.importFormRef?.validateField('file');
this.$message.success(`已读取${rows.length}条计划明细`); this.$message.success(`已读取${rows.length}条计划明细`);
// 调用后端校验接口
await this.performValidation();
} catch (error) { } catch (error) {
this.handleFileRemove(); this.handleFileRemove();
this.$message.error('计划明细表读取失败,请检查文件格式'); this.$message.error('计划明细表读取失败,请检查文件格式');
@@ -376,6 +380,63 @@ export default {
this.editingKey = ''; this.editingKey = '';
this.editSnapshot = null; this.editSnapshot = null;
}, },
async performValidation() {
if (!this.rows.length || !this.form.file) return;
try {
// 校验表单必填项
await this.$refs.importFormRef?.validate();
const response = await validateTransportPlan({
file: this.form.file,
projectId: this.form.projectId,
projectName: this.form.projectName,
customerName: this.form.customerName,
contractId: this.form.contractId,
contractName: this.form.contractName,
});
// 检查响应类型
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);
this.$message.warning('数据校验失败,已自动下载错误明细表,请修正后重新上传');
} else if (blob.type.includes('application/json')) {
// 可能是 JSON 响应被当作 Blob,需要解析
const text = await blob.text();
const json = JSON.parse(text);
if (json.success) {
this.$message.success('数据校验通过');
} else {
this.$message.error(json.msg || '校验失败');
}
} else {
// 其他类型,默认认为是校验通过
this.$message.success('数据校验通过');
}
} else {
// 不是 Blob,直接判断
this.$message.success('数据校验通过');
}
} catch (error) {
console.error('校验失败:', error);
// 表单校验失败时不提示错误,让表单自己显示错误信息
if (error && error.message && !error.message.includes('validate')) {
this.$message.error('校验接口调用失败');
}
}
},
duplicateKey(row) { duplicateKey(row) {
return importColumns.map(([, prop]) => String(row[prop] || '').trim()).join('\u0001'); return importColumns.map(([, prop]) => String(row[prop] || '').trim()).join('\u0001');
}, },
+50 -5
View File
@@ -33,7 +33,7 @@
</el-dialog> </el-dialog>
<el-dialog v-model="replaceVisible" title="重新上传" width="680px" destroy-on-close> <el-dialog v-model="replaceVisible" title="重新上传" width="680px" destroy-on-close>
<div class="voucher-folder-page__replace"><el-upload action="#" :auto-upload="false" :limit="1" accept=".jpg,.jpeg,.png,.bmp,.webp,.zip" :on-change="selectReplace" :on-remove="clearReplace"><el-button type="primary">上传</el-button></el-upload><p>重新上传替换当前未匹配的凭证支持图片或压缩包大小不超过50M</p></div> <div class="voucher-folder-page__replace"><el-upload action="#" :auto-upload="false" :limit="1" accept=".jpg,.jpeg,.png,.bmp,.webp,.zip" :on-change="selectReplace" :on-remove="clearReplace"><el-button type="primary">选择文件</el-button></el-upload><el-progress v-if="replacing" class="voucher-folder-page__replace-progress" :percentage="replaceProgress" :status="replaceProgressStatus" :text-inside="false" /><p>{{ replaceProgress >= 100 ? '文件上传完成,正在处理凭证...' : '重新上传替换当前未匹配的凭证支持图片或压缩包大小不超过50M' }}</p></div>
<template #footer><el-button @click="replaceVisible = false">关闭</el-button><el-button type="primary" :loading="replacing" @click="confirmReplace">确认</el-button></template> <template #footer><el-button @click="replaceVisible = false">关闭</el-button><el-button type="primary" :loading="replacing" @click="confirmReplace">确认</el-button></template>
</el-dialog> </el-dialog>
</template> </template>
@@ -42,6 +42,7 @@
import { onMounted, reactive, ref } from 'vue'; import { onMounted, reactive, ref } from 'vue';
import { useRoute, useRouter } from 'vue-router'; import { useRoute, useRouter } from 'vue-router';
import { ElMessage, ElMessageBox } from 'element-plus'; import { ElMessage, ElMessageBox } from 'element-plus';
import md5 from 'js-md5';
import * as api from '@/api/business/voucher-manage'; import * as api from '@/api/business/voucher-manage';
const route = useRoute(); const route = useRoute();
@@ -51,7 +52,7 @@ const loading = ref(false), rows = ref([]), voucher = ref({ voucherBatchNo: rout
const page = reactive({ current: 1, size: 10, total: 0 }); const page = reactive({ current: 1, size: 10, total: 0 });
const query = reactive({ plateNo: '', matched: undefined }); const query = reactive({ plateNo: '', matched: undefined });
const viewerVisible = ref(false), viewerLoading = ref(false), viewer = ref({}), viewerFiles = ref([]); const viewerVisible = ref(false), viewerLoading = ref(false), viewer = ref({}), viewerFiles = ref([]);
const replaceVisible = ref(false), replacing = ref(false), replaceRow = ref(), replaceFile = ref(); const replaceVisible = ref(false), replacing = ref(false), replaceRow = ref(), replaceFile = ref(), replaceProgress = ref(0), replaceProgressStatus = ref('');
const load = async () => { const load = async () => {
loading.value = true; loading.value = true;
try { try {
@@ -68,14 +69,58 @@ const openViewer = async row => {
viewerVisible.value = true; viewerLoading.value = true; viewer.value = row; viewerFiles.value = []; viewerVisible.value = true; viewerLoading.value = true; viewer.value = row; viewerFiles.value = [];
try { const res = await api.getFolderDetail(voucherId, row.plateNo); viewer.value = res.data?.data || row; viewerFiles.value = viewer.value.files || []; } catch (error) { ElMessage.error(error.message || '获取凭证详情失败'); } finally { viewerLoading.value = false; } try { const res = await api.getFolderDetail(voucherId, row.plateNo); viewer.value = res.data?.data || row; viewerFiles.value = viewer.value.files || []; } catch (error) { ElMessage.error(error.message || '获取凭证详情失败'); } finally { viewerLoading.value = false; }
}; };
const openReplace = row => { replaceRow.value = row; replaceFile.value = undefined; replaceVisible.value = true; }; const openReplace = row => { replaceRow.value = row; replaceFile.value = undefined; replaceProgress.value = 0; replaceProgressStatus.value = ''; replaceVisible.value = true; };
const selectReplace = upload => { replaceFile.value = upload.raw; }; const selectReplace = upload => { replaceFile.value = upload.raw; };
const clearReplace = () => { replaceFile.value = undefined; }; const clearReplace = () => { replaceFile.value = undefined; replaceProgress.value = 0; replaceProgressStatus.value = ''; };
const uploadReplaceFile = async file => {
const chunkSize = 2 * 1024 * 1024;
const digest = md5.base64(await file.arrayBuffer());
const taskRes = await api.createFileTask({
md5: digest,
attachmentName: file.name,
size: file.size,
chunkSize,
chunkTotal: Math.ceil(file.size / chunkSize),
force: true,
businessType: 'voucher-replace',
businessId: `${voucherId}:${replaceRow.value.plateNo}`,
});
const task = taskRes.data?.data;
if (!task?.id || !task.objectKey) throw new Error('创建分片上传任务失败');
replaceProgress.value = 0;
for (let partNumber = 1; partNumber <= task.chunkTotal; partNumber += 1) {
const urlRes = await api.getPartUploadUrl(task.id, partNumber);
const part = file.slice((partNumber - 1) * chunkSize, Math.min(partNumber * chunkSize, file.size));
const response = await fetch(urlRes.data?.data, { method: 'PUT', body: part });
if (!response.ok) throw new Error(`分片${partNumber}上传失败`);
const etag = String(response.headers.get('etag') || '').replaceAll('"', '');
if (!etag) throw new Error('未获取到分片 ETag');
await api.updateFileTask({ id: task.id, partNumber, etag });
replaceProgress.value = Math.round((partNumber / task.chunkTotal) * 100);
}
const completedRes = await api.getFileTask(task.id);
const completedTask = completedRes.data?.data;
if (!completedTask || completedTask.status !== 'completed') throw new Error('文件分片合并未完成');
return completedTask;
};
const confirmReplace = async () => { const confirmReplace = async () => {
if (!replaceFile.value) return ElMessage.warning('请选择凭证文件'); if (!replaceFile.value) return ElMessage.warning('请选择凭证文件');
if (replaceFile.value.size > 50 * 1024 * 1024) return ElMessage.warning('凭证文件大小不能超过50M'); if (replaceFile.value.size > 50 * 1024 * 1024) return ElMessage.warning('凭证文件大小不能超过50M');
replacing.value = true; replacing.value = true;
try { await api.replaceFolder(voucherId, replaceRow.value.plateNo, replaceFile.value); ElMessage.success('上传成功,已重新匹配运单'); replaceVisible.value = false; await load(); } catch (error) { ElMessage.error(error.message || '上传失败'); } finally { replacing.value = false; } replaceProgress.value = 0;
replaceProgressStatus.value = '';
try {
const task = await uploadReplaceFile(replaceFile.value);
await api.replaceFolderByObject(voucherId, replaceRow.value.plateNo, {
objectKey: task.objectKey,
fileName: task.attachmentName || replaceFile.value.name,
size: replaceFile.value.size,
contentType: replaceFile.value.type,
});
ElMessage.success('上传成功,已重新匹配运单');
replaceVisible.value = false;
await load();
} catch (error) { replaceProgressStatus.value = 'exception'; ElMessage.error(error.message || '上传失败'); } finally { replacing.value = false; }
}; };
const removeFolder = row => ElMessageBox.confirm(`确认删除车牌”${row.plateNo}”的凭证吗?`, '提示', { type: 'warning' }).then(async () => { await api.removeFolder(voucherId, row.plateNo); ElMessage.success('删除成功'); load(); }); const removeFolder = row => ElMessageBox.confirm(`确认删除车牌”${row.plateNo}”的凭证吗?`, '提示', { type: 'warning' }).then(async () => { await api.removeFolder(voucherId, row.plateNo); ElMessage.success('删除成功'); load(); });
const goToLoading = loadingNo => { router.push({ path: '/business/loading-manage', query: { loadingNo } }); }; const goToLoading = loadingNo => { router.push({ path: '/business/loading-manage', query: { loadingNo } }); };
+17 -4
View File
@@ -100,7 +100,7 @@
width="120" width="120"
fixed="right" fixed="right"
align="center" align="center"
/><el-table-column label="操作" width="200" fixed="right" align="left" /><el-table-column label="操作" width="320" fixed="right" align="left"
><template #default="{ row }" ><template #default="{ row }"
><div class="voucher-manage-page__actions"> ><div class="voucher-manage-page__actions">
<el-link <el-link
@@ -120,6 +120,16 @@
type="primary" type="primary"
@click="download(row)" @click="download(row)"
>下载</el-link >下载</el-link
><el-link
v-if="row.auditStatus === '审核驳回'"
type="primary"
@click="openUpload(row, 'reupload')"
>重新上传</el-link
><el-link
v-if="row.auditStatus === '审核驳回'"
type="primary"
@click="openUpload(row, 'changeBatch')"
>更换运单批次</el-link
><el-link ><el-link
v-if="row.processStatus !== '处理完成' || row.auditStatus === '待审核'" v-if="row.processStatus !== '处理完成' || row.auditStatus === '待审核'"
type="primary" type="primary"
@@ -207,7 +217,7 @@
<el-dialog <el-dialog
v-model="uploadVisible" v-model="uploadVisible"
:title="editing.id ? '更换运单批次' : '批量导入凭证'" :title="editing.id ? (uploadMode === 'reupload' ? '重新上传' : '更换运单批次') : '批量导入凭证'"
width="1200px" width="1200px"
destroy-on-close destroy-on-close
:before-close="beforeUploadDialogClose" :before-close="beforeUploadDialogClose"
@@ -525,7 +535,8 @@ const loading = ref(false),
cancelledTaskIds = ref(new Set()), cancelledTaskIds = ref(new Set()),
uploadCancelled = ref(false), uploadCancelled = ref(false),
activeUploadTaskId = ref(), activeUploadTaskId = ref(),
selectedVoucherFile = ref(); selectedVoucherFile = ref(),
uploadMode = ref('');
const query = reactive({ const query = reactive({
voucherBatchNo: '', voucherBatchNo: '',
auditStatus: '', auditStatus: '',
@@ -597,9 +608,10 @@ const loadProjects = async () => {
const changeProject = projectId => { const changeProject = projectId => {
editing.projectName = projectOptions.value.find(item => item.id === projectId)?.projectName || ''; editing.projectName = projectOptions.value.find(item => item.id === projectId)?.projectName || '';
}; };
const openUpload = async row => { const openUpload = async (row, mode = '') => {
await loadProjects(); await loadProjects();
selectedVoucherFile.value = undefined; selectedVoucherFile.value = undefined;
uploadMode.value = mode;
Object.assign(editing, { Object.assign(editing, {
id: row?.id || '', id: row?.id || '',
voucherBatchNo: row?.voucherBatchNo || '', voucherBatchNo: row?.voucherBatchNo || '',
@@ -610,6 +622,7 @@ const openUpload = async row => {
fileTaskId: row?.fileTaskId || '', fileTaskId: row?.fileTaskId || '',
waybillImportBatchIds: [], waybillImportBatchIds: [],
}); });
if (mode === 'reupload') Object.assign(editing, { fileName: '', fileUrl: '', fileTaskId: '' });
uploadPercent.value = 0; uploadPercent.value = 0;
selectedBatches.value = []; selectedBatches.value = [];
uploadVisible.value = true; uploadVisible.value = true;
@@ -2371,6 +2371,7 @@ export default {
buildSavePayload() { buildSavePayload() {
return { return {
id: this.form.id, id: this.form.id,
formalSettlementNo: this.form.formalSettlementNo,
contractId: this.form.contractId, contractId: this.form.contractId,
settlementType: this.form.settlementType, settlementType: this.form.settlementType,
sourcePreSettlementIds: this.form.sourcePreSettlementIds, sourcePreSettlementIds: this.form.sourcePreSettlementIds,
@@ -2444,7 +2445,7 @@ export default {
} }
if (!this.validateInvoices()) return; if (!this.validateInvoices()) return;
// 附件类型仅做提示,不阻断正式结算单新增/保存提交。 // 附件类型仅做提示,不阻断正式结算单新增/保存提交。
this.validateAttachments(); // this.validateAttachments();
this.saving = true; this.saving = true;
try { try {
await save(this.buildSavePayload()); await save(this.buildSavePayload());
@@ -1181,12 +1181,17 @@ export default {
} }
}, },
async handleUpdate() { async handleUpdate() {
await this.$confirm('将以外部账单匹配金额更新内部结算明细,是否继续?', '更新账单', { await this.$confirm('将以外部账单数据更新运单及内部结算明细,是否继续?', '更新账单', {
type: 'warning', type: 'warning',
}); });
this.actionLoading = true; this.actionLoading = true;
try { try {
await api.updateByMatch(this.currentId); await api.updateByMatch(this.currentId, {
id: this.currentId,
reconciliationMode: this.form.reconciliationMode,
internalDetails: this.serializeInternalRows(),
externalDetails: this.serializeExternalRows(),
});
await this.loadDetail(); await this.loadDetail();
this.$message.success('账单更新完成'); this.$message.success('账单更新完成');
} finally { } finally {