Merge remote-tracking branch 'origin/master'
This commit is contained in:
@@ -6,17 +6,17 @@ export const getList = (current, size, params) =>
|
|||||||
request({ url: `${baseUrl}/list`, method: 'get', params: { current, size, ...params } });
|
request({ url: `${baseUrl}/list`, method: 'get', params: { current, size, ...params } });
|
||||||
export const getDetail = id => request({ url: `${baseUrl}/detail`, method: 'get', params: { id } });
|
export const getDetail = id => request({ url: `${baseUrl}/detail`, method: 'get', params: { id } });
|
||||||
export const getExpiryCounts = () => request({ url: `${baseUrl}/expiry-counts`, method: 'get' });
|
export const getExpiryCounts = () => request({ url: `${baseUrl}/expiry-counts`, method: 'get' });
|
||||||
export const getAvailableOptions = (keyword, deptId, selectedId) =>
|
export const getAvailableOptions = (keyword, deptId, selectedId, payerName, payeeName) =>
|
||||||
request({
|
request({
|
||||||
url: `${baseUrl}/available-options`,
|
url: `${baseUrl}/available-options`,
|
||||||
method: 'get',
|
method: 'get',
|
||||||
params: { keyword, deptId, selectedId },
|
params: { keyword, deptId, selectedId, payerName, payeeName },
|
||||||
});
|
});
|
||||||
export const getAvailablePage = (current, size, keyword, deptId, selectedId) =>
|
export const getAvailablePage = (current, size, keyword, deptId, selectedId, payerName, payeeName) =>
|
||||||
request({
|
request({
|
||||||
url: `${baseUrl}/available-page`,
|
url: `${baseUrl}/available-page`,
|
||||||
method: 'get',
|
method: 'get',
|
||||||
params: { current, size, keyword, deptId, selectedId },
|
params: { current, size, keyword, deptId, selectedId, payerName, payeeName },
|
||||||
});
|
});
|
||||||
export const submit = data => request({ url: `${baseUrl}/submit`, method: 'post', data });
|
export const submit = data => request({ url: `${baseUrl}/submit`, method: 'post', data });
|
||||||
export const remove = id => request({ url: `${baseUrl}/remove`, method: 'post', params: { id } });
|
export const remove = id => request({ url: `${baseUrl}/remove`, method: 'post', params: { id } });
|
||||||
|
|||||||
@@ -55,7 +55,6 @@ export const detailColumns = [
|
|||||||
{ label: '运费', prop: 'freightAmount', minWidth: 120, money: true },
|
{ label: '运费', prop: 'freightAmount', minWidth: 120, money: true },
|
||||||
{ label: '调整金额', prop: 'adjustAmount', minWidth: 120, money: true },
|
{ label: '调整金额', prop: 'adjustAmount', minWidth: 120, money: true },
|
||||||
{ label: '结算金额(含税)', prop: 'settlementAmountTax', minWidth: 155, money: true },
|
{ label: '结算金额(含税)', prop: 'settlementAmountTax', minWidth: 155, money: true },
|
||||||
{ label: '结算金额(不含税)', prop: 'settlementAmountNoTax', minWidth: 170, money: true },
|
|
||||||
{ label: '备注', prop: 'remark', minWidth: 180 },
|
{ label: '备注', prop: 'remark', minWidth: 180 },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -96,22 +96,12 @@
|
|||||||
prop="carrierName"
|
prop="carrierName"
|
||||||
label="承运商"
|
label="承运商"
|
||||||
min-width="140"
|
min-width="140"
|
||||||
/><el-table-column
|
|
||||||
v-if="columnVisible.carrierType"
|
|
||||||
prop="carrierType"
|
|
||||||
label="承运类型"
|
|
||||||
min-width="120"
|
|
||||||
/>
|
/>
|
||||||
<el-table-column
|
<el-table-column
|
||||||
v-if="columnVisible.waybillCount"
|
v-if="columnVisible.waybillCount"
|
||||||
prop="waybillCount"
|
prop="waybillCount"
|
||||||
label="运单数"
|
label="运单数"
|
||||||
width="90"
|
width="90"
|
||||||
/> <el-table-column
|
|
||||||
v-if="columnVisible.importTypeName"
|
|
||||||
prop="importTypeName"
|
|
||||||
label="导入方式"
|
|
||||||
width="100"
|
|
||||||
/>
|
/>
|
||||||
<el-table-column
|
<el-table-column
|
||||||
v-if="columnVisible.statusName"
|
v-if="columnVisible.statusName"
|
||||||
@@ -119,6 +109,12 @@
|
|||||||
label="状态"
|
label="状态"
|
||||||
width="100"
|
width="100"
|
||||||
/>
|
/>
|
||||||
|
<el-table-column
|
||||||
|
v-if="columnVisible.settlementStatusName"
|
||||||
|
prop="settlementStatusName"
|
||||||
|
label="结算状态"
|
||||||
|
width="110"
|
||||||
|
/>
|
||||||
<el-table-column
|
<el-table-column
|
||||||
v-if="columnVisible.createUserName"
|
v-if="columnVisible.createUserName"
|
||||||
prop="createUserName"
|
prop="createUserName"
|
||||||
@@ -159,7 +155,7 @@
|
|||||||
>编辑</el-link
|
>编辑</el-link
|
||||||
>
|
>
|
||||||
<el-link
|
<el-link
|
||||||
v-if="row.importStatus === 'draft'"
|
v-if="canRemoveBatch(row)"
|
||||||
type="danger"
|
type="danger"
|
||||||
@click="removeBatch(row)"
|
@click="removeBatch(row)"
|
||||||
>删除</el-link
|
>删除</el-link
|
||||||
@@ -641,10 +637,9 @@ const columnOptions = [
|
|||||||
{ prop: 'batchNo', label: '运单批次号' },
|
{ prop: 'batchNo', label: '运单批次号' },
|
||||||
{ prop: 'projectName', label: '项目名称' },
|
{ prop: 'projectName', label: '项目名称' },
|
||||||
{ prop: 'carrierName', label: '承运商' },
|
{ prop: 'carrierName', label: '承运商' },
|
||||||
{ prop: 'carrierType', label: '承运类型' },
|
|
||||||
{ prop: 'waybillCount', label: '运单数' },
|
{ prop: 'waybillCount', label: '运单数' },
|
||||||
{ prop: 'importTypeName', label: '导入方式' },
|
|
||||||
{ prop: 'statusName', label: '状态' },
|
{ prop: 'statusName', label: '状态' },
|
||||||
|
{ prop: 'settlementStatusName', label: '结算状态' },
|
||||||
{ prop: 'createUserName', label: '创建人' },
|
{ prop: 'createUserName', label: '创建人' },
|
||||||
{ prop: 'createTime', label: '创建时间' },
|
{ prop: 'createTime', label: '创建时间' },
|
||||||
{ prop: 'updateTime', label: '更新时间' },
|
{ prop: 'updateTime', label: '更新时间' },
|
||||||
@@ -1266,26 +1261,46 @@ const fillEditForm = async row => {
|
|||||||
if (carrierValue) carrierChange(carrierValue);
|
if (carrierValue) carrierChange(carrierValue);
|
||||||
await loadBatchRows(snapshot.id);
|
await loadBatchRows(snapshot.id);
|
||||||
};
|
};
|
||||||
/** 与行操作一致:仅草稿可删除 */
|
/** 与后端一致:仅未结算批次可整批删除 */
|
||||||
const canRemoveBatch = row => String(row?.importStatus || '') === 'draft';
|
const canRemoveBatch = row => String(row?.settlementStatus || 'unsettled') === 'unsettled';
|
||||||
const removeBatches = () => {
|
const removeBatches = async () => {
|
||||||
if (!selected.value.length) {
|
if (!selected.value.length) {
|
||||||
ElMessage.warning('请选择至少一条数据');
|
ElMessage.warning('请选择至少一条数据');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// 选中含不可删条目时整批拦截(含已完成/进行中),不静默跳过
|
// 选中含部分结算/已结算时整批拦截,不静默跳过
|
||||||
if (selected.value.some(row => !canRemoveBatch(row))) {
|
if (selected.value.some(row => !canRemoveBatch(row))) {
|
||||||
ElMessage.warning('已完成运单不可删除');
|
ElMessage.warning('仅未结算的运单批次可以删除');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
return api.removeImportBatches(selected.value.map(item => item.id).join(',')).then(loadBatches);
|
try {
|
||||||
|
await ElMessageBox.confirm('确认删除选中的运单批次?删除后不可恢复。', '提示', {
|
||||||
|
type: 'warning',
|
||||||
|
});
|
||||||
|
} catch {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await api.removeImportBatches(selected.value.map(item => item.id).join(','));
|
||||||
|
ElMessage.success('删除成功');
|
||||||
|
return loadBatches();
|
||||||
};
|
};
|
||||||
const removeBatch = row => {
|
const removeBatch = async row => {
|
||||||
if (!canRemoveBatch(row)) {
|
if (!canRemoveBatch(row)) {
|
||||||
ElMessage.warning('已完成运单不可删除');
|
ElMessage.warning('仅未结算的运单批次可以删除');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
return api.removeImportBatches(row.id).then(loadBatches);
|
try {
|
||||||
|
await ElMessageBox.confirm(
|
||||||
|
`确认删除运单批次「${row.batchNo || ''}」?删除后不可恢复。`,
|
||||||
|
'提示',
|
||||||
|
{ type: 'warning' }
|
||||||
|
);
|
||||||
|
} catch {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await api.removeImportBatches(row.id);
|
||||||
|
ElMessage.success('删除成功');
|
||||||
|
return loadBatches();
|
||||||
};
|
};
|
||||||
const projectChange = async id => {
|
const projectChange = async id => {
|
||||||
const requestId = ++projectRequestId;
|
const requestId = ++projectRequestId;
|
||||||
|
|||||||
@@ -44,7 +44,8 @@
|
|||||||
reserve-keyword
|
reserve-keyword
|
||||||
:remote-method="searchCustomers"
|
:remote-method="searchCustomers"
|
||||||
:loading="customerLoading"
|
:loading="customerLoading"
|
||||||
placeholder="请选择客商基础档案"
|
placeholder="请选择"
|
||||||
|
@change="syncFeeBearer"
|
||||||
>
|
>
|
||||||
<el-option
|
<el-option
|
||||||
v-for="item in customerOptions"
|
v-for="item in customerOptions"
|
||||||
@@ -63,6 +64,7 @@
|
|||||||
allow-create
|
allow-create
|
||||||
default-first-option
|
default-first-option
|
||||||
placeholder="请选择或输入"
|
placeholder="请选择或输入"
|
||||||
|
@change="syncFeeBearer"
|
||||||
>
|
>
|
||||||
<el-option
|
<el-option
|
||||||
v-for="item in receiverOptions"
|
v-for="item in receiverOptions"
|
||||||
@@ -74,7 +76,7 @@
|
|||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="6">
|
<el-col :span="6">
|
||||||
<el-form-item label="票面金额" prop="faceAmount">
|
<el-form-item label="票面金额" prop="faceAmount" required>
|
||||||
<el-input-number
|
<el-input-number
|
||||||
v-model="form.faceAmount"
|
v-model="form.faceAmount"
|
||||||
:min="0"
|
:min="0"
|
||||||
@@ -111,17 +113,15 @@
|
|||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="6">
|
<el-col :span="6">
|
||||||
<el-form-item label="可用部门" prop="availableDeptIds">
|
<el-form-item label="可用部门" prop="availableDeptIds">
|
||||||
<el-tree-select
|
<el-cascader
|
||||||
v-model="form.availableDeptIds"
|
v-model="availableDeptCascaderValue"
|
||||||
:data="deptTree"
|
:options="businessDeptTreeOptions"
|
||||||
:props="deptProps"
|
:props="deptCascaderProps"
|
||||||
node-key="id"
|
placeholder="请选择"
|
||||||
multiple
|
filterable
|
||||||
show-checkbox
|
clearable
|
||||||
collapse-tags
|
collapse-tags
|
||||||
collapse-tags-tooltip
|
collapse-tags-tooltip
|
||||||
placeholder="请选择,默认全部"
|
|
||||||
@change="handleDeptChange"
|
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
@@ -140,17 +140,9 @@
|
|||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="6">
|
<el-col :span="6">
|
||||||
<el-form-item label="费用承担方" prop="feeBearerId">
|
<el-form-item label="费用承担方" prop="feeBearerId">
|
||||||
<el-select
|
<el-select v-model="form.feeBearerId" filterable clearable placeholder="请选择">
|
||||||
v-model="form.feeBearerId"
|
|
||||||
filterable
|
|
||||||
remote
|
|
||||||
reserve-keyword
|
|
||||||
:remote-method="searchCustomers"
|
|
||||||
:loading="customerLoading"
|
|
||||||
placeholder="请选择客商基础档案"
|
|
||||||
>
|
|
||||||
<el-option
|
<el-option
|
||||||
v-for="item in customerOptions"
|
v-for="item in feeBearerOptions"
|
||||||
:key="item.id"
|
:key="item.id"
|
||||||
:label="customerName(item)"
|
:label="customerName(item)"
|
||||||
:value="item.id"
|
:value="item.id"
|
||||||
@@ -245,13 +237,13 @@ const emptyForm = () => ({
|
|||||||
billNo: '',
|
billNo: '',
|
||||||
issuerId: null,
|
issuerId: null,
|
||||||
receiverName: '',
|
receiverName: '',
|
||||||
billType: 'issued',
|
billType: '',
|
||||||
faceAmount: null,
|
faceAmount: null,
|
||||||
availableBalance: 0,
|
availableBalance: 0,
|
||||||
issueDate: '',
|
issueDate: '',
|
||||||
maturityDate: '',
|
maturityDate: '',
|
||||||
availableDeptIds: ['all'],
|
availableDeptIds: [],
|
||||||
availableDeptNames: '全部',
|
availableDeptNames: '',
|
||||||
feeBearerId: null,
|
feeBearerId: null,
|
||||||
confirmedDiscountRate: null,
|
confirmedDiscountRate: null,
|
||||||
issuingBank: '',
|
issuingBank: '',
|
||||||
@@ -272,9 +264,17 @@ export default {
|
|||||||
receiverOptions: [],
|
receiverOptions: [],
|
||||||
customerLoading: false,
|
customerLoading: false,
|
||||||
saving: false,
|
saving: false,
|
||||||
deptTree: [],
|
businessDeptTreeOptions: [],
|
||||||
deptMap: new Map(),
|
deptMap: new Map(),
|
||||||
deptProps: { label: 'title', value: 'id', children: 'children' },
|
deptCascaderProps: {
|
||||||
|
label: 'label',
|
||||||
|
value: 'value',
|
||||||
|
children: 'children',
|
||||||
|
multiple: true,
|
||||||
|
checkStrictly: true,
|
||||||
|
emitPath: true,
|
||||||
|
expandTrigger: 'click',
|
||||||
|
},
|
||||||
bankOptions: [
|
bankOptions: [
|
||||||
'中国工商银行',
|
'中国工商银行',
|
||||||
'中国农业银行',
|
'中国农业银行',
|
||||||
@@ -309,7 +309,10 @@ export default {
|
|||||||
issuerId: [{ required: true, message: '请选择出票单位', trigger: 'change' }],
|
issuerId: [{ required: true, message: '请选择出票单位', trigger: 'change' }],
|
||||||
receiverName: [{ required: true, message: '请选择或输入收票单位', trigger: 'change' }],
|
receiverName: [{ required: true, message: '请选择或输入收票单位', trigger: 'change' }],
|
||||||
billType: [{ required: true, message: '请选择汇票类型', trigger: 'change' }],
|
billType: [{ required: true, message: '请选择汇票类型', trigger: 'change' }],
|
||||||
faceAmount: [{ validator: this.validateFaceAmount, trigger: 'change' }],
|
faceAmount: [
|
||||||
|
{ required: true, message: '请输入票面金额', trigger: 'change' },
|
||||||
|
{ validator: this.validateFaceAmount, trigger: 'change' },
|
||||||
|
],
|
||||||
issueDate: [{ required: true, message: '请选择出票日期', trigger: 'change' }],
|
issueDate: [{ required: true, message: '请选择出票日期', trigger: 'change' }],
|
||||||
maturityDate: [{ required: true, validator: this.validateMaturityDate, trigger: 'change' }],
|
maturityDate: [{ required: true, validator: this.validateMaturityDate, trigger: 'change' }],
|
||||||
availableDeptIds: [{ validator: this.validateDepartments, trigger: 'change' }],
|
availableDeptIds: [{ validator: this.validateDepartments, trigger: 'change' }],
|
||||||
@@ -340,6 +343,42 @@ export default {
|
|||||||
(Number(this.form.faceAmount || 0) * Number(this.form.confirmedDiscountRate || 0)) / 100
|
(Number(this.form.faceAmount || 0) * Number(this.form.confirmedDiscountRate || 0)) / 100
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
// 可用部门:表单存部门 id,级联多选回显用根到节点的路径
|
||||||
|
availableDeptCascaderValue: {
|
||||||
|
get() {
|
||||||
|
return (this.form.availableDeptIds || [])
|
||||||
|
.filter(id => id !== undefined && id !== null && String(id) !== 'all' && String(id) !== '')
|
||||||
|
.map(id => this.findDeptPath(id, this.businessDeptTreeOptions) || [String(id)]);
|
||||||
|
},
|
||||||
|
set(paths) {
|
||||||
|
const ids = [
|
||||||
|
...new Set(
|
||||||
|
(paths || [])
|
||||||
|
.map(path => {
|
||||||
|
const nodes = Array.isArray(path) ? path.filter(item => item !== undefined && item !== null && item !== '') : [];
|
||||||
|
return nodes.length ? String(nodes.at(-1)) : '';
|
||||||
|
})
|
||||||
|
.filter(Boolean)
|
||||||
|
),
|
||||||
|
];
|
||||||
|
this.form.availableDeptIds = ids;
|
||||||
|
this.form.availableDeptNames = ids
|
||||||
|
.map(id => this.deptMap.get(String(id)))
|
||||||
|
.filter(Boolean)
|
||||||
|
.join('、');
|
||||||
|
},
|
||||||
|
},
|
||||||
|
feeBearerOptions() {
|
||||||
|
const issuer = this.customerOptions.find(item => String(item.id) === String(this.form.issuerId));
|
||||||
|
const receiverName = String(this.form.receiverName || '').trim();
|
||||||
|
const receiver = receiverName
|
||||||
|
? this.customerOptions.find(item => this.customerName(item) === receiverName)
|
||||||
|
: null;
|
||||||
|
return [issuer, receiver].filter(
|
||||||
|
(item, index, rows) =>
|
||||||
|
item && rows.findIndex(row => row && String(row.id) === String(item.id)) === index
|
||||||
|
);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
this.initialize();
|
this.initialize();
|
||||||
@@ -352,7 +391,6 @@ export default {
|
|||||||
async initialize() {
|
async initialize() {
|
||||||
await Promise.all([this.loadDepartments(), this.searchCustomers('')]);
|
await Promise.all([this.loadDepartments(), this.searchCustomers('')]);
|
||||||
if (!this.recordId) {
|
if (!this.recordId) {
|
||||||
this.form.issueDate = this.$dayjs().format('YYYY-MM-DD');
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const data = this.unwrapData(await api.getDetail(this.recordId));
|
const data = this.unwrapData(await api.getDetail(this.recordId));
|
||||||
@@ -361,14 +399,20 @@ export default {
|
|||||||
...data,
|
...data,
|
||||||
confirmedDiscountRate: this.normalizeDiscountRate(data.confirmedDiscountRate),
|
confirmedDiscountRate: this.normalizeDiscountRate(data.confirmedDiscountRate),
|
||||||
bankDiscountReferenceRate: this.normalizeDiscountRate(data.bankDiscountReferenceRate),
|
bankDiscountReferenceRate: this.normalizeDiscountRate(data.bankDiscountReferenceRate),
|
||||||
availableDeptIds: this.parse(data.availableDeptIdsJson),
|
availableDeptIds: this.parse(data.availableDeptIdsJson)
|
||||||
|
.filter(id => String(id) !== 'all')
|
||||||
|
.map(id => String(id)),
|
||||||
attachments: this.parse(data.attachmentsJson),
|
attachments: this.parse(data.attachmentsJson),
|
||||||
};
|
};
|
||||||
this.usageRecords = data.usageRecords || [];
|
this.usageRecords = data.usageRecords || [];
|
||||||
this.receiverOptions = data.receiverName ? [data.receiverName] : [];
|
this.receiverOptions = data.receiverName ? [data.receiverName] : [];
|
||||||
this.ensureCustomerOption(data.issuerId, data.issuerName);
|
this.ensureCustomerOption(data.issuerId, data.issuerName);
|
||||||
this.ensureCustomerOption(data.feeBearerId, data.feeBearerName);
|
this.ensureCustomerOption(data.feeBearerId, data.feeBearerName);
|
||||||
this.handleDeptChange(this.form.availableDeptIds);
|
this.form.availableDeptNames = (this.form.availableDeptIds || [])
|
||||||
|
.map(id => this.deptMap.get(String(id)))
|
||||||
|
.filter(Boolean)
|
||||||
|
.join('、');
|
||||||
|
this.syncFeeBearer();
|
||||||
},
|
},
|
||||||
parse(value) {
|
parse(value) {
|
||||||
if (!value) return [];
|
if (!value) return [];
|
||||||
@@ -381,22 +425,52 @@ export default {
|
|||||||
},
|
},
|
||||||
async loadDepartments() {
|
async loadDepartments() {
|
||||||
const response = await getDeptTree(this.userInfo?.tenantId);
|
const response = await getDeptTree(this.userInfo?.tenantId);
|
||||||
const rows = this.unwrapData(response) || [];
|
const rows = this.excludeExternalOrganization(this.unwrapData(response) || []);
|
||||||
this.deptTree = [{ id: 'all', title: '全部', children: rows }];
|
this.businessDeptTreeOptions = this.toDeptCascaderOptions(rows);
|
||||||
this.deptMap = new Map([['all', '全部']]);
|
this.deptMap = new Map();
|
||||||
this.flattenDepartments(rows).forEach(item => this.deptMap.set(String(item.id), item.title));
|
this.flattenDepartments(rows).forEach(item =>
|
||||||
|
this.deptMap.set(String(item.id), item.title || item.deptName || item.name)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
excludeExternalOrganization(list = []) {
|
||||||
|
return list.reduce((result, item) => {
|
||||||
|
const deptName = item.title || item.deptName || item.name;
|
||||||
|
if (String(deptName || '').trim() === '外部组织') return result;
|
||||||
|
result.push({
|
||||||
|
...item,
|
||||||
|
children: item.children?.length
|
||||||
|
? this.excludeExternalOrganization(item.children)
|
||||||
|
: item.children,
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
}, []);
|
||||||
|
},
|
||||||
|
toDeptCascaderOptions(list = []) {
|
||||||
|
return list.map(item => ({
|
||||||
|
label: item.title || item.deptName || item.name,
|
||||||
|
value: item.id == null || item.id === '' ? '' : String(item.id),
|
||||||
|
children: item.children?.length ? this.toDeptCascaderOptions(item.children) : undefined,
|
||||||
|
}));
|
||||||
},
|
},
|
||||||
flattenDepartments(rows) {
|
flattenDepartments(rows) {
|
||||||
return (rows || []).flatMap(item => [item, ...this.flattenDepartments(item.children || [])]);
|
return (rows || []).flatMap(item => [item, ...this.flattenDepartments(item.children || [])]);
|
||||||
},
|
},
|
||||||
handleDeptChange(values) {
|
findDeptPath(id, tree = [], parents = []) {
|
||||||
const ids = values || [];
|
const target = Array.isArray(id) ? id.at(-1) : id;
|
||||||
this.form.availableDeptNames = ids.some(item => String(item) === 'all')
|
if (target === undefined || target === null || target === '') return null;
|
||||||
? '全部'
|
for (const node of tree || []) {
|
||||||
: ids
|
const path = [...parents, node.value];
|
||||||
.map(item => this.deptMap.get(String(item)))
|
if (String(node.value) === String(target)) return path;
|
||||||
.filter(Boolean)
|
const matched = this.findDeptPath(target, node.children, path);
|
||||||
.join('、');
|
if (matched) return matched;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
syncFeeBearer() {
|
||||||
|
const allowed = this.feeBearerOptions.map(item => String(item.id));
|
||||||
|
if (this.form.feeBearerId && !allowed.includes(String(this.form.feeBearerId))) {
|
||||||
|
this.form.feeBearerId = null;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
async searchCustomers(keyword) {
|
async searchCustomers(keyword) {
|
||||||
this.customerLoading = true;
|
this.customerLoading = true;
|
||||||
@@ -545,7 +619,7 @@ export default {
|
|||||||
.bill-ledger-form-page__form :deep(.el-select),
|
.bill-ledger-form-page__form :deep(.el-select),
|
||||||
.bill-ledger-form-page__form :deep(.el-input-number),
|
.bill-ledger-form-page__form :deep(.el-input-number),
|
||||||
.bill-ledger-form-page__form :deep(.el-date-editor),
|
.bill-ledger-form-page__form :deep(.el-date-editor),
|
||||||
.bill-ledger-form-page__form :deep(.el-tree-select) {
|
.bill-ledger-form-page__form :deep(.el-cascader) {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
.bill-ledger-form-page__form :deep(.el-input-number .el-input__inner) {
|
.bill-ledger-form-page__form :deep(.el-input-number .el-input__inner) {
|
||||||
|
|||||||
@@ -173,7 +173,7 @@
|
|||||||
<el-input
|
<el-input
|
||||||
v-model="settlementDialog.keyword"
|
v-model="settlementDialog.keyword"
|
||||||
clearable
|
clearable
|
||||||
placeholder="3结算单号、项目或合同"
|
placeholder="结算单号、项目或合同"
|
||||||
@keyup.enter="loadSettlementCandidates"
|
@keyup.enter="loadSettlementCandidates"
|
||||||
/>
|
/>
|
||||||
<el-button type="primary" @click="loadSettlementCandidates">查询</el-button>
|
<el-button type="primary" @click="loadSettlementCandidates">查询</el-button>
|
||||||
@@ -182,10 +182,11 @@
|
|||||||
ref="settlementTable"
|
ref="settlementTable"
|
||||||
v-loading="settlementDialog.loading"
|
v-loading="settlementDialog.loading"
|
||||||
:data="settlementDialog.rows"
|
:data="settlementDialog.rows"
|
||||||
|
row-key="id"
|
||||||
border
|
border
|
||||||
@selection-change="settlementDialog.selection = $event"
|
@selection-change="settlementDialog.selection = $event"
|
||||||
>
|
>
|
||||||
<el-table-column type="selection" width="52" align="center" />
|
<el-table-column type="selection" width="52" align="center" reserve-selection />
|
||||||
<el-table-column prop="formalSettlementNo" label="结算单号" min-width="170" />
|
<el-table-column prop="formalSettlementNo" label="结算单号" min-width="170" />
|
||||||
<el-table-column prop="projectName" label="所属项目" min-width="150" />
|
<el-table-column prop="projectName" label="所属项目" min-width="150" />
|
||||||
<el-table-column prop="deptName" label="所属组织" min-width="150" />
|
<el-table-column prop="deptName" label="所属组织" min-width="150" />
|
||||||
@@ -206,7 +207,9 @@
|
|||||||
</el-table>
|
</el-table>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<el-button @click="settlementDialog.visible = false">取消</el-button>
|
<el-button @click="settlementDialog.visible = false">取消</el-button>
|
||||||
<el-button type="primary" @click="confirmSettlements">确定</el-button>
|
<el-button type="primary" @click="confirmSettlements">
|
||||||
|
确定{{ settlementDialog.selection.length ? `(${settlementDialog.selection.length})` : '' }}
|
||||||
|
</el-button>
|
||||||
</template>
|
</template>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
</basic-container>
|
</basic-container>
|
||||||
@@ -437,7 +440,15 @@ export default {
|
|||||||
},
|
},
|
||||||
async openSettlementDialog() {
|
async openSettlementDialog() {
|
||||||
this.settlementDialog.visible = true;
|
this.settlementDialog.visible = true;
|
||||||
|
this.settlementDialog.selection = [];
|
||||||
await this.loadSettlementCandidates();
|
await this.loadSettlementCandidates();
|
||||||
|
await this.$nextTick();
|
||||||
|
const selectedIds = new Set(this.settlementIds.map(id => String(id)));
|
||||||
|
this.settlementDialog.rows.forEach(row => {
|
||||||
|
if (selectedIds.has(String(row.id))) {
|
||||||
|
this.$refs.settlementTable?.toggleRowSelection(row, true);
|
||||||
|
}
|
||||||
|
});
|
||||||
},
|
},
|
||||||
async loadSettlementCandidates() {
|
async loadSettlementCandidates() {
|
||||||
this.settlementDialog.loading = true;
|
this.settlementDialog.loading = true;
|
||||||
@@ -468,11 +479,21 @@ export default {
|
|||||||
this.$message.warning('关联结算单必须属于同一项目、组织及收付款方');
|
this.$message.warning('关联结算单必须属于同一项目、组织及收付款方');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const previousAmount = new Map(
|
||||||
|
this.form.settlements.map(item => [
|
||||||
|
String(item.formalSettlementId || item.settlementId),
|
||||||
|
item.allocatedInvoiceAmount,
|
||||||
|
])
|
||||||
|
);
|
||||||
this.form.settlements = rows.map(item => ({
|
this.form.settlements = rows.map(item => ({
|
||||||
...item,
|
...item,
|
||||||
formalSettlementId: item.id,
|
formalSettlementId: item.id,
|
||||||
settlementId: item.id,
|
settlementId: item.id,
|
||||||
allocatedInvoiceAmount: rows.length === 1 ? Number(item.settlementAmount || 0) : 0,
|
allocatedInvoiceAmount: previousAmount.has(String(item.id))
|
||||||
|
? Number(previousAmount.get(String(item.id)) || 0)
|
||||||
|
: rows.length === 1
|
||||||
|
? Number(item.settlementAmount || 0)
|
||||||
|
: 0,
|
||||||
}));
|
}));
|
||||||
this.form.projectName = first.projectName;
|
this.form.projectName = first.projectName;
|
||||||
this.form.deptName = first.deptName;
|
this.form.deptName = first.deptName;
|
||||||
@@ -482,9 +503,8 @@ export default {
|
|||||||
await this.loadReferenceInformation();
|
await this.loadReferenceInformation();
|
||||||
this.$refs.formRef?.validateField('settlementIds').catch(() => {});
|
this.$refs.formRef?.validateField('settlementIds').catch(() => {});
|
||||||
},
|
},
|
||||||
async selectSettlement(row) {
|
selectSettlement(row) {
|
||||||
this.settlementDialog.selection = [row];
|
this.$refs.settlementTable?.toggleRowSelection(row);
|
||||||
await this.confirmSettlements();
|
|
||||||
},
|
},
|
||||||
async loadReferenceInformation() {
|
async loadReferenceInformation() {
|
||||||
const data = this.unwrapData(await api.getReferenceInformation(this.settlementIds.join(',')));
|
const data = this.unwrapData(await api.getReferenceInformation(this.settlementIds.join(',')));
|
||||||
|
|||||||
@@ -852,6 +852,8 @@ export default {
|
|||||||
billOptions: [],
|
billOptions: [],
|
||||||
billLoading: false,
|
billLoading: false,
|
||||||
billDialogVisible: false,
|
billDialogVisible: false,
|
||||||
|
settlementPayerName: '',
|
||||||
|
settlementPayeeName: '',
|
||||||
billQuery: {
|
billQuery: {
|
||||||
keyword: '',
|
keyword: '',
|
||||||
},
|
},
|
||||||
@@ -1221,8 +1223,12 @@ export default {
|
|||||||
callback();
|
callback();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (selected.billType && selected.billType !== 'received') {
|
if (
|
||||||
callback(new Error('汇票付款仅可选择收票类型的汇票'));
|
selected.billType &&
|
||||||
|
selected.billType !== 'received' &&
|
||||||
|
!this.billMatchesSettlementParties(selected)
|
||||||
|
) {
|
||||||
|
callback(new Error('汇票付款仅可选择收票类型,或出票单位与收票单位均为当前结算收付款方的汇票'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (selected.maturityDate) {
|
if (selected.maturityDate) {
|
||||||
@@ -1580,6 +1586,7 @@ export default {
|
|||||||
this.createInvoiceRows(item.invoices || [], item.formalSettlementNo || item.settlementNo)
|
this.createInvoiceRows(item.invoices || [], item.formalSettlementNo || item.settlementNo)
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
|
this.rememberSettlementParties(first.payerName, first.payeeName);
|
||||||
this.paymentRecords = rows.flatMap(item =>
|
this.paymentRecords = rows.flatMap(item =>
|
||||||
(item.paymentRecords || item.paymentApplications || []).map(record => ({
|
(item.paymentRecords || item.paymentApplications || []).map(record => ({
|
||||||
...record,
|
...record,
|
||||||
@@ -1667,7 +1674,11 @@ export default {
|
|||||||
? 0
|
? 0
|
||||||
: Number(row.payableAmount || 0);
|
: Number(row.payableAmount || 0);
|
||||||
return {
|
return {
|
||||||
id: row.preSettlementId || (isPre ? row.settlementId : null) || row.settlementId || row.id,
|
id:
|
||||||
|
row.preSettlementId ||
|
||||||
|
(isPre ? row.settlementId : null) ||
|
||||||
|
row.settlementId ||
|
||||||
|
(row.paymentNo ? null : row.id),
|
||||||
settlementNo: row.preSettlementNo || row.formalSettlementNo || row.settlementNo || '',
|
settlementNo: row.preSettlementNo || row.formalSettlementNo || row.settlementNo || '',
|
||||||
referenceType: isPre ? 'pre' : 'formal',
|
referenceType: isPre ? 'pre' : 'formal',
|
||||||
settlementAmount: Number(Number(settlementAmount || 0).toFixed(2)),
|
settlementAmount: Number(Number(settlementAmount || 0).toFixed(2)),
|
||||||
@@ -1868,6 +1879,7 @@ export default {
|
|||||||
paymentRatio,
|
paymentRatio,
|
||||||
appliedAmount: headerAppliedAmount,
|
appliedAmount: headerAppliedAmount,
|
||||||
});
|
});
|
||||||
|
this.rememberSettlementParties(first.payerName, first.payeeName);
|
||||||
this.paymentRecords = [];
|
this.paymentRecords = [];
|
||||||
this.clearReceiptAccount();
|
this.clearReceiptAccount();
|
||||||
await this.fillBillTypeFromContract(first.contractId, true);
|
await this.fillBillTypeFromContract(first.contractId, true);
|
||||||
@@ -2063,12 +2075,31 @@ export default {
|
|||||||
},
|
},
|
||||||
resolveAttachmentMaterial(file, materials = Object.values(ATTACHMENT_MATERIALS)) {
|
resolveAttachmentMaterial(file, materials = Object.values(ATTACHMENT_MATERIALS)) {
|
||||||
if (!this.attachmentFileUrl(file)) return null;
|
if (!this.attachmentFileUrl(file)) return null;
|
||||||
const typeMatched = (materials || []).find(
|
const typeText = String(file.attachmentType || file.type || '')
|
||||||
material => file.attachmentType === MATERIAL_TYPE_MAP[material.key]
|
.trim()
|
||||||
);
|
.toLocaleLowerCase();
|
||||||
|
const typeMatched = (materials || []).find(material => {
|
||||||
|
if (file.attachmentType === MATERIAL_TYPE_MAP[material.key]) return true;
|
||||||
|
if (!typeText) return false;
|
||||||
|
const label = String(material.label || '').toLocaleLowerCase();
|
||||||
|
return (
|
||||||
|
typeText === label ||
|
||||||
|
material.keywords.some(keyword => {
|
||||||
|
const normalizedKeyword = String(keyword).toLocaleLowerCase();
|
||||||
|
return normalizedKeyword && typeText.includes(normalizedKeyword);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
return typeMatched || this.matchAttachmentMaterialByFileName(file, materials);
|
return typeMatched || this.matchAttachmentMaterialByFileName(file, materials);
|
||||||
},
|
},
|
||||||
mergeAutomaticAttachments(files, materials, sourceName, defaultMaterial = null, sourceKey = '') {
|
mergeAutomaticAttachments(
|
||||||
|
files,
|
||||||
|
materials,
|
||||||
|
sourceName,
|
||||||
|
defaultMaterial = null,
|
||||||
|
sourceKey = '',
|
||||||
|
keepUnmatched = false
|
||||||
|
) {
|
||||||
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)
|
||||||
);
|
);
|
||||||
@@ -2077,16 +2108,20 @@ export default {
|
|||||||
file,
|
file,
|
||||||
material: this.resolveAttachmentMaterial(file, materials) || defaultMaterial,
|
material: this.resolveAttachmentMaterial(file, materials) || defaultMaterial,
|
||||||
}))
|
}))
|
||||||
.filter(item => item.material && !existingUrls.has(this.attachmentFileUrl(item.file)))
|
.filter(item => {
|
||||||
|
const url = this.attachmentFileUrl(item.file);
|
||||||
|
return Boolean(url) && !existingUrls.has(url) && (item.material || keepUnmatched);
|
||||||
|
})
|
||||||
.map(({ file, material }) => {
|
.map(({ file, material }) => {
|
||||||
existingUrls.add(this.attachmentFileUrl(file));
|
const url = this.attachmentFileUrl(file);
|
||||||
|
existingUrls.add(url);
|
||||||
return {
|
return {
|
||||||
...file,
|
...file,
|
||||||
originalName: this.attachmentFileName(file),
|
originalName: this.attachmentFileName(file),
|
||||||
name: this.attachmentFileName(file),
|
name: this.attachmentFileName(file),
|
||||||
url: this.attachmentFileUrl(file),
|
url,
|
||||||
link: file.link || this.attachmentFileUrl(file),
|
link: file.link || url,
|
||||||
attachmentType: MATERIAL_TYPE_MAP[material.key],
|
attachmentType: material ? MATERIAL_TYPE_MAP[material.key] : 'other',
|
||||||
description: file.description || `自动取自${sourceName}`,
|
description: file.description || `自动取自${sourceName}`,
|
||||||
sourceImported: true,
|
sourceImported: true,
|
||||||
sourceKey: sourceKey || undefined,
|
sourceKey: sourceKey || undefined,
|
||||||
@@ -2119,11 +2154,7 @@ export default {
|
|||||||
},
|
},
|
||||||
/** 按当前结算单附件整批同步:先清旧关联,再写入新结算单上传的附件 */
|
/** 按当前结算单附件整批同步:先清旧关联,再写入新结算单上传的附件 */
|
||||||
syncSettlementAttachments(settlementRows = []) {
|
syncSettlementAttachments(settlementRows = []) {
|
||||||
const settlementMaterials = [
|
const settlementMaterials = Object.values(ATTACHMENT_MATERIALS);
|
||||||
ATTACHMENT_MATERIALS.weighingSlip,
|
|
||||||
ATTACHMENT_MATERIALS.entrustOrder,
|
|
||||||
ATTACHMENT_MATERIALS.settlement,
|
|
||||||
];
|
|
||||||
const settlementFiles = (settlementRows || []).flatMap(row => this.parse(row.attachmentsJson));
|
const settlementFiles = (settlementRows || []).flatMap(row => this.parse(row.attachmentsJson));
|
||||||
const nextUrlSet = new Set(
|
const nextUrlSet = new Set(
|
||||||
settlementFiles.map(file => this.attachmentFileUrl(file)).filter(Boolean)
|
settlementFiles.map(file => this.attachmentFileUrl(file)).filter(Boolean)
|
||||||
@@ -2140,7 +2171,8 @@ export default {
|
|||||||
settlementMaterials,
|
settlementMaterials,
|
||||||
'结算单',
|
'结算单',
|
||||||
null,
|
null,
|
||||||
'settlement'
|
'settlement',
|
||||||
|
true
|
||||||
);
|
);
|
||||||
this.linkedSettlementAttachmentUrls = imported
|
this.linkedSettlementAttachmentUrls = imported
|
||||||
.map(file => this.attachmentFileUrl(file))
|
.map(file => this.attachmentFileUrl(file))
|
||||||
@@ -2547,7 +2579,13 @@ export default {
|
|||||||
try {
|
try {
|
||||||
this.billOptions =
|
this.billOptions =
|
||||||
this.unwrapData(
|
this.unwrapData(
|
||||||
await billLedgerApi.getAvailableOptions(keyword, this.form.deptId, selectedId)
|
await billLedgerApi.getAvailableOptions(
|
||||||
|
keyword,
|
||||||
|
this.form.deptId,
|
||||||
|
selectedId,
|
||||||
|
this.settlementPayerName,
|
||||||
|
this.settlementPayeeName
|
||||||
|
)
|
||||||
) || [];
|
) || [];
|
||||||
} finally {
|
} finally {
|
||||||
this.billLoading = false;
|
this.billLoading = false;
|
||||||
@@ -2569,7 +2607,9 @@ export default {
|
|||||||
this.billPage.size,
|
this.billPage.size,
|
||||||
String(this.billQuery.keyword || '').trim() || undefined,
|
String(this.billQuery.keyword || '').trim() || undefined,
|
||||||
this.form.deptId,
|
this.form.deptId,
|
||||||
this.form.billLedgerId
|
this.form.billLedgerId,
|
||||||
|
this.settlementPayerName,
|
||||||
|
this.settlementPayeeName
|
||||||
);
|
);
|
||||||
const data = this.unwrapData(response) || {};
|
const data = this.unwrapData(response) || {};
|
||||||
this.billRows = data.records || [];
|
this.billRows = data.records || [];
|
||||||
@@ -2590,9 +2630,27 @@ export default {
|
|||||||
this.billPage.current = 1;
|
this.billPage.current = 1;
|
||||||
this.loadBillPage();
|
this.loadBillPage();
|
||||||
},
|
},
|
||||||
|
rememberSettlementParties(payerName, payeeName) {
|
||||||
|
this.settlementPayerName = String(payerName || '').trim();
|
||||||
|
this.settlementPayeeName = String(payeeName || '').trim();
|
||||||
|
},
|
||||||
|
billMatchesSettlementParties(row) {
|
||||||
|
const issuer = String(row?.issuerName || '').trim();
|
||||||
|
const receiver = String(row?.receiverName || '').trim();
|
||||||
|
const payer = this.settlementPayerName;
|
||||||
|
const payee = this.settlementPayeeName;
|
||||||
|
if (!issuer || !receiver || !payer || !payee) return false;
|
||||||
|
return (
|
||||||
|
(issuer === payer && receiver === payee) || (issuer === payee && receiver === payer)
|
||||||
|
);
|
||||||
|
},
|
||||||
selectBill(row) {
|
selectBill(row) {
|
||||||
if (row?.billType && row.billType !== 'received') {
|
if (
|
||||||
ElMessage.warning('汇票付款仅可选择收票类型的汇票');
|
row?.billType &&
|
||||||
|
row.billType !== 'received' &&
|
||||||
|
!this.billMatchesSettlementParties(row)
|
||||||
|
) {
|
||||||
|
ElMessage.warning('汇票付款仅可选择收票类型,或出票单位与收票单位均为当前结算收付款方的汇票');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (row?.maturityDate) {
|
if (row?.maturityDate) {
|
||||||
@@ -2848,6 +2906,7 @@ export default {
|
|||||||
payerName: row.payerName,
|
payerName: row.payerName,
|
||||||
payeeName: '',
|
payeeName: '',
|
||||||
});
|
});
|
||||||
|
this.rememberSettlementParties(row.payerName, row.payeeName);
|
||||||
this.referenceVisible = false;
|
this.referenceVisible = false;
|
||||||
this.refreshReferenceValidation();
|
this.refreshReferenceValidation();
|
||||||
const detail = this.unwrapData(detailResponse);
|
const detail = this.unwrapData(detailResponse);
|
||||||
@@ -2930,6 +2989,7 @@ export default {
|
|||||||
payerName: row.payerName,
|
payerName: row.payerName,
|
||||||
payeeName: '',
|
payeeName: '',
|
||||||
});
|
});
|
||||||
|
this.rememberSettlementParties(row.payerName, row.payeeName);
|
||||||
this.referenceVisible = false;
|
this.referenceVisible = false;
|
||||||
this.refreshReferenceValidation();
|
this.refreshReferenceValidation();
|
||||||
const detail = this.unwrapData(detailResponse);
|
const detail = this.unwrapData(detailResponse);
|
||||||
@@ -3147,6 +3207,14 @@ export default {
|
|||||||
: resolvedPreSettlementId
|
: resolvedPreSettlementId
|
||||||
? [resolvedPreSettlementId]
|
? [resolvedPreSettlementId]
|
||||||
: [];
|
: [];
|
||||||
|
const preSettlementNo = usePreSettlementOnly
|
||||||
|
? this.form.preSettlementNo ||
|
||||||
|
(this.referenceRows || [])
|
||||||
|
.filter(item => item.referenceType === 'pre')
|
||||||
|
.map(item => item.settlementNo)
|
||||||
|
.filter(Boolean)
|
||||||
|
.join('、')
|
||||||
|
: this.form.preSettlementNo;
|
||||||
return {
|
return {
|
||||||
id,
|
id,
|
||||||
paymentType,
|
paymentType,
|
||||||
@@ -3154,6 +3222,7 @@ export default {
|
|||||||
settlementIds: usePreSettlementOnly ? [] : settlementIds,
|
settlementIds: usePreSettlementOnly ? [] : settlementIds,
|
||||||
preSettlementId: usePreSettlementOnly ? resolvedPreSettlementId : preSettlementId,
|
preSettlementId: usePreSettlementOnly ? resolvedPreSettlementId : preSettlementId,
|
||||||
preSettlementIds: usePreSettlementOnly ? resolvedPreSettlementIds : preSettlementIds,
|
preSettlementIds: usePreSettlementOnly ? resolvedPreSettlementIds : preSettlementIds,
|
||||||
|
preSettlementNo,
|
||||||
projectId,
|
projectId,
|
||||||
projectName,
|
projectName,
|
||||||
deptId,
|
deptId,
|
||||||
|
|||||||
@@ -53,7 +53,7 @@
|
|||||||
<el-option
|
<el-option
|
||||||
v-for="item in contracts"
|
v-for="item in contracts"
|
||||||
:key="item.id"
|
:key="item.id"
|
||||||
:label="formatContractOptionLabel(item)"
|
:label="item.contractName || ''"
|
||||||
:value="item.id"
|
:value="item.id"
|
||||||
/>
|
/>
|
||||||
</el-select>
|
</el-select>
|
||||||
@@ -1070,7 +1070,6 @@ import {
|
|||||||
import { getDictionary } from '@/api/system/dictbiz';
|
import { getDictionary } from '@/api/system/dictbiz';
|
||||||
import { h } from 'vue';
|
import { h } from 'vue';
|
||||||
import { mapGetters } from 'vuex';
|
import { mapGetters } from 'vuex';
|
||||||
import { formatContractOptionLabel } from '@/utils/contract';
|
|
||||||
import { isTierUnitPrice, isUnitPriceAdjustable } from '@/utils/settlement-adjust-rule';
|
import { isTierUnitPrice, isUnitPriceAdjustable } from '@/utils/settlement-adjust-rule';
|
||||||
import { normalizeCurrencyCode } from '@/utils/currency';
|
import { normalizeCurrencyCode } from '@/utils/currency';
|
||||||
import {
|
import {
|
||||||
@@ -1389,7 +1388,6 @@ export default {
|
|||||||
methods: {
|
methods: {
|
||||||
isTierUnitPrice,
|
isTierUnitPrice,
|
||||||
isUnitPriceAdjustable,
|
isUnitPriceAdjustable,
|
||||||
formatContractOptionLabel,
|
|
||||||
hasPermission(code) {
|
hasPermission(code) {
|
||||||
if (this.isAdmin) return true;
|
if (this.isAdmin) return true;
|
||||||
const action = String(code || '').replace(
|
const action = String(code || '').replace(
|
||||||
|
|||||||
@@ -251,29 +251,32 @@
|
|||||||
:key="item.key"
|
:key="item.key"
|
||||||
:label="item.kind === 'fee' ? item.name : item.column.label"
|
:label="item.kind === 'fee' ? item.name : item.column.label"
|
||||||
:prop="item.kind === 'fee' ? undefined : item.column.prop"
|
:prop="item.kind === 'fee' ? undefined : item.column.prop"
|
||||||
:min-width="item.kind === 'fee' ? 130 : item.column.minWidth"
|
:min-width="item.kind === 'fee' ? 150 : item.column.minWidth"
|
||||||
align="center"
|
align="center"
|
||||||
show-overflow-tooltip
|
show-overflow-tooltip
|
||||||
>
|
>
|
||||||
<template #default="{ row }">
|
<template #header>
|
||||||
<span v-if="item.kind === 'fee'" class="pre-settlement-editor__billing-rule-cell">
|
<span class="pre-settlement-editor__billing-rule-cell">
|
||||||
<span>
|
<span>{{ item.kind === 'fee' ? item.name : item.column.label }}</span>
|
||||||
{{
|
|
||||||
formatMoney(
|
|
||||||
parseFeeItems(row.feeItemsJson)[item.name],
|
|
||||||
row.currency || form.currency
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
</span>
|
|
||||||
<el-icon
|
<el-icon
|
||||||
v-if="hasDetailBillingRule(row)"
|
v-if="showDetailColumnRuleIcon(item)"
|
||||||
class="pre-settlement-editor__billing-rule-icon"
|
class="pre-settlement-editor__billing-rule-icon"
|
||||||
title="查看计费规则详情"
|
title="查看计费规则详情"
|
||||||
@click.stop="openDetailBillingRule(row, item.name)"
|
@click.stop="openDetailColumnRule(item)"
|
||||||
>
|
>
|
||||||
<InfoFilled />
|
<InfoFilled />
|
||||||
</el-icon>
|
</el-icon>
|
||||||
</span>
|
</span>
|
||||||
|
</template>
|
||||||
|
<template #default="{ row }">
|
||||||
|
<span v-if="item.kind === 'fee'">
|
||||||
|
{{
|
||||||
|
formatMoney(
|
||||||
|
parseFeeItems(row.feeItemsJson)[item.name],
|
||||||
|
row.currency || form.currency
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
</span>
|
||||||
<el-link
|
<el-link
|
||||||
v-else-if="item.column.link && row[item.column.prop] && !publicMode"
|
v-else-if="item.column.link && row[item.column.prop] && !publicMode"
|
||||||
type="primary"
|
type="primary"
|
||||||
@@ -284,19 +287,8 @@
|
|||||||
<span v-else-if="item.column.money && item.column.prop === 'unitPrice'">
|
<span v-else-if="item.column.money && item.column.prop === 'unitPrice'">
|
||||||
{{ formatUnitPrice(row) }}
|
{{ formatUnitPrice(row) }}
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span v-else-if="item.column.prop === 'freightAmount'">
|
||||||
v-else-if="item.column.prop === 'freightAmount'"
|
{{ formatMoney(row[item.column.prop], row.currency || form.currency) }}
|
||||||
class="pre-settlement-editor__billing-rule-cell"
|
|
||||||
>
|
|
||||||
<span>{{ formatMoney(row[item.column.prop], row.currency || form.currency) }}</span>
|
|
||||||
<el-icon
|
|
||||||
v-if="hasDetailBillingRule(row)"
|
|
||||||
class="pre-settlement-editor__billing-rule-icon"
|
|
||||||
title="查看计费规则详情"
|
|
||||||
@click.stop="openDetailBillingRule(row, '运输费')"
|
|
||||||
>
|
|
||||||
<InfoFilled />
|
|
||||||
</el-icon>
|
|
||||||
</span>
|
</span>
|
||||||
<span v-else-if="item.column.money">
|
<span v-else-if="item.column.money">
|
||||||
{{ formatMoney(row[item.column.prop], row.currency || form.currency) }}
|
{{ formatMoney(row[item.column.prop], row.currency || form.currency) }}
|
||||||
@@ -499,6 +491,11 @@
|
|||||||
<span v-else>{{ displayValue(row[column.prop]) }}</span>
|
<span v-else>{{ displayValue(row[column.prop]) }}</span>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
|
<el-table-column label="操作" width="100" align="center" fixed="right">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-link type="primary" @click="openAdjustChangeRecord(row)">查看</el-link>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
</section-card>
|
</section-card>
|
||||||
</div>
|
</div>
|
||||||
@@ -824,25 +821,28 @@
|
|||||||
min-width="160"
|
min-width="160"
|
||||||
align="center"
|
align="center"
|
||||||
>
|
>
|
||||||
<template #default="{ row }">
|
<template #header>
|
||||||
<div class="pre-settlement-editor__billing-rule-cell">
|
<span class="pre-settlement-editor__billing-rule-cell">
|
||||||
<el-input-number
|
<span>{{ feeItem }}</span>
|
||||||
v-model="row.feeItems[feeItem]"
|
|
||||||
:min="0"
|
|
||||||
:precision="2"
|
|
||||||
:controls="false"
|
|
||||||
:disabled="adjustDialog.readonly"
|
|
||||||
@change="recalculateAdjustRow(row, feeItem)"
|
|
||||||
/>
|
|
||||||
<el-icon
|
<el-icon
|
||||||
v-if="hasBillingRule(row)"
|
v-if="showAdjustFeeRuleIcon(feeItem)"
|
||||||
class="pre-settlement-editor__billing-rule-icon"
|
class="pre-settlement-editor__billing-rule-icon"
|
||||||
title="查看计费规则详情"
|
title="查看计费规则详情"
|
||||||
@click="openBillingRuleInfo(row, feeItem)"
|
@click.stop="openAdjustFeeRule(feeItem)"
|
||||||
>
|
>
|
||||||
<InfoFilled />
|
<InfoFilled />
|
||||||
</el-icon>
|
</el-icon>
|
||||||
</div>
|
</span>
|
||||||
|
</template>
|
||||||
|
<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>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="结算金额(含税)" min-width="170" align="center">
|
<el-table-column label="结算金额(含税)" min-width="170" align="center">
|
||||||
@@ -1522,7 +1522,9 @@ export default {
|
|||||||
availableWriteOffAmount: Number(row.availableWriteOffAmount || 0),
|
availableWriteOffAmount: Number(row.availableWriteOffAmount || 0),
|
||||||
createTime: row.paymentCreateTime || row.createTime,
|
createTime: row.paymentCreateTime || row.createTime,
|
||||||
}));
|
}));
|
||||||
this.changeRecords = detail.changeRecords || [];
|
this.changeRecords = (detail.changeRecords || []).filter(
|
||||||
|
row => row.changeContent !== '预结算审批通过'
|
||||||
|
);
|
||||||
this.attachments = this.parseAttachments(detail.attachmentsJson);
|
this.attachments = this.parseAttachments(detail.attachmentsJson);
|
||||||
this.sortAttachments();
|
this.sortAttachments();
|
||||||
this.selectedAttachmentRows = [];
|
this.selectedAttachmentRows = [];
|
||||||
@@ -2307,6 +2309,17 @@ export default {
|
|||||||
},
|
},
|
||||||
formatAdjustChangeValue(value) {
|
formatAdjustChangeValue(value) {
|
||||||
if (value === undefined || value === null || value === '') return '空';
|
if (value === undefined || value === null || value === '') return '空';
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
if (!value.length) return '空';
|
||||||
|
return value
|
||||||
|
.map(item => {
|
||||||
|
if (!item || typeof item !== 'object') return String(item);
|
||||||
|
return Object.entries(item)
|
||||||
|
.map(([key, itemValue]) => `${key}:${this.formatAdjustChangeValue(itemValue)}`)
|
||||||
|
.join(',');
|
||||||
|
})
|
||||||
|
.join('\n');
|
||||||
|
}
|
||||||
if (typeof value === 'object') return JSON.stringify(value);
|
if (typeof value === 'object') return JSON.stringify(value);
|
||||||
return String(value);
|
return String(value);
|
||||||
},
|
},
|
||||||
@@ -2369,12 +2382,60 @@ export default {
|
|||||||
ruleFeeName(rule = {}) {
|
ruleFeeName(rule = {}) {
|
||||||
return String(rule.feeItem || rule.feeItemName || rule.name || '').trim();
|
return String(rule.feeItem || rule.feeItemName || rule.name || '').trim();
|
||||||
},
|
},
|
||||||
|
isHandlingFeeItem(name) {
|
||||||
|
return String(name || '').includes('装卸');
|
||||||
|
},
|
||||||
feeNamesMatch(left, right) {
|
feeNamesMatch(left, right) {
|
||||||
const source = String(left || '').trim();
|
const source = String(left || '').trim();
|
||||||
const target = String(right || '').trim();
|
const target = String(right || '').trim();
|
||||||
if (!source || !target) return false;
|
if (!source || !target) return false;
|
||||||
if (source === target) return true;
|
if (source === target) return true;
|
||||||
return this.isFreightFeeItem(source) && this.isFreightFeeItem(target);
|
if (this.isFreightFeeItem(source) && this.isFreightFeeItem(target)) return true;
|
||||||
|
return this.isHandlingFeeItem(source) && this.isHandlingFeeItem(target);
|
||||||
|
},
|
||||||
|
detailColumnFeeName(item = {}) {
|
||||||
|
if (item.kind === 'fee') return item.name || '';
|
||||||
|
if (item.column?.prop === 'freightAmount') return '运输费';
|
||||||
|
return '';
|
||||||
|
},
|
||||||
|
showDetailColumnRuleIcon(item) {
|
||||||
|
const feeName = this.detailColumnFeeName(item);
|
||||||
|
if (!feeName) return false;
|
||||||
|
return this.details.some(row => this.rowHasNamedBillingRule(row, feeName));
|
||||||
|
},
|
||||||
|
openDetailColumnRule(item) {
|
||||||
|
const feeName = this.detailColumnFeeName(item);
|
||||||
|
const row =
|
||||||
|
this.details.find(detail => this.rowHasNamedBillingRule(detail, feeName)) ||
|
||||||
|
this.details[0];
|
||||||
|
if (row) this.openDetailBillingRule(row, feeName);
|
||||||
|
},
|
||||||
|
showAdjustFeeRuleIcon(feeName) {
|
||||||
|
return (this.adjustRows || []).some(row => this.sourceHasNamedBillingRule(row, feeName));
|
||||||
|
},
|
||||||
|
openAdjustFeeRule(feeName) {
|
||||||
|
const row =
|
||||||
|
(this.adjustRows || []).find(item => this.sourceHasNamedBillingRule(item, feeName)) ||
|
||||||
|
this.adjustRows[0];
|
||||||
|
if (row) this.openBillingRuleInfo(row, feeName);
|
||||||
|
},
|
||||||
|
rowHasNamedBillingRule(row, feeName) {
|
||||||
|
return [row, ...this.detailFeeRows(row)].some(source =>
|
||||||
|
this.sourceHasNamedBillingRule(source, feeName)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
sourceHasNamedBillingRule(source = {}, feeName) {
|
||||||
|
const rules = [
|
||||||
|
...this.collectBillingRules(source),
|
||||||
|
...(Array.isArray(source.billingRule?.rules) ? source.billingRule.rules : []),
|
||||||
|
];
|
||||||
|
if (rules.some(rule => this.feeNamesMatch(this.ruleFeeName(rule), feeName))) return true;
|
||||||
|
if (!this.hasBillingRule(source)) return false;
|
||||||
|
const named = String(
|
||||||
|
source.billingFeeItem || source.feeItem || this.ruleFeeName(source.billingRule) || ''
|
||||||
|
).trim();
|
||||||
|
if (!named) return true;
|
||||||
|
return this.feeNamesMatch(named, feeName);
|
||||||
},
|
},
|
||||||
collectBillingRules(row = {}) {
|
collectBillingRules(row = {}) {
|
||||||
const source = row || {};
|
const source = row || {};
|
||||||
|
|||||||
@@ -604,6 +604,38 @@ const dedupeColumns = columns => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const settlementAmountColumn = {
|
||||||
|
prop: 'settlementAmount',
|
||||||
|
label: '结算金额',
|
||||||
|
editor: 'number',
|
||||||
|
minWidth: 120,
|
||||||
|
};
|
||||||
|
|
||||||
|
/** 模板已去掉结算金额;明细仍展示,并插在费用项之后、日期列之前 */
|
||||||
|
const ensureSettlementAmountColumn = columns => {
|
||||||
|
if (columns.some(column => column.prop === 'settlementAmount')) {
|
||||||
|
return columns;
|
||||||
|
}
|
||||||
|
const result = [...columns];
|
||||||
|
let insertAt = result.findIndex(column =>
|
||||||
|
['actualStartDate', 'actualEndDate', 'planStartDate', 'planEndDate', 'remark', 'waybillIdentifier'].includes(
|
||||||
|
column.prop
|
||||||
|
)
|
||||||
|
);
|
||||||
|
if (insertAt < 0) {
|
||||||
|
const lastFeeIndex = [...result].map((column, index) => (column.isFeeItem ? index : -1)).filter(index => index >= 0).pop();
|
||||||
|
const transportFeeIndex = result.findIndex(column => column.prop === 'transportFee');
|
||||||
|
insertAt =
|
||||||
|
lastFeeIndex !== undefined
|
||||||
|
? lastFeeIndex + 1
|
||||||
|
: transportFeeIndex >= 0
|
||||||
|
? transportFeeIndex + 1
|
||||||
|
: result.length;
|
||||||
|
}
|
||||||
|
result.splice(insertAt, 0, { ...settlementAmountColumn });
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
const detailColumns = computed(() => {
|
const detailColumns = computed(() => {
|
||||||
const historyColumns =
|
const historyColumns =
|
||||||
form.waybillMode === 'history'
|
form.waybillMode === 'history'
|
||||||
@@ -619,16 +651,13 @@ const detailColumns = computed(() => {
|
|||||||
const fallback = [
|
const fallback = [
|
||||||
...columnsBeforeFee,
|
...columnsBeforeFee,
|
||||||
...feeItemNames.value.map(name => feeColumn(name)),
|
...feeItemNames.value.map(name => feeColumn(name)),
|
||||||
{
|
{ ...settlementAmountColumn },
|
||||||
prop: 'settlementAmount',
|
|
||||||
label: '结算金额',
|
|
||||||
editor: 'number',
|
|
||||||
minWidth: 120,
|
|
||||||
},
|
|
||||||
...columnsAfterFee,
|
...columnsAfterFee,
|
||||||
];
|
];
|
||||||
const uploaded = parsedHeaders.value.map(columnFromHeader).filter(Boolean);
|
const uploaded = parsedHeaders.value.map(columnFromHeader).filter(Boolean);
|
||||||
const columns = dedupeColumns(uploaded.length ? uploaded : fallback);
|
const columns = ensureSettlementAmountColumn(
|
||||||
|
dedupeColumns(uploaded.length ? uploaded : fallback)
|
||||||
|
);
|
||||||
return [...historyColumns, ...columns];
|
return [...historyColumns, ...columns];
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -717,6 +746,7 @@ const formatCell = (row, column) => {
|
|||||||
return value ?? '';
|
return value ?? '';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** 导入解析后自动合计结算金额;编辑运输费/费用项时不再回算 */
|
||||||
const recalcSettlementAmount = row => {
|
const recalcSettlementAmount = row => {
|
||||||
const transportFee = toNumber(row.transportFee ?? row.freight);
|
const transportFee = toNumber(row.transportFee ?? row.freight);
|
||||||
row.transportFee = transportFee;
|
row.transportFee = transportFee;
|
||||||
@@ -735,6 +765,18 @@ const recalcSettlementAmount = row => {
|
|||||||
row.settlementAmount = settlementAmount;
|
row.settlementAmount = settlementAmount;
|
||||||
row.otherFeeTotal = Number(feeSum.toFixed(2));
|
row.otherFeeTotal = Number(feeSum.toFixed(2));
|
||||||
row.freightTotal = settlementAmount;
|
row.freightTotal = settlementAmount;
|
||||||
|
row['运费合计'] = settlementAmount;
|
||||||
|
};
|
||||||
|
|
||||||
|
const syncOtherFeeTotal = row => {
|
||||||
|
if (!row.feeItems || typeof row.feeItems !== 'object') {
|
||||||
|
row.feeItems = {};
|
||||||
|
}
|
||||||
|
let feeSum = 0;
|
||||||
|
feeItemNames.value.forEach(name => {
|
||||||
|
feeSum += toNumber(row.feeItems[name] ?? row[name]);
|
||||||
|
});
|
||||||
|
row.otherFeeTotal = Number(feeSum.toFixed(2));
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateNumberCell = (row, column, value) => {
|
const updateNumberCell = (row, column, value) => {
|
||||||
@@ -744,17 +786,16 @@ const updateNumberCell = (row, column, value) => {
|
|||||||
if (!row.feeItems) row.feeItems = {};
|
if (!row.feeItems) row.feeItems = {};
|
||||||
row.feeItems[column.prop] = '';
|
row.feeItems[column.prop] = '';
|
||||||
row[column.prop] = '';
|
row[column.prop] = '';
|
||||||
} else {
|
syncOtherFeeTotal(row);
|
||||||
row[column.prop] = column.editor === 'optionalNumber' ? '' : '';
|
|
||||||
if (column.prop === 'transportFee') row.freight = '';
|
|
||||||
if (column.prop === 'unitPrice') row.transportUnitPrice = '';
|
|
||||||
}
|
|
||||||
if (column.prop === 'settlementAmount') {
|
|
||||||
row.settlementAmount = '';
|
|
||||||
row.freightTotal = '';
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
recalcSettlementAmount(row);
|
row[column.prop] = '';
|
||||||
|
if (column.prop === 'transportFee') row.freight = '';
|
||||||
|
if (column.prop === 'unitPrice') row.transportUnitPrice = '';
|
||||||
|
if (column.prop === 'settlementAmount') {
|
||||||
|
row.freightTotal = '';
|
||||||
|
row['运费合计'] = '';
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const [integer = '', decimal = ''] = raw.replace(/[^\d.]/g, '').split('.');
|
const [integer = '', decimal = ''] = raw.replace(/[^\d.]/g, '').split('.');
|
||||||
@@ -764,21 +805,20 @@ const updateNumberCell = (row, column, value) => {
|
|||||||
if (!row.feeItems) row.feeItems = {};
|
if (!row.feeItems) row.feeItems = {};
|
||||||
row.feeItems[column.prop] = normalized;
|
row.feeItems[column.prop] = normalized;
|
||||||
row[column.prop] = normalized;
|
row[column.prop] = normalized;
|
||||||
} else {
|
syncOtherFeeTotal(row);
|
||||||
row[column.prop] = normalized;
|
return;
|
||||||
if (column.prop === 'transportFee') {
|
}
|
||||||
row.freight = normalized;
|
row[column.prop] = normalized;
|
||||||
}
|
if (column.prop === 'transportFee') {
|
||||||
if (column.prop === 'unitPrice') {
|
row.freight = normalized;
|
||||||
row.transportUnitPrice = normalized;
|
}
|
||||||
}
|
if (column.prop === 'unitPrice') {
|
||||||
if (column.prop === 'settlementAmount') {
|
row.transportUnitPrice = normalized;
|
||||||
row.freightTotal = normalized;
|
}
|
||||||
row['运费合计'] = normalized;
|
if (column.prop === 'settlementAmount') {
|
||||||
return;
|
row.freightTotal = normalized;
|
||||||
}
|
row['运费合计'] = normalized;
|
||||||
}
|
}
|
||||||
recalcSettlementAmount(row);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const importFieldText = value => String(value ?? '').trim();
|
const importFieldText = value => String(value ?? '').trim();
|
||||||
@@ -854,11 +894,8 @@ const normalizeParsedRow = (item, index) => {
|
|||||||
applyVehicleNo(row, vehicleNo);
|
applyVehicleNo(row, vehicleNo);
|
||||||
row.transportUnitPrice = row.unitPrice;
|
row.transportUnitPrice = row.unitPrice;
|
||||||
row.freight = row.transportFee;
|
row.freight = row.transportFee;
|
||||||
if (row.settlementAmount === '' || row.settlementAmount === null || row.settlementAmount === undefined) {
|
// 模板无结算金额列:导入后按运输费+费用项自动合计
|
||||||
recalcSettlementAmount(row);
|
recalcSettlementAmount(row);
|
||||||
} else {
|
|
||||||
row.freightTotal = row.settlementAmount;
|
|
||||||
}
|
|
||||||
return row;
|
return row;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1341,22 +1378,18 @@ const confirmImport = async () => {
|
|||||||
|
|
||||||
const loadTransportTypeOptions = async () => {
|
const loadTransportTypeOptions = async () => {
|
||||||
try {
|
try {
|
||||||
|
// 运输方式取业务字典 transport_type 的字典名称(dictValue),不写死枚举
|
||||||
const response = await getDictionary({ code: 'transport_type' });
|
const response = await getDictionary({ code: 'transport_type' });
|
||||||
const list = unwrapData(response) || [];
|
const list = unwrapData(response) || [];
|
||||||
transportTypeOptions.value = (Array.isArray(list) ? list : [])
|
transportTypeOptions.value = (Array.isArray(list) ? list : [])
|
||||||
.map(item => {
|
.map(item => {
|
||||||
const label = item.dictValue || item.label || item.name || '';
|
const label = String(item.dictValue || item.label || item.name || '').trim();
|
||||||
const value = item.dictValue || item.dictKey || item.value || label;
|
if (!label || String(item.dictKey) === '-1') return null;
|
||||||
return label ? { label, value } : null;
|
return { label, value: label };
|
||||||
})
|
})
|
||||||
.filter(Boolean);
|
.filter(Boolean);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
transportTypeOptions.value = [
|
transportTypeOptions.value = [];
|
||||||
{ label: '公路运输', value: '公路运输' },
|
|
||||||
{ label: '铁路运输', value: '铁路运输' },
|
|
||||||
{ label: '水路运输', value: '水路运输' },
|
|
||||||
{ label: '航空运输', value: '航空运输' },
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -241,7 +241,12 @@
|
|||||||
<div
|
<div
|
||||||
class="settlement-detail-page__detail-panel-body settlement-detail-page__adjust-panel-body"
|
class="settlement-detail-page__detail-panel-body settlement-detail-page__adjust-panel-body"
|
||||||
>
|
>
|
||||||
<el-table v-loading="adjustDialog.loading" :data="adjustRows" border>
|
<el-table
|
||||||
|
v-loading="adjustDialog.loading"
|
||||||
|
:data="adjustRows"
|
||||||
|
:max-height="adjustTableMaxHeight"
|
||||||
|
border
|
||||||
|
>
|
||||||
<el-table-column type="index" label="序号" width="64" align="center" />
|
<el-table-column type="index" label="序号" width="64" align="center" />
|
||||||
<el-table-column label="来源" width="110" align="center">
|
<el-table-column label="来源" width="110" align="center">
|
||||||
<template #default="{ row }">{{ feeSourceLabel(row.dataSource) }}</template>
|
<template #default="{ row }">{{ feeSourceLabel(row.dataSource) }}</template>
|
||||||
@@ -1115,6 +1120,8 @@ export default {
|
|||||||
page: { current: 1, size: 10, total: 0 },
|
page: { current: 1, size: 10, total: 0 },
|
||||||
// 底部详情/调整面板打开时压缩主表高度,露出表格滚动条
|
// 底部详情/调整面板打开时压缩主表高度,露出表格滚动条
|
||||||
listTableHeight: undefined,
|
listTableHeight: undefined,
|
||||||
|
savedListTableHeight: null,
|
||||||
|
listTableLayoutToken: 0,
|
||||||
detailDialog: {
|
detailDialog: {
|
||||||
visible: false,
|
visible: false,
|
||||||
title: '费用明细',
|
title: '费用明细',
|
||||||
@@ -1129,6 +1136,8 @@ 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'],
|
||||||
@@ -1330,8 +1339,11 @@ export default {
|
|||||||
'$route.fullPath'() {
|
'$route.fullPath'() {
|
||||||
this.openRouteDetail();
|
this.openRouteDetail();
|
||||||
},
|
},
|
||||||
isBottomPanelOpen() {
|
isBottomPanelOpen: {
|
||||||
this.updateListTableHeight();
|
flush: 'post',
|
||||||
|
handler() {
|
||||||
|
this.updateListTableHeight();
|
||||||
|
},
|
||||||
},
|
},
|
||||||
searchVisible() {
|
searchVisible() {
|
||||||
this.updateListTableHeight();
|
this.updateListTableHeight();
|
||||||
@@ -1396,36 +1408,62 @@ export default {
|
|||||||
},
|
},
|
||||||
/** 表格和分页撑满剩余可视区域;单据详情展开时贴住详情面板,关闭后恢复 */
|
/** 表格和分页撑满剩余可视区域;单据详情展开时贴住详情面板,关闭后恢复 */
|
||||||
updateListTableHeight() {
|
updateListTableHeight() {
|
||||||
|
this.listTableLayoutToken = (this.listTableLayoutToken || 0) + 1;
|
||||||
|
const token = this.listTableLayoutToken;
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
window.requestAnimationFrame(() => this.applyListTableHeight());
|
window.requestAnimationFrame(() => {
|
||||||
|
if (token !== this.listTableLayoutToken) return;
|
||||||
|
this.applyListTableHeight(token);
|
||||||
|
window.requestAnimationFrame(() => {
|
||||||
|
if (token !== this.listTableLayoutToken) return;
|
||||||
|
this.applyListTableHeight(token);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
applyListTableHeight() {
|
applyListTableHeight(token = this.listTableLayoutToken) {
|
||||||
|
if (token !== this.listTableLayoutToken) return;
|
||||||
const tableElement = this.$refs.mainTableRef?.$el;
|
const tableElement = this.$refs.mainTableRef?.$el;
|
||||||
if (!tableElement) return;
|
if (!tableElement) return;
|
||||||
const pagination = this.$el?.querySelector?.('.settlement-detail-page__pagination');
|
const pagination = this.$el?.querySelector?.(
|
||||||
|
'.settlement-detail-page__table-panel .settlement-detail-page__pagination'
|
||||||
|
);
|
||||||
const paginationHeight = pagination?.offsetHeight || 0;
|
const paginationHeight = pagination?.offsetHeight || 0;
|
||||||
const tableTop = tableElement.getBoundingClientRect().top;
|
const tableTop = tableElement.getBoundingClientRect().top;
|
||||||
const view = this.$el?.closest?.('#avue-view');
|
const view = this.$el?.closest?.('#avue-view');
|
||||||
const viewRect = view?.getBoundingClientRect?.();
|
const viewRect = view?.getBoundingClientRect?.();
|
||||||
let bottomEdge = viewRect ? viewRect.bottom - 12 : window.innerHeight;
|
// 用滚动容器的可视高度,避免表格被压矮后按内容底部把高度锁死
|
||||||
if (this.isBottomPanelOpen) {
|
let bottomEdge = view
|
||||||
|
? viewRect.top + view.clientHeight - 12
|
||||||
|
: window.innerHeight;
|
||||||
|
const panelOpen = this.isBottomPanelOpen;
|
||||||
|
if (panelOpen) {
|
||||||
|
if (!this.savedListTableHeight) {
|
||||||
|
this.savedListTableHeight = this.listTableHeight || null;
|
||||||
|
}
|
||||||
const panelRect = document
|
const panelRect = document
|
||||||
.querySelector('.settlement-detail-page__detail-panel')
|
.querySelector('.settlement-detail-page__detail-panel')
|
||||||
?.getBoundingClientRect?.();
|
?.getBoundingClientRect?.();
|
||||||
if (panelRect && panelRect.height > 0) {
|
if (panelRect && panelRect.height > 0) {
|
||||||
bottomEdge = panelRect.top;
|
bottomEdge = Math.min(bottomEdge, panelRect.top);
|
||||||
this.listTableHeightRetry = 0;
|
this.listTableHeightRetry = 0;
|
||||||
} else if ((this.listTableHeightRetry || 0) < 5) {
|
} else if ((this.listTableHeightRetry || 0) < 5) {
|
||||||
this.listTableHeightRetry = (this.listTableHeightRetry || 0) + 1;
|
this.listTableHeightRetry = (this.listTableHeightRetry || 0) + 1;
|
||||||
window.requestAnimationFrame(() => this.applyListTableHeight());
|
window.requestAnimationFrame(() => this.applyListTableHeight(token));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.listTableHeightRetry = 0;
|
this.listTableHeightRetry = 0;
|
||||||
}
|
}
|
||||||
const gap = this.isBottomPanelOpen ? 8 : 0;
|
const gap = panelOpen ? 8 : 0;
|
||||||
this.listTableHeight = Math.max(180, Math.floor(bottomEdge - tableTop - paginationHeight - gap));
|
let nextHeight = Math.max(180, Math.floor(bottomEdge - tableTop - paginationHeight - gap));
|
||||||
|
if (!panelOpen && this.savedListTableHeight) {
|
||||||
|
if (nextHeight + 24 < this.savedListTableHeight) {
|
||||||
|
nextHeight = this.savedListTableHeight;
|
||||||
|
}
|
||||||
|
this.savedListTableHeight = null;
|
||||||
|
}
|
||||||
|
this.listTableHeight = nextHeight;
|
||||||
this.$nextTick(() => this.$refs.mainTableRef?.doLayout?.());
|
this.$nextTick(() => this.$refs.mainTableRef?.doLayout?.());
|
||||||
},
|
},
|
||||||
contractCategoryName(value) {
|
contractCategoryName(value) {
|
||||||
@@ -1858,11 +1896,13 @@ export default {
|
|||||||
this.changeRecordsDialog.visible = false;
|
this.changeRecordsDialog.visible = false;
|
||||||
this.billingRuleDialog.visible = false;
|
this.billingRuleDialog.visible = false;
|
||||||
this.billingRuleDialog.rule = null;
|
this.billingRuleDialog.rule = null;
|
||||||
|
this.updateListTableHeight();
|
||||||
},
|
},
|
||||||
closeAdjustPanel() {
|
closeAdjustPanel() {
|
||||||
this.clearAdjustCalculations();
|
this.clearAdjustCalculations();
|
||||||
this.adjustDialog.visible = false;
|
this.adjustDialog.visible = false;
|
||||||
this.adjustDialog.row = null;
|
this.adjustDialog.row = null;
|
||||||
|
this.updateListTableHeight();
|
||||||
},
|
},
|
||||||
handleTableLink(row, column) {
|
handleTableLink(row, column) {
|
||||||
if (column.prop === 'waybillNo') {
|
if (column.prop === 'waybillNo') {
|
||||||
@@ -2446,7 +2486,8 @@ export default {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const options = this.syncBillingPlanOptions(contractId);
|
const options = this.syncBillingPlanOptions(contractId);
|
||||||
const selected = options.find(item => item.defaultPlan) || options[0];
|
const defaultOptions = options.filter(item => item.defaultPlan);
|
||||||
|
const selected = defaultOptions[defaultOptions.length - 1] || options[options.length - 1];
|
||||||
this.updateFeeForm.billingPlanId = selected?.id || '';
|
this.updateFeeForm.billingPlanId = selected?.id || '';
|
||||||
},
|
},
|
||||||
async confirmUpdateFee() {
|
async confirmUpdateFee() {
|
||||||
@@ -3333,6 +3374,7 @@ export default {
|
|||||||
|
|
||||||
.settlement-detail-page__adjust-panel-body {
|
.settlement-detail-page__adjust-panel-body {
|
||||||
padding-top: 16px;
|
padding-top: 16px;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.settlement-detail-page__billing-rule-cell {
|
.settlement-detail-page__billing-rule-cell {
|
||||||
|
|||||||
Reference in New Issue
Block a user