✨ 业务数量单位改走计量单位,并放开运输计划日期必填

发货模板、运输计划、运单与总单调度从计量单位取 unitName;修复调度提交顶层单位为空,并取消计划开始/结束日期必填。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-10-04 20:26:19 +08:00
parent 0b81001ce7
commit 7751a735c5
13 changed files with 323 additions and 139 deletions
+3
View File
@@ -2,6 +2,9 @@ const dimensionOptions = [
{ label: '重量', value: '重量' },
{ label: '体积', value: '体积' },
{ label: '数量', value: '数量' },
{ label: '整车', value: '整车' },
{ label: '里程', value: '里程' },
{ label: '吨·公里', value: '吨·公里' },
];
const statusOptions = [
-2
View File
@@ -400,7 +400,6 @@ export const option = {
span: 6,
order: 350,
minWidth: 150,
rules: [{ required: true, message: '请选择计划开始日期', trigger: 'change' }],
},
{
label: '计划结束日期',
@@ -411,7 +410,6 @@ export const option = {
span: 6,
order: 340,
minWidth: 150,
rules: [{ required: true, message: '请选择计划结束日期', trigger: 'change' }],
},
{
label: '数据来源',
+27
View File
@@ -0,0 +1,27 @@
import { getList as getMeasurementUnitList } from '@/api/base/measurement-unit';
/**
* 加载启用状态的计量单位选项,展示/取值均为「计量单位」字段(unitName)。
* @returns {Promise<Array<{ label: string, value: string, dimension: string, id: string|number }>>}
*/
export const loadMeasurementUnitOptions = () => {
return getMeasurementUnitList(1, 9999, { status: 1 }).then(res => {
const data = res?.data?.data || res?.data || {};
const records = Array.isArray(data) ? data : data.records || [];
const names = new Set();
return records
.filter(item => item.status === undefined || item.status === null || Number(item.status) === 1)
.map(item => {
const name = String(item.unitName || '').trim();
if (!name || names.has(name)) return null;
names.add(name);
return {
id: item.id,
label: name,
value: name,
dimension: String(item.dimension || '').trim(),
};
})
.filter(Boolean);
});
};
@@ -386,12 +386,15 @@ const BILLING_ELEMENT_DIMENSION_MAP = {
按重量: '重量',
按体积: '体积',
按数量: '数量',
按里程: '里程',
'按吨·公里': '吨·公里',
'固定金额(整单一口价)': '整车',
};
const BILLING_UNIT_SELECTABLE_ELEMENTS = Object.keys(BILLING_ELEMENT_DIMENSION_MAP);
/** 固定计费单位(不可编辑) */
const FIXED_BILLING_UNIT_MAP = {
/** 对应维度下无计量单位时的默认计费单位 */
const DEFAULT_BILLING_UNIT_MAP = {
按里程: '公里',
'按吨·公里': '吨公里',
'按吨·公里': '吨·公里',
'固定金额(整单一口价)': '单',
};
const defaultRule = () => ({
@@ -652,58 +655,61 @@ export default {
}),
]).finally(() => {
this.unitLoading = false;
(this.draft.rules || []).forEach(row => this.syncBillingUnit(row));
});
},
measurementDimension(billingElement) {
return BILLING_ELEMENT_DIMENSION_MAP[billingElement] || '';
},
fixedBillingUnit(billingElement) {
return FIXED_BILLING_UNIT_MAP[String(billingElement || '').trim()] || '';
defaultBillingUnit(billingElement) {
return DEFAULT_BILLING_UNIT_MAP[String(billingElement || '').trim()] || '';
},
canSelectBillingUnit(row) {
return BILLING_UNIT_SELECTABLE_ELEMENTS.includes(String(row?.billingElement || '').trim());
},
billingUnitPlaceholder(row) {
if (!row?.billingElement) return '请先选择计费要素';
if (this.fixedBillingUnit(row.billingElement)) return this.fixedBillingUnit(row.billingElement);
if (!this.canSelectBillingUnit(row)) return '当前计费要素无需选择';
return '请选择';
},
unitOptionsFor(row) {
const fixedUnit = this.fixedBillingUnit(row?.billingElement);
if (fixedUnit) {
return [{ id: fixedUnit, label: fixedUnit, value: fixedUnit }];
}
if (!this.canSelectBillingUnit(row)) {
return [];
}
const dimension = this.measurementDimension(row?.billingElement);
if (dimension) {
return this.measurementUnits
.filter(item => String(item.dimension || '').trim() === dimension)
.map(item => ({
id: item.id,
label: item.unitName,
value: item.unitName,
}))
.filter(item => item.value);
const defaultUnit = this.defaultBillingUnit(row?.billingElement);
const options = dimension
? this.measurementUnits
.filter(item => String(item.dimension || '').trim() === dimension)
.map(item => ({
id: item.id,
label: item.unitName,
value: item.unitName,
}))
.filter(item => item.value)
: [];
// 对应维度未配置计量单位时,下拉仅展示默认值
if (!options.length && defaultUnit) {
return [{ id: `default-${defaultUnit}`, label: defaultUnit, value: defaultUnit }];
}
return [];
return options;
},
syncBillingUnit(row) {
const fixedUnit = this.fixedBillingUnit(row?.billingElement);
if (fixedUnit) {
row.billingUnit = fixedUnit;
return;
}
if (!this.canSelectBillingUnit(row)) {
row.billingUnit = '';
return;
}
const options = this.unitOptionsFor(row);
if (!options.some(item => String(item.value) === String(row.billingUnit || ''))) {
row.billingUnit = '';
const defaultUnit = this.defaultBillingUnit(row?.billingElement);
const current = String(row.billingUnit || '').trim();
if (options.some(item => String(item.value) === current)) {
return;
}
if (defaultUnit && options.some(item => String(item.value) === defaultUnit)) {
row.billingUnit = defaultUnit;
return;
}
row.billingUnit = options[0]?.value || defaultUnit || '';
},
feeTypeKey(row) {
const option = this.feeCategories.find(
@@ -5244,6 +5244,7 @@ import { formatUpdateUserName } from '@/utils/audit';
import { getToken, isAdministrator } from '@/utils/auth';
import { openImportDialog } from '@/utils/import-excel';
import { normalizeMapSearchResults, searchAmapPlaceText } from '@/utils/map-search';
import { loadMeasurementUnitOptions } from '@/utils/measurement-unit';
import { applyTableMenuWidth } from '@/utils/table-menu';
import { downloadFileByUrl, downloadXls } from '@/utils/util';
import { isMobile } from '@/utils/validate';
@@ -5822,7 +5823,7 @@ export default {
billCycleTypeOptions: ['固定截单日', '自然月', '自定义多周期'],
reconciliationModeOptions: ['明细逐行核对', '按账单汇总核对', '跳过自动核对'],
packageOptions,
quantityUnitOptions: ['吨', '千克', '立方米', '件', '车', '箱', '托盘'],
quantityUnitOptions: [],
attachmentFileTypes: [
'pdf',
'bmp',
@@ -5865,7 +5866,6 @@ export default {
this.ensureBillingUnitOptions();
}
if (this.config.enableTaskInfoForm) {
this.loadTaskQuantityUnitOptions();
this.loadTaskFeeUnitOptions();
this.ensureBillingCargoTypeOptions();
}
@@ -5886,6 +5886,7 @@ export default {
} else {
this.consumeTemplateCreatePayload();
}
this.loadTaskQuantityUnitOptions();
},
computed: {
...mapGetters(['permission', 'userInfo']),
@@ -7789,8 +7790,6 @@ export default {
['contractName', '客户合同'],
['planName', '计划名称'],
['transportType', '运输方式'],
['planStartDate', '计划开始日期'],
['planEndDate', '计划结束日期'],
['departureAddress', '发货地址'],
['arrivalAddress', '收货地址'],
];
@@ -9270,9 +9269,10 @@ export default {
return Promise.resolve(this.taskQuantityUnitOptions);
}
this.taskQuantityUnitLoading = true;
return getDictionary({ code: 'quantity_unit' })
.then(res => {
this.taskQuantityUnitOptions = this.normalizeDictOptions(res.data.data || []);
return loadMeasurementUnitOptions()
.then(options => {
this.taskQuantityUnitOptions = options;
this.quantityUnitOptions = options.map(item => item.value);
return this.taskQuantityUnitOptions;
})
.finally(() => {
@@ -317,6 +317,7 @@
<script>
import * as api from '@/api/business/master-order';
import { fetchDriversByCarrierOrganizations } from '@/utils/carrier-org-resource';
import { loadMeasurementUnitOptions } from '@/utils/measurement-unit';
import { isMobile } from '@/utils/validate';
const unwrapRecords = res => {
@@ -347,7 +348,8 @@ export default {
carrierLoading: false,
driverLoading: false,
escortLoading: false,
quantityUnitOptions: ['吨', '千克', '立方米', '件', '车', '箱', '托盘'],
quantityUnitOptions: [],
quantityUnitLoading: false,
};
},
computed: {
@@ -389,10 +391,24 @@ export default {
},
},
async mounted() {
await this.loadQuantityUnitOptions();
await Promise.all([this.load(), this.loadCarrierOptions()]);
this.applySelfOperatedCarrierDefaults();
},
methods: {
loadQuantityUnitOptions() {
if (this.quantityUnitLoading) return this.quantityUnitLoadPromise;
this.quantityUnitLoading = true;
this.quantityUnitLoadPromise = loadMeasurementUnitOptions()
.then(options => {
this.quantityUnitOptions = options.map(item => item.value);
return this.quantityUnitOptions;
})
.finally(() => {
this.quantityUnitLoading = false;
});
return this.quantityUnitLoadPromise;
},
async load() {
if (!this.id) return;
this.loading = true;
@@ -597,10 +597,10 @@ import { fallbackPlugin, imagePlugin, officePlugin, pdfPlugin, textPlugin } from
import '@open-file-viewer/core/style.css';
import pdfWorkerSrc from 'pdfjs-dist/build/pdf.worker.mjs?url';
import { mapGetters } from 'vuex';
import { loadMeasurementUnitOptions } from '@/utils/measurement-unit';
const AMAP_KEY = '653b7cf105ad7fb8ec9b2f5198ade315';
const AMAP_SECURITY_CODE = '5aab65c632e48ebae0d0e28f5b28e21a';
const quantityUnitOptions = ['吨', '件', '方'];
const attachmentViewerPlugins = [
imagePlugin(),
pdfPlugin({ workerSrc: pdfWorkerSrc, useFetchData: true }),
@@ -703,10 +703,11 @@ export default {
'zip',
],
transports: ['公路运输', '铁路运输', '水路运输', '航空运输'],
quantityUnitOptions,
quantityUnitOptions: [],
quantityUnitLoading: false,
form: {
routes: [{ segmentNo: '段1', transportType: '' }],
goods: [{ quantityUnit: quantityUnitOptions[0] }],
goods: [{ quantityUnit: '吨' }],
attachmentsJson: '[]',
transportOrganizationType: '多式联运',
},
@@ -773,6 +774,7 @@ export default {
async mounted() {
this.loading = true;
try {
await this.loadQuantityUnitOptions();
await Promise.all([this.loadProjects(), this.loadCargoTypeOptions(), this.loadRegionOptions()]);
if (this.id) {
await this.loadDetail(this.id);
@@ -782,6 +784,22 @@ export default {
}
},
methods: {
defaultQuantityUnit() {
return this.quantityUnitOptions[0] || '吨';
},
loadQuantityUnitOptions() {
if (this.quantityUnitLoading) return this.quantityUnitLoadPromise;
this.quantityUnitLoading = true;
this.quantityUnitLoadPromise = loadMeasurementUnitOptions()
.then(options => {
this.quantityUnitOptions = options.map(item => item.value);
return this.quantityUnitOptions;
})
.finally(() => {
this.quantityUnitLoading = false;
});
return this.quantityUnitLoadPromise;
},
responseData(response) {
return response?.data?.data || response?.data || response || {};
},
@@ -958,7 +976,7 @@ export default {
finalTransportType: hasFinalSegment
? finalSegment.transportType
: detail.finalTransportType || '',
goods: detail.goods || [{ quantityUnit: quantityUnitOptions[0] }],
goods: detail.goods || [{ quantityUnit: this.defaultQuantityUnit() }],
attachmentsJson: detail.attachmentsJson || '[]',
transportOrganizationType: '多式联运',
};
@@ -1659,7 +1677,7 @@ export default {
'',
cargoTypePath,
quantity: row.quantity || '',
quantityUnit: row.quantityUnit || quantityUnitOptions[0],
quantityUnit: row.quantityUnit || this.defaultQuantityUnit(),
packageType: row.packageType || row.package || '',
brand: row.brand || '',
specification: row.specification || row.spec || '',
@@ -1785,7 +1803,7 @@ export default {
},
addGoods(index) {
this.form.goods.splice(index + 1, 0, {
quantityUnit: quantityUnitOptions[0],
quantityUnit: this.defaultQuantityUnit(),
materialCode: '',
deviceCode: '',
remark: '',
@@ -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 { loadMeasurementUnitOptions } from '@/utils/measurement-unit';
import { normalizeMapSearchResults, searchAmapPlaceText } from '@/utils/map-search';
import { downloadFileByUrl, downloadXls } from '@/utils/util';
import { isMobile } from '@/utils/validate';
@@ -1278,7 +1279,8 @@ export default {
cargoTypeRequest: null,
transportCargoRows: [],
packageOptions,
quantityUnitOptions: ['吨', '千克', '立方米', '立方', '公里', '单', '件', '车', '箱', '托盘'],
quantityUnitOptions: [],
quantityUnitLoading: false,
feeUnitOptions: [],
currencyOptions: [],
currencyLoading: false,
@@ -1525,11 +1527,25 @@ export default {
created() {
this.loadProjectOptions();
this.ensureCargoTypeOptions();
this.loadQuantityUnitOptions();
this.loadFeeUnitOptions();
this.loadCurrencyOptions();
if (this.isStandaloneFormPage) this.$nextTick(() => this.initStandaloneFormPage());
},
methods: {
loadQuantityUnitOptions() {
if (this.quantityUnitLoading) return this.quantityUnitLoadPromise;
this.quantityUnitLoading = true;
this.quantityUnitLoadPromise = loadMeasurementUnitOptions()
.then(options => {
this.quantityUnitOptions = options.map(item => item.value);
return this.quantityUnitOptions;
})
.finally(() => {
this.quantityUnitLoading = false;
});
return this.quantityUnitLoadPromise;
},
buildOption(option) {
const next = { ...option, column: (option.column || []).map(column => ({ ...column })) };
const tableOrder = this.config.tableColumnOrder || [];
@@ -2742,6 +2742,7 @@ import { packageOptions } from '@/option/business/common';
import { formatUpdateUserName } from '@/utils/audit';
import { getToken, isAdministrator } from '@/utils/auth';
import { normalizeMapSearchResults, searchAmapPlaceText } from '@/utils/map-search';
import { loadMeasurementUnitOptions } from '@/utils/measurement-unit';
import { applyTableMenuWidth } from '@/utils/table-menu';
import { downloadFileByUrl, downloadXls } from '@/utils/util';
import { isMobile } from '@/utils/validate';
@@ -3206,7 +3207,7 @@ export default {
billingCargoTypeLoading: false,
billingCargoTypeRequest: null,
packageOptions,
quantityUnitOptions: ['吨', '千克', '立方米', '件', '车', '箱', '托盘'],
quantityUnitOptions: [],
attachmentFileTypes: [
'pdf',
'bmp',
@@ -3245,6 +3246,7 @@ export default {
} else {
this.consumeTemplateCreatePayload();
}
this.loadTaskQuantityUnitOptions();
},
computed: {
...mapGetters(['permission', 'userInfo']),
@@ -4307,8 +4309,6 @@ export default {
['contractName', '客户合同'],
['planName', '计划名称'],
['transportType', '运输方式'],
['planStartDate', '计划开始日期'],
['planEndDate', '计划结束日期'],
['departureAddress', '发货地址'],
['arrivalAddress', '收货地址'],
];
@@ -5076,15 +5076,40 @@ export default {
return Promise.resolve(this.taskQuantityUnitOptions);
}
this.taskQuantityUnitLoading = true;
return getDictionary({ code: 'quantity_unit' })
.then(res => {
this.taskQuantityUnitOptions = this.normalizeDictOptions(res.data.data || []);
return loadMeasurementUnitOptions()
.then(options => {
this.taskQuantityUnitOptions = options;
this.quantityUnitOptions = options.map(item => item.value);
this.ensureQuantityUnitOptions([
this.dispatchItemForm?.quantityUnit,
...(this.dispatchItemCargoRows || []).map(row => row.quantityUnit),
...(this.transportCargoRows || []).map(row => row.quantityUnit),
]);
return this.taskQuantityUnitOptions;
})
.finally(() => {
this.taskQuantityUnitLoading = false;
});
},
ensureQuantityUnitOptions(units = []) {
const pending = [...new Set(
(Array.isArray(units) ? units : [units])
.map(item => String(item || '').trim())
.filter(Boolean)
)];
if (!pending.length) return;
pending.forEach(unit => {
if (!this.quantityUnitOptions.includes(unit)) {
this.quantityUnitOptions = [...this.quantityUnitOptions, unit];
}
if (!this.taskQuantityUnitOptions.some(item => item.value === unit)) {
this.taskQuantityUnitOptions = [
...this.taskQuantityUnitOptions,
{ label: unit, value: unit },
];
}
});
},
loadTaskFeeUnitOptions() {
if (this.taskFeeUnitOptions.length || this.taskFeeUnitLoading) {
return Promise.resolve(this.taskFeeUnitOptions);
@@ -5146,6 +5171,9 @@ export default {
});
},
normalizeTransportCargoRow(row = {}) {
const quantityUnit = String(
row.quantityUnit || row.goodsQuantityUnit || row.unit || ''
).trim();
const cargo = {
...defaultTransportCargo(),
...row,
@@ -5162,10 +5190,14 @@ export default {
packageType: row.packageType || row.package || '',
specification: row.specification || row.spec || '',
freightAmount: row.freightAmount ?? row.amount ?? row.totalAmount ?? '',
quantityUnit,
};
['quantity', 'mileage', 'unitPrice', 'freightAmount'].forEach(prop => {
if (Number(cargo[prop]) === -1) cargo[prop] = '';
});
if (quantityUnit) {
this.ensureQuantityUnitOptions(quantityUnit);
}
return cargo;
},
hasMultipleConfiguredGoods(source = {}, rows = []) {
@@ -7594,6 +7626,9 @@ export default {
...baseRow,
...(index >= 0 ? row : {}),
};
this.dispatchItemForm.quantityUnit =
this.dispatchItemForm.quantityUnit || defaultUnit || TRANSPORT_PLAN_QUANTITY_UNIT;
this.ensureQuantityUnitOptions(this.dispatchItemForm.quantityUnit);
this.dispatchItemForm.otherFeeTotal = this.normalizeDispatchFeeValue(
this.dispatchItemForm.otherFeeTotal
);
@@ -7603,7 +7638,11 @@ export default {
});
const goodsRows = this.parseJsonArray(this.dispatchItemForm.goodsJson);
this.dispatchItemCargoRows = (goodsRows.length ? goodsRows : [this.dispatchItemForm]).map(
item => this.normalizeTransportCargoRow(item)
item =>
this.normalizeTransportCargoRow({
...item,
quantityUnit: item.quantityUnit || this.dispatchItemForm.quantityUnit || defaultUnit,
})
);
const hasMultiplePlanGoods = this.dispatchPlanGoodsRows.length > 1;
const shouldAutoFillFullGoods =
@@ -7613,11 +7652,18 @@ export default {
const unscheduledGoodsRows = this.getDispatchUnscheduledGoodsRows();
this.dispatchItemForm.taskEntryMode = 'full';
this.dispatchItemForm.quantity = '';
this.dispatchItemCargoRows = unscheduledGoodsRows.length
? unscheduledGoodsRows
: this.dispatchPlanGoodsRows.map(goods =>
this.normalizeTransportCargoRow({ ...goods, quantity: '' })
);
this.dispatchItemCargoRows = (
unscheduledGoodsRows.length
? unscheduledGoodsRows
: this.dispatchPlanGoodsRows.map(goods =>
this.normalizeTransportCargoRow({ ...goods, quantity: '' })
)
).map(goods =>
this.normalizeTransportCargoRow({
...goods,
quantityUnit: goods.quantityUnit || defaultUnit || TRANSPORT_PLAN_QUANTITY_UNIT,
})
);
}
if (!this.dispatchItemCargoRows.length) {
this.dispatchItemCargoRows = [this.normalizeTransportCargoRow()];
@@ -8309,7 +8355,10 @@ export default {
this.dispatchItemForm.cargoName ||
'',
quantity: quantityText,
quantityUnit: firstGoods.quantityUnit || this.dispatchItemForm.quantityUnit || '',
quantityUnit:
firstGoods.quantityUnit ||
this.dispatchItemForm.quantityUnit ||
TRANSPORT_PLAN_QUANTITY_UNIT,
unitPrice: firstGoods.unitPrice || '',
priceUnit: firstGoods.priceUnit || '',
freightTotal: this.dispatchItemFreightTotal,
@@ -8324,7 +8373,7 @@ export default {
unitPrice: cargo.unitPrice || '',
priceUnit: cargo.priceUnit || '',
quantity: cargo.quantity || '',
quantityUnit: cargo.quantityUnit || '',
quantityUnit: cargo.quantityUnit || firstGoods.quantityUnit || TRANSPORT_PLAN_QUANTITY_UNIT,
freightAmount: this.dispatchCargoFreightAmount(cargo),
})),
}),
@@ -8413,26 +8462,40 @@ export default {
return {
id: this.dispatchRow.id,
mode,
waybills: rows.map(row => ({
...row,
taskEntryMode: row.taskEntryMode || 'simple',
taskRemark: row.remark || '',
estimatedStartTime: this.formatDispatchDate(row.estimatedStartTime),
estimatedEndTime: this.formatDispatchDate(row.estimatedEndTime),
goodsJson:
row.goodsJson ||
JSON.stringify([
waybills: rows.map(row => {
const quantityUnit = this.getDispatchQuantityUnit(row);
let goodsJson = row.goodsJson;
const goodsRows = this.parseJsonArray(goodsJson);
if (goodsRows.length) {
goodsJson = JSON.stringify(
goodsRows.map(goods => ({
...goods,
quantityUnit: goods.quantityUnit || quantityUnit,
}))
);
} else {
goodsJson = JSON.stringify([
{
cargoName: row.cargoName,
cargoType: row.cargoType,
quantity: row.quantity,
quantityUnit: row.quantityUnit,
quantityUnit,
specification: row.specification,
model: row.model,
packageType: row.packageType,
},
]),
})),
]);
}
return {
...row,
quantityUnit,
taskEntryMode: row.taskEntryMode || 'simple',
taskRemark: row.remark || '',
estimatedStartTime: this.formatDispatchDate(row.estimatedStartTime),
estimatedEndTime: this.formatDispatchDate(row.estimatedEndTime),
goodsJson,
};
}),
};
},
formatDispatchDate(value) {
@@ -3100,6 +3100,7 @@ import { formatUpdateUserName } from '@/utils/audit';
import { getToken, isAdministrator } from '@/utils/auth';
import { openImportDialog } from '@/utils/import-excel';
import { normalizeMapSearchResults, searchAmapPlaceText } from '@/utils/map-search';
import { loadMeasurementUnitOptions } from '@/utils/measurement-unit';
import { applyTableMenuWidth } from '@/utils/table-menu';
import { downloadFileByUrl, downloadXls } from '@/utils/util';
import { isMobile } from '@/utils/validate';
@@ -3556,7 +3557,7 @@ export default {
cargoTypeLoading: false,
cargoTypeRequest: null,
packageOptions,
quantityUnitOptions: ['吨', '千克', '立方米', '件', '车', '箱', '托盘'],
quantityUnitOptions: [],
attachmentFileTypes: [
'pdf',
'bmp',
@@ -3613,7 +3614,6 @@ export default {
this.loadProjectOptions();
}
if (this.config.enableTaskInfoForm) {
this.loadTaskQuantityUnitOptions();
this.loadTaskFeeUnitOptions();
this.ensureCargoTypeOptions();
}
@@ -3628,6 +3628,7 @@ export default {
} else {
this.consumeTemplateCreatePayload();
}
this.loadTaskQuantityUnitOptions();
},
// 标签切走(实例被 keep-alive 缓存)或销毁时,收起本页所有 append-to-body 的弹窗。
// 否则 Teleport 出去的弹窗 DOM 会一直留在 body 上,盖住后续打开的页面。
@@ -6983,9 +6984,10 @@ export default {
return Promise.resolve(this.taskQuantityUnitOptions);
}
this.taskQuantityUnitLoading = true;
return getDictionary({ code: 'quantity_unit' })
.then(res => {
this.taskQuantityUnitOptions = this.normalizeDictOptions(res.data.data || []);
return loadMeasurementUnitOptions()
.then(options => {
this.taskQuantityUnitOptions = options;
this.quantityUnitOptions = options.map(item => item.value);
return this.taskQuantityUnitOptions;
})
.finally(() => {
+13 -4
View File
@@ -846,15 +846,15 @@
v-model="userQuery.realName"
placeholder="用户姓名"
clearable
@keyup.enter="loadUsers"
@keyup.enter="searchUsers"
/>
<el-input
v-model="userQuery.phone"
placeholder="手机号"
clearable
@keyup.enter="loadUsers"
@keyup.enter="searchUsers"
/>
<el-button type="primary" @click="loadUsers">查询</el-button>
<el-button type="primary" @click="searchUsers">查询</el-button>
<el-button @click="resetUserQuery">重置</el-button>
</div>
<el-table :data="userData" border v-loading="userLoading" @row-dblclick="selectUser">
@@ -2756,11 +2756,20 @@ export default {
if (this.dialogReadonly) return;
this.userPickType = type;
this.userBox = true;
this.userPage.currentPage = 1;
this.loadUsers();
},
searchUsers() {
this.userPage.currentPage = 1;
this.loadUsers();
},
loadUsers() {
this.userLoading = true;
getUserList(this.userPage.currentPage, this.userPage.pageSize, this.userQuery)
const params = {
realName: String(this.userQuery.realName || '').trim(),
phone: String(this.userQuery.phone || '').trim(),
};
getUserList(this.userPage.currentPage, this.userPage.pageSize, params)
.then(res => {
const result = res.data.data || {};
this.userData = result.records || [];
+51 -22
View File
@@ -585,6 +585,10 @@ export default {
this.$message.warning(`第${index + 1}条调度明细缺少车牌号`);
return;
}
if (!this.resolveDispatchQuantityUnit(row)) {
this.$message.warning(`第${index + 1}条调度明细缺少数量单位`);
return;
}
}
this.$confirm(
@@ -600,32 +604,57 @@ export default {
this.submitDispatch();
});
},
resolveDispatchQuantityUnit(row = {}) {
const topUnit = String(row.quantityUnit || '').trim();
if (topUnit) return topUnit;
const goodsList = this.parseGoodsList(row.goodsJson);
for (const goods of goodsList) {
const unit = String(
goods?.quantityUnit || goods?.goodsQuantityUnit || goods?.unit || ''
).trim();
if (unit) return unit;
}
return TRANSPORT_PLAN_QUANTITY_UNIT;
},
enrichDispatchGoodsJson(row = {}, quantityUnit) {
const goodsList = this.parseGoodsList(row.goodsJson);
if (goodsList.length) {
const nextGoods = goodsList.map(goods => ({
...goods,
quantityUnit: String(goods.quantityUnit || quantityUnit || '').trim() || quantityUnit,
}));
return JSON.stringify(nextGoods);
}
return JSON.stringify([
{
cargoName: row.cargoName,
cargoType: row.cargoType,
quantity: row.quantity,
quantityUnit,
specification: row.specification,
model: row.model,
packageType: row.packageType,
brand: row.brand,
},
]);
},
submitDispatch() {
this.saving = true;
const payload = {
id: this.planId,
mode: 'submit',
waybills: this.dispatchList.map(row => ({
...row,
taskEntryMode: row.taskEntryMode || 'simple',
taskRemark: row.remark || '',
estimatedStartTime: row.estimatedStartTime || this.planData.planStartDate || '',
estimatedEndTime: row.estimatedEndTime || this.planData.planEndDate || '',
goodsJson:
row.goodsJson ||
JSON.stringify([
{
cargoName: row.cargoName,
cargoType: row.cargoType,
quantity: row.quantity,
quantityUnit: row.quantityUnit,
specification: row.specification,
model: row.model,
packageType: row.packageType,
brand: row.brand,
},
]),
})),
waybills: this.dispatchList.map(row => {
const quantityUnit = this.resolveDispatchQuantityUnit(row);
return {
...row,
quantityUnit,
taskEntryMode: row.taskEntryMode || 'simple',
taskRemark: row.remark || '',
estimatedStartTime: row.estimatedStartTime || this.planData.planStartDate || '',
estimatedEndTime: row.estimatedEndTime || this.planData.planEndDate || '',
goodsJson: this.enrichDispatchGoodsJson(row, quantityUnit),
};
}),
};
dispatch(payload)
@@ -639,7 +668,7 @@ export default {
})
.catch(err => {
console.error('调度失败:', err);
this.$message.error('调度失败');
this.$message.error(err?.response?.data?.msg || err?.message || '调度失败');
})
.finally(() => {
this.saving = false;
+35 -38
View File
@@ -189,6 +189,20 @@ import func from '@/utils/func';
const escapeRegExp = value => value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
const ORGANIZATION_TYPE_DICT_CODE = 'organization_type';
/**
* 组织类型名称 → 字典数字键(兼容 dictKey 配成中文的情况)
*/
const ORGANIZATION_TYPE_CODE_BY_LABEL = {
集团: 1,
一级集团: 1,
事业部: 2,
二级事业部: 2,
部门: 3,
分公司: 4,
三级分公司: 4,
物流园区: 5,
承运商: 6,
};
export default {
data() {
@@ -755,7 +769,7 @@ export default {
.filter(Boolean);
this.resetDeptCategoryOptions();
if (list.length && !this.organizationTypeOptions.length) {
this.$message.warning('组织类型字典 dictKey 须为数字 1~6,当前无法解析,请检查业务字典配置');
this.$message.warning('组织类型字典无法解析,请检查业务字典 organization_type 的字典键配置');
}
// 字典异步返回后触发表格组织类型标签重绘
if (Array.isArray(this.data) && this.data.length) {
@@ -765,29 +779,31 @@ export default {
},
/**
* 业务字典项转下拉选项。后端 deptCategory 为 Integer,value 必须始终为数字。
* dictKey 为纯数字时直接使用;若配成中文则回退用 sort(1~6)作为层级码。
* 优先用数字 dictKey;若 dictKey 配成中文,则按名称映射已知编码。
*/
normalizeOrganizationTypeOption(item) {
const label = item?.dictValue ?? item?.label;
if (label === undefined || label === null || String(label).trim() === '') {
return null;
}
const labelText = String(label).trim();
const rawKey = item?.dictKey ?? item?.value;
const keyText = rawKey === undefined || rawKey === null ? '' : String(rawKey).trim();
const keyNumber = Number(keyText);
const isPureNumber = keyText !== '' && !Number.isNaN(keyNumber) && String(keyNumber) === keyText;
if (isPureNumber) {
return {
label: String(label),
label: labelText,
value: keyNumber,
};
}
// 兼容 dictKey 误配成中文:用排序号作为 1~6 层级码,保证下拉有数据且可提交 Integer
const sortNumber = Number(item?.sort);
if (!Number.isNaN(sortNumber) && sortNumber >= 1 && sortNumber <= 6) {
// dictKey / 名称 配成中文时,按标准名称映射到数字键
const codeByLabel =
ORGANIZATION_TYPE_CODE_BY_LABEL[labelText] ?? ORGANIZATION_TYPE_CODE_BY_LABEL[keyText];
if (codeByLabel) {
return {
label: String(label),
value: sortNumber,
label: labelText,
value: codeByLabel,
};
}
return null;
@@ -813,18 +829,25 @@ export default {
this.$message.warning('请选择组织类型');
return null;
}
if (typeof value === 'number' && !Number.isNaN(value)) {
return value;
}
const asNumber = Number(value);
if (!Number.isNaN(asNumber) && String(asNumber) === String(value).trim()) {
return asNumber;
}
// 容错:表单里残留了中文标签时,按字典项 label 反查数字编码
// 容错:表单里残留了中文标签时,按字典项 label / 标准名称反查数字编码
const matched = this.organizationTypeOptions.find(
item => String(item.label) === String(value) || this.isSameDeptCategory(item.value, value)
);
if (matched && typeof matched.value === 'number' && !Number.isNaN(matched.value)) {
return matched.value;
}
this.$message.warning('组织类型无效,请重新选择(字典键须为数字 1~6)');
const codeByLabel = ORGANIZATION_TYPE_CODE_BY_LABEL[String(value).trim()];
if (codeByLabel) {
return codeByLabel;
}
this.$message.warning('组织类型无效,请重新选择');
return null;
},
resetDeptCategoryOptions() {
@@ -860,35 +883,9 @@ export default {
const text = matched?.label || value;
return String(text).includes('承运商');
},
filterOrganizationTypeOptions(values) {
const valueSet = new Set(values.map(item => String(item)));
const filtered = this.organizationTypeOptions.filter(item => valueSet.has(String(item.value)));
// 字典 key 非数字时无法按历史层级码过滤,回退展示全部,避免下拉无选项
return filtered.length ? filtered : [...this.organizationTypeOptions];
},
updateDeptCategoryOptions() {
const categoryColumn = this.findColumn(this.option.column, 'deptCategory');
const parent = this.parentDept;
if (!parent || !categoryColumn) {
return;
}
let dicData = [];
if (this.isRootDept(parent)) {
dicData =
parent.deptName === '外部组织'
? this.filterOrganizationTypeOptions([6])
: this.filterOrganizationTypeOptions([1]);
} else if (Number(parent.deptCategory) === 1) {
dicData = this.filterOrganizationTypeOptions([2, 3]);
} else if ([2, 3].includes(Number(parent.deptCategory))) {
dicData = this.filterOrganizationTypeOptions([4, 5, 6]);
} else {
dicData = [...this.organizationTypeOptions];
}
categoryColumn.dicData = dicData;
if (!dicData.some(item => this.isSameDeptCategory(item.value, this.form.deptCategory))) {
this.form.deptCategory = dicData[0]?.value;
}
// 组织层级仅由 parentId 决定,组织类型不再按上级过滤,始终展示全部字典项
this.resetDeptCategoryOptions();
if (this.isCarrierDeptCategory(this.form.deptCategory)) {
this.openCarrierDialog();
}