✨ 完善付款申请多结算行、结算导入编辑与计费规则展示
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -149,7 +149,7 @@
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<div
|
||||
v-if="isFreightFeeColumn(column)"
|
||||
v-if="isChargeFeeColumn(column)"
|
||||
class="settlement-detail-page__billing-rule-cell"
|
||||
>
|
||||
<span>{{ formatFeeDetailCell(row, column.prop) }}</span>
|
||||
@@ -157,7 +157,7 @@
|
||||
v-if="hasBillingRule(row)"
|
||||
class="settlement-detail-page__billing-rule-icon"
|
||||
title="查看计费规则详情"
|
||||
@click.stop="openBillingRuleInfo(row)"
|
||||
@click.stop="openBillingRuleInfo(row, chargeFeeName(column))"
|
||||
>
|
||||
<InfoFilled />
|
||||
</el-icon>
|
||||
@@ -376,12 +376,14 @@
|
||||
inputmode="decimal"
|
||||
@input="value => handleAdjustDecimalInput(row, 'transportQuantity', value)"
|
||||
/>
|
||||
<span v-else-if="column.prop === 'unitPrice' && isTierUnitPrice(row)">-</span>
|
||||
<el-input
|
||||
v-else-if="column.prop === 'unitPrice'"
|
||||
class="settlement-detail-page__adjust-input"
|
||||
:model-value="row.unitPrice"
|
||||
:disabled="!isUnitPriceAdjustable(row)"
|
||||
inputmode="decimal"
|
||||
@input="value => handleAdjustDecimalInput(row, 'unitPrice', value)"
|
||||
@input="value => handleAdjustDecimalInput(row, 'unitPrice', value, 'unitPrice')"
|
||||
/>
|
||||
<el-input
|
||||
v-else-if="column.prop === 'mileage'"
|
||||
@@ -1034,6 +1036,7 @@ import { getList as getCargoTypeList } from '@/api/base/cargo-type';
|
||||
import { exportBlob } from '@/api/common';
|
||||
import { getDictionary } from '@/api/system/dictbiz';
|
||||
import { formatContractOptionLabel } from '@/utils/contract';
|
||||
import { isTierUnitPrice, isUnitPriceAdjustable } from '@/utils/settlement-adjust-rule';
|
||||
import { downloadXls } from '@/utils/util';
|
||||
import { createSettlementTransfer } from '@/utils/settlement-transfer';
|
||||
import {
|
||||
@@ -1221,7 +1224,9 @@ export default {
|
||||
return this.searchExpanded ? this.searchFields : this.searchFields.slice(0, 4);
|
||||
},
|
||||
feeDetailColumns() {
|
||||
const baseColumns = feeDetailBaseColumns.filter(column => column.prop !== 'freightAmount');
|
||||
const baseColumns = feeDetailBaseColumns.filter(
|
||||
column => !['freightAmount', 'billingFactor', 'billingType'].includes(column.prop)
|
||||
);
|
||||
const hasAdjustment = this.feeRows.some(row => Number(row.adjustAmount || 0) !== 0);
|
||||
const tailColumns = feeDetailTailColumns.map(col =>
|
||||
col.prop === 'originalAmountText' && hasAdjustment
|
||||
@@ -1302,17 +1307,13 @@ export default {
|
||||
this.openRouteDetail();
|
||||
},
|
||||
isBottomPanelOpen() {
|
||||
this.$nextTick(() => this.updateListTableHeight());
|
||||
this.updateListTableHeight();
|
||||
},
|
||||
searchVisible() {
|
||||
if (this.isBottomPanelOpen) {
|
||||
this.$nextTick(() => this.updateListTableHeight());
|
||||
}
|
||||
this.updateListTableHeight();
|
||||
},
|
||||
searchExpanded() {
|
||||
if (this.isBottomPanelOpen) {
|
||||
this.$nextTick(() => this.updateListTableHeight());
|
||||
}
|
||||
this.updateListTableHeight();
|
||||
},
|
||||
displayTableColumns: {
|
||||
immediate: true,
|
||||
@@ -1343,6 +1344,10 @@ export default {
|
||||
this.loadPriceUnitOptions();
|
||||
await this.loadTable();
|
||||
await this.openRouteDetail();
|
||||
this.updateListTableHeight();
|
||||
},
|
||||
activated() {
|
||||
this.updateListTableHeight();
|
||||
},
|
||||
// 标签切走(keep-alive 缓存)或销毁时,收起 teleport/append-to-body 弹层,避免残留盖住其它页面
|
||||
deactivated() {
|
||||
@@ -1353,6 +1358,8 @@ export default {
|
||||
this.closeInnerDialogs();
|
||||
},
|
||||
methods: {
|
||||
isTierUnitPrice,
|
||||
isUnitPriceAdjustable,
|
||||
closeInnerDialogs() {
|
||||
this.closeDetailPanel();
|
||||
this.closeAdjustPanel();
|
||||
@@ -1363,24 +1370,38 @@ export default {
|
||||
this.previewDialog.visible = false;
|
||||
this.generateContractDialog.visible = false;
|
||||
},
|
||||
/** 底部面板打开时按剩余视口压缩主表高度,关闭后恢复自适应 */
|
||||
/** 表格和分页撑满剩余可视区域;单据详情展开时贴住详情面板,关闭后恢复 */
|
||||
updateListTableHeight() {
|
||||
if (!this.isBottomPanelOpen) {
|
||||
this.listTableHeight = undefined;
|
||||
this.$nextTick(() => this.$refs.mainTableRef?.doLayout?.());
|
||||
return;
|
||||
this.$nextTick(() => {
|
||||
window.requestAnimationFrame(() => this.applyListTableHeight());
|
||||
});
|
||||
},
|
||||
applyListTableHeight() {
|
||||
const tableElement = this.$refs.mainTableRef?.$el;
|
||||
if (!tableElement) return;
|
||||
const pagination = this.$el?.querySelector?.('.settlement-detail-page__pagination');
|
||||
const paginationHeight = pagination?.offsetHeight || 0;
|
||||
const tableTop = tableElement.getBoundingClientRect().top;
|
||||
const view = this.$el?.closest?.('#avue-view');
|
||||
const viewRect = view?.getBoundingClientRect?.();
|
||||
let bottomEdge = viewRect ? viewRect.bottom - 12 : window.innerHeight;
|
||||
if (this.isBottomPanelOpen) {
|
||||
const panelRect = document
|
||||
.querySelector('.settlement-detail-page__detail-panel')
|
||||
?.getBoundingClientRect?.();
|
||||
if (panelRect && panelRect.height > 0) {
|
||||
bottomEdge = panelRect.top;
|
||||
this.listTableHeightRetry = 0;
|
||||
} else if ((this.listTableHeightRetry || 0) < 5) {
|
||||
this.listTableHeightRetry = (this.listTableHeightRetry || 0) + 1;
|
||||
window.requestAnimationFrame(() => this.applyListTableHeight());
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
this.listTableHeightRetry = 0;
|
||||
}
|
||||
const tablePanel = this.$el?.querySelector?.('.settlement-detail-page__table-panel');
|
||||
const toolbar = tablePanel?.querySelector('.settlement-detail-page__toolbar');
|
||||
const pagination = tablePanel?.querySelector('.settlement-detail-page__pagination');
|
||||
const panelTop = tablePanel?.getBoundingClientRect?.()?.top ?? 200;
|
||||
const toolbarHeight = toolbar?.offsetHeight ?? 56;
|
||||
const paginationHeight = pagination?.offsetHeight ?? 64;
|
||||
// 与 .settlement-detail-page__detail-panel 的 max-height: min(60vh, 560px) 对齐
|
||||
const bottomPanelHeight = Math.min(window.innerHeight * 0.6, 560);
|
||||
const available =
|
||||
window.innerHeight - panelTop - toolbarHeight - paginationHeight - bottomPanelHeight - 8;
|
||||
this.listTableHeight = Math.max(180, Math.floor(available));
|
||||
const gap = this.isBottomPanelOpen ? 8 : 0;
|
||||
this.listTableHeight = Math.max(180, Math.floor(bottomEdge - tableTop - paginationHeight - gap));
|
||||
this.$nextTick(() => this.$refs.mainTableRef?.doLayout?.());
|
||||
},
|
||||
contractCategoryName(value) {
|
||||
@@ -1982,8 +2003,9 @@ export default {
|
||||
},
|
||||
handleAdjustDecimalInput(row, prop, value, changedField) {
|
||||
row[prop] = this.normalizeAdjustDecimal(value);
|
||||
if (!row.manualFee && row.id && ['transportQuantity', 'mileage'].includes(prop)) {
|
||||
this.scheduleAdjustFeeCalculation(row);
|
||||
if (!row.manualFee && row.id && ['transportQuantity', 'mileage', 'unitPrice'].includes(prop)) {
|
||||
if (prop === 'unitPrice' && !isUnitPriceAdjustable(row)) return;
|
||||
this.scheduleAdjustFeeCalculation(row, prop);
|
||||
return;
|
||||
}
|
||||
this.recalculateAdjustRow(row, changedField);
|
||||
@@ -2010,23 +2032,25 @@ export default {
|
||||
);
|
||||
row.adjustAmount = Number((row.afterAmount - Number(row.originalAmount || 0)).toFixed(2));
|
||||
},
|
||||
scheduleAdjustFeeCalculation(row) {
|
||||
scheduleAdjustFeeCalculation(row, changedField) {
|
||||
if (row.adjustCalculateTimer) clearTimeout(row.adjustCalculateTimer);
|
||||
row.adjustCalculateVersion += 1;
|
||||
row.calculating = true;
|
||||
const version = row.adjustCalculateVersion;
|
||||
row.adjustCalculateTimer = setTimeout(() => {
|
||||
row.adjustCalculateTimer = null;
|
||||
this.calculateAdjustRow(row, version);
|
||||
this.calculateAdjustRow(row, version, changedField);
|
||||
}, 300);
|
||||
},
|
||||
async calculateAdjustRow(row, version) {
|
||||
async calculateAdjustRow(row, version, changedField) {
|
||||
try {
|
||||
const res = await api.calculateAdjustedFee({
|
||||
detailId: this.adjustDialog.row?.id,
|
||||
feeId: row.id,
|
||||
transportQuantity: Number(row.transportQuantity || 0),
|
||||
mileage: Number(row.mileage || 0),
|
||||
unitPrice: row.unitPrice === null || row.unitPrice === '' ? null : Number(row.unitPrice),
|
||||
changedField,
|
||||
freightAmount: Number(row.freightAmount || 0),
|
||||
feeItems: Object.fromEntries(
|
||||
Object.entries(row.feeItems || {}).map(([name, amount]) => [name, Number(amount || 0)])
|
||||
@@ -2034,18 +2058,24 @@ export default {
|
||||
});
|
||||
if (version !== row.adjustCalculateVersion || !this.adjustDialog.visible) return;
|
||||
const data = res.data?.data || res.data || res || {};
|
||||
row.freightAmount = Number(data.freightAmount || 0);
|
||||
row.feeItems = Object.fromEntries(
|
||||
Object.entries(data.feeItems || {}).map(([name, amount]) => [name, Number(amount || 0)])
|
||||
);
|
||||
row.adjustAmount = Number(data.adjustAmount || 0);
|
||||
row.afterAmount = Number(data.afterAmount || 0);
|
||||
this.applyCalculatedAdjustRow(row, data);
|
||||
} catch (error) {
|
||||
row.calculateError = error.message;
|
||||
} finally {
|
||||
if (version === row.adjustCalculateVersion) row.calculating = false;
|
||||
}
|
||||
},
|
||||
applyCalculatedAdjustRow(row, data) {
|
||||
row.freightAmount = Number(data.freightAmount || 0);
|
||||
row.feeItems = Object.fromEntries(
|
||||
Object.entries(data.feeItems || {}).map(([name, amount]) => [name, Number(amount || 0)])
|
||||
);
|
||||
if (!isTierUnitPrice(row) && data.unitPrice !== undefined && data.unitPrice !== null) {
|
||||
row.unitPrice = Number(data.unitPrice);
|
||||
}
|
||||
row.adjustAmount = Number(data.adjustAmount || 0);
|
||||
row.afterAmount = Number(data.afterAmount || 0);
|
||||
},
|
||||
clearAdjustCalculations() {
|
||||
this.adjustRows.forEach(row => {
|
||||
if (row.adjustCalculateTimer) clearTimeout(row.adjustCalculateTimer);
|
||||
@@ -2057,12 +2087,14 @@ export default {
|
||||
isFreightFeeItem(name) {
|
||||
return String(name || '').includes('运费') || String(name || '').includes('运输费');
|
||||
},
|
||||
isFreightFeeColumn(column = {}) {
|
||||
return (
|
||||
column.prop === 'freightAmount' ||
|
||||
this.isFreightFeeItem(column.label) ||
|
||||
this.isFreightFeeItem(column.feeItemName)
|
||||
);
|
||||
isChargeFeeColumn(column = {}) {
|
||||
if (column.prop === 'priceUnit') return false;
|
||||
if (column.prop === 'freightAmount') return true;
|
||||
return String(column.prop || '').startsWith('feeItem');
|
||||
},
|
||||
chargeFeeName(column = {}) {
|
||||
if (column.prop === 'freightAmount') return column.label || '运输费';
|
||||
return column.label || '';
|
||||
},
|
||||
hasBillingRule(row) {
|
||||
const rule = this.normalizeBillingRule(row);
|
||||
@@ -2078,11 +2110,21 @@ export default {
|
||||
rule.matchCondition?.cargoType
|
||||
);
|
||||
},
|
||||
openBillingRuleInfo(row) {
|
||||
this.billingRuleDialog.rule = this.normalizeBillingRule(row);
|
||||
openBillingRuleInfo(row, feeName = '') {
|
||||
this.billingRuleDialog.rule = this.normalizeBillingRule(row, feeName);
|
||||
this.billingRuleDialog.visible = true;
|
||||
},
|
||||
normalizeBillingRule(row) {
|
||||
ruleFeeName(rule = {}) {
|
||||
return String(rule.feeItem || rule.feeItemName || rule.name || '').trim();
|
||||
},
|
||||
feeNamesMatch(left, right) {
|
||||
const source = String(left || '').trim();
|
||||
const target = String(right || '').trim();
|
||||
if (!source || !target) return false;
|
||||
if (source === target) return true;
|
||||
return this.isFreightFeeItem(source) && this.isFreightFeeItem(target);
|
||||
},
|
||||
normalizeBillingRule(row, feeName = '') {
|
||||
const source = row || {};
|
||||
const nested = [
|
||||
source.billingRule,
|
||||
@@ -2095,28 +2137,48 @@ export default {
|
||||
source.rule,
|
||||
].find(value => value !== undefined && value !== null && value !== '');
|
||||
const parsedNested = this.parseJsonValue(nested);
|
||||
const matchedRules = Array.isArray(parsedNested)
|
||||
const parsedRules = Array.isArray(parsedNested)
|
||||
? parsedNested.filter(item => item && typeof item === 'object')
|
||||
: [];
|
||||
let rule = this.parseObject(matchedRules[0] || parsedNested || nested);
|
||||
if (!Object.keys(rule).length && typeof nested === 'string') rule = { name: nested };
|
||||
const requestedFeeName = String(feeName || '').trim();
|
||||
const matchedRules = requestedFeeName
|
||||
? parsedRules.filter(item => this.feeNamesMatch(this.ruleFeeName(item), requestedFeeName))
|
||||
: parsedRules;
|
||||
const sharedRule =
|
||||
parsedRules.length === 1 && !this.ruleFeeName(parsedRules[0]) ? parsedRules[0] : null;
|
||||
const ruleSource =
|
||||
matchedRules[0] || sharedRule || (!requestedFeeName ? parsedNested || nested : null);
|
||||
let rule = this.parseObject(ruleSource);
|
||||
if (!Object.keys(rule).length && typeof nested === 'string' && !requestedFeeName) {
|
||||
rule = { name: nested };
|
||||
}
|
||||
const matchCondition = this.parseObject(
|
||||
source.matchCondition ?? source.billingMatchCondition ?? rule.matchCondition
|
||||
requestedFeeName
|
||||
? (rule.matchCondition ?? source.matchCondition ?? source.billingMatchCondition)
|
||||
: (source.matchCondition ?? source.billingMatchCondition ?? rule.matchCondition)
|
||||
);
|
||||
return {
|
||||
...rule,
|
||||
feeItem: source.billingFeeItem || source.feeItem || rule.feeItem,
|
||||
billingElement:
|
||||
source.billingElement ||
|
||||
source.billingFactor ||
|
||||
rule.billingElement ||
|
||||
rule.billingFactor,
|
||||
billingType: source.billingType || rule.billingType,
|
||||
billingUnit: source.billingUnit || source.priceUnit || rule.billingUnit || rule.priceUnit,
|
||||
unitPrice: source.billingUnitPrice ?? source.unitPrice ?? rule.unitPrice,
|
||||
minimumBillingWeight:
|
||||
source.minimumBillingWeight ?? rule.minimumBillingWeight ?? rule.lowerLimit,
|
||||
remark: source.billingRuleRemark || source.ruleRemark || rule.remark,
|
||||
feeItem: requestedFeeName || source.billingFeeItem || source.feeItem || rule.feeItem,
|
||||
billingElement: requestedFeeName
|
||||
? rule.billingElement || rule.billingFactor || ''
|
||||
: source.billingElement ||
|
||||
source.billingFactor ||
|
||||
rule.billingElement ||
|
||||
rule.billingFactor,
|
||||
billingType: requestedFeeName ? rule.billingType || '' : source.billingType || rule.billingType,
|
||||
billingUnit: requestedFeeName
|
||||
? rule.billingUnit || rule.priceUnit || ''
|
||||
: source.billingUnit || source.priceUnit || rule.billingUnit || rule.priceUnit,
|
||||
unitPrice: requestedFeeName
|
||||
? (rule.unitPrice ?? '')
|
||||
: (source.billingUnitPrice ?? source.unitPrice ?? rule.unitPrice),
|
||||
minimumBillingWeight: requestedFeeName
|
||||
? (rule.minimumBillingWeight ?? rule.lowerLimit ?? '')
|
||||
: (source.minimumBillingWeight ?? rule.minimumBillingWeight ?? rule.lowerLimit),
|
||||
remark: requestedFeeName
|
||||
? rule.remark || ''
|
||||
: source.billingRuleRemark || source.ruleRemark || rule.remark,
|
||||
rules: matchedRules,
|
||||
matchCondition: {
|
||||
...matchCondition,
|
||||
|
||||
Reference in New Issue
Block a user