1、修复基础配置

2、修复业务模块
3、拆分页面代码
This commit is contained in:
2026-09-03 15:59:34 +08:00
parent 40925cb21f
commit 024e77801e
9 changed files with 720 additions and 179 deletions
+56
View File
@@ -0,0 +1,56 @@
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>合同弹窗调试</title>
</head>
<body>
<div id="app"></div>
<script type="module">
import { createApp, nextTick, onMounted, ref } from 'vue/dist/vue.esm-bundler.js';
import ElementPlus from 'element-plus';
import 'element-plus/dist/index.css';
import './src/styles/common.scss';
import ReceivablePayableDetail from './src/views/settlement/receivable-payable-detail.vue';
import { setupTableColumnDrag } from './src/utils/table-column-drag.js';
const fields = [
'contractNo',
'contractName',
'projectName',
'organizationName',
'contractCategory',
'signType',
'partyA',
'partyB',
'startDate',
'endDate',
'temporaryStartDate',
'temporaryEndDate',
];
const row = Object.fromEntries(fields.map(field => [field, `${field}数据`]));
const TestComponent = { ...ReceivablePayableDetail, mounted() {} };
const app = createApp({
components: { TestComponent },
setup() {
const target = ref();
onMounted(async () => {
await nextTick();
target.value.generateContractDialog.rows = Array.from({ length: 10 }, () => ({ ...row }));
target.value.generateContractDialog.page.total = 10;
target.value.generateContractDialog.visible = true;
});
return { target };
},
template: '<test-component ref="target" settlement-type="payable" />',
});
app.component('basic-container', { template: '<div><slot /></div>' });
app.config.globalProperties.$route = { fullPath: '/debug', query: {} };
app.config.globalProperties.$store = { getters: { permission: {}, userInfo: {} } };
app.use(ElementPlus, { table: { showOverflowTooltip: true } });
setupTableColumnDrag(app);
app.mount('#app');
</script>
</body>
</html>
+7
View File
@@ -86,3 +86,10 @@ export const getDictionary = params => {
params, params,
}); });
}; };
export const getDictionaryAll = () => {
return request({
url: '/blade-system/dict-biz/select-all',
method: 'get',
});
};
+27 -6
View File
@@ -30,6 +30,9 @@
{{ row.status === 1 ? '启用' : '停用' }} {{ row.status === 1 ? '启用' : '停用' }}
</el-tag> </el-tag>
</template> </template>
<template #feeCategory="{ row }">
{{ formatFeeCategory(row.feeCategory) }}
</template>
<template #feeCategory-form> <template #feeCategory-form>
<el-select <el-select
v-model="form.feeCategory" v-model="form.feeCategory"
@@ -78,7 +81,7 @@
<script> <script>
import { getList, getDetail, submit, remove, changeStatus } from '@/api/base/fee-item'; import { getList, getDetail, submit, remove, changeStatus } from '@/api/base/fee-item';
import { exportBlob } from '@/api/common'; import { exportBlob } from '@/api/common';
import { getDictionary } from '@/api/system/dictbiz'; import { getDictionaryAll } from '@/api/system/dictbiz';
import { getDeptTree } from '@/api/system/dept'; import { getDeptTree } from '@/api/system/dept';
import { downloadXls } from '@/utils/util'; import { downloadXls } from '@/utils/util';
import { getToken } from '@/utils/auth'; import { getToken } from '@/utils/auth';
@@ -134,10 +137,11 @@ export default {
searchOrder: 3, searchOrder: 3,
filterable: true, filterable: true,
formslot: true, formslot: true,
slot: true,
span: 12, span: 12,
dicData: [], dicData: [],
props: { props: {
label: 'dictValue', label: 'displayLabel',
value: 'dictKey', value: 'dictKey',
}, },
minWidth: 120, minWidth: 120,
@@ -168,7 +172,8 @@ export default {
type: 'textarea', type: 'textarea',
minRows: 2, minRows: 2,
span: 24, span: 24,
hide: true, minWidth: 200,
overHidden: true,
maxlength: 200, maxlength: 200,
showWordLimit: true, showWordLimit: true,
rules: [{ max: 200, message: '备注不能超过200个字', trigger: 'blur' }], rules: [{ max: 200, message: '备注不能超过200个字', trigger: 'blur' }],
@@ -273,10 +278,17 @@ export default {
}, },
methods: { methods: {
initFeeCategoryOptions() { initFeeCategoryOptions() {
getDictionary({ code: 'fee_category' }).then(res => { getDictionaryAll().then(res => {
this.feeCategoryOptions = res.data.data || []; const options = (res.data.data || [])
.filter(item => item.code === 'fee_category' && Number(item.parentId) > 0)
.sort((first, second) => Number(first.sort || 0) - Number(second.sort || 0))
.map(item => ({
...item,
displayLabel: `${item.dictValue}/${item.dictKey}`,
}));
this.feeCategoryOptions = options;
const column = this.findColumn(this.option.column, 'feeCategory'); const column = this.findColumn(this.option.column, 'feeCategory');
column.dicData = this.feeCategoryOptions; column.dicData = options;
}); });
}, },
initDeptTree() { initDeptTree() {
@@ -305,6 +317,15 @@ export default {
); );
return String((option && option.dictKey) || feeCategory); return String((option && option.dictKey) || feeCategory);
}, },
formatFeeCategory(value) {
const feeCategory = String(value || '').trim();
if (!feeCategory) return '';
const option = this.feeCategoryOptions.find(
item =>
String(item.dictKey || '') === feeCategory || String(item.dictValue || '') === feeCategory
);
return option ? `${option.dictValue}/${option.dictKey}` : feeCategory;
},
getFeeItemCodeSuffix(code, feeCategory) { getFeeItemCodeSuffix(code, feeCategory) {
const value = String(code || '').trim(); const value = String(code || '').trim();
const prefix = this.getFeeCategoryPrefix(feeCategory); const prefix = this.getFeeCategoryPrefix(feeCategory);
+131 -38
View File
@@ -93,6 +93,24 @@
<el-radio label="码头">码头</el-radio> <el-radio label="码头">码头</el-radio>
</el-radio-group> </el-radio-group>
</template> </template>
<template #provinceCode-form>
<el-select
v-model="form.provinceCode"
clearable
filterable
:disabled="isProvinceDisabled"
placeholder="请选择"
style="width: 100%"
@change="handleProvinceChange"
>
<el-option
v-for="item in provinceOptions"
:key="item.id"
:label="item.title"
:value="item.id"
/>
</el-select>
</template>
<template #code-form> <template #code-form>
<el-input <el-input
v-if="form.category !== '码头'" v-if="form.category !== '码头'"
@@ -300,6 +318,8 @@ export default {
excelForm: {}, excelForm: {},
portOptions: [], portOptions: [],
countryOptions: [], countryOptions: [],
provinceOptions: [],
provinceOptionCache: {},
cityOptions: [], cityOptions: [],
cityLoading: false, cityLoading: false,
cityOptionCache: {}, cityOptionCache: {},
@@ -436,13 +456,30 @@ export default {
change: ({ value }) => this.handleCountryNameChange(value), change: ({ value }) => this.handleCountryNameChange(value),
rules: [{ required: true, message: '请选择国家', trigger: 'change' }], rules: [{ required: true, message: '请选择国家', trigger: 'change' }],
}, },
{
label: '所属省份',
prop: 'provinceCode',
formslot: true,
hide: true,
span: 12,
order: 88,
placeholder: '请选择',
rules: [{ required: true, message: '请选择所属省份', trigger: 'change' }],
},
{
label: '所属省份',
prop: 'provinceName',
addDisplay: false,
editDisplay: false,
minWidth: 120,
},
{ {
label: '城市', label: '城市',
prop: 'cityCode', prop: 'cityCode',
formslot: true, formslot: true,
hide: true, hide: true,
span: 12, span: 12,
order: 88, order: 87,
placeholder: '请选择', placeholder: '请选择',
rules: [{ required: true, message: '请选择所属城市', trigger: 'change' }], rules: [{ required: true, message: '请选择所属城市', trigger: 'change' }],
}, },
@@ -465,7 +502,7 @@ export default {
type: 'select', type: 'select',
formslot: true, formslot: true,
hide: true, hide: true,
order: 87, order: 86,
props: { props: {
label: 'title', label: 'title',
value: 'id', value: 'id',
@@ -534,7 +571,7 @@ export default {
span: 12, span: 12,
minWidth: 220, minWidth: 220,
overHidden: false, overHidden: false,
order: 86, order: 85,
placeholder: '请输入', placeholder: '请输入',
maxlength: 255, maxlength: 255,
showWordLimit: true, showWordLimit: true,
@@ -667,6 +704,9 @@ export default {
return this.form.category === '码头'; return this.form.category === '码头';
}, },
isCityDisabled() { isCityDisabled() {
return this.isParentRegionLocked || !this.form.provinceCode;
},
isProvinceDisabled() {
return this.isParentRegionLocked || !this.form.countryCode; return this.isParentRegionLocked || !this.form.countryCode;
}, },
isDistrictDisabled() { isDistrictDisabled() {
@@ -755,7 +795,7 @@ export default {
this.form.parentName = ''; this.form.parentName = '';
this.form.countryCode = ''; this.form.countryCode = '';
this.form.country = ''; this.form.country = '';
this.clearCityValue(); this.clearProvinceValue();
}, },
handleCategoryChange(value, reset = true) { handleCategoryChange(value, reset = true) {
const required = value === '码头'; const required = value === '码头';
@@ -779,13 +819,15 @@ export default {
handleCountryChange(value) { handleCountryChange(value) {
const country = this.countryOptions.find(item => item.id === value); const country = this.countryOptions.find(item => item.id === value);
this.form.country = country ? country.title : ''; this.form.country = country ? country.title : '';
this.clearCityValue(); this.clearProvinceValue();
this.loadCityOptions(value); this.loadProvinceOptions(value);
}, },
handleCountryNameChange(value) { handleCountryNameChange(value) {
const country = this.countryOptions.find(item => item.title === value); const country = this.countryOptions.find(item => item.title === value);
this.setProvinceOptions([]);
this.setCityOptions([]);
this.setDistrictOptions([]); this.setDistrictOptions([]);
this.loadCityOptions(country ? country.id : ''); this.loadProvinceOptions(country ? country.id : '');
}, },
syncCountryName(countryCode) { syncCountryName(countryCode) {
const country = this.countryOptions.find(item => item.id === countryCode); const country = this.countryOptions.find(item => item.id === countryCode);
@@ -818,6 +860,18 @@ export default {
})); }));
} }
}, },
setProvinceOptions(provinceOptions) {
this.provinceOptions = provinceOptions;
const provinceCodeColumn = this.findColumn(this.option.column, 'provinceCode');
if (provinceCodeColumn) {
provinceCodeColumn.dicData = provinceOptions;
}
},
clearProvinceValue() {
this.form.provinceCode = undefined;
this.form.provinceName = '';
this.clearCityValue();
},
clearCityValue() { clearCityValue() {
this.form.cityCode = undefined; this.form.cityCode = undefined;
this.form.city = ''; this.form.city = '';
@@ -828,36 +882,38 @@ export default {
this.form.districtName = ''; this.form.districtName = '';
this.form.regionCode = ''; this.form.regionCode = '';
}, },
loadCityOptions(countryCode) { loadProvinceOptions(countryCode) {
if (!countryCode) { if (!countryCode) {
this.setProvinceOptions([]);
return Promise.resolve([]);
}
if (this.provinceOptionCache[countryCode]) {
this.setProvinceOptions(this.provinceOptionCache[countryCode]);
return Promise.resolve(this.provinceOptionCache[countryCode]);
}
return getLazyTree(countryCode).then(res => {
const options = (res.data.data || []).map(this.normalizeRegionOption);
this.provinceOptionCache[countryCode] = options;
this.setProvinceOptions(options);
return options;
});
},
loadCityOptions(provinceCode) {
if (!provinceCode) {
this.setCityOptions([]); this.setCityOptions([]);
return Promise.resolve([]); return Promise.resolve([]);
} }
if (this.cityOptionCache[countryCode]) { if (this.cityOptionCache[provinceCode]) {
this.setCityOptions(this.cityOptionCache[countryCode]); this.setCityOptions(this.cityOptionCache[provinceCode]);
return Promise.resolve(this.cityOptionCache[countryCode]); return Promise.resolve(this.cityOptionCache[provinceCode]);
} }
this.cityLoading = true; this.cityLoading = true;
return getLazyTree(countryCode) return getLazyTree(provinceCode)
.then(res => { .then(res => {
const firstLevelList = (res.data.data || []).map(this.normalizeRegionOption); const options = (res.data.data || []).map(this.normalizeRegionOption);
return Promise.all( this.cityOptionCache[provinceCode] = options;
firstLevelList.map(parent => {
return getLazyTree(parent.id).then(childRes => {
const childList = (childRes.data.data || []).map(this.normalizeRegionOption);
return childList.map(child => ({
...child,
parentCode: child.parentCode || parent.id,
}));
});
})
).then(cityGroups => {
const cityList = cityGroups.flat();
const options = cityList.length > 0 ? cityList : firstLevelList;
this.cityOptionCache[countryCode] = options;
this.setCityOptions(options); this.setCityOptions(options);
return options; return options;
});
}) })
.finally(() => { .finally(() => {
this.cityLoading = false; this.cityLoading = false;
@@ -867,6 +923,12 @@ export default {
const city = this.cityOptions.find(item => item.title === cityName || item.id === regionCode); const city = this.cityOptions.find(item => item.title === cityName || item.id === regionCode);
return city ? city.id : ''; return city ? city.id : '';
}, },
resolveProvinceCode(provinceName) {
const province = this.provinceOptions.find(
item => item.title === provinceName || item.id === provinceName
);
return province ? province.id : '';
},
setDistrictOptions(districtOptions) { setDistrictOptions(districtOptions) {
this.districtOptions = districtOptions; this.districtOptions = districtOptions;
const districtCodeColumn = this.findColumn(this.option.column, 'districtCode'); const districtCodeColumn = this.findColumn(this.option.column, 'districtCode');
@@ -927,6 +989,8 @@ export default {
this.form.terminalCode = terminalCode; this.form.terminalCode = terminalCode;
this.handleTerminalCodeChange(terminalCode); this.handleTerminalCodeChange(terminalCode);
this.form.country = parent.country; this.form.country = parent.country;
this.form.provinceCode = parent.provinceCode;
this.form.provinceName = parent.provinceName;
this.form.city = parent.city; this.form.city = parent.city;
this.form.districtCode = parent.districtCode; this.form.districtCode = parent.districtCode;
this.form.districtName = parent.districtName; this.form.districtName = parent.districtName;
@@ -935,7 +999,12 @@ export default {
.then(countryCode => { .then(countryCode => {
this.form.countryCode = countryCode; this.form.countryCode = countryCode;
this.syncCountryName(countryCode); this.syncCountryName(countryCode);
return this.loadCityOptions(countryCode); return this.loadProvinceOptions(countryCode);
})
.then(() => {
this.form.provinceCode =
String(parent.provinceCode || '') || this.resolveProvinceCode(parent.provinceName);
return this.loadCityOptions(this.form.provinceCode);
}) })
.then(() => { .then(() => {
this.form.cityCode = this.resolveCityCode(parent.city, parent.regionCode); this.form.cityCode = this.resolveCityCode(parent.city, parent.regionCode);
@@ -952,6 +1021,12 @@ export default {
this.clearDistrictValue(); this.clearDistrictValue();
this.loadDistrictOptions(value); this.loadDistrictOptions(value);
}, },
handleProvinceChange(value) {
const province = this.provinceOptions.find(item => item.id === value);
this.form.provinceName = province ? province.title : '';
this.clearCityValue();
this.loadCityOptions(value);
},
handleCityNameChange(value) { handleCityNameChange(value) {
const city = this.cityOptions.find(item => item.title === value); const city = this.cityOptions.find(item => item.title === value);
this.setDistrictOptions([]); this.setDistrictOptions([]);
@@ -988,6 +1063,8 @@ export default {
submitRow.regionCode = String(submitRow.regionCode || '').trim(); submitRow.regionCode = String(submitRow.regionCode || '').trim();
submitRow.districtCode = String(submitRow.districtCode || '').trim(); submitRow.districtCode = String(submitRow.districtCode || '').trim();
submitRow.districtName = String(submitRow.districtName || '').trim(); submitRow.districtName = String(submitRow.districtName || '').trim();
submitRow.provinceCode = String(submitRow.provinceCode || '').trim();
submitRow.provinceName = String(submitRow.provinceName || '').trim();
submitRow.detailAddress = String(submitRow.detailAddress || '').trim(); submitRow.detailAddress = String(submitRow.detailAddress || '').trim();
submitRow.longitude = normalizeCoordinateInput(submitRow.longitude); submitRow.longitude = normalizeCoordinateInput(submitRow.longitude);
submitRow.latitude = normalizeCoordinateInput(submitRow.latitude); submitRow.latitude = normalizeCoordinateInput(submitRow.latitude);
@@ -1021,6 +1098,10 @@ export default {
this.$message.warning('请选择上级港口'); this.$message.warning('请选择上级港口');
return false; return false;
} }
if (isBlank(row.provinceCode)) {
this.$message.warning('请选择所属省份');
return false;
}
if (isBlank(row.regionCode)) { if (isBlank(row.regionCode)) {
this.$message.warning('请选择区县'); this.$message.warning('请选择区县');
return false; return false;
@@ -1153,7 +1234,13 @@ export default {
.then(countryCode => { .then(countryCode => {
this.form.countryCode = countryCode; this.form.countryCode = countryCode;
this.syncCountryName(countryCode); this.syncCountryName(countryCode);
return this.loadCityOptions(countryCode); return this.loadProvinceOptions(countryCode);
})
.then(() => {
this.form.provinceCode =
String(this.form.provinceCode || '') ||
this.resolveProvinceCode(this.form.provinceName);
return this.loadCityOptions(this.form.provinceCode);
}) })
.then(() => { .then(() => {
this.form.cityCode = this.resolveCityCode(this.form.city, this.form.regionCode); this.form.cityCode = this.resolveCityCode(this.form.city, this.form.regionCode);
@@ -1179,7 +1266,13 @@ export default {
.then(countryCode => { .then(countryCode => {
this.form.countryCode = countryCode; this.form.countryCode = countryCode;
this.syncCountryName(countryCode); this.syncCountryName(countryCode);
return this.loadCityOptions(countryCode); return this.loadProvinceOptions(countryCode);
})
.then(() => {
this.form.provinceCode =
String(this.form.provinceCode || '') ||
this.resolveProvinceCode(this.form.provinceName);
return this.loadCityOptions(this.form.provinceCode);
}) })
.then(() => { .then(() => {
this.form.cityCode = this.resolveCityCode(this.form.city, this.form.regionCode); this.form.cityCode = this.resolveCityCode(this.form.city, this.form.regionCode);
@@ -1211,6 +1304,7 @@ export default {
}, },
searchReset() { searchReset() {
this.query = {}; this.query = {};
this.setProvinceOptions([]);
this.setCityOptions([]); this.setCityOptions([]);
this.setDistrictOptions([]); this.setDistrictOptions([]);
this.onLoad(this.page); this.onLoad(this.page);
@@ -1251,14 +1345,10 @@ export default {
}); });
}, },
handleImport() { handleImport() {
openImportDialog( openImportDialog(this, '港口码头主数据', () => {
this,
'港口码头主数据',
() => {
this.loadPortOptions(); this.loadPortOptions();
this.onLoad(this.page, this.query); this.onLoad(this.page, this.query);
} });
);
}, },
handleExport() { handleExport() {
this.$confirm('是否导出港口码头主数据?', '提示', { this.$confirm('是否导出港口码头主数据?', '提示', {
@@ -1271,7 +1361,10 @@ export default {
feedback: true, feedback: true,
}) })
.then(res => { .then(res => {
downloadXls(res.data, `港口码头主数据${this.$dayjs().format('YYYY-MM-DD HH:mm:ss')}.xlsx`); downloadXls(
res.data,
`港口码头主数据${this.$dayjs().format('YYYY-MM-DD HH:mm:ss')}.xlsx`
);
}) })
.finally(() => { .finally(() => {
NProgress.done(); NProgress.done();
+2 -4
View File
@@ -411,9 +411,7 @@ export default {
computed: { computed: {
...mapGetters(['permission']), ...mapGetters(['permission']),
isCountryRegion() { isCountryRegion() {
return ( return Number(this.regionForm.regionLevel) === 0;
Number(this.regionForm.regionLevel) === 0 || this.regionForm.parentCode === this.topCode
);
}, },
isProvinceRegion() { isProvinceRegion() {
return Number(this.regionForm.regionLevel) === 1; return Number(this.regionForm.regionLevel) === 1;
@@ -641,7 +639,7 @@ export default {
() => { () => {
this.initTree(); this.initTree();
}, },
{ timeout: 180000 } { timeout: 300000 }
); );
}, },
syncImportAction() { syncImportAction() {
@@ -110,24 +110,24 @@
<div class="freight-heading"><h3>运费信息</h3></div> <div class="freight-heading"><h3>运费信息</h3></div>
<el-form :model="route" label-position="right" label-width="auto" class="dispatch-form freight-form"> <el-form :model="route" label-position="right" label-width="auto" class="dispatch-form freight-form">
<el-row v-for="(item, index) in route.freightItems" :key="item.sourceIndex" :gutter="16"> <el-row v-for="(group, index) in freightGroups(route)" :key="group.key" :gutter="16">
<el-col :span="6"> <el-col :span="6">
<el-form-item :label="`单价${index + 1}`"> <el-form-item :label="`单价${index + 1}`">
<el-input :model-value="item.unitPrice" inputmode="decimal" placeholder="请输入" @input="value => handleFreightUnitPriceInput(item, value)"> <el-input :model-value="freightGroupUnitPrice(group)" inputmode="decimal" placeholder="请输入" @input="value => handleFreightGroupUnitPriceInput(group, value)" @clear="() => handleFreightGroupUnitPriceInput(group, '')">
<template #append><el-select v-model="item.priceUnit" class="freight-unit-select"><el-option label="元/吨" value="元/吨" /><el-option label="元/件" value="元/件" /><el-option label="元/方" value="元/方" /></el-select></template> <template #append><el-select :model-value="freightGroupPriceUnit(group)" class="freight-unit-select" @change="value => handleFreightGroupPriceUnitChange(group, value)"><el-option label="元/吨" value="元/吨" /><el-option label="元/件" value="元/件" /><el-option label="元/方" value="元/方" /></el-select></template>
</el-input> </el-input>
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="6"> <el-col :span="6">
<el-form-item :label="`数量${index + 1}`"> <el-form-item :label="`数量${index + 1}`">
<el-input :model-value="formatQuantity(item.quantity)" readonly> <el-input :model-value="formatQuantity(freightGroupQuantity(group))" readonly>
<template #append><el-select v-model="item.quantityUnit" class="freight-unit-select" @change="value => handleFreightQuantityUnitChange(route, item, value)"><el-option v-for="unit in quantityUnitOptions" :key="unit" :label="unit" :value="unit" /></el-select></template> <template #append><el-select :model-value="group.quantityUnit" class="freight-unit-select" @change="value => handleFreightGroupQuantityUnitChange(route, group, value)"><el-option v-for="unit in quantityUnitOptions" :key="unit" :label="unit" :value="unit" /></el-select></template>
</el-input> </el-input>
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="6"> <el-col :span="6">
<el-form-item :label="`运费${index + 1}`"> <el-form-item :label="`运费${index + 1}`">
<el-input :model-value="formatAmount(freightAmount(item))" readonly><template #suffix>{{ currencyLabel(route.currency) }}</template></el-input> <el-input :model-value="formatAmount(freightGroupAmount(group))" readonly><template #suffix>{{ currencyLabel(route.currency) }}</template></el-input>
</el-form-item> </el-form-item>
</el-col> </el-col>
</el-row> </el-row>
@@ -360,6 +360,42 @@ export default {
formatAmount(value) { return Number(value || 0).toFixed(2); }, formatAmount(value) { return Number(value || 0).toFixed(2); },
dispatchWeight(route) { return (route.goods || []).reduce((sum, item) => sum + Number(item.dispatchQuantity || 0), 0); }, dispatchWeight(route) { return (route.goods || []).reduce((sum, item) => sum + Number(item.dispatchQuantity || 0), 0); },
freightAmount(item = {}) { return Number(item.unitPrice || 0) * Number(item.quantity || 0); }, freightAmount(item = {}) { return Number(item.unitPrice || 0) * Number(item.quantity || 0); },
freightGroups(route = {}) {
const groups = [];
const groupMap = new Map();
(route.freightItems || []).forEach((item, index) => {
const quantityUnit = String(item.quantityUnit || '').trim();
const key = quantityUnit || `__empty_${index}`;
let group = groupMap.get(key);
if (!group) {
group = { key, quantityUnit, rows: [] };
groupMap.set(key, group);
groups.push(group);
}
group.rows.push(item);
});
return groups;
},
freightGroupQuantity(group = {}) { return (group.rows || []).reduce((sum, item) => sum + Number(item.quantity || 0), 0); },
freightGroupUnitPrice(group = {}) {
const prices = (group.rows || []).map(item => String(item.unitPrice ?? '').trim()).filter(Boolean);
return !prices.length || prices.some(price => price !== prices[0]) ? '' : prices[0];
},
freightGroupPriceUnit(group = {}) { return group.rows?.[0]?.priceUnit || (group.quantityUnit ? `元/${group.quantityUnit}` : ''); },
freightGroupAmount(group = {}) { return (group.rows || []).reduce((sum, item) => sum + this.freightAmount(item), 0); },
handleFreightGroupUnitPriceInput(group, value) {
const [integer = '', decimal = ''] = String(value || '').replace(/[^\d.]/g, '').split('.');
const normalized = decimal || String(value || '').includes('.') ? `${integer}.${decimal.slice(0, 2)}` : integer;
(group.rows || []).forEach(item => { item.unitPrice = normalized; });
},
handleFreightGroupPriceUnitChange(group, value) { (group.rows || []).forEach(item => { item.priceUnit = value || ''; }); },
handleFreightGroupQuantityUnitChange(route, group, value) {
(group.rows || []).forEach(item => {
item.quantityUnit = value || '';
const goods = (route.goods || []).find(row => String(row.sourceIndex) === String(item.sourceIndex));
if (goods) goods.quantityUnit = value || '';
});
},
freightTotal(route) { freightTotal(route) {
const goodsFreight = (route.freightItems || []).reduce( const goodsFreight = (route.freightItems || []).reduce(
(sum, item) => sum + this.freightAmount(item), (sum, item) => sum + this.freightAmount(item),
@@ -1708,18 +1708,20 @@
<div <div
class="transport-plan-page__dispatch-item-grid transport-plan-page__dispatch-item-grid--full" class="transport-plan-page__dispatch-item-grid transport-plan-page__dispatch-item-grid--full"
> >
<template v-for="(cargo, index) in dispatchItemCargoRows" :key="cargo._key || index"> <template v-for="(group, index) in dispatchItemFreightGroups" :key="group.key">
<el-form-item :label="`单价${index + 1}`"> <el-form-item :label="`单价${index + 1}`">
<el-input <el-input
v-model="cargo.unitPrice" :model-value="dispatchFreightGroupUnitPrice(group)"
placeholder="请输入" placeholder="请输入"
@input="value => handleDispatchCargoNumberInput(cargo, 'unitPrice', value)" @input="value => handleDispatchFreightGroupNumberInput(group, value)"
@clear="() => handleDispatchFreightGroupNumberInput(group, '')"
> >
<template #append <template #append
><el-select ><el-select
v-model="cargo.priceUnit" :model-value="dispatchFreightGroupPriceUnit(group)"
class="transport-plan-page__dispatch-unit-select" class="transport-plan-page__dispatch-unit-select"
placeholder="元/吨" placeholder="元/吨"
@change="value => handleDispatchFreightGroupPriceUnitChange(group, value)"
@visible-change="visible => visible && loadTaskFeeUnitOptions()" @visible-change="visible => visible && loadTaskFeeUnitOptions()"
><el-option ><el-option
v-for="item in taskFeeUnitOptions" v-for="item in taskFeeUnitOptions"
@@ -1730,12 +1732,12 @@
</el-input> </el-input>
</el-form-item> </el-form-item>
<el-form-item :label="`数量合计${index + 1}`" <el-form-item :label="`数量合计${index + 1}`"
><el-input :model-value="formatDispatchQuantity(cargo.quantity)" disabled ><el-input :model-value="dispatchFreightGroupQuantity(group)" disabled
><template #append>{{ cargo.quantityUnit || '吨' }}</template></el-input ><template #append>{{ group.quantityUnit || '吨' }}</template></el-input
></el-form-item ></el-form-item
> >
<el-form-item :label="`运费${index + 1}`" <el-form-item :label="`运费${index + 1}`"
><el-input :model-value="dispatchCargoFreightAmount(cargo)" disabled ><el-input :model-value="dispatchFreightGroupAmount(group)" disabled
><template #suffix>{{ dispatchItemFreightCurrencyLabel }}</template></el-input ><template #suffix>{{ dispatchItemFreightCurrencyLabel }}</template></el-input
></el-form-item ></el-form-item
> >
@@ -1786,8 +1788,22 @@
:key=" :key="
item.id || item.customerName || item.carrierName || item.fullName || item.name item.id || item.customerName || item.carrierName || item.fullName || item.name
" "
:label="item.label || item.customerName || item.carrierName || item.fullName || item.name" :label="
:value="dispatchIsCarrierMode ? item.carrierContractId : item.value || item.customerName || item.carrierName || item.fullName || item.name" item.label ||
item.customerName ||
item.carrierName ||
item.fullName ||
item.name
"
:value="
dispatchIsCarrierMode
? item.carrierContractId
: item.value ||
item.customerName ||
item.carrierName ||
item.fullName ||
item.name
"
/> />
</el-select> </el-select>
</el-form-item> </el-form-item>
@@ -1998,8 +2014,18 @@
:key=" :key="
item.id || item.customerName || item.carrierName || item.fullName || item.name item.id || item.customerName || item.carrierName || item.fullName || item.name
" "
:label="item.label || item.customerName || item.carrierName || item.fullName || item.name" :label="
:value="dispatchIsCarrierMode ? item.carrierContractId : item.value || item.customerName || item.carrierName || item.fullName || item.name" item.label || item.customerName || item.carrierName || item.fullName || item.name
"
:value="
dispatchIsCarrierMode
? item.carrierContractId
: item.value ||
item.customerName ||
item.carrierName ||
item.fullName ||
item.name
"
/> />
</el-select> </el-select>
</el-form-item> </el-form-item>
@@ -3448,8 +3474,8 @@ export default {
return Number.isInteger(total) ? String(total) : String(Number(total.toFixed(2))); return Number.isInteger(total) ? String(total) : String(Number(total.toFixed(2)));
} }
const otherFeeTotal = Number(this.dispatchItemForm.otherFeeTotal || 0); const otherFeeTotal = Number(this.dispatchItemForm.otherFeeTotal || 0);
const freightTotal = this.dispatchItemCargoRows.reduce( const freightTotal = this.dispatchItemFreightGroups.reduce(
(total, cargo) => total + Number(this.dispatchCargoFreightAmount(cargo) || 0), (total, group) => total + Number(this.dispatchFreightGroupAmount(group) || 0),
0 0
); );
const hasOtherFee = this.dispatchItemForm.otherFeeTotal !== ''; const hasOtherFee = this.dispatchItemForm.otherFeeTotal !== '';
@@ -3465,8 +3491,8 @@ export default {
const total = quantity * unitPrice; const total = quantity * unitPrice;
return Number.isInteger(total) ? String(total) : String(Number(total.toFixed(2))); return Number.isInteger(total) ? String(total) : String(Number(total.toFixed(2)));
} }
const total = this.dispatchItemCargoRows.reduce( const total = this.dispatchItemFreightGroups.reduce(
(sum, cargo) => sum + Number(this.dispatchCargoFreightAmount(cargo) || 0), (sum, group) => sum + Number(this.dispatchFreightGroupAmount(group) || 0),
0 0
); );
return total return total
@@ -3475,6 +3501,22 @@ export default {
: String(Number(total.toFixed(2))) : String(Number(total.toFixed(2)))
: ''; : '';
}, },
dispatchItemFreightGroups() {
const groups = [];
const groupMap = new Map();
(this.dispatchItemCargoRows || []).forEach((cargo, index) => {
const quantityUnit = String(cargo.quantityUnit || '').trim();
const key = quantityUnit || `__empty_${index}`;
let group = groupMap.get(key);
if (!group) {
group = { key, quantityUnit, rows: [] };
groupMap.set(key, group);
groups.push(group);
}
group.rows.push(cargo);
});
return groups;
},
dispatchItemFreightCurrencyLabel() { dispatchItemFreightCurrencyLabel() {
const value = this.dispatchItemForm.freightCurrency || 'RMB'; const value = this.dispatchItemForm.freightCurrency || 'RMB';
return this.currencyRemark(value); return this.currencyRemark(value);
@@ -7321,7 +7363,8 @@ export default {
if (carrierTypeAtRequest === '承运商') { if (carrierTypeAtRequest === '承运商') {
this.dispatchCarrierOptions = carrierContracts || []; this.dispatchCarrierOptions = carrierContracts || [];
const current = this.dispatchCarrierOptions.find( const current = this.dispatchCarrierOptions.find(
item => String(item.carrierContractId) === String(this.dispatchItemForm.carrierContractId) item =>
String(item.carrierContractId) === String(this.dispatchItemForm.carrierContractId)
); );
if (current) { if (current) {
this.dispatchItemForm.carrierName = current.carrierName; this.dispatchItemForm.carrierName = current.carrierName;
@@ -7446,13 +7489,47 @@ export default {
const amount = quantity * unitPrice; const amount = quantity * unitPrice;
return Number.isInteger(amount) ? String(amount) : String(Number(amount.toFixed(2))); return Number.isInteger(amount) ? String(amount) : String(Number(amount.toFixed(2)));
}, },
dispatchFreightGroupQuantity(group = {}) {
const quantity = (group.rows || []).reduce(
(sum, cargo) => sum + Number(cargo.quantity || 0),
0
);
return this.formatDispatchQuantity(quantity);
},
dispatchFreightGroupUnitPrice(group = {}) {
const prices = (group.rows || [])
.map(cargo => String(cargo.unitPrice ?? '').trim())
.filter(Boolean);
if (!prices.length || prices.some(price => price !== prices[0])) return '';
return prices[0];
},
dispatchFreightGroupPriceUnit(group = {}) {
return group.rows?.[0]?.priceUnit || (group.quantityUnit ? `元/${group.quantityUnit}` : '');
},
dispatchFreightGroupAmount(group = {}) {
const amount = (group.rows || []).reduce(
(sum, cargo) => sum + Number(this.dispatchCargoFreightAmount(cargo) || 0),
0
);
return Number.isInteger(amount) ? String(amount) : String(Number(amount.toFixed(2)));
},
handleDispatchFreightGroupNumberInput(group, value) {
this.handleFreightNumberInput(group.rows?.[0] || {}, 'unitPrice', value);
const unitPrice = group.rows?.[0]?.unitPrice || '';
(group.rows || []).forEach(cargo => {
cargo.unitPrice = unitPrice;
});
},
handleDispatchFreightGroupPriceUnitChange(group, value) {
(group.rows || []).forEach(cargo => {
cargo.priceUnit = value || '';
});
},
dispatchItemRemainingQuantity(row = {}) { dispatchItemRemainingQuantity(row = {}) {
const unit = this.getDispatchQuantityUnit(row); const unit = this.getDispatchQuantityUnit(row);
const hasCargoIdentity = Boolean( const hasCargoIdentity = Boolean(
String(row.cargoName || row.goodsName || row.name || '').trim() || String(row.cargoName || row.goodsName || row.name || '').trim() ||
String( String(row.cargoType || row.secondCargoTypeName || row.goodsType || row.type || '').trim()
row.cargoType || row.secondCargoTypeName || row.goodsType || row.type || ''
).trim()
); );
const planGoodsRows = this.dispatchPlanGoodsRows; const planGoodsRows = this.dispatchPlanGoodsRows;
const cargoKey = this.getDispatchCargoIdentity(row); const cargoKey = this.getDispatchCargoIdentity(row);
@@ -7472,10 +7549,7 @@ export default {
0 0
); );
const assigned = this.dispatchRows.reduce((sum, dispatchRow, index) => { const assigned = this.dispatchRows.reduce((sum, dispatchRow, index) => {
if ( if (index === this.dispatchItemIndex || !this.hasDispatchVehicleIdentifier(dispatchRow)) {
index === this.dispatchItemIndex ||
!this.hasDispatchVehicleIdentifier(dispatchRow)
) {
return sum; return sum;
} }
return ( return (
@@ -1041,21 +1041,25 @@
label-width="auto" label-width="auto"
class="waybill-manage-page__freight-form waybill-manage-page__freight-form--road" class="waybill-manage-page__freight-form waybill-manage-page__freight-form--road"
> >
<template v-for="(cargo, index) in transportCargoRows" :key="index"> <template v-for="(group, index) in taskFreightGroups" :key="group.key">
<el-form-item :label="`单价${index + 1}`"> <el-form-item :label="`单价${index + 1}`">
<el-input <el-input
v-model="cargo.unitPrice" :model-value="taskFullFreightGroupUnitPrice(group)"
placeholder="请输入" placeholder="请输入"
clearable clearable
:disabled="dialogReadonly" :disabled="dialogReadonly"
@input="value => handleTaskFullFreightNumberInput(cargo, 'unitPrice', value)" @input="
value => handleTaskFullFreightGroupNumberInput(group, 'unitPrice', value)
"
@clear="() => handleTaskFullFreightGroupNumberInput(group, 'unitPrice', '')"
> >
<template #append> <template #append>
<el-select <el-select
v-model="cargo.priceUnit" :model-value="taskFullFreightGroupPriceUnit(group)"
class="waybill-manage-page__append-select waybill-manage-page__append-select--wide" class="waybill-manage-page__append-select waybill-manage-page__append-select--wide"
placeholder="单位" placeholder="单位"
:disabled="dialogReadonly" :disabled="dialogReadonly"
@change="value => handleTaskFullFreightGroupPriceUnitChange(group, value)"
@visible-change="visible => visible && loadTaskFeeUnitOptions()" @visible-change="visible => visible && loadTaskFeeUnitOptions()"
> >
<el-option <el-option
@@ -1069,17 +1073,17 @@
</el-input> </el-input>
</el-form-item> </el-form-item>
<el-form-item :label="`数量合计${index + 1}`"> <el-form-item :label="`数量合计${index + 1}`">
<el-input :model-value="taskFullFreightQuantity(index)" disabled> <el-input :model-value="taskFullFreightGroupQuantity(group)" disabled>
<template #append> <template #append>
<el-select <el-select
:model-value="cargo.quantityUnit" :model-value="group.quantityUnit"
class="waybill-manage-page__append-select waybill-manage-page__append-select--wide" class="waybill-manage-page__append-select waybill-manage-page__append-select--wide"
placeholder="单位" placeholder="单位"
disabled disabled
> >
<el-option <el-option
:label="cargo.quantityUnit || '单位'" :label="group.quantityUnit || '单位'"
:value="cargo.quantityUnit || ''" :value="group.quantityUnit || ''"
/> />
</el-select> </el-select>
</template> </template>
@@ -1087,10 +1091,11 @@
</el-form-item> </el-form-item>
<el-form-item :label="`运费${index + 1}`"> <el-form-item :label="`运费${index + 1}`">
<el-input <el-input
:model-value="taskFullFreightAmount(cargo)" :model-value="taskFullFreightGroupAmount(group)"
placeholder="请输入运费" placeholder="请输入运费"
:disabled="dialogReadonly" :disabled="dialogReadonly"
@input="value => handleTaskFullFreightAmountInput(cargo, value)" @input="value => handleTaskFullFreightGroupAmountInput(group, value)"
@clear="() => handleTaskFullFreightGroupAmountInput(group, '')"
> >
<template #suffix>{{ taskFreightCurrencyLabel }}</template> <template #suffix>{{ taskFreightCurrencyLabel }}</template>
</el-input> </el-input>
@@ -1520,11 +1525,7 @@
<span> <span>
运费合计<strong>{{ formatFooterFreightTotal }}</strong> 运费合计<strong>{{ formatFooterFreightTotal }}</strong>
</span> </span>
<el-link <el-link v-if="hasProjectProcessConfig" type="primary" @click="openWaybillProcessConfig">
v-if="hasProjectProcessConfig"
type="primary"
@click="openWaybillProcessConfig"
>
查看过程配置 查看过程配置
</el-link> </el-link>
</div> </div>
@@ -3362,9 +3363,25 @@ export default {
const amount = quantity * unitPrice; const amount = quantity * unitPrice;
return Number.isInteger(amount) ? String(amount) : String(Number(amount.toFixed(2))); return Number.isInteger(amount) ? String(amount) : String(Number(amount.toFixed(2)));
}, },
taskFreightGroups() {
const groups = [];
const groupMap = new Map();
(this.transportCargoRows || []).forEach((cargo, index) => {
const quantityUnit = String(cargo.quantityUnit || '').trim();
const key = quantityUnit || `__empty_${index}`;
let group = groupMap.get(key);
if (!group) {
group = { key, quantityUnit, rows: [] };
groupMap.set(key, group);
groups.push(group);
}
group.rows.push(cargo);
});
return groups;
},
taskFullFreightTotal() { taskFullFreightTotal() {
const total = this.transportCargoRows.reduce( const total = this.taskFreightGroups.reduce(
(sum, cargo) => sum + Number(this.taskFullFreightAmount(cargo) || 0), (sum, group) => sum + Number(this.taskFullFreightGroupAmount(group) || 0),
0 0
); );
return this.formatTaskFullFreightNumber(total); return this.formatTaskFullFreightNumber(total);
@@ -3852,7 +3869,8 @@ export default {
fillCustomerNameFromProject(projectId = this.form.projectId) { fillCustomerNameFromProject(projectId = this.form.projectId) {
if (this.form.customerName || !projectId) return Promise.resolve(this.form.customerName); if (this.form.customerName || !projectId) return Promise.resolve(this.form.customerName);
const project = this.projectOptions.find(item => String(item.id) === String(projectId)); const project = this.projectOptions.find(item => String(item.id) === String(projectId));
const customerName = project?.customerNames || project?.customerName || project?.customer || ''; const customerName =
project?.customerNames || project?.customerName || project?.customer || '';
if (customerName) { if (customerName) {
this.form.customerName = customerName; this.form.customerName = customerName;
return Promise.resolve(customerName); return Promise.resolve(customerName);
@@ -5253,6 +5271,69 @@ export default {
Number(cargo.unitPrice || 0) * Number(cargo.quantity || 0) Number(cargo.unitPrice || 0) * Number(cargo.quantity || 0)
); );
}, },
taskFullFreightGroupQuantity(group = {}) {
const total = (group.rows || []).reduce((sum, cargo) => sum + Number(cargo.quantity || 0), 0);
return this.formatTaskFullFreightNumber(total);
},
taskFullFreightGroupUnitPrice(group = {}) {
const prices = (group.rows || [])
.map(cargo => String(cargo.unitPrice ?? '').trim())
.filter(Boolean);
if (!prices.length || prices.some(price => price !== prices[0])) return '';
return prices[0];
},
taskFullFreightGroupPriceUnit(group = {}) {
return group.rows?.[0]?.priceUnit || (group.quantityUnit ? `元/${group.quantityUnit}` : '');
},
taskFullFreightGroupAmount(group = {}) {
const total = (group.rows || []).reduce(
(sum, cargo) => sum + Number(this.taskFullFreightAmount(cargo) || 0),
0
);
return this.formatTaskFullFreightNumber(total);
},
handleTaskFullFreightGroupNumberInput(group, prop, value) {
const text = String(value || '').replace(/[^\d.]/g, '');
const parts = text.split('.');
const normalized =
parts.length > 1 ? `${parts[0]}.${parts.slice(1).join('').slice(0, 2)}` : parts[0];
(group.rows || []).forEach(cargo => {
cargo[prop] = normalized;
});
this.syncTransportCargoJson();
},
handleTaskFullFreightGroupPriceUnitChange(group, value) {
(group.rows || []).forEach(cargo => {
cargo.priceUnit = value || '';
});
this.syncTransportCargoJson();
},
handleTaskFullFreightGroupAmountInput(group, value) {
const text = String(value || '').replace(/[^\d.]/g, '');
const parts = text.split('.');
const normalized =
parts.length > 1 ? `${parts[0]}.${parts.slice(1).join('').slice(0, 2)}` : parts[0];
const rows = group.rows || [];
const total = Number(normalized || 0);
const quantities = rows.map(cargo => Math.max(0, Number(cargo.quantity || 0)));
const quantityTotal = quantities.reduce((sum, quantity) => sum + quantity, 0);
let allocated = 0;
rows.forEach((cargo, index) => {
if (index === rows.length - 1) {
cargo.freightAmount = this.formatTaskFullFreightNumber(total - allocated);
return;
}
let amount = 0;
if (quantityTotal) {
amount = Number((total * (quantities[index] / quantityTotal)).toFixed(2));
} else if (index === 0) {
amount = total;
}
allocated += amount;
cargo.freightAmount = this.formatTaskFullFreightNumber(amount);
});
this.syncTransportCargoJson();
},
formatTaskFullFreightNumber(value) { formatTaskFullFreightNumber(value) {
if (value === undefined || value === null || String(value).trim() === '') return ''; if (value === undefined || value === null || String(value).trim() === '') return '';
const number = Number(value || 0); const number = Number(value || 0);
@@ -438,20 +438,16 @@
class="settlement-detail-page__dialog-form" class="settlement-detail-page__dialog-form"
> >
<el-form-item label="更新范围" prop="contractId"> <el-form-item label="更新范围" prop="contractId">
<el-select <el-input
v-model="updateFeeForm.contractId" :model-value="updateContractName"
clearable class="settlement-detail-page__generate-select"
filterable readonly
placeholder="请选择合同" placeholder="请选择合同"
@change="handleUpdateContractChange"
> >
<el-option <template #append>
v-for="item in updateContractOptions" <el-button @click="openUpdateContractDialog">选择</el-button>
:key="item.id" </template>
:label="item.contractName" </el-input>
:value="item.id"
/>
</el-select>
</el-form-item> </el-form-item>
<el-form-item label="合同计费方案" prop="billingPlanId"> <el-form-item label="合同计费方案" prop="billingPlanId">
<el-select <el-select
@@ -817,35 +813,112 @@
ref="generateContractTable" ref="generateContractTable"
v-loading="generateContractDialog.loading" v-loading="generateContractDialog.loading"
:data="generateContractDialog.rows" :data="generateContractDialog.rows"
style="width: 100%"
border border
highlight-current-row highlight-current-row
@current-change="generateContractDialog.current = $event" @current-change="generateContractDialog.current = $event"
@row-dblclick="selectGenerateContract" @row-dblclick="selectContract"
> >
<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 contractSelectionColumns" prop="contractNo"
:key="column.prop" label="合同编号"
:prop="column.prop" min-width="180"
:label="column.label"
:min-width="column.minWidth"
align="center" align="center"
show-overflow-tooltip show-overflow-tooltip
> :formatter="formatContractSelectionCell"
<template #default="{ row }"> />
<span v-if="column.prop === 'organizationName'">{{ <el-table-column
row.organizationName || row.deptName || '-' prop="contractName"
}}</span> label="合同名称"
<span v-else-if="column.prop === 'contractCategory'">{{ min-width="220"
contractCategoryName(row.contractCategory) align="center"
}}</span> show-overflow-tooltip
<span v-else-if="column.prop === 'signType'">{{ signTypeName(row.signType) }}</span> :formatter="formatContractSelectionCell"
<span v-else>{{ displayValue(row[column.prop]) }}</span> />
</template> <el-table-column
</el-table-column> prop="projectName"
label="所属项目"
min-width="170"
align="center"
show-overflow-tooltip
:formatter="formatContractSelectionCell"
/>
<el-table-column
prop="organizationName"
label="所属组织"
min-width="170"
align="center"
show-overflow-tooltip
:formatter="formatContractSelectionCell"
/>
<el-table-column
prop="contractCategory"
label="合同类别"
min-width="130"
align="center"
show-overflow-tooltip
:formatter="formatContractSelectionCell"
/>
<el-table-column
prop="signType"
label="签约类型"
min-width="120"
align="center"
show-overflow-tooltip
:formatter="formatContractSelectionCell"
/>
<el-table-column
prop="partyA"
label="甲方"
min-width="170"
align="center"
show-overflow-tooltip
:formatter="formatContractSelectionCell"
/>
<el-table-column
prop="partyB"
label="乙方"
min-width="170"
align="center"
show-overflow-tooltip
:formatter="formatContractSelectionCell"
/>
<el-table-column
prop="startDate"
label="开始日期"
min-width="130"
align="center"
show-overflow-tooltip
:formatter="formatContractSelectionCell"
/>
<el-table-column
prop="endDate"
label="结束日期"
min-width="130"
align="center"
show-overflow-tooltip
:formatter="formatContractSelectionCell"
/>
<el-table-column
prop="temporaryStartDate"
label="临时效力起"
min-width="130"
align="center"
show-overflow-tooltip
:formatter="formatContractSelectionCell"
/>
<el-table-column
prop="temporaryEndDate"
label="临时效力止"
min-width="130"
align="center"
show-overflow-tooltip
:formatter="formatContractSelectionCell"
/>
<el-table-column label="操作" width="90" fixed="right" align="center"> <el-table-column label="操作" width="90" fixed="right" align="center">
<template #default="{ row }"> <template #default="{ row }">
<el-link type="primary" @click.stop="selectGenerateContract(row)">选择</el-link> <el-link type="primary" @click.stop="selectContract(row)">选择</el-link>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
@@ -862,7 +935,7 @@
</div> </div>
<template #footer> <template #footer>
<el-button @click="generateContractDialog.visible = false">取消</el-button> <el-button @click="generateContractDialog.visible = false">取消</el-button>
<el-button type="primary" @click="selectGenerateContract(generateContractDialog.current)"> <el-button type="primary" @click="selectContract(generateContractDialog.current)">
确定 确定
</el-button> </el-button>
</template> </template>
@@ -900,21 +973,6 @@ import {
signTypeOptions, signTypeOptions,
} from '@/option/business/common'; } from '@/option/business/common';
const contractSelectionColumns = [
{ prop: 'contractNo', label: '合同编号', minWidth: 180 },
{ prop: 'contractName', label: '合同名称', minWidth: 220 },
{ prop: 'projectName', label: '所属项目', minWidth: 170 },
{ prop: 'organizationName', label: '所属组织', minWidth: 170 },
{ prop: 'contractCategory', label: '合同类别', minWidth: 130 },
{ prop: 'signType', label: '签约类型', minWidth: 120 },
{ prop: 'partyA', label: '甲方', minWidth: 170 },
{ prop: 'partyB', label: '乙方', minWidth: 170 },
{ prop: 'startDate', label: '开始日期', minWidth: 130 },
{ prop: 'endDate', label: '结束日期', minWidth: 130 },
{ prop: 'temporaryStartDate', label: '临时效力起', minWidth: 130 },
{ prop: 'temporaryEndDate', label: '临时效力止', minWidth: 130 },
];
const ADJUST_BILLING_ELEMENTS = [ const ADJUST_BILLING_ELEMENTS = [
'按重量', '按重量',
'按体积', '按体积',
@@ -1008,7 +1066,6 @@ export default {
effectiveTypeOptions, effectiveTypeOptions,
contractStageOptions, contractStageOptions,
contractApprovalStatusOptions, contractApprovalStatusOptions,
contractSelectionColumns,
transportTypeOptions: [], transportTypeOptions: [],
billingPlanOptions: [], billingPlanOptions: [],
transferDialog: { visible: false, loading: false, submitting: false }, transferDialog: { visible: false, loading: false, submitting: false },
@@ -1019,6 +1076,7 @@ export default {
transferPage: { current: 1, size: 10, total: 0 }, transferPage: { current: 1, size: 10, total: 0 },
generateDialog: { visible: false, loading: false }, generateDialog: { visible: false, loading: false },
generateQuery: {}, generateQuery: {},
contractDialogMode: 'generate',
generateContractDialog: { generateContractDialog: {
visible: false, visible: false,
loading: false, loading: false,
@@ -1053,24 +1111,16 @@ export default {
); );
return contract?.contractName || ''; return contract?.contractName || '';
}, },
updateContractName() {
const contractId = this.updateFeeForm.contractId;
const contract = [...this.updateContractOptions, ...this.contractOptions].find(
item => String(item.id) === String(contractId)
);
return contract?.contractName || '';
},
generateContractCategory() { generateContractCategory() {
return this.settlementType === 'payable' ? '承运商合同' : '客户合同'; return this.settlementType === 'payable' ? '承运商合同' : '客户合同';
}, },
contractCategoryName(value) {
return (
this.contractCategoryOptions.find(item => String(item.value) === String(value))?.label ||
this.formatCell(value)
);
},
signTypeName(value) {
return (
this.signTypeOptions.find(item => String(item.value) === String(value))?.label ||
this.formatCell(value)
);
},
displayValue(value) {
return this.formatCell(value);
},
settlementTypeLabel() { settlementTypeLabel() {
if (this.settlementType === 'receivable') return '应收'; if (this.settlementType === 'receivable') return '应收';
if (this.settlementType === 'payable') return '应付'; if (this.settlementType === 'payable') return '应付';
@@ -1158,6 +1208,21 @@ export default {
this.clearAdjustCalculations(); this.clearAdjustCalculations();
}, },
methods: { methods: {
contractCategoryName(value) {
return (
this.contractCategoryOptions.find(item => String(item.value) === String(value))?.label ||
this.formatCell(value)
);
},
signTypeName(value) {
return (
this.signTypeOptions.find(item => String(item.value) === String(value))?.label ||
this.formatCell(value)
);
},
displayValue(value) {
return this.formatCell(value);
},
async loadTransportTypeOptions() { async loadTransportTypeOptions() {
const res = await getDictionary({ code: 'transport_type' }); const res = await getDictionary({ code: 'transport_type' });
const records = res.data?.data || []; const records = res.data?.data || [];
@@ -1435,6 +1500,26 @@ export default {
} }
}, },
openGenerateContractDialog() { openGenerateContractDialog() {
this.contractDialogMode = 'generate';
this.generateContractDialog.visible = true;
this.generateContractDialog.expanded = false;
this.generateContractDialog.current = null;
this.generateContractDialog.page.current = 1;
this.loadGenerateContracts();
},
openUpdateContractDialog() {
this.contractDialogMode = 'update';
this.generateContractDialog.query = {
contractNo: '',
contractName: '',
projectName: '',
organizationName: '',
contractCategory: '',
signType: '',
effectiveType: '',
contractStage: '',
approvalStatus: '',
};
this.generateContractDialog.visible = true; this.generateContractDialog.visible = true;
this.generateContractDialog.expanded = false; this.generateContractDialog.expanded = false;
this.generateContractDialog.current = null; this.generateContractDialog.current = null;
@@ -1456,15 +1541,29 @@ export default {
const data = this.unwrapPage(res); const data = this.unwrapPage(res);
this.generateContractDialog.rows = data.records || []; this.generateContractDialog.rows = data.records || [];
this.generateContractDialog.page.total = Number(data.total || 0); this.generateContractDialog.page.total = Number(data.total || 0);
this.layoutGenerateContractTable();
} finally { } finally {
this.generateContractDialog.loading = false; this.generateContractDialog.loading = false;
this.layoutGenerateContractTable();
} }
}, },
layoutGenerateContractTable() { layoutGenerateContractTable() {
this.$nextTick(() => { this.$nextTick(() => {
window.requestAnimationFrame(() => {
this.$refs.generateContractTable?.doLayout?.(); this.$refs.generateContractTable?.doLayout?.();
}); });
});
},
formatContractSelectionCell(row, column, cellValue) {
if (column.property === 'organizationName') {
return this.displayValue(row.organizationName || row.deptName);
}
if (column.property === 'contractCategory') {
return this.contractCategoryName(row.contractCategory);
}
if (column.property === 'signType') {
return this.signTypeName(row.signType);
}
return this.displayValue(cellValue);
}, },
searchGenerateContracts() { searchGenerateContracts() {
this.generateContractDialog.page.current = 1; this.generateContractDialog.page.current = 1;
@@ -1488,7 +1587,7 @@ export default {
this.generateContractDialog.page.current = 1; this.generateContractDialog.page.current = 1;
this.loadGenerateContracts(); this.loadGenerateContracts();
}, },
selectGenerateContract(row) { selectContract(row) {
if (!row) { if (!row) {
this.$message.warning('请选择合同'); this.$message.warning('请选择合同');
return; return;
@@ -1500,11 +1599,18 @@ export default {
partyA: row.partyA || row.payerName, partyA: row.partyA || row.payerName,
partyB: row.partyB || row.payeeName, partyB: row.partyB || row.payeeName,
}; };
const index = this.contractOptions.findIndex(item => String(item.id) === String(contract.id)); const targetOptions =
if (index >= 0) this.contractOptions.splice(index, 1, contract); this.contractDialogMode === 'update' ? this.updateContractOptions : this.contractOptions;
else this.contractOptions.push(contract); const index = targetOptions.findIndex(item => String(item.id) === String(contract.id));
if (index >= 0) targetOptions.splice(index, 1, contract);
else targetOptions.push(contract);
if (this.contractDialogMode === 'update') {
this.updateFeeForm.contractId = contract.id;
this.handleUpdateContractChange(contract.id);
} else {
this.generateQuery.contractId = contract.id; this.generateQuery.contractId = contract.id;
this.handleGenerateContractChange(contract.id); this.handleGenerateContractChange(contract.id);
}
this.generateContractDialog.visible = false; this.generateContractDialog.visible = false;
this.generateContractDialog.current = null; this.generateContractDialog.current = null;
}, },
@@ -1918,22 +2024,30 @@ export default {
this.changePage.current = 1; this.changePage.current = 1;
this.loadChangeRecords(); this.loadChangeRecords();
}, },
async openUpdateFeeDialog(row) { openUpdateFeeDialog(row) {
await this.loadUpdateFeeContracts();
this.updateFeeDialog = { ...this.updateFeeDialog, visible: true, row: row || null }; this.updateFeeDialog = { ...this.updateFeeDialog, visible: true, row: row || null };
this.updateFeeForm = { this.updateFeeForm = {
settlementType: this.settlementType || undefined, settlementType: this.settlementType || undefined,
contractId: row?.contractId || '', contractId: row?.contractId || '',
billingPlanId: '', billingPlanId: '',
}; };
if (row?.contractId) {
const contract = {
...row,
id: row.contractId,
deptId: row.deptId || row.organizationId,
deptName: row.deptName || row.organizationName,
partyA: row.partyA || row.payerName,
partyB: row.partyB || row.payeeName,
};
const index = this.updateContractOptions.findIndex(
item => String(item.id) === String(contract.id)
);
if (index >= 0) this.updateContractOptions.splice(index, 1, contract);
else this.updateContractOptions.push(contract);
}
this.handleUpdateContractChange(this.updateFeeForm.contractId); this.handleUpdateContractChange(this.updateFeeForm.contractId);
}, },
async loadUpdateFeeContracts() {
const res = await api.getUpdateFeeContracts({
settlementType: this.settlementType || undefined,
});
this.updateContractOptions = res.data?.data || [];
},
handleUpdateContractChange(contractId) { handleUpdateContractChange(contractId) {
this.updateFeeForm.billingPlanId = ''; this.updateFeeForm.billingPlanId = '';
if (!contractId) { if (!contractId) {
@@ -2392,7 +2506,7 @@ export default {
const currency = row.currency || 'RMB'; const currency = row.currency || 'RMB';
return { return {
...row, ...row,
unitPriceText: this.money(row.unitPrice, currency), unitPriceText: this.hasMultipleCargo(row) ? '-' : this.money(row.unitPrice, currency),
freightAmountText: this.money(row.freightAmount, currency), freightAmountText: this.money(row.freightAmount, currency),
otherFeeAmountText: this.money(row.otherFeeAmount, currency), otherFeeAmountText: this.money(row.otherFeeAmount, currency),
totalAmountText: this.money(row.totalAmount, currency), totalAmountText: this.money(row.totalAmount, currency),
@@ -2402,6 +2516,67 @@ export default {
'-', '-',
}; };
}, },
parseCargoRows(value) {
if (value === null || value === undefined || value === '') return [];
if (Array.isArray(value)) return value;
if (typeof value === 'object') return [value];
try {
const parsed = JSON.parse(value);
if (Array.isArray(parsed)) return parsed;
return parsed && typeof parsed === 'object' ? [parsed] : [];
} catch (error) {
return [];
}
},
hasMultipleCargo(row = {}) {
const countFields = [
'cargoCount',
'goodsCount',
'cargoNum',
'goodsNum',
'cargoNumber',
'goodsNumber',
'cargoSize',
'goodsSize',
'cargoItemCount',
'goodsItemCount',
];
if (
countFields.some(field => {
const count = Number(row[field]);
return Number.isFinite(count) && count > 1;
})
) {
return true;
}
const cargoSources = [
row.goodsJson,
row.goodsList,
row.goodsRows,
row.cargoList,
row.cargoRows,
row.goods,
row.cargoDetails,
row.cargoItems,
row.cargoNames,
row.goodsNames,
];
if (cargoSources.some(source => this.parseCargoRows(source).length > 1)) {
return true;
}
const summaryValues = [row.cargoName, row.cargoInfo, row.goodsInfo];
if (summaryValues.some(value => Array.isArray(value) && value.length > 1)) return true;
const summaryText = summaryValues
.filter(value => value !== null && value !== undefined)
.map(value => String(value))
.join('');
return (
/等\s*\d+\s*[种个]\s*货物/.test(summaryText) ||
summaryText.split(/[、,,;]/).filter(Boolean).length > 1
);
},
collectFeeItemNames(rows) { collectFeeItemNames(rows) {
const names = []; const names = [];
(rows || []).forEach(row => { (rows || []).forEach(row => {