Compare commits

...

6 Commits

5 changed files with 58 additions and 91 deletions
@@ -23,6 +23,7 @@ export const searchFields = [
{ label: '预付结算单号', prop: 'preSettlementNo', type: 'input' }, { label: '预付结算单号', prop: 'preSettlementNo', type: 'input' },
{ label: '正式结算单号', prop: 'formalSettlementNo', type: 'input' }, { label: '正式结算单号', prop: 'formalSettlementNo', type: 'input' },
{ label: '批次号', prop: 'batchNo', type: 'input' }, { label: '批次号', prop: 'batchNo', type: 'input' },
{ label: '运单号', prop: 'waybillNo', type: 'input' },
{ label: '车号', prop: 'vehicleNo', type: 'input' }, { label: '车号', prop: 'vehicleNo', type: 'input' },
]; ];
+36 -31
View File
@@ -97,7 +97,7 @@
<el-option <el-option
v-for="item in contractOptions" v-for="item in contractOptions"
:key="item.id" :key="item.id"
:label="formatContractOptionLabel(item)" :label="item.contractName || item.contractNo"
:value="item.id" :value="item.id"
/> />
</el-select> </el-select>
@@ -415,7 +415,8 @@
><el-input ><el-input
v-model="row.description" v-model="row.description"
:disabled="readonly" :disabled="readonly"
maxlength="200" /></template></el-table-column maxlength="200"
placeholder="请输入" /></template></el-table-column
><el-table-column label="文件大小" width="120" ><el-table-column label="文件大小" width="120"
><template #default="{ row }">{{ ><template #default="{ row }">{{
formatAttachmentSize(row) formatAttachmentSize(row)
@@ -732,7 +733,6 @@ import {
getList as getCustomerArchiveList, getList as getCustomerArchiveList,
} from '@/api/vehicle/customer-archive'; } from '@/api/vehicle/customer-archive';
import { readSettlementTransfer, removeSettlementTransfer } from '@/utils/settlement-transfer'; import { readSettlementTransfer, removeSettlementTransfer } from '@/utils/settlement-transfer';
import { formatContractOptionLabel } from '@/utils/contract';
import { downloadFileByUrl } from '@/utils/util'; import { downloadFileByUrl } from '@/utils/util';
import { loadMkEnabled, submitMkApprovalFlow } from '@/utils/mk-approval'; import { loadMkEnabled, submitMkApprovalFlow } from '@/utils/mk-approval';
import { import {
@@ -1140,7 +1140,6 @@ export default {
this.initialize(); this.initialize();
}, },
methods: { methods: {
formatContractOptionLabel,
hasPermission(code) { hasPermission(code) {
return this.permission?.[code] !== false; return this.permission?.[code] !== false;
}, },
@@ -2086,10 +2085,10 @@ export default {
mergeAutomaticAttachments( mergeAutomaticAttachments(
files, files,
materials, materials,
sourceName,
defaultMaterial = null, defaultMaterial = null,
sourceKey = '', sourceKey = '',
keepUnmatched = false keepUnmatched = false,
insertIndex = -1
) { ) {
const existingUrls = new Set( const existingUrls = new Set(
(this.form.attachments || []).map(file => this.attachmentFileUrl(file)).filter(Boolean) (this.form.attachments || []).map(file => this.attachmentFileUrl(file)).filter(Boolean)
@@ -2113,35 +2112,39 @@ export default {
url, url,
link: file.link || url, link: file.link || url,
attachmentType: material ? MATERIAL_TYPE_MAP[material.key] : 'other', attachmentType: material ? MATERIAL_TYPE_MAP[material.key] : 'other',
description: file.description || `自动取自${sourceName}`, description: file.description || '',
sourceImported: true, sourceImported: true,
sourceKey: sourceKey || undefined, sourceKey: sourceKey || undefined,
}; };
}); });
if (imported.length) this.form.attachments = [...this.form.attachments, ...imported]; if (imported.length) {
// 回填到被替换附件的原起始位置,避免回显/切换时把用户新传附件顶到前面
const attachments = [...this.form.attachments];
const insertAt =
insertIndex >= 0 ? Math.min(insertIndex, attachments.length) : attachments.length;
attachments.splice(insertAt, 0, ...imported);
this.form.attachments = attachments;
}
return imported; return imported;
}, },
/** 移除指定来源的自动关联附件(切换结算单/合同时整批替换) */ /** 移除指定来源的自动关联附件(切换结算单/合同时整批替换),返回首个被移除的位置 */
removeAutomaticAttachments(sourceKey, sourceName = '') { removeAutomaticAttachments(sourceKey) {
const previousUrls = new Set( const previousUrls = new Set(
sourceKey === 'settlement' ? this.linkedSettlementAttachmentUrls || [] : [] sourceKey === 'settlement' ? this.linkedSettlementAttachmentUrls || [] : []
); );
this.form.attachments = (this.form.attachments || []).filter(file => { let firstRemovedIndex = -1;
this.form.attachments = (this.form.attachments || []).filter((file, index) => {
const url = this.attachmentFileUrl(file); const url = this.attachmentFileUrl(file);
if (sourceKey && file.sourceKey === sourceKey) return false; const keep =
if ( !(sourceKey && file.sourceKey === sourceKey) &&
sourceName && !(previousUrls.size && url && previousUrls.has(url));
file.sourceImported && if (!keep && firstRemovedIndex === -1) firstRemovedIndex = index;
String(file.description || '').includes(`自动取自${sourceName}`) return keep;
) {
return false;
}
if (previousUrls.size && url && previousUrls.has(url)) return false;
return true;
}); });
if (sourceKey === 'settlement') { if (sourceKey === 'settlement') {
this.linkedSettlementAttachmentUrls = []; this.linkedSettlementAttachmentUrls = [];
} }
return firstRemovedIndex;
}, },
/** 按当前结算单附件整批同步:先清旧关联,再写入新结算单上传的附件 */ /** 按当前结算单附件整批同步:先清旧关联,再写入新结算单上传的附件 */
syncSettlementAttachments(settlementRows = []) { syncSettlementAttachments(settlementRows = []) {
@@ -2156,14 +2159,14 @@ export default {
.map(file => this.attachmentFileUrl(file)) .map(file => this.attachmentFileUrl(file))
.filter(url => url && nextUrlSet.has(url)); .filter(url => url && nextUrlSet.has(url));
} }
this.removeAutomaticAttachments('settlement', '结算单'); const removedAt = this.removeAutomaticAttachments('settlement');
const imported = this.mergeAutomaticAttachments( const imported = this.mergeAutomaticAttachments(
settlementFiles, settlementFiles,
settlementMaterials, settlementMaterials,
'结算单',
null, null,
'settlement', 'settlement',
true true,
removedAt
); );
this.linkedSettlementAttachmentUrls = imported this.linkedSettlementAttachmentUrls = imported
.map(file => this.attachmentFileUrl(file)) .map(file => this.attachmentFileUrl(file))
@@ -2214,7 +2217,7 @@ export default {
this.syncSettlementAttachments(settlementRows); this.syncSettlementAttachments(settlementRows);
if (!this.form.contractId) { if (!this.form.contractId) {
this.firstContractPayment = false; this.firstContractPayment = false;
this.removeAutomaticAttachments('contract', '合同'); this.removeAutomaticAttachments('contract');
return; return;
} }
const contractResponse = await getContractDetail(this.form.contractId); const contractResponse = await getContractDetail(this.form.contractId);
@@ -2226,13 +2229,14 @@ export default {
this.contractSignatureFileAvailable = contractFiles.some(file => this.contractSignatureFileAvailable = contractFiles.some(file =>
Boolean(this.attachmentFileUrl(file)) Boolean(this.attachmentFileUrl(file))
); );
this.removeAutomaticAttachments('contract', '合同'); const contractRemovedAt = this.removeAutomaticAttachments('contract');
this.mergeAutomaticAttachments( this.mergeAutomaticAttachments(
contractFiles, contractFiles,
[ATTACHMENT_MATERIALS.contract], [ATTACHMENT_MATERIALS.contract],
'合同',
ATTACHMENT_MATERIALS.contract, ATTACHMENT_MATERIALS.contract,
'contract' 'contract',
false,
contractRemovedAt
); );
return; return;
} }
@@ -2246,14 +2250,15 @@ export default {
String(item.id || '') !== String(this.form.id || '') && String(item.id || '') !== String(this.form.id || '') &&
item.approvalStatus !== 'voided' item.approvalStatus !== 'voided'
); );
this.removeAutomaticAttachments('contract', '合同'); const contractRemovedAt = this.removeAutomaticAttachments('contract');
if (this.firstContractPayment) { if (this.firstContractPayment) {
this.mergeAutomaticAttachments( this.mergeAutomaticAttachments(
this.parse(contract.contractFileJson), this.parse(contract.contractFileJson),
[ATTACHMENT_MATERIALS.contract], [ATTACHMENT_MATERIALS.contract],
'合同',
ATTACHMENT_MATERIALS.contract, ATTACHMENT_MATERIALS.contract,
'contract' 'contract',
false,
contractRemovedAt
); );
} }
} catch (error) { } catch (error) {
@@ -306,7 +306,7 @@
</el-link> </el-link>
<span v-else-if="item.column.money">{{ formatMoney(row[item.column.prop]) }}</span> <span v-else-if="item.column.money">{{ formatMoney(row[item.column.prop]) }}</span>
<span v-else-if="item.column.prop === 'transportQuantity'"> <span v-else-if="item.column.prop === 'transportQuantity'">
{{ formatDetailTransportQuantity(row[item.column.prop]) }} {{ formatDetailTransportQuantity(row) }}
</span> </span>
<span v-else-if="item.column.prop === 'mileage'"> <span v-else-if="item.column.prop === 'mileage'">
{{ formatDetailMileage(row[item.column.prop]) }} {{ formatDetailMileage(row[item.column.prop]) }}
@@ -362,7 +362,6 @@
v-model="row.type" v-model="row.type"
filterable filterable
placeholder="请选择类型" placeholder="请选择类型"
@change="sortAttachments"
> >
<el-option <el-option
v-for="item in attachmentTypeOptions" v-for="item in attachmentTypeOptions"
@@ -1530,7 +1529,6 @@ export default {
this.selectedAttachmentRows = []; this.selectedAttachmentRows = [];
this.invoices = data.invoices || []; this.invoices = data.invoices || [];
this.issuedInvoices = data.issuedInvoices || []; this.issuedInvoices = data.issuedInvoices || [];
this.sortAttachments();
this.contracts = this.filterContracts(this.allContracts, this.form.projectId); this.contracts = this.filterContracts(this.allContracts, this.form.projectId);
}, },
async initialize() { async initialize() {
@@ -3172,13 +3170,13 @@ export default {
formatMoney(value) { formatMoney(value) {
return `${Number(value || 0).toFixed(2)} RMB`; return `${Number(value || 0).toFixed(2)} RMB`;
}, },
formatQuantity(value) { formatDetailTransportQuantity(row) {
if (value === undefined || value === null || value === '') return '-'; const value = row.transportQuantity;
if (this.readonly && Number(value) === -1) return '-';
const quantity = Number(value); const quantity = Number(value);
return Number.isFinite(quantity) ? quantity.toFixed(2) : '-'; if (!Number.isFinite(quantity)) return '-';
}, // 与预结算编辑器 formatQuantity 保持一致,拼接计量单位
formatDetailTransportQuantity(value) { return `${quantity.toFixed(2)}${row.quantityUnit ? ` ${row.quantityUnit}` : ''}`;
return this.readonly && Number(value) === -1 ? '-' : this.formatQuantity(value);
}, },
formatDetailMileage(value) { formatDetailMileage(value) {
return this.readonly && Number(value) === -1 ? '-' : this.displayValue(value); return this.readonly && Number(value) === -1 ? '-' : this.displayValue(value);
@@ -3235,7 +3233,6 @@ export default {
label: item.dictValue, label: item.dictValue,
value: item.dictValue, value: item.dictValue,
})); }));
this.sortAttachments();
}, },
resolveAttachmentType(fileName) { resolveAttachmentType(fileName) {
const normalizedName = String(fileName || '').toLocaleLowerCase(); const normalizedName = String(fileName || '').toLocaleLowerCase();
@@ -3259,25 +3256,6 @@ export default {
const typeText = String(item?.label || item?.value || '').trim(); const typeText = String(item?.label || item?.value || '').trim();
return typeText === '其他' || typeText === '其他附件'; return 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);
},
getMissingAttachmentTypes(files = this.attachments) { getMissingAttachmentTypes(files = this.attachments) {
const uploadedTypes = new Set( const uploadedTypes = new Set(
(files || []) (files || [])
@@ -3307,7 +3285,6 @@ export default {
size: this.formatAttachmentSize(file.size), size: this.formatAttachmentSize(file.size),
url: file.url || file.link || '', url: file.url || file.link || '',
}); });
this.sortAttachments();
}, },
handleAttachmentSelectionChange(rows) { handleAttachmentSelectionChange(rows) {
this.selectedAttachmentRows = rows || []; this.selectedAttachmentRows = rows || [];
@@ -371,7 +371,6 @@
v-model="row.type" v-model="row.type"
filterable filterable
placeholder="请选择" placeholder="请选择"
@change="sortAttachments"
> >
<el-option <el-option
v-for="item in attachmentTypeOptions" v-for="item in attachmentTypeOptions"
@@ -1573,7 +1572,6 @@ export default {
row => !isApprovalSettlementChange(row.changeContent) row => !isApprovalSettlementChange(row.changeContent)
); );
this.attachments = this.parseAttachments(detail.attachmentsJson); this.attachments = this.parseAttachments(detail.attachmentsJson);
this.sortAttachments();
this.selectedAttachmentRows = []; this.selectedAttachmentRows = [];
if (detail.contractId) { if (detail.contractId) {
await this.ensureContractPartyOptions(detail.contractId, { await this.ensureContractPartyOptions(detail.contractId, {
@@ -3099,7 +3097,6 @@ export default {
uploadTime: existing?.uploadTime || file.uploadTime || uploadTime, uploadTime: existing?.uploadTime || file.uploadTime || uploadTime,
}; };
}); });
this.sortAttachments();
this.selectedAttachmentRows = []; this.selectedAttachmentRows = [];
}, },
async loadAttachmentTypeOptions() { async loadAttachmentTypeOptions() {
@@ -3115,7 +3112,6 @@ export default {
} catch (error) { } catch (error) {
this.attachmentTypeOptions = []; this.attachmentTypeOptions = [];
} }
this.sortAttachments();
}, },
resolveAttachmentType(fileName) { resolveAttachmentType(fileName) {
const normalizedName = String(fileName || '').toLocaleLowerCase(); const normalizedName = String(fileName || '').toLocaleLowerCase();
@@ -3143,25 +3139,6 @@ export default {
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) { attachmentTypeName(type) {
const option = this.attachmentTypeOptions.find( const option = this.attachmentTypeOptions.find(
item => String(item.value) === String(type) || String(item.label) === String(type) item => String(item.value) === String(type) || String(item.label) === String(type)
@@ -136,7 +136,7 @@
<div class="settlement-detail-page__detail-panel-body"> <div class="settlement-detail-page__detail-panel-body">
<el-tabs v-model="detailDialog.activeTab" @tab-change="handleDetailTabChange"> <el-tabs v-model="detailDialog.activeTab" @tab-change="handleDetailTabChange">
<el-tab-pane label="费用明细" name="fee"> <el-tab-pane label="费用明细" name="fee">
<el-table v-loading="detailDialog.loading" :data="feeRows" border> <el-table v-loading="detailDialog.loading" :data="feeRows" :max-height="detailTableMaxHeight" border>
<el-table-column type="index" label="序号" width="64" align="center" /> <el-table-column type="index" label="序号" width="64" align="center" />
<el-table-column <el-table-column
v-for="column in feeDetailColumns" v-for="column in feeDetailColumns"
@@ -168,7 +168,7 @@
</el-table> </el-table>
</el-tab-pane> </el-tab-pane>
<el-tab-pane label="变更记录" name="change"> <el-tab-pane label="变更记录" name="change">
<el-table v-loading="latestChangeLoading" :data="latestChangeRows" border> <el-table v-loading="latestChangeLoading" :data="latestChangeRows" :max-height="detailTableMaxHeight" border>
<el-table-column type="index" label="序号" width="64" align="center" /> <el-table-column type="index" label="序号" width="64" align="center" />
<el-table-column <el-table-column
v-for="column in changeRecordColumns" v-for="column in changeRecordColumns"
@@ -1063,7 +1063,6 @@ import { getContractOptions as getSettlementContractOptions } from '@/api/settle
import { getList as getCargoTypeList } from '@/api/base/cargo-type'; import { getList as getCargoTypeList } from '@/api/base/cargo-type';
import { exportBlob } from '@/api/common'; import { exportBlob } from '@/api/common';
import { getDictionary } from '@/api/system/dictbiz'; import { getDictionary } from '@/api/system/dictbiz';
import { formatContractOptionLabel } from '@/utils/contract';
import { isTierUnitPrice, isUnitPriceAdjustable } from '@/utils/settlement-adjust-rule'; import { isTierUnitPrice, isUnitPriceAdjustable } from '@/utils/settlement-adjust-rule';
import { downloadXls } from '@/utils/util'; import { downloadXls } from '@/utils/util';
import { createSettlementTransfer } from '@/utils/settlement-transfer'; import { createSettlementTransfer } from '@/utils/settlement-transfer';
@@ -1145,8 +1144,6 @@ export default {
rule: null, rule: null,
}, },
adjustDialog: { visible: false, loading: false, submitting: false, row: null }, adjustDialog: { visible: false, loading: false, submitting: false, row: null },
// 表头 + 最多 3 行费用 + 横向滚动条,超出后表体上下滚动
adjustTableMaxHeight: 40 + 49 * 3 + 16,
adjustRows: [], adjustRows: [],
adjustDynamicFeeColumns: [], adjustDynamicFeeColumns: [],
adjustTextProps: ['specification', 'model', 'billingFactor', 'billingType'], adjustTextProps: ['specification', 'model', 'billingFactor', 'billingType'],
@@ -1224,18 +1221,28 @@ export default {
}; };
}, },
computed: { computed: {
adjustTableMaxHeight() {
// 面板满高 min(60vh,560) 减去头部(49)、底部(58)、内边距(32);费用行多时表体内部滚动,表头固定
if (!this.adjustDialog.visible) return 40 + 49 * 3 + 16;
return Math.round(Math.min(window.innerHeight * 0.6, 560)) - 138;
},
detailTableMaxHeight() {
// 同上,另减 body 下内边距(16) 和 tabs 头(55)
if (!this.detailDialog.visible) return 40 + 49 * 3 + 16;
return Math.round(Math.min(window.innerHeight * 0.6, 560)) - 178;
},
generateContractName() { generateContractName() {
const contract = this.contractOptions.find( const contract = this.contractOptions.find(
item => String(item.id) === String(this.generateQuery.contractId) item => String(item.id) === String(this.generateQuery.contractId)
); );
return formatContractOptionLabel(contract); return contract?.contractName || '';
}, },
updateContractName() { updateContractName() {
const contractId = this.updateFeeForm.contractId; const contractId = this.updateFeeForm.contractId;
const contract = [...this.updateContractOptions, ...this.contractOptions].find( const contract = [...this.updateContractOptions, ...this.contractOptions].find(
item => String(item.id) === String(contractId) item => String(item.id) === String(contractId)
); );
return formatContractOptionLabel(contract); return contract?.contractName || '';
}, },
updateBillingPlanName() { updateBillingPlanName() {
const billingPlanId = this.updateFeeForm.billingPlanId; const billingPlanId = this.updateFeeForm.billingPlanId;