✨ 优化港口地图回填、合同展示与运单导入空值

地图选点回填国家省市区;合同选择显示编号/名称;常用数据组织筛选修正;运单详情将-1费用字段显示为空。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-10-09 17:07:56 +08:00
parent 570fbeb537
commit a1da644c49
25 changed files with 568 additions and 217 deletions
+2 -1
View File
@@ -352,7 +352,8 @@ export default {
return result;
},
normalizeListQuery(params = {}) {
const query = { ...params, ...this.query };
// 搜索条件优先于缓存的 query,避免默认组织 ID 覆盖用户新选择的组织。
const query = { ...this.query, ...params };
const rawDeptId = query.deptId ?? query.searchDeptId;
delete query.searchDeptId;
if (rawDeptId !== undefined && rawDeptId !== null && String(rawDeptId).trim() !== '') {
+17 -8
View File
@@ -415,19 +415,27 @@ export default {
});
},
buildDeptTree(tree) {
return tree.map(item => ({
label: item.title || item.deptName || item.name,
value: item.id,
children:
item.children && item.children.length ? this.buildDeptTree(item.children) : undefined,
}));
return tree.map(item => {
// 雪花 ID 必须保持字符串,避免 JS Number 精度丢失导致筛错组织。
const id =
item.id === undefined || item.id === null || item.id === '' ? '' : String(item.id);
return {
label: item.title || item.deptName || item.name,
value: id,
children:
item.children && item.children.length ? this.buildDeptTree(item.children) : undefined,
};
}).filter(item => item.value);
},
flattenDept(tree, level = 0) {
const result = [];
tree.forEach(item => {
const id =
item.id === undefined || item.id === null || item.id === '' ? '' : String(item.id);
if (!id) return;
result.push({
label: `${' '.repeat(level)}${item.title || item.deptName || item.name}`,
value: item.id,
value: id,
});
if (item.children && item.children.length) {
result.push(...this.flattenDept(item.children, level + 1));
@@ -445,7 +453,8 @@ export default {
);
},
normalizeListQuery(params = {}) {
const query = { ...params, ...this.query };
// 搜索条件优先于缓存的 query,避免默认组织 ID 覆盖用户新选择的组织。
const query = { ...this.query, ...params };
const rawDeptId = query.deptId ?? query.searchDeptId;
delete query.searchDeptId;
if (rawDeptId !== undefined && rawDeptId !== null && String(rawDeptId).trim() !== '') {
@@ -215,7 +215,7 @@
<el-option
v-for="item in contractOptions"
:key="item.id"
:label="item.contractName"
:label="formatContractOptionLabel(item)"
:value="item.id"
/>
</el-select>
@@ -3056,7 +3056,7 @@
<el-option
v-for="item in transportPlanImportContractOptions"
:key="item.id"
:label="item.contractName"
:label="formatContractOptionLabel(item)"
:value="item.id"
/>
</el-select>
@@ -5242,6 +5242,7 @@ import { addressTypeOptions } from '@/option/base/common-address';
import { packageOptions } from '@/option/business/common';
import { formatUpdateUserName } from '@/utils/audit';
import { getToken, isAdministrator } from '@/utils/auth';
import { formatContractOptionLabel } from '@/utils/contract';
import { openImportDialog } from '@/utils/import-excel';
import { normalizeMapSearchResults, searchAmapPlaceText } from '@/utils/map-search';
import { loadMeasurementUnitOptions } from '@/utils/measurement-unit';
@@ -6590,6 +6591,7 @@ export default {
},
},
methods: {
formatContractOptionLabel,
initStandaloneFormPage() {
if (!this.isStandaloneFormPage) return;
const mode = this.$route.query.mode === 'edit' ? 'edit' : 'add';
@@ -317,6 +317,7 @@
<script>
import * as api from '@/api/business/master-order';
import { fetchDriversByCarrierOrganizations } from '@/utils/carrier-org-resource';
import { formatContractOptionLabel } from '@/utils/contract';
import { loadMeasurementUnitOptions } from '@/utils/measurement-unit';
import { isMobile } from '@/utils/validate';
@@ -904,9 +905,11 @@ export default {
},
carrierOptionLabel(item) {
const duplicateCount = this.carrierOptions.filter(option => option.carrierName === item.carrierName).length;
return duplicateCount > 1
? `${item.carrierName}(${item.contractName || item.contractId})`
: item.carrierName;
if (duplicateCount <= 1) {
return item.carrierName;
}
const contractLabel = formatContractOptionLabel(item);
return contractLabel ? `${item.carrierName}(${contractLabel})` : item.carrierName;
},
driverOptionLabel(item) { return item.driverName || item.name || ''; },
driverOptionKey(item) { return item.id || item.driverId || this.driverOptionLabel(item) || item.mobile; },
@@ -27,7 +27,7 @@
><el-option
v-for="contract in contracts"
:key="contract.id"
:label="contract.contractName"
:label="formatContractOptionLabel(contract)"
:value="contract.id" /></el-select></el-form-item></el-col
><el-col :span="6"
><el-form-item label="总单号" required
@@ -589,6 +589,7 @@ import { getList as getCargoTypeList } from '@/api/base/cargo-type';
import { getLazyTree as getRegionLazyTree } from '@/api/base/region';
import { getList as getCommonCargoList } from '@/api/business/common-cargo';
import { exportBlob, importBlob } from '@/api/common';
import { formatContractOptionLabel } from '@/utils/contract';
import { downloadFileByUrl, downloadXls } from '@/utils/util';
import { OfficeBuilding, Search } from '@element-plus/icons-vue';
import { ElImageViewer } from 'element-plus';
@@ -784,6 +785,7 @@ export default {
}
},
methods: {
formatContractOptionLabel,
defaultQuantityUnit() {
return this.quantityUnitOptions[0] || '吨';
},
@@ -113,7 +113,7 @@
<el-option
v-for="item in contractOptions"
:key="item.id"
:label="item.contractName"
:label="formatContractOptionLabel(item)"
:value="item.id"
/>
</el-select>
@@ -1098,6 +1098,7 @@ import {
import { getDictionary as getSystemDictionary } from '@/api/system/dict';
import { getDictionary } from '@/api/system/dictbiz';
import { packageOptions } from '@/option/business/common';
import { formatContractOptionLabel } from '@/utils/contract';
import { loadMeasurementUnitOptions } from '@/utils/measurement-unit';
import { normalizeMapSearchResults, searchAmapPlaceText } from '@/utils/map-search';
import { downloadFileByUrl, downloadXls } from '@/utils/util';
@@ -1533,6 +1534,7 @@ export default {
if (this.isStandaloneFormPage) this.$nextTick(() => this.initStandaloneFormPage());
},
methods: {
formatContractOptionLabel,
loadQuantityUnitOptions() {
if (this.quantityUnitLoading) return this.quantityUnitLoadPromise;
this.quantityUnitLoading = true;
@@ -149,7 +149,7 @@
<el-option
v-for="item in contractOptions"
:key="item.id"
:label="item.contractName"
:label="formatContractOptionLabel(item)"
:value="item.id"
/>
</el-select>
@@ -980,7 +980,7 @@
<el-option
v-for="item in transportPlanImportContractOptions"
:key="item.id"
:label="item.contractName"
:label="formatContractOptionLabel(item)"
:value="item.id"
/>
</el-select>
@@ -2741,6 +2741,7 @@ import { addressTypeOptions } from '@/option/base/common-address';
import { packageOptions } from '@/option/business/common';
import { formatUpdateUserName } from '@/utils/audit';
import { getToken, isAdministrator } from '@/utils/auth';
import { formatContractOptionLabel } from '@/utils/contract';
import { normalizeMapSearchResults, searchAmapPlaceText } from '@/utils/map-search';
import { loadMeasurementUnitOptions } from '@/utils/measurement-unit';
import { applyTableMenuWidth } from '@/utils/table-menu';
@@ -3721,6 +3722,7 @@ export default {
},
},
methods: {
formatContractOptionLabel,
initStandaloneFormPage() {
if (!this.isStandaloneFormPage) return;
const mode = this.$route.query.mode === 'edit' ? 'edit' : 'add';
@@ -374,7 +374,7 @@
<el-option
v-for="item in contracts"
:key="item.id"
:label="item.contractName"
:label="formatContractOptionLabel(item)"
:value="item.id"
/>
</el-select>
@@ -610,6 +610,7 @@ import { getList as getProjectList } from '@/api/business/project-apply';
import { getList as getDriverList } from '@/api/transportCapacity/driver';
import { getDictionary } from '@/api/system/dictbiz';
import * as api from '@/api/business/waybill-manage';
import { formatContractOptionLabel } from '@/utils/contract';
import { downloadXls } from '@/utils/util';
import SectionCard from '@/components/section-card/main.vue';
@@ -1297,6 +1298,31 @@ const carrierChange = value => {
form.carrierName = item.carrierName || '';
if (!isSelfOperated.value) form.carrierContractId = item.carrierContractId || '';
};
const firstNotEmpty = (...values) => values.find(value => String(value ?? '').trim()) ?? '';
const OPTIONAL_NUMBER_EMPTY_SENTINELS = [-1];
const normalizeOptionalImportNumber = (value, emptySentinels = OPTIONAL_NUMBER_EMPTY_SENTINELS) => {
if (!String(value ?? '').trim()) return null;
return emptySentinels.some(sentinel => Number(value) === sentinel) ? null : value;
};
const normalizeImportRow = row => ({
...row,
mileage: normalizeOptionalImportNumber(
firstNotEmpty(row.mileage, row['里程'], row['里程(km)'], row['里程(公里)'])
),
otherFeeTotal: normalizeOptionalImportNumber(
firstNotEmpty(row.otherFeeTotal, row['其他费用合计'])
),
unitPrice: normalizeOptionalImportNumber(firstNotEmpty(row.unitPrice, row['单价'])),
freight: normalizeOptionalImportNumber(firstNotEmpty(row.freight, row['运费'])),
freightTotal: normalizeOptionalImportNumber(firstNotEmpty(row.freightTotal, row['运费合计'])),
transportType: firstNotEmpty(
row.transportType,
row['运输类型'],
row['*运输类型'],
row['运输方式'],
row['*运输方式']
),
});
const fileChange = async (file, list) => {
if (!projectInfoReady.value) {
ElMessage.warning('请先选择项目、客户合同和承运信息,再导入运单明细');
@@ -1370,6 +1396,9 @@ const fileChange = async (file, list) => {
item[key] ??
'';
});
['mileage', 'unitPrice', 'freight', 'otherFeeTotal', 'freightTotal'].forEach(prop => {
row[prop] = normalizeOptionalImportNumber(row[prop]);
});
row.actualStartDate = normalizeImportDate(row.actualStartDate);
row.actualEndDate = normalizeImportDate(row.actualEndDate);
row.planStartDate = normalizeImportDate(row.planStartDate);
@@ -1656,31 +1685,6 @@ const removeSelectedRows = () => {
rowSelection.value = [];
refreshDuplicateFlags();
};
const firstNotEmpty = (...values) => values.find(value => String(value ?? '').trim()) ?? '';
const normalizeOptionalImportNumber = (value, emptySentinels = []) => {
if (!String(value ?? '').trim()) return null;
return emptySentinels.some(sentinel => Number(value) === sentinel) ? null : value;
};
const normalizeImportRow = row => ({
...row,
mileage: normalizeOptionalImportNumber(
firstNotEmpty(row.mileage, row['里程'], row['里程(km)'], row['里程(公里)']),
[-1]
),
otherFeeTotal: normalizeOptionalImportNumber(
firstNotEmpty(row.otherFeeTotal, row['其他费用合计'])
),
unitPrice: normalizeOptionalImportNumber(firstNotEmpty(row.unitPrice, row['单价'])),
freight: normalizeOptionalImportNumber(firstNotEmpty(row.freight, row['运费'])),
freightTotal: normalizeOptionalImportNumber(firstNotEmpty(row.freightTotal, row['运费合计'])),
transportType: firstNotEmpty(
row.transportType,
row['运输类型'],
row['*运输类型'],
row['运输方式'],
row['*运输方式']
),
});
const normalizeOptionalPlanId = value => {
if (value === undefined || value === null || value === '') return null;
return Number(value) === -1 || Number(value) <= 0 ? null : value;
@@ -212,7 +212,7 @@
<el-option
v-for="item in contractOptions"
:key="item.id"
:label="item.contractName"
:label="formatContractOptionLabel(item)"
:value="item.id"
/>
</el-select>
@@ -3642,6 +3642,7 @@ import { addressTypeOptions } from '@/option/base/common-address';
import { packageOptions } from '@/option/business/common';
import { formatUpdateUserName } from '@/utils/audit';
import { getToken, isAdministrator } from '@/utils/auth';
import { formatContractOptionLabel } from '@/utils/contract';
import { wgs84ToGcj02 } from '@/utils/coordinate';
import { openImportDialog } from '@/utils/import-excel';
import { normalizeMapSearchResults, searchAmapPlaceText } from '@/utils/map-search';
@@ -4724,6 +4725,7 @@ export default {
},
},
methods: {
formatContractOptionLabel,
// 页面被 keep-alive 缓存/卸载时调用,关闭本页所有 append-to-body 的弹窗,
// 避免 Teleport 出去的 DOM 残留在 body 上盖住后续页面(详见 data 里的说明)。
closeInnerDialogs() {
@@ -6958,9 +6960,11 @@ export default {
applyFormDetail(row) {
this.suppressTransportTypeClear = true;
this.form = { ...(row || {}) };
['quantity', 'mileage', 'unitPrice'].forEach(prop => {
if (Number(this.form[prop]) === -1) this.form[prop] = '';
});
['quantity', 'mileage', 'unitPrice', 'otherFeeTotal', 'freight', 'freightTotal'].forEach(
prop => {
if (this.isSentinelMinusOne(this.form[prop])) this.form[prop] = '';
}
);
if ([this.form.planId, this.form.planName].some(value => Number(value) === -1)) {
this.form.planId = '';
this.form.planName = '';
@@ -7371,17 +7375,17 @@ export default {
this.form.quantityUnit || taskInfo.quantityUnit || goodsInfo.quantityUnit || '吨';
const mileage = this.form.mileage || taskInfo.mileage || goodsInfo.mileage || '';
this.form.mileage = this.normalizeMileageValue(mileage);
this.form.unitPrice = this.form.unitPrice || taskInfo.unitPrice || goodsInfo.unitPrice || '';
this.form.unitPrice = this.normalizeOptionalAmount(
this.form.unitPrice || taskInfo.unitPrice || goodsInfo.unitPrice || ''
);
['quantity', 'mileage', 'unitPrice'].forEach(prop => {
if (Number(this.form[prop]) === -1) this.form[prop] = '';
if (this.isSentinelMinusOne(this.form[prop])) this.form[prop] = '';
});
this.form.priceUnit =
this.form.priceUnit || taskInfo.priceUnit || goodsInfo.priceUnit || '元/吨';
this.form.otherFeeTotal =
this.form.otherFeeTotal || taskInfo.otherFeeTotal || goodsInfo.otherFeeTotal || '';
if (Number(this.form.otherFeeTotal) === -1) {
this.form.otherFeeTotal = '';
}
this.form.otherFeeTotal = this.normalizeOptionalAmount(
this.form.otherFeeTotal || taskInfo.otherFeeTotal || goodsInfo.otherFeeTotal || ''
);
this.form.estimatedStartTime =
this.form.estimatedStartTime ||
taskInfo.estimatedStartTime ||
@@ -7463,7 +7467,7 @@ export default {
},
taskFullFreightGroupUnitPrice(group = {}) {
const prices = (group.rows || [])
.map(cargo => String(cargo.unitPrice ?? '').trim())
.map(cargo => this.normalizeOptionalAmount(cargo.unitPrice))
.filter(Boolean);
if (!prices.length || prices.some(price => price !== prices[0])) return '';
return prices[0];
@@ -7526,10 +7530,21 @@ export default {
},
formatTaskFullFreightNumber(value) {
if (value === undefined || value === null || String(value).trim() === '') return '';
const number = Number(value || 0);
if (this.isSentinelMinusOne(value)) return '';
const number = Number(value);
if (!Number.isFinite(number)) return '';
return Number(number.toFixed(2)).toString();
},
isSentinelMinusOne(value) {
if (value === undefined || value === null || value === '') return false;
return Number(value) === -1 || String(value).trim() === '-1';
},
normalizeOptionalAmount(value) {
if (value === undefined || value === null || value === '' || this.isSentinelMinusOne(value)) {
return '';
}
return value;
},
handleTaskCarrierTypeChange(value) {
const nextValue = value || '承运商';
this.form.carrierName = '';
@@ -8082,7 +8097,9 @@ export default {
}
cargo.priceUnit = cargo.priceUnit || this.form.priceUnit || '元/吨';
if (this.isBillingFieldEmpty(cargo.freightAmount)) {
cargo.freightAmount = freightItems[index]?.freightAmount ?? '';
cargo.freightAmount = this.normalizeOptionalAmount(
freightItems[index]?.freightAmount ?? ''
);
}
return cargo;
});
@@ -8274,8 +8291,10 @@ export default {
},
syncTaskInfoToRow(row) {
row.taskEntryMode = row.taskEntryMode || 'simple';
['quantity', 'mileage', 'unitPrice'].forEach(prop => {
if (this.isBillingFieldEmpty(row[prop]) || Number(row[prop]) === -1) row[prop] = '';
['quantity', 'mileage', 'unitPrice', 'otherFeeTotal'].forEach(prop => {
if (this.isBillingFieldEmpty(row[prop]) || this.isSentinelMinusOne(row[prop])) {
row[prop] = '';
}
});
const taskInfo = {
taskEntryMode: row.taskEntryMode,
@@ -8300,7 +8319,7 @@ export default {
priceUnit: row.priceUnit,
taskRemark: row.taskRemark,
};
if (!this.isBillingFieldEmpty(row.otherFeeTotal) && Number(row.otherFeeTotal) !== -1) {
if (!this.isBillingFieldEmpty(row.otherFeeTotal) && !this.isSentinelMinusOne(row.otherFeeTotal)) {
taskInfo.otherFeeTotal = row.otherFeeTotal;
} else {
delete row.otherFeeTotal;
@@ -8434,10 +8453,12 @@ export default {
cargoName: row.cargoName || row.goodsName || '',
packageType: row.packageType || row.package || '',
specification: row.specification || row.spec || '',
freightAmount: row.freightAmount ?? row.amount ?? row.totalAmount ?? '',
freightAmount: this.normalizeOptionalAmount(
row.freightAmount ?? row.amount ?? row.totalAmount ?? ''
),
};
['quantity', 'mileage', 'unitPrice'].forEach(prop => {
if (Number(cargo[prop]) === -1) cargo[prop] = '';
['quantity', 'mileage', 'unitPrice', 'freightAmount'].forEach(prop => {
if (this.isSentinelMinusOne(cargo[prop])) cargo[prop] = '';
});
return cargo;
},
@@ -8680,7 +8701,12 @@ export default {
}
},
isBillingFieldEmpty(value) {
return value === undefined || value === null || String(value).trim() === '';
return (
value === undefined ||
value === null ||
String(value).trim() === '' ||
this.isSentinelMinusOne(value)
);
},
ensureTransportTypeBeforeAddress() {
if (!this.shippingInfoFormEnabled || !this.isBillingFieldEmpty(this.form.transportType)) {
+14 -1
View File
@@ -1383,7 +1383,11 @@
<el-option
v-for="item in taskCarrierOptions"
:key="item.id || item.carrierName"
:label="item.carrierName || item.partyB"
:label="
dialogForm.carrierType === '承运商'
? formatTaskCarrierContractLabel(item)
: item.carrierName || item.partyB
"
:value="
dialogForm.carrierType === '承运商'
? item.id
@@ -1699,6 +1703,7 @@ import { getParentOptions as getCargoTypeOptions } from '@/api/base/cargo-type';
import { getList as getCustomerList } from '@/api/vehicle/customer-archive';
import { getList as getDriverList } from '@/api/transportCapacity/driver';
import { fetchDriversByCarrierOrganizations } from '@/utils/carrier-org-resource';
import { formatContractOptionLabel } from '@/utils/contract';
import {
getList as getProcessConfigList,
getDetail as getProcessConfigDetail,
@@ -2174,6 +2179,14 @@ export default {
},
},
methods: {
formatTaskCarrierContractLabel(item = {}) {
const carrierName = item.carrierName || item.partyB || '';
const contractLabel = formatContractOptionLabel(item);
if (carrierName && contractLabel) {
return `${carrierName}(${contractLabel})`;
}
return carrierName || contractLabel;
},
syncStandaloneTagTitle() {
if (!this.isStandalonePage) return;
const title = this.isStandaloneDetailPage ? this.detailPageTitle : this.formPageDefaultTitle;
+16 -4
View File
@@ -573,7 +573,7 @@
type="textarea"
:rows="2"
placeholder="请输入"
maxlength="200"
maxlength="1000"
show-word-limit
/>
</el-form-item>
@@ -583,7 +583,7 @@
type="textarea"
:rows="2"
placeholder="请输入"
maxlength="200"
maxlength="1000"
show-word-limit
/>
</el-form-item>
@@ -593,7 +593,7 @@
type="textarea"
:rows="2"
placeholder="请输入"
maxlength="200"
maxlength="1000"
show-word-limit
/>
</el-form-item>
@@ -603,7 +603,7 @@
type="textarea"
:rows="2"
placeholder="请输入"
maxlength="200"
maxlength="1000"
show-word-limit
/>
</el-form-item>
@@ -1188,6 +1188,18 @@ export default {
changeReason: [
{ max: 2000, message: '变更原因不能超过2000个字符', trigger: 'blur' },
],
projectIntro: [
{ max: 1000, message: '项目简介不能超过1000个字符', trigger: 'blur' },
],
profitRemark: [
{ max: 1000, message: '盈利模式及利润测算情况说明不能超过1000个字符', trigger: 'blur' },
],
riskPoint: [
{ max: 1000, message: '项目风险点不能超过1000个字符', trigger: 'blur' },
],
emergencyPlan: [
{ max: 1000, message: '风险防范措施及应急预案不能超过1000个字符', trigger: 'blur' },
],
},
deptOptions: [],
businessDeptTreeOptions: [],
+3 -1
View File
@@ -51,7 +51,7 @@
<el-option
v-for="item in contractOptions"
:key="item.id"
:label="item.contractName"
:label="formatContractOptionLabel(item)"
:value="item.id"
/>
</el-select>
@@ -165,6 +165,7 @@ import { getList as getContractList } from '@/api/business/contract-manage';
import { getList as getProjectList } from '@/api/business/project-apply';
import { importTransportPlan, validateTransportPlan } from '@/api/business/transport-plan';
import { config } from '@/option/business/transport-plan';
import { formatContractOptionLabel } from '@/utils/contract';
import { downloadXls } from '@/utils/util';
import dayjs from 'dayjs';
@@ -271,6 +272,7 @@ export default {
this.loadProjectOptions();
},
methods: {
formatContractOptionLabel,
loadProjectOptions() {
if (this.projectLoading) return;
this.projectLoading = true;