✨ 完善配载确认、承运商组织过滤司机与地址展示
配载草稿确认生成正式单并优化详情/列表地址市区展示;运单调度等按承运商联系人所属组织筛选司机车辆;统一地图搜索结果与登录默认账号清理。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user