✨ 完善配载确认、承运商组织过滤司机与地址展示

配载草稿确认生成正式单并优化详情/列表地址市区展示;运单调度等按承运商联系人所属组织筛选司机车辆;统一地图搜索结果与登录默认账号清理。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-09-23 18:35:27 +08:00
parent 694fe1a433
commit a919781d4f
19 changed files with 1701 additions and 309 deletions
+556 -81
View File
@@ -289,29 +289,53 @@
<el-table-column label="承运类型" prop="carrierType" min-width="120" show-overflow-tooltip />
<el-table-column label="发货地" prop="departureAddress" min-width="180">
<template #default="{ row }">
<el-tooltip :content="formatFullRouteAddress(row, 'departure')" placement="top">
<el-tooltip
v-if="formatFullRouteAddress(row, 'departure')"
:content="formatFullRouteAddress(row, 'departure')"
placement="top"
:show-after="120"
>
<div class="loading-manage-page__address-cell">
{{ formatRouteAddress(row, 'departure') }}
</div>
</el-tooltip>
<div v-else class="loading-manage-page__address-cell">
{{ formatRouteAddress(row, 'departure') }}
</div>
</template>
</el-table-column>
<el-table-column label="途经地" prop="transitAddress" min-width="190">
<template #default="{ row }">
<el-tooltip :content="formatFullRouteAddress(row, 'transit')" placement="top">
<el-tooltip
v-if="formatFullRouteAddress(row, 'transit')"
:content="formatFullRouteAddress(row, 'transit')"
placement="top"
:show-after="120"
>
<div class="loading-manage-page__address-cell">
{{ formatRouteAddress(row, 'transit') }}
</div>
</el-tooltip>
<div v-else class="loading-manage-page__address-cell">
{{ formatRouteAddress(row, 'transit') }}
</div>
</template>
</el-table-column>
<el-table-column label="到货地" prop="arrivalAddress" min-width="180">
<template #default="{ row }">
<el-tooltip :content="formatFullRouteAddress(row, 'arrival')" placement="top">
<el-tooltip
v-if="formatFullRouteAddress(row, 'arrival')"
:content="formatFullRouteAddress(row, 'arrival')"
placement="top"
:show-after="120"
>
<div class="loading-manage-page__address-cell">
{{ formatRouteAddress(row, 'arrival') }}
</div>
</el-tooltip>
<div v-else class="loading-manage-page__address-cell">
{{ formatRouteAddress(row, 'arrival') }}
</div>
</template>
</el-table-column>
<el-table-column label="承运商" prop="carrierName" min-width="160" show-overflow-tooltip />
@@ -386,26 +410,74 @@
v-if="isStandalonePage"
class="loading-manage-dialog loading-manage-form-dialog loading-manage-form-page"
>
<div class="archive-page-form__title">{{
isStandaloneDetailPage ? detailPageTitle : formPageTitle
}}</div>
<div v-if="!isStandaloneDetailPage" class="archive-page-form__title">{{ formPageTitle }}</div>
<div v-loading="dialogLoading" class="loading-manage-dialog__body">
<div v-if="dialogReadonly" class="loading-detail">
<section-card title="配载单详情">
<div class="loading-detail__summary">
<div class="loading-detail__heading">配载单详情</div>
<div>{{ dialogForm.loadingNo || '-' }}</div>
<el-tag :type="detailStatusType">{{ statusText(dialogForm) }}</el-tag>
<el-tag type="primary">{{ transportTypeLabel(dialogForm.transportType) }}</el-tag>
</div>
<div class="loading-detail__task-row">
<div class="loading-detail__field">
<span class="loading-detail__label">任务信息</span
><span class="loading-detail__value">{{ taskSummary }}</span>
<div class="loading-detail__task-main">
<span class="loading-detail__label">任务信息</span>
<div class="loading-detail__task-grid">
<div class="loading-detail__field">
<span class="loading-detail__label">承运商</span>
<span class="loading-detail__value">{{ detailTaskField('carrierName') }}</span>
</div>
<div class="loading-detail__field">
<span class="loading-detail__label">司机</span>
<span class="loading-detail__value">{{ detailTaskField('driverName') }}</span>
</div>
<div class="loading-detail__field">
<span class="loading-detail__label">车牌号</span>
<span class="loading-detail__value">{{ detailTaskField('vehicleNo') }}</span>
</div>
<div class="loading-detail__field">
<span class="loading-detail__label">手机号</span>
<span class="loading-detail__value">{{ detailTaskField('driverPhone') }}</span>
</div>
<div class="loading-detail__field">
<span class="loading-detail__label">里程</span>
<span class="loading-detail__value">{{
detailTaskMileage ? `${detailTaskMileage} km` : '-'
}}</span>
</div>
<div class="loading-detail__field">
<span class="loading-detail__label">预计发货日期</span>
<span class="loading-detail__value">{{
detailTaskDate('estimatedStartDate')
}}</span>
</div>
<div class="loading-detail__field">
<span class="loading-detail__label">预计完成日期</span>
<span class="loading-detail__value">{{
detailTaskDate('estimatedEndDate')
}}</span>
</div>
<template v-if="isSelfCarrierDetail">
<div class="loading-detail__field">
<span class="loading-detail__label">挂车车牌号</span>
<span class="loading-detail__value">{{
detailTaskField('trailerVehicleNo')
}}</span>
</div>
<div class="loading-detail__field">
<span class="loading-detail__label">押运人</span>
<span class="loading-detail__value">{{ detailTaskField('escortName') }}</span>
</div>
<div class="loading-detail__field">
<span class="loading-detail__label">押运人手机号</span>
<span class="loading-detail__value">{{ detailTaskField('escortPhone') }}</span>
</div>
</template>
</div>
</div>
<div class="loading-detail__field">
<span class="loading-detail__label">附件</span
><span class="loading-detail__value"
<div class="loading-detail__field loading-detail__attachment-field">
<span class="loading-detail__label">附件</span>
<span class="loading-detail__value"
><el-link
v-for="file in detailAttachments"
:key="file.url || file.name"
@@ -441,7 +513,19 @@
>
</div>
<div class="loading-detail__step-address">
{{ node.displayAddress || formatAddressText(node.address) }}
<el-tooltip
v-if="formatDetailRouteTooltip(node)"
:content="formatDetailRouteTooltip(node)"
placement="top"
:show-after="120"
>
<span class="loading-detail__step-address-text">{{
formatDetailRouteShort(node)
}}</span>
</el-tooltip>
<span v-else class="loading-detail__step-address-text">{{
formatDetailRouteShort(node)
}}</span>
</div>
<div class="loading-detail__step-tags">
<el-tag v-for="tag in node.tags || []" :key="tag" size="small">{{ tag }}</el-tag>
@@ -1175,9 +1259,10 @@
</el-form-item>
<el-form-item label="里程(km)">
<el-input
v-model="dialogForm.mileage"
:model-value="normalizeLoadingMileage(dialogForm.mileage)"
clearable
placeholder="请输入"
:disabled="dialogReadonly"
@input="handleMileageInput"
/>
</el-form-item>
@@ -1408,6 +1493,7 @@ import { getList as getContractList } from '@/api/business/contract-manage';
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 { getList as getProcessConfigList } from '@/api/business/process-config';
import { getList as getCommonAddressList } from '@/api/base/common-address';
import {
@@ -1427,6 +1513,7 @@ const defaultCandidateTransportType = '';
const AMAP_KEY = '653b7cf105ad7fb8ec9b2f5198ade315';
const AMAP_SECURITY_CODE = '5aab65c632e48ebae0d0e28f5b28e21a';
const loadingCreateWaybillsStorageKey = 'loading-manage-create-waybills';
const loadingManageListRefreshKey = 'loading-manage-list-refresh';
let detailAmapLoader;
const createSearchForm = () => ({
@@ -1511,6 +1598,14 @@ const normalizeLoadingMileage = value => {
return value;
};
// 提交时未填写里程传 null,禁止自动填充 -1
const toSubmitMileage = value => {
const normalized = normalizeLoadingMileage(value);
if (normalized === '') return null;
const number = Number(normalized);
return Number.isFinite(number) ? number : null;
};
const createCopiedDialogForm = (detail = {}) => {
const form = createDialogForm();
Object.keys(form).forEach(prop => {
@@ -1691,6 +1786,12 @@ export default {
.join(' / ') || '-'
);
},
isSelfCarrierDetail() {
return String(this.dialogForm.carrierType || '').trim() === '自运';
},
detailTaskMileage() {
return normalizeLoadingMileage(this.dialogForm.mileage);
},
detailAttachments() {
return parseJsonArray(this.dialogForm.attachmentsJson || this.dialogForm.attachmentJson).map(
item =>
@@ -1748,12 +1849,29 @@ export default {
deactivated() {
this.closeInnerDialogs();
},
activated() {
if (this.isStandalonePage) return;
if (sessionStorage.getItem(loadingManageListRefreshKey)) {
sessionStorage.removeItem(loadingManageListRefreshKey);
this.loadTable();
}
},
beforeUnmount() {
this.closeInnerDialogs();
},
watch: {
$route(to, from) {
if (!this.isStandalonePage) return;
if (!this.isStandalonePage) {
// 从表单/详情返回列表时刷新,避免草稿确认后仍显示旧状态
if (
from?.path?.includes('/loading-manage/form') ||
from?.path?.includes('/loading-manage/detail')
) {
sessionStorage.removeItem(loadingManageListRefreshKey);
this.loadTable();
}
return;
}
this.syncStandaloneTagTitle();
if (this.isStandaloneDetailPage) {
// 记录变化或上一个页面不是详情态(例如从表单页/列表页进来)时重新拉取详情
@@ -1982,6 +2100,157 @@ export default {
value.includes('highway')
);
},
// 与总单详情一致:公路地址压缩为「市 区县」
formatRoadLocation(value) {
const text = String(value || '')
.replace(/[\//\s]+/g, '')
.trim();
if (!text) return '';
const parts = this.parseWaybillAddress(text);
let city = String(parts.city || '').trim();
let district = String(parts.district || '').trim();
let detail = String(parts.detailAddress || '').trim();
// 自治州/地区/盟下还有「XX市 + 区县」时,以市级作为展示主体
if (/(?:自治州|地区|盟)$/.test(city) && detail) {
const nestedCityMatch = detail.match(/^(.+?市)/);
if (nestedCityMatch) {
city = nestedCityMatch[1];
detail = detail.slice(nestedCityMatch[1].length);
const nestedDistrictMatch = detail.match(
/^(.+?(?:自治县|自治旗|林区|矿区|新区|开发区|区|县|旗))/
);
if (nestedDistrictMatch) district = nestedDistrictMatch[1];
} else if (!district) {
const nestedDistrictMatch = detail.match(
/^(.+?(?:自治县|自治旗|林区|矿区|新区|开发区|区|县|旗))/
);
if (nestedDistrictMatch) district = nestedDistrictMatch[1];
}
}
// 市级后紧跟区县,但 parse 未拆出时再补一次
if (city && !district && detail) {
const nestedDistrictMatch = detail.match(
/^(.+?(?:自治县|自治旗|林区|矿区|新区|开发区|区|县|旗))/
);
if (nestedDistrictMatch) district = nestedDistrictMatch[1];
}
if (city && district) {
// 兼容「梅县区」:命中「县」后还需保留后面的「区」
if (district.endsWith('县') && detail.startsWith(`${district}区`)) {
district = `${district}区`;
}
return `${city} ${district}`;
}
if (city) return city;
if (district) return district;
// 兜底压缩
const withoutProvince = text.replace(/^.*?(?:省|自治区|特别行政区)/, '');
const cityMatch = withoutProvince.match(/.+?(?:市|自治州|州(?!市)|盟(?!市)|地区)/);
if (!cityMatch) return text;
const fallbackCity = cityMatch[0];
const districtSource = withoutProvince.slice(fallbackCity.length);
const districtMatch = districtSource.match(
/^[^市州盟]*?(?:自治县|自治旗|林区|矿区|新区|开发区|区|县|旗)/
);
if (!districtMatch) {
// 州后面是县级市时,继续解析市 + 区
const nestedCityMatch = districtSource.match(/^(.+?市)/);
if (nestedCityMatch) {
const nestedDistrictSource = districtSource.slice(nestedCityMatch[1].length);
const nestedDistrictMatch = nestedDistrictSource.match(
/^[^市州盟]*?(?:自治县|自治旗|林区|矿区|新区|开发区|区|县|旗)/
);
if (nestedDistrictMatch) {
return `${nestedCityMatch[1]} ${nestedDistrictMatch[0]}`;
}
return nestedCityMatch[1];
}
return fallbackCity;
}
const fallbackDistrict = `${districtMatch[0]}${
districtSource.slice(districtMatch[0].length).startsWith('区') ? '区' : ''
}`;
return `${fallbackCity} ${fallbackDistrict}`;
},
isStationLikeName(value) {
const text = String(value || '').trim();
if (!text) return false;
return (
/(?:火车站|高铁站|客运站|货运站|港口|码头|机场|空港|航站楼)$/.test(text) ||
/(?<!市|州|盟|区|县|旗)站$/.test(text)
);
},
// 详情运输路线短展示:公路仅市区,非公路仅名称
formatDetailRouteShort(node = {}) {
const name = String(node.name || '').trim();
const address = String(node.address || '').trim();
const displayAddress = String(node.displayAddress || '').trim();
if (!this.isRoadTransportType(this.dialogForm.transportType)) {
if (name) return name;
if (this.isStationLikeName(displayAddress)) return displayAddress;
if (this.isStationLikeName(address)) return address;
return displayAddress || address || '-';
}
const enriched = this.enrichRouteNodeRegion(node);
// 合并节点地址与运单上的市/区字段,避免只解析到市
const region = this.mergeRouteAddressParts(enriched.cityName, enriched.districtName, '');
const source = this.mergeRouteAddressParts(
address,
displayAddress,
region,
enriched.name || name
);
const formatted = this.formatRoadLocation(source);
if (enriched.cityName && enriched.districtName) {
return `${enriched.cityName} ${enriched.districtName}`;
}
if (/(?:市|州|盟|区|县|旗)/.test(formatted)) return formatted;
return formatted || name || displayAddress || address || '-';
},
// 公路悬停展示完整地址;非公路不展示 tooltip
formatDetailRouteTooltip(node = {}) {
if (!this.isRoadTransportType(this.dialogForm.transportType)) return '';
const enriched = this.enrichRouteNodeRegion(node);
const region = this.mergeRouteAddressParts(enriched.cityName, enriched.districtName, '');
const full = this.mergeRouteAddressParts(
String(node.address || '').trim(),
String(node.displayAddress || '').trim(),
region,
String(node.name || enriched.name || '').trim()
);
const short = this.formatDetailRouteShort(node);
if (!full) return '';
return full === short ? '' : full;
},
// 按装/卸标签从已选运单回填市、区,补充 routeJson 中缺失的区县
enrichRouteNodeRegion(node = {}) {
const tags = Array.isArray(node.tags) ? node.tags : [];
const result = { cityName: '', districtName: '', name: '' };
for (const tag of tags) {
const text = String(tag || '');
const loadMatch = text.match(/^装(\d+)$/);
const unloadMatch = text.match(/^卸(\d+)$/);
const index = Number((loadMatch || unloadMatch)?.[1] || 0) - 1;
if (index < 0) continue;
const row = this.selectedWaybillRows[index];
if (!row) continue;
const prefix = loadMatch ? 'departure' : 'arrival';
result.cityName = String(
row[`${prefix}CityName`] || row[`${prefix}City`] || ''
).trim();
result.districtName = String(
row[`${prefix}DistrictName`] || row[`${prefix}District`] || ''
).trim();
result.name = String(row[`${prefix}Name`] || '').trim();
if (result.cityName || result.districtName || result.name) break;
}
return result;
},
splitText(value) {
if (!value) return [];
return String(value)
@@ -2019,37 +2288,72 @@ export default {
formatRouteAddress(row, type) {
const prefix =
type === 'departure' ? 'departure' : type === 'arrival' ? 'arrival' : 'transit';
if (type !== 'transit') {
const address = row[`${prefix}Address`];
const region = this.routeProvinceName(row, prefix);
const district = row[`${prefix}DistrictName`];
const source = this.mergeRouteAddressParts(region, district, address);
return this.formatProvinceCityDistrict(source) || '-';
if (type === 'transit') {
const values = this.splitText(row.transitAddress || row.transitName || '')
.map(value => this.formatListRouteShort(row, value, value))
.filter(value => value && value !== '-');
return [...new Set(values)].join(',') || '-';
}
const values = [
row[`${prefix}RegionName`],
row[`${prefix}DistrictName`],
row[`${prefix}Address`],
]
.filter(Boolean)
.flatMap(value => this.splitText(value))
.map(value => this.formatProvinceCityDistrict(value))
.filter(Boolean);
return [...new Set(values)].join(',') || '-';
const name = String(row[`${prefix}Name`] || '').trim();
const address = String(row[`${prefix}Address`] || '').trim();
const cityName = String(
row[`${prefix}CityName`] || row[`${prefix}City`] || ''
).trim();
const districtName = String(
row[`${prefix}DistrictName`] || row[`${prefix}District`] || ''
).trim();
if (
this.isRoadTransportType(row?.transportType) &&
cityName &&
districtName
) {
return `${cityName} ${districtName}`;
}
const region = this.mergeRouteAddressParts(
this.routeProvinceName(row, prefix),
cityName,
districtName
);
const source = this.mergeRouteAddressParts(region, '', address || name);
return this.formatListRouteShort(row, name, source || address || name);
},
formatFullRouteAddress(row, type) {
if (!this.isRoadTransportType(row?.transportType)) return '';
const prefix =
type === 'departure' ? 'departure' : type === 'arrival' ? 'arrival' : 'transit';
if (type !== 'transit') {
const address = row[`${prefix}Address`];
const region = this.routeProvinceName(row, prefix);
const district = row[`${prefix}DistrictName`];
return this.mergeRouteAddressParts(region, district, address) || '-';
if (type === 'transit') {
const values = this.splitText(row.transitAddress || '')
.map(value => String(value || '').trim())
.filter(Boolean);
const full = [...new Set(values)].join(',');
const short = this.formatRouteAddress(row, 'transit');
return full && full !== short ? full : '';
}
const values = [row.transitRegionName, row.transitDistrictName, row.transitAddress]
.filter(Boolean)
.flatMap(value => this.splitText(value));
return [...new Set(values)].join(',') || '-';
const name = String(row[`${prefix}Name`] || '').trim();
const address = String(row[`${prefix}Address`] || '').trim();
const region = this.mergeRouteAddressParts(
this.routeProvinceName(row, prefix),
row[`${prefix}CityName`] || row[`${prefix}City`],
row[`${prefix}DistrictName`] || row[`${prefix}District`]
);
const full = this.mergeRouteAddressParts(region, '', address || name);
const short = this.formatRouteAddress(row, type);
if (!full) return '';
return full === short ? '' : full;
},
// 列表短地址:公路「市 区县」,非公路仅名称
formatListRouteShort(row = {}, name = '', address = '') {
const rawName = String(name || '').trim();
const rawAddress = String(address || '').trim();
if (!this.isRoadTransportType(row?.transportType)) {
if (rawName) return rawName;
if (this.isStationLikeName(rawAddress)) return rawAddress;
return rawAddress || '-';
}
const source = rawAddress || rawName;
const formatted = this.formatRoadLocation(source);
if (/(?:市|州|盟)/.test(formatted)) return formatted;
return formatted || rawName || rawAddress || '-';
},
formatWaybillAddress(row, type) {
const prefix = type === 'departure' ? 'departure' : 'arrival';
@@ -2275,7 +2579,8 @@ export default {
this.dialogForm = row.copy
? createCopiedDialogForm(detail)
: { ...createDialogForm(), ...detail };
this.dialogForm.mileage = normalizeLoadingMileage(this.dialogForm.mileage);
this.applyLoadingTaskInfoFromDetail(this.dialogForm);
this.applyLoadingMileageFromDetail(this.dialogForm);
await this.restoreWaybillRows(
this.dialogForm.waybillIdsJson,
this.dialogForm.loadingSubNos
@@ -2612,6 +2917,8 @@ export default {
...this.buildQueryParams(this.candidateQuery),
businessStatus: 'pending',
onlyUnassignedLoading: 1,
// 仅查当前账户所属组织及子组织下的运单
allDept: 0,
...(this.dialogMode === 'add' ? { transportType: 'road' } : {}),
};
const transportType = this.getCandidateTransportTypeQuery(query.transportType);
@@ -2706,12 +3013,16 @@ export default {
.map(row => row.waybillNo)
.filter(Boolean)
.join(',');
this.dialogForm.waybillIdsJson = JSON.stringify(
rows
.map(row => row.id)
.filter(Boolean)
.map(id => String(id))
);
const rowIds = rows
.map(row => row.id)
.filter(Boolean)
.map(id => String(id));
// 回显失败仅有运单号时,保留原 waybillIdsJson,避免确认提交清空运单绑定
if (rowIds.length) {
this.dialogForm.waybillIdsJson = JSON.stringify(rowIds);
} else if (!String(this.dialogForm.waybillIdsJson || '').trim()) {
this.dialogForm.waybillIdsJson = '[]';
}
this.dialogForm.projectName = uniqueText(rows, 'projectName');
this.dialogForm.customerName = uniqueText(rows, 'customerName');
this.dialogForm.originalNo = uniqueText(rows, 'originalNo');
@@ -2838,29 +3149,95 @@ export default {
remark: item.remark || '',
};
},
applyLoadingMileageFromDetail(form = this.dialogForm) {
let taskMileage = '';
try {
const taskInfo = JSON.parse(form.taskInfoJson || '{}');
taskMileage = taskInfo?.mileage;
} catch (error) {
taskMileage = '';
}
form.mileage = normalizeLoadingMileage(
form.mileage !== undefined && form.mileage !== null && form.mileage !== ''
? form.mileage
: taskMileage
);
},
applyLoadingTaskInfoFromDetail(form = this.dialogForm) {
let taskInfo = {};
try {
taskInfo = JSON.parse(form.taskInfoJson || '{}') || {};
} catch (error) {
taskInfo = {};
}
if (!taskInfo || typeof taskInfo !== 'object' || Array.isArray(taskInfo)) return;
const fields = [
'carrierType',
'carrierName',
'carrierContractId',
'driverName',
'driverPhone',
'vehicleNo',
'trailerVehicleNo',
'escortName',
'escortPhone',
'estimatedStartDate',
'estimatedEndDate',
'taskRemark',
];
fields.forEach(prop => {
if (
(form[prop] === undefined || form[prop] === null || form[prop] === '') &&
taskInfo[prop] !== undefined &&
taskInfo[prop] !== null &&
taskInfo[prop] !== ''
) {
form[prop] = taskInfo[prop];
}
});
if (
(form.mileage === undefined || form.mileage === null || form.mileage === '') &&
taskInfo.mileage !== undefined
) {
form.mileage = normalizeLoadingMileage(taskInfo.mileage);
}
},
detailTaskField(prop) {
const value = String(this.dialogForm?.[prop] ?? '').trim();
return value || '-';
},
detailTaskDate(prop) {
const value = String(this.dialogForm?.[prop] ?? '').trim();
return value ? value.slice(0, 10) : '-';
},
normalizePayload() {
this.syncRouteAddressFields();
this.rebuildSummaryFromWaybills(false);
this.dialogForm.mileage = normalizeLoadingMileage(this.dialogForm.mileage);
const mileage = toSubmitMileage(this.dialogForm.mileage);
const taskInfo = {
carrierType: this.dialogForm.carrierType,
carrierName: this.dialogForm.carrierName,
carrierContractId: this.dialogForm.carrierContractId,
driverName: this.dialogForm.driverName,
driverPhone: this.dialogForm.driverPhone,
vehicleNo: this.dialogForm.vehicleNo,
trailerVehicleNo: this.dialogForm.trailerVehicleNo,
escortName: this.dialogForm.escortName,
escortPhone: this.dialogForm.escortPhone,
estimatedStartDate: this.dialogForm.estimatedStartDate,
estimatedEndDate: this.dialogForm.estimatedEndDate,
taskRemark: this.dialogForm.taskRemark,
};
if (mileage !== null) {
taskInfo.mileage = mileage;
}
return {
...this.dialogForm,
mileage,
routeJson: JSON.stringify(this.routeNodes),
goodsJson: JSON.stringify(this.cargoRows),
taskInfoJson: JSON.stringify({
carrierType: this.dialogForm.carrierType,
carrierName: this.dialogForm.carrierName,
carrierContractId: this.dialogForm.carrierContractId,
driverName: this.dialogForm.driverName,
driverPhone: this.dialogForm.driverPhone,
vehicleNo: this.dialogForm.vehicleNo,
trailerVehicleNo: this.dialogForm.trailerVehicleNo,
escortName: this.dialogForm.escortName,
escortPhone: this.dialogForm.escortPhone,
mileage: this.dialogForm.mileage,
estimatedStartDate: this.dialogForm.estimatedStartDate,
estimatedEndDate: this.dialogForm.estimatedEndDate,
taskRemark: this.dialogForm.taskRemark,
}),
taskInfoJson: JSON.stringify(taskInfo),
};
},
validatePayload(draftMode = false) {
@@ -2915,7 +3292,7 @@ export default {
return true;
},
async saveDraft() {
if (!this.validatePayload(true)) return;
// 暂存不做任何业务校验,直接保存
this.dialogSaving = 'draft';
try {
const res = await loadingApi.saveDraft(this.normalizePayload());
@@ -2923,7 +3300,7 @@ export default {
...this.dialogForm,
...(res.data?.data || res.data || {}),
};
this.dialogForm.mileage = normalizeLoadingMileage(this.dialogForm.mileage);
this.applyLoadingMileageFromDetail(this.dialogForm);
ElMessage.success('暂存成功');
this.loadTable();
} finally {
@@ -2934,7 +3311,11 @@ export default {
if (!this.validatePayload(false)) return;
this.dialogSaving = 'submit';
try {
const payload = this.normalizePayload();
const payload = {
...this.normalizePayload(),
// 确认生成正式配载单(待执行),覆盖草稿状态
businessStatus: 'pending',
};
if (this.dialogMode === 'reassign') {
await loadingApi.reassign(payload);
ElMessage.success('派单成功');
@@ -2942,6 +3323,7 @@ export default {
await loadingApi.submit(payload);
ElMessage.success('确认成功');
}
sessionStorage.setItem(loadingManageListRefreshKey, '1');
this.closeLoadingDialog();
this.loadTable();
} finally {
@@ -3027,6 +3409,7 @@ export default {
this.dialogForm.carrierContractId = '';
this.dialogForm.carrierName = '';
this.dialogForm.carrierId = '';
this.clearDialogDriverVehicleFields();
if (this.dialogForm.carrierType === '承运商') {
this.dialogForm.trailerVehicleNo = '';
this.dialogForm.escortName = '';
@@ -3046,6 +3429,23 @@ export default {
this.dialogForm.carrierContractId =
this.dialogForm.carrierType === '承运商' && contract?.id ? contract.id : '';
this.dialogForm.carrierName = contract?.carrierName || '';
this.dialogForm.carrierId = contract?.carrierId || contract?.customerId || '';
this.clearDialogDriverVehicleFields();
},
clearDialogDriverVehicleFields() {
this.dialogForm.driverName = '';
this.dialogForm.driverPhone = '';
this.dialogForm.vehicleNo = '';
this.dialogForm.trailerVehicleNo = '';
this.dialogForm.escortName = '';
this.dialogForm.escortPhone = '';
this.driverOptions = [];
},
getDialogCarrierFilter() {
return {
carrierId: this.dialogForm.carrierId || '',
carrierName: this.dialogForm.carrierName || '',
};
},
handleDriverChange(value) {
const driver = this.driverOptions.find(item => (item.driverName || item.name) === value);
@@ -3057,7 +3457,7 @@ export default {
}
},
fetchDriverSuggestions(queryString, callback) {
this.loadDriverOptions(String(queryString || '').trim())
this.loadDialogDriverOptions(String(queryString || '').trim())
.then(() =>
callback(
this.driverOptions.map(item => ({
@@ -3080,11 +3480,19 @@ export default {
},
fetchEscortSuggestions(queryString, callback) {
const keyword = String(queryString || '').trim();
const carrier = this.getDialogCarrierFilter();
if (!carrier.carrierId && !carrier.carrierName) {
callback([]);
return;
}
this.escortLoading = true;
getDriverList(1, 20, { ...(keyword ? { driverName: keyword } : {}), posts: '押运员' })
.then(res => {
fetchDriversByCarrierOrganizations(
{ size: 20, ...(keyword ? { driverName: keyword } : {}), posts: '押运员' },
carrier
)
.then(records => {
callback(
unwrapRecords(res).map(item => ({
records.map(item => ({
...item,
value: item.driverName || item.name || '',
}))
@@ -3104,6 +3512,7 @@ export default {
handleMileageInput(value) {
this.dialogForm.mileage = String(value || '').replace(/[^\d.]/g, '');
},
normalizeLoadingMileage,
transportTypeLabel(value) {
if (!value) return '-';
return (
@@ -3153,12 +3562,29 @@ export default {
this.driverLoading = true;
try {
this.driverOptions = unwrapRecords(
await getDriverList(1, 20, { driverName: keyword, posts: '司机' })
await getDriverList(1, 20, { ...(keyword ? { driverName: keyword } : {}), posts: '司机' })
);
} finally {
this.driverLoading = false;
}
},
async loadDialogDriverOptions(keyword = '') {
const carrier = this.getDialogCarrierFilter();
if (!carrier.carrierId && !carrier.carrierName) {
this.driverOptions = [];
return [];
}
this.driverLoading = true;
try {
this.driverOptions = await fetchDriversByCarrierOrganizations(
{ size: 20, ...(keyword ? { driverName: keyword } : {}), posts: '司机' },
carrier
);
return this.driverOptions;
} finally {
this.driverLoading = false;
}
},
async loadCarrierOptions(keyword = '') {
this.carrierLoading = true;
try {
@@ -3220,15 +3646,39 @@ export default {
} else {
this.dialogForm.carrierContractId = '';
this.dialogForm.carrierName = '';
this.dialogForm.carrierId = '';
}
return this.taskCarrierOptions;
}
if (this.dialogForm.carrierType === '自运' && contracts.length) {
this.taskCarrierOptions = contracts;
const current = contracts.find(item => item.carrierName === this.dialogForm.carrierName);
this.taskCarrierOptions = contracts
.map(item => ({
...item,
carrierName:
item.partyB ||
item.partyBName ||
item.contractPartyB ||
item.customerContractPartyB ||
item.carrierName ||
'',
carrierId:
item.partyBId ||
item.partyBUserId ||
item.contractPartyBId ||
item.customerContractPartyBId ||
item.carrierId ||
'',
}))
.filter(item => item.carrierName);
const current = this.taskCarrierOptions.find(
item => item.carrierName === this.dialogForm.carrierName
);
this.dialogForm.carrierContractId = '';
this.dialogForm.carrierName = current?.carrierName || contracts[0].carrierName || '';
return contracts;
this.dialogForm.carrierName =
current?.carrierName || this.taskCarrierOptions[0]?.carrierName || '';
this.dialogForm.carrierId =
current?.carrierId || this.taskCarrierOptions[0]?.carrierId || '';
return this.taskCarrierOptions;
}
if (this.dialogForm.carrierType !== '自运') {
this.taskCarrierOptions = [];
@@ -3262,6 +3712,7 @@ export default {
}));
this.dialogForm.carrierContractId = '';
this.dialogForm.carrierName = partyBNames[0] || '';
this.dialogForm.carrierId = '';
return this.taskCarrierOptions;
} catch (error) {
if (requestId !== this.taskCarrierRequestId) return [];
@@ -3611,11 +4062,6 @@ export default {
line-height: 28px;
}
}
.loading-detail__heading {
font-size: 20px;
font-weight: 600;
margin-right: 8px;
}
.loading-detail__route-title {
display: inline-flex;
align-items: center;
@@ -3627,9 +4073,24 @@ export default {
}
.loading-detail__task-row {
display: grid;
grid-template-columns: 1fr 1fr;
grid-template-columns: minmax(0, 1fr) 220px;
gap: 24px;
padding-bottom: 4px;
align-items: start;
}
.loading-detail__task-main {
display: flex;
flex-direction: column;
gap: 12px;
min-width: 0;
}
.loading-detail__task-grid {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 16px 24px;
}
.loading-detail__attachment-field {
min-width: 0;
}
.loading-detail__field {
display: flex;
@@ -3645,6 +4106,8 @@ export default {
flex-wrap: wrap;
gap: 8px;
text-align: left;
color: #303133;
word-break: break-all;
}
.loading-detail__steps {
display: flex;
@@ -3701,6 +4164,14 @@ export default {
overflow: hidden;
text-overflow: ellipsis;
}
.loading-detail__step-address-text {
display: inline-block;
max-width: 100%;
overflow: hidden;
text-overflow: ellipsis;
vertical-align: bottom;
cursor: default;
}
.loading-detail__step-tags {
display: flex;
justify-content: center;
@@ -3887,5 +4358,9 @@ export default {
.loading-detail__bottom-grid {
grid-template-columns: 1fr;
}
.loading-detail__task-grid {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
</style>