1、修复基础配置

2、修复业务模块
3、拆分页面代码
This commit is contained in:
2026-09-03 03:31:30 +08:00
parent 008ec7a6a8
commit 6f0f1842cb
25 changed files with 22346 additions and 192 deletions
@@ -134,7 +134,6 @@
<el-row v-if="route.freightItems.length" :gutter="16">
<el-col :span="6"><el-form-item label="运费合计"><el-input :model-value="formatAmount(freightTotal(route))" readonly><template #suffix>{{ currencyLabel(route.currency) }}</template></el-input></el-form-item></el-col>
<el-col :span="6"><el-form-item label="其他费用合计"><el-input :model-value="route.otherFeeTotal" inputmode="decimal" placeholder="请输入" @input="value => handleOtherFeeTotalInput(route, value)" /></el-form-item></el-col>
<el-col :span="6"><el-form-item label="币种"><el-select v-model="route.currency"><el-option label="人民币" value="CNY" /><el-option label="美元" value="USD" /><el-option label="欧元" value="EUR" /></el-select></el-form-item></el-col>
</el-row>
</el-form>
@@ -143,16 +142,16 @@
<el-form :model="route" label-position="right" label-width="auto" class="dispatch-form carrier-form">
<el-row :gutter="16">
<template v-if="isRoad(route)">
<el-col v-if="route.carrierType === '承运商'" :span="6"><el-form-item label="承运商" required><el-select v-model="route.carrierContractId" filterable clearable placeholder="请选择" :loading="carrierLoading" @change="value => handleCarrierContractChange(route, value)"><el-option v-for="item in carrierOptions" :key="item.contractId" :label="carrierOptionLabel(item)" :value="item.contractId" /></el-select></el-form-item></el-col>
<el-col :span="6"><el-form-item label="承运商" :required="route.carrierType === '承运商'"><el-select :model-value="route.carrierType === '承运商' ? route.carrierContractId : route.carrierName" filterable clearable placeholder="请选择" :loading="carrierLoading" @change="value => handleCarrierContractChange(route, value)"><el-option v-for="item in carrierOptions" :key="carrierOptionValue(item)" :label="carrierOptionLabel(item)" :value="route.carrierType === '承运商' ? carrierOptionValue(item) : item.carrierName" /></el-select></el-form-item></el-col>
<el-col :span="6"><el-form-item label="司机" :required="route.carrierType !== '承运商'"><el-autocomplete :ref="element => setDriverInput(route.segmentNo, element)" v-model="route.driverName" :debounce="300" :fetch-suggestions="fetchDriverSuggestions" clearable placeholder="请输入司机" :loading="driverLoading" @select="item => handleDriverSuggestionSelect(route, item)" /></el-form-item></el-col>
<el-col :span="6"><el-form-item :label="route.carrierType === '承运商' ? '手机号' : '司机手机号'" :required="route.carrierType !== '承运商'"><el-input v-model="route.driverPhone" placeholder="请输入" /></el-form-item></el-col>
<el-col :span="6"><el-form-item label="车牌号" required><el-input v-model="route.vehicleNo" placeholder="请输入" /></el-form-item></el-col>
<el-col v-if="route.carrierType !== '承运商'" :span="6"><el-form-item label="挂车车牌号" required><el-input v-model="route.trailerVehicleNo" placeholder="请输入" /></el-form-item></el-col>
<el-col v-if="route.carrierType !== '承运商'" :span="6"><el-form-item label="押运人" required><el-input v-model="route.escortName" placeholder="请输入" /></el-form-item></el-col>
<el-col v-if="route.carrierType !== '承运商'" :span="6"><el-form-item label="押运人手机号" required><el-input v-model="route.escortPhone" placeholder="请输入" /></el-form-item></el-col>
<el-col v-if="route.carrierType !== '承运商'" :span="6"><el-form-item label="挂车车牌号"><el-input v-model="route.trailerVehicleNo" placeholder="请输入" /></el-form-item></el-col>
<el-col v-if="route.carrierType !== '承运商'" :span="6"><el-form-item label="押运人"><el-autocomplete v-model="route.escortName" :debounce="300" :fetch-suggestions="fetchEscortSuggestions" clearable placeholder="请输入" :loading="escortLoading" @select="item => handleEscortSuggestionSelect(route, item)" /></el-form-item></el-col>
<el-col v-if="route.carrierType !== '承运商'" :span="6"><el-form-item label="押运人手机号"><el-input v-model="route.escortPhone" placeholder="请输入" /></el-form-item></el-col>
</template>
<template v-else>
<el-col v-if="route.carrierType === '承运商'" :span="6"><el-form-item label="承运商" required><el-select v-model="route.carrierContractId" filterable clearable placeholder="请选择" :loading="carrierLoading" @change="value => handleCarrierContractChange(route, value)"><el-option v-for="item in carrierOptions" :key="item.contractId" :label="carrierOptionLabel(item)" :value="item.contractId" /></el-select></el-form-item></el-col>
<el-col :span="6"><el-form-item label="承运商" :required="route.carrierType === '承运商'"><el-select :model-value="route.carrierType === '承运商' ? route.carrierContractId : route.carrierName" filterable clearable placeholder="请选择" :loading="carrierLoading" @change="value => handleCarrierContractChange(route, value)"><el-option v-for="item in carrierOptions" :key="carrierOptionValue(item)" :label="carrierOptionLabel(item)" :value="route.carrierType === '承运商' ? carrierOptionValue(item) : item.carrierName" /></el-select></el-form-item></el-col>
<el-col :span="6"><el-form-item label="船/航/班列号" required><el-input v-model="route.vehicleNo" placeholder="请输入" /></el-form-item></el-col>
<el-col :span="6"><el-form-item label="船长"><el-input v-model="route.captainName" placeholder="请输入" /></el-form-item></el-col>
<el-col :span="6"><el-form-item label="联系电话" required><el-input v-model="route.driverPhone" placeholder="请输入" /></el-form-item></el-col>
@@ -208,7 +207,7 @@ export default {
props: { id: [String, Number] },
emits: ['back'],
data() {
return { loading: false, submitting: false, master: null, routes: [], pending: [], pendingExpanded: false, editingId: null, driverInputs: {}, carrierOptions: [], driverOptions: [], carrierLoading: false, driverLoading: false, cargoTypeOptions: [], cargoTypeCascaderProps: { label: 'cargoName', value: 'id', children: 'children', emitPath: true }, quantityUnitOptions: ['吨', '千克', '立方米', '件', '车', '箱', '托盘'] };
return { loading: false, submitting: false, master: null, routes: [], pending: [], pendingExpanded: false, editingId: null, driverInputs: {}, carrierOptions: [], driverOptions: [], carrierLoading: false, driverLoading: false, escortLoading: false, cargoTypeOptions: [], cargoTypeCascaderProps: { label: 'cargoName', value: 'id', children: 'children', emitPath: true }, quantityUnitOptions: ['吨', '千克', '立方米', '件', '车', '箱', '托盘'] };
},
computed: {
totalQuantity() { return (this.master?.goods || []).reduce((sum, item) => sum + Number(item.quantity || 0), 0); },
@@ -231,6 +230,7 @@ export default {
},
async mounted() {
await Promise.all([this.load(), this.loadCarrierOptions(), this.loadDriverOptions()]);
this.applySelfOperatedCarrierDefaults();
},
methods: {
async load() {
@@ -238,10 +238,36 @@ export default {
try {
const res = await api.getDetail(this.id);
this.master = res.data?.data || res.data || res;
await this.loadContractCurrency();
this.cargoTypeOptions = this.buildMasterCargoTypeOptions(this.master.goods || []);
this.routes = this.dispatchRouteNodes().map((node, index) => this.createRoute(node, index));
} finally { this.loading = false; }
},
normalizeCurrencyValue(value) {
const raw = typeof value === 'object'
? value.dictKey || value.value || value.code || value.dictValue || ''
: value;
const normalized = String(raw || 'RMB').trim().toUpperCase();
return normalized.includes(' - ') ? normalized.split(' - ')[0].trim() : normalized;
},
async loadContractCurrency() {
const directCurrency = this.master?.settlementCurrency || this.master?.settlementCurrencyCode || this.master?.currency;
if (!this.master?.projectId || !this.master?.contractId) {
this.master.settlementCurrency = this.normalizeCurrencyValue(directCurrency || 'RMB');
return;
}
try {
const response = await api.getProjectContracts(this.master.projectId);
const contract = unwrapRecords(response).find(item => String(item.id) === String(this.master.contractId));
this.master.customerContractPartyB = contract?.partyB || this.master.customerContractPartyB || '';
this.master.settlementCurrency = this.normalizeCurrencyValue(
directCurrency || contract?.settlementCurrency || contract?.settlementCurrencyCode || contract?.currency || 'RMB'
);
} catch {
this.master.customerContractPartyB = '';
this.master.settlementCurrency = 'RMB';
}
},
dispatchRouteNodes() {
const routes = Array.isArray(this.master?.routes) ? this.master.routes : [];
if (routes.length) return routes;
@@ -261,9 +287,9 @@ export default {
...node,
selected: false,
documentType: '运单',
carrierType: '承运商',
carrierType: node.carrierType || this.master?.carrierType || '承运商',
carrierContractId: '',
currency: 'CNY',
currency: this.normalizeCurrencyValue(this.master?.settlementCurrency || 'RMB'),
otherFeeTotal: '',
freightItems: [],
departureName: previous.departureName || '', departureAddress: previous.departureAddress || '', departureContact: previous.departureContact || '', departurePhone: previous.departurePhone || '',
@@ -343,7 +369,7 @@ export default {
},
buildFreightJson(route) {
return JSON.stringify({
currency: route.currency || 'CNY',
currency: route.currency || 'RMB',
totalFreightAmount: this.freightTotal(route),
otherFreightAmount: route.otherFeeTotal || '',
freightItems: (route.freightItems || []).map(item => ({
@@ -352,7 +378,7 @@ export default {
})),
});
},
currencyLabel(value) { return ({ CNY: '人民币', USD: '美元', EUR: '欧元' })[value] || value || '人民币'; },
currencyLabel(value) { return ({ CNY: '人民币', RMB: '人民币', USD: '美元', EUR: '欧元' })[value] || value || '人民币'; },
quantityNumber(value) { return Number(value || 0); },
goodsKey(goods = {}, includeUnit = true) {
return [
@@ -533,16 +559,23 @@ export default {
}
if (value !== '承运商') {
route.carrierContractId = '';
route.carrierName = '';
if (value === '网货平台') route.carrierName = '';
if (value === '自运') this.applySelfOperatedCarrierDefaults(route);
}
},
handleCarrierContractChange(route, contractId) {
const contract = this.carrierOptions.find(
item => String(item.contractId) === String(contractId)
item =>
[item.contractId, item.carrierName, item.id].some(
value => String(value || '') === String(contractId || '')
)
);
route.carrierContractId = contract?.contractId || '';
route.carrierContractId = route.carrierType === '承运商' ? contract?.contractId || '' : '';
route.carrierName = contract?.carrierName || '';
},
carrierOptionValue(item = {}) {
return item.contractId || item.id || item.value || item.carrierName || '';
},
carrierOptionLabel(item) {
const duplicateCount = this.carrierOptions.filter(option => option.carrierName === item.carrierName).length;
return duplicateCount > 1
@@ -568,10 +601,35 @@ export default {
this.carrierLoading = false;
}
},
applySelfOperatedCarrierDefaults(targetRoute) {
const routes = targetRoute ? [targetRoute] : this.routes;
if (!this.carrierOptions.length) {
const carrierName = String(this.master?.customerContractPartyB || '').trim();
if (carrierName) {
this.carrierOptions = [{
id: `customer-contract-${carrierName}`,
contractId: `customer-contract-${carrierName}`,
carrierName,
}];
}
}
routes.forEach(route => {
if (route.carrierType !== '自运') return;
if (this.carrierOptions.length) {
const current = this.carrierOptions.find(
item => String(item.carrierName || '') === String(route.carrierName || '')
);
route.carrierName = current?.carrierName || this.carrierOptions[0].carrierName || '';
}
route.carrierContractId = '';
});
},
async loadDriverOptions(keyword = '') {
this.driverLoading = true;
try {
this.driverOptions = unwrapRecords(await getDriverList(1, 20, { driverName: keyword }));
this.driverOptions = unwrapRecords(
await getDriverList(1, 20, { driverName: keyword, posts: '司机' })
);
} finally {
this.driverLoading = false;
}
@@ -595,6 +653,28 @@ export default {
const drivingVehicle = String(item.drivingVehicle || '').trim();
if (drivingVehicle) route.vehicleNo = drivingVehicle;
},
fetchEscortSuggestions(queryString, callback) {
const keyword = String(queryString || '').trim();
this.escortLoading = true;
getDriverList(1, 20, { ...(keyword ? { driverName: keyword } : {}), posts: '押运员' })
.then(res => {
callback(
unwrapRecords(res).map(item => ({
...item,
value: this.driverOptionLabel(item),
}))
);
})
.catch(() => callback([]))
.finally(() => {
this.escortLoading = false;
});
},
handleEscortSuggestionSelect(route, item = {}) {
route.escortName = this.driverOptionLabel(item);
const phone = item.mobile || item.driverPhone || item.phone || '';
if (phone) route.escortPhone = phone;
},
setDriverInput(segmentNo, element) {
if (element) this.driverInputs[segmentNo] = element;
else delete this.driverInputs[segmentNo];
@@ -622,7 +702,7 @@ export default {
if (route.documentType === '运单') {
if (this.isRoad(route)) {
if (route.carrierType === '承运商' && (!route.carrierContractId || !route.carrierName || !route.vehicleNo)) return this.$message.warning('请选择承运商并填写车牌号');
if (route.carrierType !== '承运商' && (!route.driverName || !route.driverPhone || !route.vehicleNo || !route.trailerVehicleNo || !route.escortName || !route.escortPhone)) return this.$message.warning('请补全自运或网货平台的车辆与人员信息');
if (route.carrierType !== '承运商' && (!route.driverName || !route.driverPhone || !route.vehicleNo)) return this.$message.warning('请补全自运或网货平台的车辆与人员信息');
} else if (!route.vehicleNo || !route.driverPhone || (route.carrierType === '承运商' && (!route.carrierContractId || !route.carrierName))) {
return this.$message.warning('请补全非公路运输的承运信息');
}
@@ -632,7 +712,7 @@ export default {
id: `${route.segmentNo}-${item.cargoName}-${Date.now()}-${Math.random()}`,
batchNo,
segmentNo: route.segmentNo, relationNo: route.segmentNo, documentType: route.documentType, transportType: route.transportType, carrierType: route.carrierType,
carrierContractId: route.carrierContractId, carrierName: route.carrierName, driverName: route.driverName, driverPhone: route.driverPhone, vehicleNo: route.vehicleNo, captainName: route.captainName, cabinNo: route.cabinNo, containerNo: route.containerNo, trailerVehicleNo: route.trailerVehicleNo, escortName: route.escortName, escortPhone: route.escortPhone, mileage: this.normalizeMileage(route.mileage), unitPrice: this.freightItemsForGoods(route, item).unitPrice || '', priceUnit: this.freightItemsForGoods(route, item).priceUnit || '', currency: route.currency || 'CNY', freightAmount: this.freightAmount(this.freightItemsForGoods(route, item)), freightTotal: this.freightTotal(route), otherFeeTotal: route.otherFeeTotal || '', freightJson: this.buildFreightJson(route), remark: route.remark,
carrierContractId: route.carrierContractId, carrierName: route.carrierName, driverName: route.driverName, driverPhone: route.driverPhone, vehicleNo: route.vehicleNo, captainName: route.captainName, cabinNo: route.cabinNo, containerNo: route.containerNo, trailerVehicleNo: route.trailerVehicleNo, escortName: route.escortName, escortPhone: route.escortPhone, mileage: this.normalizeMileage(route.mileage), unitPrice: this.freightItemsForGoods(route, item).unitPrice || '', priceUnit: this.freightItemsForGoods(route, item).priceUnit || '', currency: route.currency || 'RMB', freightAmount: this.freightAmount(this.freightItemsForGoods(route, item)), freightTotal: this.freightTotal(route), otherFeeTotal: route.otherFeeTotal || '', freightJson: this.buildFreightJson(route), remark: route.remark,
departureName: route.departureName, departureAddress: route.departureAddress, departureContact: route.departureContact, departurePhone: route.departurePhone,
arrivalName: route.arrivalName, arrivalAddress: route.arrivalAddress, arrivalContact: route.arrivalContact, arrivalPhone: route.arrivalPhone,
estimatedStartTime: route.estimatedStartTime, estimatedEndTime: route.estimatedEndTime,
@@ -661,6 +741,9 @@ export default {
}
if (goods) goods.dispatchQuantity = item.quantity;
Object.assign(route, item);
route.currency = this.normalizeCurrencyValue(
this.master?.settlementCurrency || route.currency || 'RMB'
);
this.syncFreightItems(route);
const freightItem = this.freightItemsForGoods(route, goods);
if (freightItem) { freightItem.unitPrice = item.unitPrice || ''; freightItem.priceUnit = item.priceUnit || freightItem.priceUnit; }