Merge remote-tracking branch 'origin/master' into feature/bfi-api-rate

# Conflicts:
#	src/views/vehicle/customer-archive.vue
This commit is contained in:
刘泉佳
2026-09-24 03:15:23 +08:00
114 changed files with 10215 additions and 2861 deletions
@@ -212,8 +212,15 @@
</el-button>
</el-form-item>
</el-form>
<el-table v-show="!detailCollapsed" :data="filteredDetails" border>
<el-table-column type="index" label="序号" width="64" fixed="left" align="center" />
<el-table v-show="!detailCollapsed" :data="pagedDetails" border>
<el-table-column
type="index"
label="序号"
width="64"
fixed="left"
align="center"
:index="detailIndexMethod"
/>
<el-table-column
v-for="column in detailTableColumns"
:key="column.prop"
@@ -246,6 +253,16 @@
</template>
</el-table-column>
</el-table>
<div v-show="!detailCollapsed" class="formal-editor__pagination">
<el-pagination
v-model:current-page="detailPage.current"
v-model:page-size="detailPage.size"
:total="filteredDetails.length"
:page-sizes="[10, 20, 50, 100]"
layout="total, sizes, prev, pager, next, jumper"
@size-change="handleDetailPageSizeChange"
/>
</div>
</section-card>
<section-card>
@@ -573,7 +590,7 @@
>提交</el-button
>
</template>
<div v-if="pageMode" class="formal-editor__page-actions">
<div v-if="pageMode && !publicMode" class="formal-editor__page-actions">
<el-button @click="visible = false">取消</el-button>
<el-button
v-if="editable && !recordId"
@@ -879,6 +896,7 @@
</template>
<script>
import { getMkPublicDetail } from '@/api/mk-process';
import {
adjustDetail,
getCandidateDetails,
@@ -930,6 +948,10 @@ export default {
type: Object,
default: null,
},
publicMode: {
type: Boolean,
default: false,
},
},
emits: ['update:modelValue', 'success'],
data() {
@@ -1038,6 +1060,10 @@ export default {
batchNo: '',
cargoName: '',
},
detailPage: {
current: 1,
size: 10,
},
};
},
computed: {
@@ -1085,6 +1111,13 @@ export default {
})
);
},
pagedDetails() {
const list = this.filteredDetails;
const size = Number(this.detailPage.size) || 10;
const current = Math.max(Number(this.detailPage.current) || 1, 1);
const start = (current - 1) * size;
return list.slice(start, start + size);
},
missingAttachmentTypeText() {
const missing = this.getMissingAttachmentTypes();
return missing.length ? `未上传:${missing.join('、')}` : '';
@@ -1104,6 +1137,16 @@ export default {
if (value) this.initialize();
},
},
filteredDetails: {
handler(list) {
const size = Number(this.detailPage.size) || 10;
const maxPage = Math.max(1, Math.ceil((list?.length || 0) / size) || 1);
if (this.detailPage.current > maxPage) {
this.detailPage.current = maxPage;
}
},
immediate: true,
},
summaryTotal(value) {
this.form.settlementAmount = Number(value || 0).toFixed(2);
this.form.localSettlementAmount = (
@@ -1118,7 +1161,9 @@ export default {
},
methods: {
async loadFormalDetail(id) {
const response = await getDetail(id);
const response = this.publicMode
? await getMkPublicDetail('formal-settlement', id)
: await getDetail(id);
const data = this.unwrapData(response);
this.form = {
...createFormalSettlementForm(),
@@ -1130,12 +1175,13 @@ export default {
};
this.sources = data.sources || [];
this.details = data.details || [];
this.detailPage.current = 1;
this.summaryFees = data.summaryFees || [];
// 服务端数据已刷新,费用快照与暂存调整全部作废。
this.detailFeeSnapshots = {};
this.pendingAdjustments = {};
this.paymentApplications = data.paymentApplications || [];
if (this.readonly && this.form.settlementType === 'receivable') {
if (this.readonly && this.form.settlementType === 'receivable' && !this.publicMode) {
this.receiptClaims = this.unwrapData(await getReceiptClaims(id)) || [];
}
this.adjustments = data.adjustments || [];
@@ -1181,14 +1227,17 @@ export default {
rule: null,
};
this.detailCollapsed = false;
this.detailPage = { current: 1, size: 10 };
this.resetDetailQuery(false);
await Promise.all([
this.loadAllContracts(),
this.loadFeeOptions(),
this.loadFeeCategoryOptions(),
this.loadAttachmentTypeOptions(),
this.loadTransportTypeOptions(),
]);
if (!this.publicMode) {
await Promise.all([
this.loadAllContracts(),
this.loadFeeOptions(),
this.loadFeeCategoryOptions(),
this.loadAttachmentTypeOptions(),
this.loadTransportTypeOptions(),
]);
}
if (!this.recordId) {
this.form.exchangeRateDate = this.$dayjs().format('YYYY-MM-DD');
if (this.initialData) await this.applyInitialData();
@@ -1273,6 +1322,7 @@ export default {
settlementAmountTax:
row.settlementAmountTax ?? row.totalAmount ?? row.afterAmount ?? row.settlementAmount,
}));
this.detailPage.current = 1;
this.form.sourceDetailIds = this.details.map(item => item.sourceDetailId);
await this.refreshSummaryFees();
},
@@ -1318,6 +1368,7 @@ export default {
sourcePreSettlementId: settlement.id,
}))
);
this.detailPage.current = 1;
const summaryMap = new Map();
settlements
.flatMap(settlement => settlement.summaryFees || [])
@@ -1564,6 +1615,7 @@ export default {
...this.details.filter(item => item.formalSettlementId),
...existing.values(),
];
this.detailPage.current = 1;
this.form.sourceDetailIds = [...existing.values()].map(item => item.sourceDetailId);
this.detailCandidate.loading = true;
try {
@@ -1782,6 +1834,9 @@ export default {
return sourceDetailId ? `src:${sourceDetailId}` : '';
},
async loadDetailFeeRows(detail) {
if (this.publicMode) {
return [this.normalizeAdjustRow(detail)];
}
if (detail.formalSettlementId && detail.id) {
const rows = this.unwrapData(await getDetailFees(detail.id)) || [];
return rows.map(item => this.normalizeAdjustRow(item));
@@ -1883,12 +1938,19 @@ export default {
},
handleDetailQuery() {
this.appliedDetailQuery = { ...this.detailQuery };
this.detailPage.current = 1;
},
resetDetailQuery(apply = true) {
this.detailQuery = { documentNo: '', waybillNo: '', batchNo: '', cargoName: '' };
if (apply) this.handleDetailQuery();
else this.appliedDetailQuery = { ...this.detailQuery };
},
handleDetailPageSizeChange() {
this.detailPage.current = 1;
},
detailIndexMethod(index) {
return (this.detailPage.current - 1) * this.detailPage.size + index + 1;
},
exportDetails() {
if (!this.details.length) return this.$message.warning('暂无可导出的结算明细');
const rows = this.details.map((row, index) => {
@@ -66,6 +66,12 @@
<span v-else-if="field.prop === 'settlementType'" class="form-readonly">
{{ settlementTypeName }}
</span>
<span v-else-if="field.prop === 'preSettlementNo'" class="form-readonly">
{{ form.preSettlementNo || (!form.id ? '系统自动生成' : '-') }}
</span>
<span v-else-if="field.prop === 'createTime'" class="form-readonly">
{{ formatCreateDate(form.createTime) }}
</span>
<span v-else-if="field.money" class="form-readonly">
{{ formatMoney(form[field.prop], moneyCurrency(field.prop)) }}
</span>
@@ -184,7 +190,6 @@
</template>
<el-form
:model="detailQuery"
inline
label-position="right"
label-width="auto"
class="pre-settlement-editor__detail-filter"
@@ -228,8 +233,15 @@
</el-form-item>
</el-form>
<div v-show="!detailCollapsed">
<el-table :data="filteredDetails" border class="pre-settlement-editor__detail-table">
<el-table-column type="index" label="序号" width="64" fixed="left" align="center" />
<el-table :data="pagedDetails" border class="pre-settlement-editor__detail-table">
<el-table-column
type="index"
label="序号"
width="64"
fixed="left"
align="center"
:index="detailIndexMethod"
/>
<el-table-column
v-for="column in visibleDetailColumns"
:key="column.prop"
@@ -300,6 +312,16 @@
</template>
</el-table-column>
</el-table>
<div class="pre-settlement-editor__pagination">
<el-pagination
v-model:current-page="detailPage.current"
v-model:page-size="detailPage.size"
:total="filteredDetails.length"
:page-sizes="[10, 20, 50, 100]"
layout="total, sizes, prev, pager, next, jumper"
@size-change="handleDetailPageSizeChange"
/>
</div>
</div>
</section-card>
@@ -313,13 +335,43 @@
<el-table :data="attachments" border @selection-change="handleAttachmentSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column type="index" label="序号" width="70" align="center" />
<el-table-column label="文件名" min-width="260" show-overflow-tooltip>
<el-table-column label="附件类型" min-width="180" align="center">
<template #default="{ row }">
<el-select
v-if="editable"
v-model="row.type"
filterable
placeholder="请选择"
@change="sortAttachments"
>
<el-option
v-for="item in attachmentTypeOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
<span v-else>{{ attachmentTypeName(row.type) }}</span>
</template>
</el-table-column>
<el-table-column label="文件名称" min-width="260" show-overflow-tooltip>
<template #default="{ row }">
<el-link type="primary" @click="previewAttachment(row)">
{{ attachmentName(row) }}
</el-link>
</template>
</el-table-column>
<el-table-column label="附件描述" min-width="220">
<template #default="{ row }">
<span v-if="!editable">{{ row.description || '-' }}</span>
<el-input
v-else
v-model="row.description"
maxlength="200"
placeholder="请输入"
/>
</template>
</el-table-column>
<el-table-column label="文件大小" width="120" align="center">
<template #default="{ row }">{{ formatFileSize(row.size) }}</template>
</el-table-column>
@@ -425,7 +477,7 @@
提交
</el-button>
</template>
<div v-if="pageMode" class="pre-settlement-editor__page-actions">
<div v-if="pageMode && !publicMode" class="pre-settlement-editor__page-actions">
<el-button @click="visible = false">取消</el-button>
<el-button v-if="editable" type="primary" plain :loading="saving" @click="saveDraft(false)"
>保存</el-button
@@ -681,134 +733,113 @@
append-to-body
destroy-on-close
>
<el-tabs v-model="adjustDialog.activeTab" class="pre-settlement-editor__adjust-tabs">
<el-tab-pane label="结算明细调整" name="adjust">
<el-table v-loading="adjustDialog.loading" :data="adjustRows" border>
<el-table-column type="index" label="序号" width="64" fixed="left" align="center" />
<el-table-column prop="cargoName" label="货物名称" min-width="140" align="center" />
<el-table-column prop="cargoType" label="货物类型" min-width="130" align="center" />
<el-table-column label="计费规则" min-width="190" align="center">
<template #default="{ row }">
<div class="pre-settlement-editor__billing-rule-cell">
<span>{{ displayValue(billingRuleName(row)) }}</span>
<el-icon
class="pre-settlement-editor__billing-rule-icon"
title="查看计费规则详情"
@click="openBillingRuleInfo(row)"
>
<InfoFilled />
</el-icon>
</div>
</template>
</el-table-column>
<el-table-column label="运输总量" min-width="150" align="center">
<template #default="{ row }">
<span v-if="adjustDialog.readonly">{{ formatQuantity(row) }}</span>
<el-input-number
v-else
v-model="row.transportQuantity"
:min="0"
:precision="2"
:controls="false"
@change="recalculateAdjustRow(row, 'transportQuantity')"
/>
</template>
</el-table-column>
<el-table-column label="里程(KM)" min-width="140" align="center">
<template #default="{ row }">
<el-input-number
v-model="row.mileage"
:min="0"
:precision="2"
:controls="false"
:disabled="adjustDialog.readonly"
@change="recalculateAdjustRow(row, 'mileage')"
/>
</template>
</el-table-column>
<el-table-column label="运输单价" min-width="140" align="center">
<template #default="{ row }">
<el-input-number
v-model="row.unitPrice"
:min="0"
:precision="2"
:controls="false"
:disabled="adjustDialog.readonly"
@change="recalculateAdjustRow(row, 'unitPrice')"
/>
</template>
</el-table-column>
<el-table-column
v-for="feeItem in adjustFeeItemNames"
:key="feeItem"
:label="feeItem"
min-width="140"
align="center"
>
<template #default="{ row }">
<el-input-number
v-model="row.feeItems[feeItem]"
:min="0"
:precision="2"
:controls="false"
:disabled="adjustDialog.readonly"
@change="recalculateAdjustRow(row, feeItem)"
/>
</template>
</el-table-column>
<el-table-column label="结算金额(含税)" min-width="170" align="center">
<template #default="{ row }">
<el-input-number
v-model="row.settlementAmountTax"
:precision="2"
:controls="false"
:disabled="adjustDialog.readonly"
/>
</template>
</el-table-column>
<el-table-column label="备注" min-width="200" align="center">
<template #default="{ row }">
<el-input v-model="row.remark" maxlength="200" :disabled="adjustDialog.readonly" />
</template>
</el-table-column>
</el-table>
<el-form
v-if="!adjustDialog.readonly"
:model="adjustDialog"
label-position="right"
label-width="auto"
class="pre-settlement-editor__adjust-reason"
>
<el-form-item label="调整原因">
<el-input v-model="adjustDialog.reason" maxlength="200" show-word-limit />
</el-form-item>
</el-form>
</el-tab-pane>
<el-tab-pane label="变更记录" name="records">
<el-table :data="changeRecords" border>
<el-table-column type="index" label="序号" width="70" align="center" />
<el-table-column prop="changeTime" label="变更日期" min-width="170" align="center" />
<el-table-column prop="operatorName" label="经办人" min-width="140" align="center" />
<el-table-column prop="changeType" label="变更类型" min-width="160" align="center" />
<el-table-column
prop="changeReason"
label="变更原因"
min-width="240"
align="center"
show-overflow-tooltip
<el-table v-loading="adjustDialog.loading" :data="adjustRows" border>
<el-table-column type="index" label="序号" width="64" fixed="left" align="center" />
<el-table-column prop="cargoName" label="货物名称" min-width="140" align="center" />
<el-table-column prop="cargoType" label="货物类型" min-width="130" align="center" />
<el-table-column label="计费规则" min-width="190" align="center">
<template #default="{ row }">
<div class="pre-settlement-editor__billing-rule-cell">
<span>{{ displayValue(billingRuleName(row)) }}</span>
<el-icon
class="pre-settlement-editor__billing-rule-icon"
title="查看计费规则详情"
@click="openBillingRuleInfo(row)"
>
<InfoFilled />
</el-icon>
</div>
</template>
</el-table-column>
<el-table-column label="运输总量" min-width="150" align="center">
<template #default="{ row }">
<span v-if="adjustDialog.readonly">{{ formatQuantity(row) }}</span>
<el-input-number
v-else
v-model="row.transportQuantity"
:min="0"
:precision="2"
:controls="false"
@change="recalculateAdjustRow(row, 'transportQuantity')"
/>
<el-table-column label="状态" min-width="120" align="center">
<template #default>已生效</template>
</el-table-column>
<el-table-column label="操作" width="120" align="center" fixed="right">
<template #default="{ row }">
<el-link type="primary" @click="openAdjustChangeRecord(row)">查看详情</el-link>
</template>
</el-table-column>
</el-table>
<el-empty v-if="!adjustChangeRecords.length" description="暂无变更记录" :image-size="60" />
</el-tab-pane>
</el-tabs>
</template>
</el-table-column>
<el-table-column label="里程(KM)" min-width="140" align="center">
<template #default="{ row }">
<el-input-number
v-model="row.mileage"
:min="0"
:precision="2"
:controls="false"
:disabled="adjustDialog.readonly"
@change="recalculateAdjustRow(row, 'mileage')"
/>
</template>
</el-table-column>
<el-table-column label="运输单价" min-width="140" align="center">
<template #default="{ row }">
<el-input-number
v-model="row.unitPrice"
:min="0"
:precision="2"
:controls="false"
:disabled="adjustDialog.readonly"
@change="recalculateAdjustRow(row, 'unitPrice')"
/>
</template>
</el-table-column>
<el-table-column
v-for="feeItem in adjustFeeItemNames"
:key="feeItem"
:label="feeItem"
min-width="140"
align="center"
>
<template #default="{ row }">
<el-input-number
v-model="row.feeItems[feeItem]"
:min="0"
:precision="2"
:controls="false"
:disabled="adjustDialog.readonly"
@change="recalculateAdjustRow(row, feeItem)"
/>
</template>
</el-table-column>
<el-table-column label="结算金额(含税)" min-width="170" align="center">
<template #default="{ row }">
<el-input-number
v-model="row.settlementAmountTax"
:precision="2"
:controls="false"
:disabled="adjustDialog.readonly"
/>
</template>
</el-table-column>
<el-table-column label="备注" min-width="200" align="center">
<template #default="{ row }">
<el-input v-model="row.remark" maxlength="200" :disabled="adjustDialog.readonly" />
</template>
</el-table-column>
</el-table>
<el-form
v-if="!adjustDialog.readonly"
:model="adjustDialog"
label-position="right"
label-width="auto"
class="pre-settlement-editor__adjust-reason"
>
<el-form-item label="调整原因">
<el-input
v-model="adjustDialog.reason"
type="textarea"
:rows="2"
maxlength="200"
show-word-limit
placeholder="请输入"
/>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="adjustDialog.visible = false">取消</el-button>
<el-button
@@ -822,35 +853,10 @@
</template>
</el-dialog>
<el-dialog
<change-record-detail-dialog
v-model="adjustChangeRecordVisible"
title="变更记录详情"
append-to-body
destroy-on-close
width="1100px"
top="10px"
class="pre-settlement-change-record-detail-dialog"
>
<div v-if="adjustChangeRecord" class="pre-settlement-change-record-detail-meta">
<span>变更日期:{{ adjustChangeRecord.changeTime || '-' }}</span>
<span>经办人:{{ adjustChangeRecord.operatorName || '-' }}</span>
<span>变更类型:{{ adjustChangeRecord.changeType || '-' }}</span>
<span>状态:已生效</span>
</div>
<el-table :data="adjustChangeRecordDetailRows" border :show-overflow-tooltip="false">
<el-table-column prop="field" label="变更字段" min-width="220" />
<el-table-column prop="before" label="变更前" min-width="330" />
<el-table-column prop="after" label="变更后" min-width="420" />
</el-table>
<el-empty
v-if="!adjustChangeRecordDetailRows.length"
description="暂无变更内容"
:image-size="60"
/>
<template #footer>
<el-button type="primary" @click="adjustChangeRecordVisible = false">关闭</el-button>
</template>
</el-dialog>
:rows="adjustChangeRecordDetailRows"
/>
<el-dialog
v-model="billingRuleDialog.visible"
@@ -922,6 +928,8 @@
import { h } from 'vue';
import { mapGetters } from 'vuex';
import { InfoFilled } from '@element-plus/icons-vue';
import ChangeRecordDetailDialog from '@/components/change-record-detail-dialog/main.vue';
import { getMkPublicDetail } from '@/api/mk-process';
import {
adjustDetail,
getCandidateDetails,
@@ -956,11 +964,12 @@ import {
settlementSummaryColumns,
} from '@/option/settlement/preSettlementTable';
import { downloadFileByUrl } from '@/utils/util';
import { submitMkApprovalFlow } from '@/utils/mk-approval';
import * as XLSX from 'xlsx';
export default {
name: 'PreSettlementEditor',
components: { InfoFilled },
components: { InfoFilled, ChangeRecordDetailDialog },
props: {
modelValue: {
type: Boolean,
@@ -982,6 +991,10 @@ export default {
type: Object,
default: null,
},
publicMode: {
type: Boolean,
default: false,
},
},
emits: ['update:modelValue', 'success'],
data() {
@@ -1054,9 +1067,14 @@ export default {
batchNo: '',
cargoName: '',
},
detailPage: {
current: 1,
size: 10,
},
advances: [],
changeRecords: [],
attachments: [],
attachmentTypeOptions: [],
selectedAttachmentRows: [],
candidateDialog: {
visible: false,
@@ -1079,7 +1097,6 @@ export default {
loading: false,
saving: false,
readonly: false,
activeTab: 'adjust',
detailId: '',
sourceDetailId: '',
detailLineNo: '',
@@ -1206,6 +1223,13 @@ export default {
})
);
},
pagedDetails() {
const list = this.filteredDetails;
const size = Number(this.detailPage.size) || 10;
const current = Math.max(Number(this.detailPage.current) || 1, 1);
const start = (current - 1) * size;
return list.slice(start, start + size);
},
adjustFeeItemNames() {
const names = new Set();
this.adjustRows.forEach(row => {
@@ -1232,6 +1256,16 @@ export default {
this.form.settlementAmount = Number(value || 0).toFixed(2);
this.recalculateLocalAmount();
},
filteredDetails: {
handler(list) {
const size = Number(this.detailPage.size) || 10;
const maxPage = Math.max(1, Math.ceil((list?.length || 0) / size) || 1);
if (this.detailPage.current > maxPage) {
this.detailPage.current = maxPage;
}
},
immediate: true,
},
},
methods: {
hasPermission(code) {
@@ -1239,9 +1273,12 @@ export default {
},
async initialize() {
this.resetEditor();
await this.loadFeeOptions();
await this.loadFeeCategoryOptions();
await this.loadTransportTypeOptions();
if (!this.publicMode) {
await this.loadFeeOptions();
await this.loadFeeCategoryOptions();
await this.loadTransportTypeOptions();
await this.loadAttachmentTypeOptions();
}
if (this.recordId) await this.loadDetail();
else if (this.initialData) {
await this.applyInitialData();
@@ -1285,6 +1322,7 @@ export default {
localCurrency: first.localCurrency || 'RMB',
});
this.details = rows.map(row => this.normalizeSourceDetail(row));
this.detailPage.current = 1;
this.loading = true;
try {
await this.ensureSourceFeeSnapshots();
@@ -1346,6 +1384,7 @@ export default {
cargoName: '',
};
this.appliedDetailQuery = { ...this.detailQuery };
this.detailPage = { current: 1, size: 10 };
this.advances = [];
this.changeRecords = [];
this.attachments = [];
@@ -1354,7 +1393,6 @@ export default {
this.pendingAdjustments = {};
this.sourceFeeSnapshots = {};
this.detailFeeCache = {};
this.adjustDialog.activeTab = 'adjust';
this.adjustDialog.detailId = '';
this.adjustDialog.sourceDetailId = '';
this.adjustDialog.detailLineNo = '';
@@ -1374,11 +1412,14 @@ export default {
async loadDetail() {
this.loading = true;
try {
const { data } = await getDetail(this.form.id || this.recordId);
const detail = data?.data || {};
const response = this.publicMode
? await getMkPublicDetail('pre-settlement', this.form.id || this.recordId)
: await getDetail(this.form.id || this.recordId);
const detail = response.data?.data || {};
this.form = { ...emptyPreSettlementForm(), ...detail };
this.summaryFees = (detail.summaryFees || []).map(row => ({ ...row }));
this.details = (detail.details || []).map(row => this.normalizeSourceDetail(row));
this.detailPage.current = 1;
this.advances = (detail.advances || []).map(row => ({
...row,
createUserName: row.createUserName || detail.createUserName,
@@ -1386,6 +1427,7 @@ export default {
}));
this.changeRecords = detail.changeRecords || [];
this.attachments = this.parseAttachments(detail.attachmentsJson);
this.sortAttachments();
this.selectedAttachmentRows = [];
if (
detail.contractId &&
@@ -1403,8 +1445,15 @@ export default {
partyB: detail.payeeName,
});
}
// 预加载所有明细的费用明细数据,用于拼接货物名称和类型
await this.loadAllDetailFees();
if (this.publicMode && detail.detailFees) {
Object.entries(detail.detailFees).forEach(([detailId, feeRows]) => {
if (Array.isArray(feeRows) && feeRows.length) {
this.detailFeeCache[detailId] = feeRows;
}
});
} else {
await this.loadAllDetailFees();
}
} finally {
this.loading = false;
}
@@ -1563,12 +1612,19 @@ export default {
await this.persistPendingAdjustments();
if (shouldSubmit) {
await submit({ id: this.form.id });
await submitMkApprovalFlow({
bizType: 'pre-settlement',
formInstanceId: this.form.id,
subjectName: this.form.preSettlementNo || this.form.contractName || '',
approvalStatus: this.form.approvalStatus || '',
});
this.$message.success('审批流程已发起');
this.visible = false;
this.$emit('success', this.form.id);
return;
}
this.$message.success('保存成功');
this.visible = false;
this.$emit('success', this.form.id);
} finally {
this[stateKey] = false;
@@ -1918,6 +1974,7 @@ export default {
},
handleDetailQuery() {
this.appliedDetailQuery = { ...this.detailQuery };
this.detailPage.current = 1;
},
resetDetailQuery() {
this.detailQuery = {
@@ -1927,6 +1984,13 @@ export default {
cargoName: '',
};
this.appliedDetailQuery = { ...this.detailQuery };
this.detailPage.current = 1;
},
handleDetailPageSizeChange() {
this.detailPage.current = 1;
},
detailIndexMethod(index) {
return (this.detailPage.current - 1) * this.detailPage.size + index + 1;
},
normalizeAdjustRow(item, feeItemNames = []) {
const rawFeeItems =
@@ -2049,7 +2113,6 @@ export default {
this.adjustDialog.visible = true;
this.adjustDialog.loading = true;
this.adjustDialog.readonly = readonly;
this.adjustDialog.activeTab = 'adjust';
this.adjustDialog.detailId = detailRow.id;
this.adjustDialog.detailLineNo = detailRow.lineNo;
this.adjustDialog.sourceDetailId =
@@ -2065,6 +2128,11 @@ export default {
this.adjustRows = this.cloneAdjustRows(pending.rows);
return;
}
if (this.publicMode) {
const cachedFees = this.detailFeeCache[detailRow.id] || [];
this.adjustRows = cachedFees.map(item => this.normalizeAdjustRow(item));
return;
}
if (sourceDetailId) {
if (!this.sourceFeeSnapshots[sourceDetailId]) {
this.sourceFeeSnapshots[sourceDetailId] = await this.loadSourceFeeRows(sourceDetailId);
@@ -2072,6 +2140,11 @@ export default {
this.adjustRows = this.cloneAdjustRows(this.sourceFeeSnapshots[sourceDetailId]);
return;
}
const cachedFees = this.detailFeeCache[detailRow.id];
if (this.publicMode && Array.isArray(cachedFees)) {
this.adjustRows = cachedFees.map(item => this.normalizeAdjustRow(item));
return;
}
const response = await getDetailFees(detailRow.id);
const rows = response.data?.data || response.data || [];
this.adjustRows = rows.map(item => this.normalizeAdjustRow(item));
@@ -2260,10 +2333,6 @@ export default {
return String(name || '').includes('运费') || String(name || '').includes('运输费');
},
async saveAdjustment() {
if (!this.adjustDialog.reason.trim()) {
this.$message.warning('请输入调整原因');
return;
}
if (this.adjustRows.some(row => row.calculating)) {
this.$message.warning('费用正在重新计算,请稍候');
return;
@@ -2442,13 +2511,92 @@ export default {
handleAttachmentChange(list) {
const uploadUserName = this.userInfo?.realName || this.userInfo?.userName || '';
const uploadTime = this.$dayjs().format('YYYY-MM-DD HH:mm:ss');
this.attachments = (list || []).map(file => ({
...file,
uploadUserName: file.uploadUserName || uploadUserName,
uploadTime: file.uploadTime || uploadTime,
}));
const existingRows = this.attachments || [];
this.attachments = (list || []).map(file => {
const fileUrl = this.attachmentUrl(file);
const existing = existingRows.find(item => {
const sameUid = file.uid && item.uid && String(file.uid) === String(item.uid);
const sameUrl = fileUrl && this.attachmentUrl(item) === fileUrl;
return sameUid || sameUrl;
});
const originalName = this.attachmentName(file);
return {
...file,
type: existing?.type || file.type || this.resolveAttachmentType(originalName),
description: existing?.description || file.description || '',
uploadUserName: existing?.uploadUserName || file.uploadUserName || uploadUserName,
uploadTime: existing?.uploadTime || file.uploadTime || uploadTime,
};
});
this.sortAttachments();
this.selectedAttachmentRows = [];
},
async loadAttachmentTypeOptions() {
try {
const response = await getDictionary({ code: 'pre_settlement_attachment_type' });
const data = response?.data?.data || [];
this.attachmentTypeOptions = data
.map(item => ({
label: item.dictValue,
value: item.dictValue,
}))
.filter(item => item.label && item.value);
} catch (error) {
this.attachmentTypeOptions = [];
}
this.sortAttachments();
},
resolveAttachmentType(fileName) {
const normalizedName = String(fileName || '').toLocaleLowerCase();
const matchedType = [...this.attachmentTypeOptions]
.filter(item => item?.label || item?.value)
.sort(
(a, b) =>
String(b.label || b.value || '').length - String(a.label || a.value || '').length
)
.find(item => {
const typeText = String(item.label || item.value || '').toLocaleLowerCase();
return typeText && normalizedName.includes(typeText);
});
if (matchedType?.value) return matchedType.value;
const otherType = this.attachmentTypeOptions.find(item => this.isOtherAttachmentType(item));
if (otherType?.value) return otherType.value;
return '';
},
isOtherAttachmentType(item) {
const typeText = String(item?.label || item?.value || '').trim();
return (
typeText === '其他' ||
typeText === '其它' ||
typeText === '其他附件' ||
typeText === '其它附件'
);
},
getAttachmentTypeOrder(type) {
const normalizedType = String(type || '').trim();
const index = this.attachmentTypeOptions.findIndex(
item =>
String(item.value || '').trim() === normalizedType ||
String(item.label || '').trim() === normalizedType
);
return index === -1 ? Number.MAX_SAFE_INTEGER : index;
},
sortAttachments() {
this.attachments = (this.attachments || [])
.map((file, index) => ({ file, index }))
.sort((a, b) => {
const orderDifference =
this.getAttachmentTypeOrder(a.file.type) - this.getAttachmentTypeOrder(b.file.type);
return orderDifference || a.index - b.index;
})
.map(item => item.file);
},
attachmentTypeName(type) {
const option = this.attachmentTypeOptions.find(
item => String(item.value) === String(type) || String(item.label) === String(type)
);
return option?.label || type || '-';
},
handleAttachmentSelectionChange(rows) {
this.selectedAttachmentRows = rows || [];
},
@@ -2492,13 +2640,25 @@ export default {
},
parseAttachments(value) {
if (!value) return [];
if (Array.isArray(value)) return value;
try {
const parsed = JSON.parse(value);
return Array.isArray(parsed) ? parsed : [];
} catch (error) {
return [];
let list = [];
if (Array.isArray(value)) {
list = value;
} else {
try {
const parsed = JSON.parse(value);
list = Array.isArray(parsed) ? parsed : [];
} catch (error) {
return [];
}
}
return list.map(file => {
const originalName = this.attachmentName(file);
return {
...file,
type: file?.type || this.resolveAttachmentType(originalName),
description: file?.description || '',
};
});
},
parseFeeItems(value) {
if (!value) return {};
@@ -2535,6 +2695,11 @@ export default {
if (!Number.isFinite(amount)) return '-';
return `${amount.toFixed(2)} ${currency || 'RMB'}`;
},
formatCreateDate(value) {
if (value === undefined || value === null || value === '') return '-';
const parsed = this.$dayjs(value);
return parsed.isValid() ? parsed.format('YYYY-MM-DD') : this.displayValue(value);
},
formatQuantity(row) {
const value = row.transportQuantity;
if (value === undefined || value === null || value === '') return '-';
@@ -2685,22 +2850,43 @@ export default {
}
&__detail-filter {
display: flex;
flex-wrap: wrap;
gap: 0 24px;
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr)) auto;
align-items: center;
gap: 8px 16px;
margin-bottom: 12px;
padding: 12px 12px 4px;
background: #fff;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
:deep(.el-form-item) {
display: flex;
min-width: 0;
margin-right: 0;
margin-bottom: 0;
}
:deep(.el-form-item__content) {
flex: 1;
min-width: 0;
}
:deep(.el-input) {
width: 220px;
width: 100%;
}
}
&__detail-filter-actions {
margin-left: auto;
width: auto;
margin-left: 0;
white-space: nowrap;
justify-self: end;
:deep(.el-form-item__content) {
flex: none;
justify-content: flex-end;
flex-wrap: nowrap;
}
}
&__contract-filter {
@@ -2813,23 +2999,6 @@ export default {
}
}
.pre-settlement-change-record-detail-meta {
display: flex;
flex-wrap: wrap;
gap: 8px 32px;
margin-bottom: 16px;
color: #606266;
}
:deep(.pre-settlement-change-record-detail-dialog .el-dialog__body) {
padding-top: 12px;
}
:deep(.pre-settlement-change-record-detail-dialog .el-table .cell) {
white-space: pre-wrap;
word-break: break-all;
}
:deep(.pre-settlement-editor .el-dialog__body) {
padding: 12px 16px;
}