✨ 完善配载确认、承运商组织过滤司机与地址展示
配载草稿确认生成正式单并优化详情/列表地址市区展示;运单调度等按承运商联系人所属组织筛选司机车辆;统一地图搜索结果与登录默认账号清理。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -4007,7 +4007,7 @@
|
||||
</el-input>
|
||||
<div class="business-crud-page__dispatch-quantity-hint">
|
||||
剩余数量:{{
|
||||
formatDispatchQuantity(dispatchItemRemainingQuantity(dispatchItemForm))
|
||||
formatDispatchQuantity(dispatchItemHintRemainingQuantity(dispatchItemForm))
|
||||
}}
|
||||
{{ dispatchItemForm.quantityUnit || '吨' }}
|
||||
</div>
|
||||
@@ -4064,7 +4064,11 @@
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="其他费用合计">
|
||||
<el-input v-model="dispatchItemForm.otherFeeTotal" placeholder="请输入">
|
||||
<el-input
|
||||
v-model="dispatchItemForm.otherFeeTotal"
|
||||
placeholder="请输入"
|
||||
@input="value => handleDispatchNumberInput('otherFeeTotal', value)"
|
||||
>
|
||||
<template #suffix>{{ dispatchItemFreightCurrencyLabel }}</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
@@ -4996,8 +5000,8 @@
|
||||
</el-button>
|
||||
</div>
|
||||
<div class="map-picker-content">
|
||||
<map-search-results :results="transportMapSearchResults" @select="selectTransportMapSearchResult" />
|
||||
<div ref="transportAmap" class="business-crud-page__map"></div>
|
||||
<map-search-results :results="transportMapSearchResults" @select="selectTransportMapSearchResult" />
|
||||
</div>
|
||||
<div class="business-crud-page__map-info">
|
||||
<span>{{ transportMapStatus }}</span>
|
||||
@@ -5271,7 +5275,7 @@ import {
|
||||
getVoucherImages as getProcessConfigVoucherImages,
|
||||
} from '@/api/business/process-config';
|
||||
import { getList as getCarrierCustomerList } from '@/api/vehicle/customer-archive';
|
||||
import { getList as getDriverList } from '@/api/transportCapacity/driver';
|
||||
import { fetchDriversByCarrierOrganizations } from '@/utils/carrier-org-resource';
|
||||
import { getDictionary } from '@/api/system/dictbiz';
|
||||
import { getDictionary as getSystemDictionary } from '@/api/system/dict';
|
||||
import { addressTypeOptions } from '@/option/base/common-address';
|
||||
@@ -5279,6 +5283,7 @@ import { packageOptions } from '@/option/business/common';
|
||||
import { formatUpdateUserName } from '@/utils/audit';
|
||||
import { getToken } from '@/utils/auth';
|
||||
import { openImportDialog } from '@/utils/import-excel';
|
||||
import { normalizeMapSearchResults } from '@/utils/map-search';
|
||||
import { applyTableMenuWidth } from '@/utils/table-menu';
|
||||
import { downloadFileByUrl, downloadXls } from '@/utils/util';
|
||||
import { isMobile } from '@/utils/validate';
|
||||
@@ -6340,19 +6345,21 @@ export default {
|
||||
if (this.dispatchItemForm.taskEntryMode !== 'full') {
|
||||
const quantity = Number(this.dispatchItemForm.quantity || 0);
|
||||
const unitPrice = Number(this.dispatchItemForm.unitPrice || 0);
|
||||
const otherFeeTotal = Number(this.dispatchItemForm.otherFeeTotal || 0);
|
||||
const otherFeeRaw = this.normalizeDispatchFeeValue(this.dispatchItemForm.otherFeeTotal);
|
||||
const otherFeeTotal = Number(otherFeeRaw || 0);
|
||||
const hasFreight = this.dispatchItemForm.unitPrice !== '' && quantity > 0;
|
||||
const hasOtherFee = this.dispatchItemForm.otherFeeTotal !== '';
|
||||
const hasOtherFee = otherFeeRaw !== '';
|
||||
if (!hasFreight && !hasOtherFee) return '';
|
||||
const total = (hasFreight ? quantity * unitPrice : 0) + (hasOtherFee ? otherFeeTotal : 0);
|
||||
return Number.isInteger(total) ? String(total) : String(Number(total.toFixed(2)));
|
||||
}
|
||||
const otherFeeTotal = Number(this.dispatchItemForm.otherFeeTotal || 0);
|
||||
const otherFeeRaw = this.normalizeDispatchFeeValue(this.dispatchItemForm.otherFeeTotal);
|
||||
const otherFeeTotal = Number(otherFeeRaw || 0);
|
||||
const freightTotal = this.dispatchItemCargoRows.reduce(
|
||||
(total, cargo) => total + Number(this.dispatchCargoFreightAmount(cargo) || 0),
|
||||
0
|
||||
);
|
||||
const hasOtherFee = this.dispatchItemForm.otherFeeTotal !== '';
|
||||
const hasOtherFee = otherFeeRaw !== '';
|
||||
if (!freightTotal && !hasOtherFee) return '';
|
||||
const total = freightTotal + (hasOtherFee ? otherFeeTotal : 0);
|
||||
return Number.isInteger(total) ? String(total) : String(Number(total.toFixed(2)));
|
||||
@@ -8919,6 +8926,7 @@ export default {
|
||||
this.form.carrierContractId = '';
|
||||
}
|
||||
this.form.carrierType = nextValue;
|
||||
this.clearTaskDriverVehicleFields();
|
||||
if (this.isWaybillDetailLayout) {
|
||||
this.taskCarrierRequestId += 1;
|
||||
this.taskCarrierOptions = [];
|
||||
@@ -9036,25 +9044,58 @@ export default {
|
||||
},
|
||||
handleTaskCarrierChange(value) {
|
||||
const carrier = this.taskCarrierOptions.find(item =>
|
||||
[item.value, item.customerName, item.carrierName, item.fullName, item.name].some(name =>
|
||||
String(name || '') === String(value || '')
|
||||
)
|
||||
[
|
||||
item.value,
|
||||
item.customerName,
|
||||
item.carrierName,
|
||||
item.fullName,
|
||||
item.name,
|
||||
item.carrierContractId,
|
||||
].some(name => String(name || '') === String(value || ''))
|
||||
);
|
||||
if (this.isWaybillDetailLayout && this.form.carrierType === '承运商') {
|
||||
this.form.carrierContractId = carrier?.carrierContractId || '';
|
||||
this.form.carrierId = carrier?.carrierId || '';
|
||||
this.form.carrierName = carrier?.carrierName || value || '';
|
||||
return;
|
||||
} else {
|
||||
this.form.carrierId = carrier?.id || carrier?.carrierId || '';
|
||||
this.form.carrierName = value || '';
|
||||
}
|
||||
this.form.carrierId = carrier?.id || '';
|
||||
this.form.carrierName = value || '';
|
||||
this.clearTaskDriverVehicleFields();
|
||||
},
|
||||
clearTaskDriverVehicleFields() {
|
||||
this.form.driverId = '';
|
||||
this.form.driverName = '';
|
||||
this.form.driverPhone = '';
|
||||
this.form.vehicleNo = '';
|
||||
this.form.trailerVehicleNo = '';
|
||||
this.form.escortName = '';
|
||||
this.form.escortPhone = '';
|
||||
this.taskDriverOptions = [];
|
||||
},
|
||||
getActiveCarrierFilter() {
|
||||
if (this.dispatchItemBox) {
|
||||
return {
|
||||
carrierId: this.dispatchItemForm.carrierId || '',
|
||||
carrierName: this.dispatchItemForm.carrierName || '',
|
||||
};
|
||||
}
|
||||
return {
|
||||
carrierId: this.form.carrierId || '',
|
||||
carrierName: this.form.carrierName || '',
|
||||
};
|
||||
},
|
||||
loadTaskDriverOptions() {
|
||||
if (!this.taskInfoFormEnabled || this.taskDriverLoading) return Promise.resolve([]);
|
||||
const carrier = this.getActiveCarrierFilter();
|
||||
if (!carrier.carrierId && !carrier.carrierName) {
|
||||
this.taskDriverOptions = [];
|
||||
return Promise.resolve([]);
|
||||
}
|
||||
this.taskDriverLoading = true;
|
||||
return getDriverList(1, 9999, {})
|
||||
.then(res => {
|
||||
this.taskDriverOptions = extractRecords(res);
|
||||
return fetchDriversByCarrierOrganizations({ size: 9999, posts: '司机' }, carrier)
|
||||
.then(records => {
|
||||
this.taskDriverOptions = records;
|
||||
return this.taskDriverOptions;
|
||||
})
|
||||
.finally(() => {
|
||||
@@ -9063,10 +9104,17 @@ export default {
|
||||
},
|
||||
fetchTaskDriverSuggestions(queryString, callback) {
|
||||
const keyword = String(queryString || '').trim();
|
||||
const carrier = this.getActiveCarrierFilter();
|
||||
if (!carrier.carrierId && !carrier.carrierName) {
|
||||
callback([]);
|
||||
return;
|
||||
}
|
||||
this.taskDriverLoading = true;
|
||||
getDriverList(1, 20, keyword ? { driverName: keyword } : {})
|
||||
.then(res => {
|
||||
const records = extractRecords(res);
|
||||
fetchDriversByCarrierOrganizations(
|
||||
{ size: 20, ...(keyword ? { driverName: keyword } : {}), posts: '司机' },
|
||||
carrier
|
||||
)
|
||||
.then(records => {
|
||||
this.taskDriverOptions = records;
|
||||
callback(
|
||||
records.map(item => ({
|
||||
@@ -10239,12 +10287,7 @@ export default {
|
||||
this.ensureTransportAmapGeocoder()
|
||||
.then(() => this.runAmapGeocode('location', keyword))
|
||||
.then(result => {
|
||||
this.transportMapSearchResults = (result.geocodes || []).map((item, index) => ({
|
||||
id: item.id || index,
|
||||
name: item.formattedAddress || keyword,
|
||||
address: item.formattedAddress || keyword,
|
||||
location: item.location,
|
||||
}));
|
||||
this.transportMapSearchResults = normalizeMapSearchResults(result, keyword);
|
||||
const point = this.resolveMapPoint(result);
|
||||
if (!point) {
|
||||
this.transportMapStatus = '未找到匹配地址';
|
||||
@@ -12760,6 +12803,9 @@ export default {
|
||||
...baseRow,
|
||||
...(index >= 0 ? row : {}),
|
||||
};
|
||||
this.dispatchItemForm.otherFeeTotal = this.normalizeDispatchFeeValue(
|
||||
this.dispatchItemForm.otherFeeTotal
|
||||
);
|
||||
this.dispatchItemForm.carrierType = this.dispatchItemForm.carrierType || '承运商';
|
||||
const goodsRows = this.parseJsonArray(this.dispatchItemForm.goodsJson);
|
||||
this.dispatchItemCargoRows = (goodsRows.length ? goodsRows : [this.dispatchItemForm]).map(
|
||||
@@ -12816,6 +12862,7 @@ export default {
|
||||
}
|
||||
}
|
||||
this.dispatchItemForm.carrierType = nextValue;
|
||||
this.clearDispatchDriverVehicleFields();
|
||||
this.loadDispatchCarrierOptions(this.dispatchRow);
|
||||
},
|
||||
isDispatchCarrierRequired(carrierType) {
|
||||
@@ -12945,20 +12992,39 @@ export default {
|
||||
});
|
||||
if (this.dispatchIsCarrierMode) {
|
||||
this.dispatchItemForm.carrierContractId = carrier?.carrierContractId || '';
|
||||
this.dispatchItemForm.carrierId = '';
|
||||
this.dispatchItemForm.carrierId = carrier?.carrierId || '';
|
||||
this.dispatchItemForm.carrierName = carrier?.carrierName || '';
|
||||
return;
|
||||
} else {
|
||||
this.dispatchItemForm.carrierContractId = '';
|
||||
this.dispatchItemForm.carrierId = carrier?.id || carrier?.carrierId || '';
|
||||
this.dispatchItemForm.carrierName = carrier?.carrierName || value || '';
|
||||
}
|
||||
this.dispatchItemForm.carrierContractId = '';
|
||||
this.dispatchItemForm.carrierId = carrier?.id || '';
|
||||
this.dispatchItemForm.carrierName = carrier?.carrierName || value || '';
|
||||
this.clearDispatchDriverVehicleFields();
|
||||
},
|
||||
clearDispatchDriverVehicleFields() {
|
||||
this.dispatchItemForm.driverId = '';
|
||||
this.dispatchItemForm.driverName = '';
|
||||
this.dispatchItemForm.driverPhone = '';
|
||||
this.dispatchItemForm.vehicleNo = '';
|
||||
this.dispatchItemForm.trailerVehicleNo = '';
|
||||
this.dispatchItemForm.escortName = '';
|
||||
this.dispatchItemForm.escortPhone = '';
|
||||
this.taskDriverOptions = [];
|
||||
},
|
||||
loadDispatchDriverOptions() {
|
||||
if (this.taskDriverLoading) return Promise.resolve([]);
|
||||
const carrier = {
|
||||
carrierId: this.dispatchItemForm.carrierId || '',
|
||||
carrierName: this.dispatchItemForm.carrierName || '',
|
||||
};
|
||||
if (!carrier.carrierId && !carrier.carrierName) {
|
||||
this.taskDriverOptions = [];
|
||||
return Promise.resolve([]);
|
||||
}
|
||||
this.taskDriverLoading = true;
|
||||
return getDriverList(1, 9999, {})
|
||||
.then(res => {
|
||||
this.taskDriverOptions = extractRecords(res);
|
||||
return fetchDriversByCarrierOrganizations({ size: 9999, posts: '司机' }, carrier)
|
||||
.then(records => {
|
||||
this.taskDriverOptions = records;
|
||||
return this.taskDriverOptions;
|
||||
})
|
||||
.finally(() => {
|
||||
@@ -13035,6 +13101,11 @@ export default {
|
||||
this.dispatchItemForm[prop] =
|
||||
parts.length > 1 ? `${parts[0]}.${parts.slice(1).join('').slice(0, 2)}` : parts[0];
|
||||
},
|
||||
normalizeDispatchFeeValue(value) {
|
||||
if (value === undefined || value === null || value === '') return '';
|
||||
if (Number(value) === -1) return '';
|
||||
return value;
|
||||
},
|
||||
addDispatchCargoRow(index = -1) {
|
||||
const nextRow = this.normalizeTransportCargoRow({ priceUnit: '元/吨' });
|
||||
if (index > -1) this.dispatchItemCargoRows.splice(index + 1, 0, nextRow);
|
||||
@@ -13118,6 +13189,12 @@ export default {
|
||||
}, 0)
|
||||
);
|
||||
},
|
||||
/** 精简录入提示:可调度余量扣减当前本次数量后的剩余 */
|
||||
dispatchItemHintRemainingQuantity(row = {}) {
|
||||
const available = this.dispatchItemRemainingQuantity(row);
|
||||
const current = this.parseDispatchQuantity(row.quantity);
|
||||
return Math.max(available - current, 0);
|
||||
},
|
||||
pruneDispatchPendingRows(rows = []) {
|
||||
const planQuantities = new Map();
|
||||
this.dispatchPlanGoodsRows.forEach(goods => {
|
||||
@@ -13364,14 +13441,29 @@ export default {
|
||||
}),
|
||||
];
|
||||
const firstGoods = goodsRows[0] || {};
|
||||
const quantitySum = goodsRows.reduce(
|
||||
(sum, goods) => sum + this.parseDispatchQuantity(goods.quantity),
|
||||
0
|
||||
);
|
||||
const quantityText = quantitySum
|
||||
? this.formatDispatchQuantity(quantitySum)
|
||||
: firstGoods.quantity || this.dispatchItemForm.quantity || '';
|
||||
const otherFeeTotal = this.normalizeDispatchFeeValue(this.dispatchItemForm.otherFeeTotal);
|
||||
const nextRow = {
|
||||
...this.dispatchItemForm,
|
||||
goodsJson: JSON.stringify(goodsRows),
|
||||
cargoType: firstGoods.cargoType || this.dispatchItemForm.cargoType || '',
|
||||
cargoTypeCode: firstGoods.cargoTypeCode || this.dispatchItemForm.cargoTypeCode || '',
|
||||
cargoTypePath: firstGoods.cargoTypePath || this.dispatchItemForm.cargoTypePath || [],
|
||||
cargoName: firstGoods.cargoName || this.dispatchItemForm.cargoName || '',
|
||||
quantity: firstGoods.quantity || this.dispatchItemForm.quantity || '',
|
||||
cargoName:
|
||||
goodsRows
|
||||
.map(goods => goods.cargoName || goods.goodsName || '')
|
||||
.filter(Boolean)
|
||||
.join('.') ||
|
||||
firstGoods.cargoName ||
|
||||
this.dispatchItemForm.cargoName ||
|
||||
'',
|
||||
quantity: quantityText,
|
||||
quantityUnit: firstGoods.quantityUnit || this.dispatchItemForm.quantityUnit || '',
|
||||
unitPrice: firstGoods.unitPrice || '',
|
||||
priceUnit: firstGoods.priceUnit || '',
|
||||
@@ -13379,7 +13471,7 @@ export default {
|
||||
freightJson: JSON.stringify({
|
||||
currency: this.dispatchItemForm.freightCurrency || 'CNY',
|
||||
totalFreightAmount: this.dispatchItemFreightSubtotal,
|
||||
otherFreightAmount: this.dispatchItemForm.otherFeeTotal || '',
|
||||
otherFreightAmount: otherFeeTotal,
|
||||
freightItems: goodsRows.map((cargo, index) => ({
|
||||
cargoIndex: index,
|
||||
cargoName: cargo.cargoName || '',
|
||||
@@ -13395,7 +13487,7 @@ export default {
|
||||
};
|
||||
nextRow.cargoInfo = this.formatDispatchCargoInfo({}, nextRow);
|
||||
nextRow.freight = this.getDispatchFeeFields({}, nextRow).freight;
|
||||
nextRow.otherFeeTotal = this.dispatchItemForm.otherFeeTotal || '';
|
||||
nextRow.otherFeeTotal = otherFeeTotal;
|
||||
const quantityUnit = this.getDispatchQuantityUnit(nextRow);
|
||||
const otherDispatchedQuantity = this.dispatchRows.reduce((total, row, index) => {
|
||||
if (
|
||||
|
||||
@@ -156,11 +156,11 @@
|
||||
<el-row :gutter="16">
|
||||
<template v-if="isRoad(route)">
|
||||
<el-col :span="6"><el-form-item label="承运商" :required="route.carrierType !== '自运'"><el-select :model-value="route.carrierType !== '自运' ? route.carrierContractId : route.carrierName" filterable clearable placeholder="请选择" :loading="carrierLoading" @change="value => handleCarrierContractChange(route, value)"><el-option v-for="item in carrierOptions" :key="carrierOptionValue(item)" :label="carrierOptionLabel(item)" :value="route.carrierType !== '自运' ? carrierOptionValue(item) : item.carrierName" /></el-select></el-form-item></el-col>
|
||||
<el-col :span="6"><el-form-item label="司机" :required="route.carrierType !== '承运商'"><el-autocomplete :ref="element => setDriverInput(route.segmentNo, element)" v-model="route.driverName" :debounce="300" :fetch-suggestions="fetchDriverSuggestions" clearable placeholder="请输入司机" :loading="driverLoading" @select="item => handleDriverSuggestionSelect(route, item)" /></el-form-item></el-col>
|
||||
<el-col :span="6"><el-form-item label="司机" :required="route.carrierType !== '承运商'"><el-autocomplete :ref="element => setDriverInput(route.segmentNo, element)" v-model="route.driverName" :debounce="300" :fetch-suggestions="(query, cb) => fetchDriverSuggestions(route, query, cb)" clearable placeholder="请输入司机" :loading="driverLoading" @select="item => handleDriverSuggestionSelect(route, item)" /></el-form-item></el-col>
|
||||
<el-col :span="6"><el-form-item :label="route.carrierType === '承运商' ? '手机号' : '司机手机号'" :required="route.carrierType !== '承运商'"><el-input v-model="route.driverPhone" placeholder="请输入" /></el-form-item></el-col>
|
||||
<el-col :span="6"><el-form-item label="车牌号" required><el-input v-model="route.vehicleNo" placeholder="请输入" /></el-form-item></el-col>
|
||||
<el-col v-if="route.carrierType !== '承运商'" :span="6"><el-form-item label="挂车车牌号"><el-input v-model="route.trailerVehicleNo" placeholder="请输入" /></el-form-item></el-col>
|
||||
<el-col v-if="route.carrierType !== '承运商'" :span="6"><el-form-item label="押运人"><el-autocomplete v-model="route.escortName" :debounce="300" :fetch-suggestions="fetchEscortSuggestions" clearable placeholder="请输入" :loading="escortLoading" @select="item => handleEscortSuggestionSelect(route, item)" /></el-form-item></el-col>
|
||||
<el-col v-if="route.carrierType !== '承运商'" :span="6"><el-form-item label="押运人"><el-autocomplete v-model="route.escortName" :debounce="300" :fetch-suggestions="(query, cb) => fetchEscortSuggestions(route, query, cb)" clearable placeholder="请输入" :loading="escortLoading" @select="item => handleEscortSuggestionSelect(route, item)" /></el-form-item></el-col>
|
||||
<el-col v-if="route.carrierType !== '承运商'" :span="6"><el-form-item label="押运人手机号"><el-input v-model="route.escortPhone" placeholder="请输入" /></el-form-item></el-col>
|
||||
</template>
|
||||
<template v-else>
|
||||
@@ -233,7 +233,7 @@
|
||||
|
||||
<script>
|
||||
import * as api from '@/api/business/master-order';
|
||||
import { getList as getDriverList } from '@/api/transportCapacity/driver';
|
||||
import { fetchDriversByCarrierOrganizations } from '@/utils/carrier-org-resource';
|
||||
import { isMobile } from '@/utils/validate';
|
||||
|
||||
const unwrapRecords = res => {
|
||||
@@ -290,7 +290,7 @@ export default {
|
||||
},
|
||||
},
|
||||
async mounted() {
|
||||
await Promise.all([this.load(), this.loadCarrierOptions(), this.loadDriverOptions()]);
|
||||
await Promise.all([this.load(), this.loadCarrierOptions()]);
|
||||
this.applySelfOperatedCarrierDefaults();
|
||||
},
|
||||
methods: {
|
||||
@@ -727,6 +727,7 @@ export default {
|
||||
route.carrierName = '';
|
||||
route.carrierId = '';
|
||||
route.carrierContractId = '';
|
||||
this.clearRouteDriverVehicleFields(route);
|
||||
if (value === '承运商') {
|
||||
route.trailerVehicleNo = '';
|
||||
route.escortName = '';
|
||||
@@ -746,6 +747,24 @@ export default {
|
||||
route.carrierContractId = route.carrierType !== '自运' ? contract?.contractId || '' : '';
|
||||
route.carrierId = route.carrierType !== '自运' ? contract?.carrierId || '' : '';
|
||||
route.carrierName = contract?.carrierName || '';
|
||||
this.clearRouteDriverVehicleFields(route);
|
||||
},
|
||||
clearRouteDriverVehicleFields(route) {
|
||||
if (!route) return;
|
||||
route.driverId = '';
|
||||
route.driverName = '';
|
||||
route.driverPhone = '';
|
||||
route.vehicleNo = '';
|
||||
route.trailerVehicleNo = '';
|
||||
route.escortName = '';
|
||||
route.escortPhone = '';
|
||||
this.driverOptions = [];
|
||||
},
|
||||
getRouteCarrierFilter(route = {}) {
|
||||
return {
|
||||
carrierId: route.carrierId || '',
|
||||
carrierName: route.carrierName || '',
|
||||
};
|
||||
},
|
||||
carrierOptionValue(item = {}) {
|
||||
return item.contractId || item.id || item.value || item.carrierName || '';
|
||||
@@ -817,18 +836,30 @@ export default {
|
||||
route.carrierContractId = '';
|
||||
});
|
||||
},
|
||||
async loadDriverOptions(keyword = '') {
|
||||
async loadDriverOptions(route = {}, keyword = '') {
|
||||
const carrier = this.getRouteCarrierFilter(route);
|
||||
if (!carrier.carrierId && !carrier.carrierName) {
|
||||
this.driverOptions = [];
|
||||
return [];
|
||||
}
|
||||
this.driverLoading = true;
|
||||
try {
|
||||
this.driverOptions = unwrapRecords(
|
||||
await getDriverList(1, 20, { driverName: keyword, posts: '司机' })
|
||||
this.driverOptions = await fetchDriversByCarrierOrganizations(
|
||||
{ size: 20, ...(keyword ? { driverName: keyword } : {}), posts: '司机' },
|
||||
carrier
|
||||
);
|
||||
return this.driverOptions;
|
||||
} finally {
|
||||
this.driverLoading = false;
|
||||
}
|
||||
},
|
||||
fetchDriverSuggestions(queryString, callback) {
|
||||
this.loadDriverOptions(String(queryString || '').trim())
|
||||
fetchDriverSuggestions(route, queryString, callback) {
|
||||
const carrier = this.getRouteCarrierFilter(route);
|
||||
if (!carrier.carrierId && !carrier.carrierName) {
|
||||
callback([]);
|
||||
return;
|
||||
}
|
||||
this.loadDriverOptions(route, String(queryString || '').trim())
|
||||
.then(() =>
|
||||
callback(
|
||||
this.driverOptions.map(item => ({
|
||||
@@ -846,13 +877,21 @@ export default {
|
||||
const drivingVehicle = String(item.drivingVehicle || '').trim();
|
||||
if (drivingVehicle) route.vehicleNo = drivingVehicle;
|
||||
},
|
||||
fetchEscortSuggestions(queryString, callback) {
|
||||
fetchEscortSuggestions(route, queryString, callback) {
|
||||
const keyword = String(queryString || '').trim();
|
||||
const carrier = this.getRouteCarrierFilter(route);
|
||||
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: this.driverOptionLabel(item),
|
||||
}))
|
||||
|
||||
@@ -402,8 +402,8 @@
|
||||
>
|
||||
</div>
|
||||
<div class="map-picker-content">
|
||||
<map-search-results :results="mapSearchResults" @select="selectAddressMapSearchResult" />
|
||||
<div ref="addressMap" class="address-map" />
|
||||
<map-search-results :results="mapSearchResults" @select="selectAddressMapSearchResult" />
|
||||
</div>
|
||||
<div class="address-map-status">
|
||||
{{ mapStatus
|
||||
@@ -601,6 +601,7 @@ let amapLoader;
|
||||
import { getList as getCommonRouteList } from '@/api/business/common-route';
|
||||
import * as api from '@/api/business/master-order';
|
||||
import { formatUpdateUserName } from '@/utils/audit';
|
||||
import { normalizeMapSearchResults } from '@/utils/map-search';
|
||||
import { isMobile } from '@/utils/validate';
|
||||
export default {
|
||||
components: {
|
||||
@@ -1285,14 +1286,8 @@ export default {
|
||||
)
|
||||
)
|
||||
.then(result => {
|
||||
const geocodes = result?.geocodes || [];
|
||||
this.mapSearchResults = geocodes.map((item, index) => ({
|
||||
id: item.id || index,
|
||||
name: item.formattedAddress || this.mapKeyword,
|
||||
address: item.formattedAddress || this.mapKeyword,
|
||||
location: item.location,
|
||||
}));
|
||||
const point = geocodes[0]?.location;
|
||||
this.mapSearchResults = normalizeMapSearchResults(result, this.mapKeyword);
|
||||
const point = this.mapSearchResults[0]?.location;
|
||||
if (!point) return this.$message.warning('地图搜索无匹配地址');
|
||||
this.pickAddressMap(point, this.mapKeyword);
|
||||
})
|
||||
@@ -2199,8 +2194,15 @@ export default {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-bottom: 12px;
|
||||
|
||||
.el-input {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
}
|
||||
.address-map {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
height: 440px;
|
||||
}
|
||||
.address-map-status {
|
||||
|
||||
@@ -931,11 +931,11 @@
|
||||
/><el-button type="primary" @click="searchTransportMapKeyword">搜索</el-button>
|
||||
</div>
|
||||
<div class="map-picker-content">
|
||||
<div ref="transportMap" class="shipping-template-page__map" />
|
||||
<map-search-results
|
||||
:results="transportMapSearchResults"
|
||||
@select="selectTransportMapSearchResult"
|
||||
/>
|
||||
<div ref="transportMap" class="shipping-template-page__map" />
|
||||
</div>
|
||||
<div class="shipping-template-page__map-info">{{ transportMapStatus }}</div>
|
||||
<template #footer
|
||||
@@ -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 { normalizeMapSearchResults } from '@/utils/map-search';
|
||||
import { downloadFileByUrl, downloadXls } from '@/utils/util';
|
||||
import { isMobile } from '@/utils/validate';
|
||||
import { Location } from '@element-plus/icons-vue';
|
||||
@@ -2615,12 +2616,7 @@ export default {
|
||||
.then(() => this.ensureTransportMapGeocoder())
|
||||
.then(() => this.runTransportMapGeocode('location', keyword))
|
||||
.then(result => {
|
||||
this.transportMapSearchResults = (result.geocodes || []).map((item, index) => ({
|
||||
id: item.id || index,
|
||||
name: item.formattedAddress || keyword,
|
||||
address: item.formattedAddress || keyword,
|
||||
location: item.location,
|
||||
}));
|
||||
this.transportMapSearchResults = normalizeMapSearchResults(result, keyword);
|
||||
const point = this.resolveTransportMapPoint(result);
|
||||
if (!point) throw new Error('未找到匹配地址');
|
||||
return this.$nextTick().then(() => {
|
||||
@@ -3754,6 +3750,12 @@ export default {
|
||||
.shipping-template-page__map-toolbar {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-bottom: 12px;
|
||||
|
||||
.el-input {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
}
|
||||
.shipping-template-page__dialog-search {
|
||||
margin-bottom: 8px;
|
||||
@@ -3766,11 +3768,9 @@ export default {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
.shipping-template-page__map {
|
||||
flex: 1 1 auto;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
height: 480px;
|
||||
margin-top: 12px;
|
||||
}
|
||||
:deep(.shipping-template-page__map > .amap-container) {
|
||||
width: 100% !important;
|
||||
|
||||
@@ -637,13 +637,14 @@
|
||||
<span>|</span>
|
||||
{{ detailRow.planNo || '-' }}
|
||||
</h2>
|
||||
<span class="detail-status-text is-dispatching">
|
||||
<span class="detail-status-text" :class="detailStatusTextClass">
|
||||
{{ displayStatus(detailRow, 'businessStatus') }}
|
||||
</span>
|
||||
<span class="detail-status-text detail-transport-type">
|
||||
{{
|
||||
detailRow.transportTypeName ||
|
||||
formatTransportPlanDetailTransportType(detailRow.transportType)
|
||||
formatTransportPlanDetailTransportType(detailRow.transportType) ||
|
||||
'-'
|
||||
}}
|
||||
</span>
|
||||
</div>
|
||||
@@ -1231,7 +1232,7 @@
|
||||
{{ dispatchDialogStatusText }}
|
||||
</el-tag>
|
||||
<el-tag type="primary" effect="light">
|
||||
{{ formatDispatchTransportType(dispatchRow.transportType) || '公路运输' }}
|
||||
{{ formatDispatchTransportType(dispatchRow.transportType) || '-' }}
|
||||
</el-tag>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1931,7 +1932,7 @@
|
||||
class="transport-plan-page__dispatch-quantity-hint"
|
||||
>
|
||||
剩余数量:{{
|
||||
formatDispatchQuantity(dispatchItemRemainingQuantity(dispatchItemForm))
|
||||
formatDispatchQuantity(dispatchItemHintRemainingQuantity(dispatchItemForm))
|
||||
}}
|
||||
{{ dispatchItemForm.quantityUnit || '吨' }}
|
||||
</div>
|
||||
@@ -1997,7 +1998,11 @@
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="其他费用合计">
|
||||
<el-input v-model="dispatchItemForm.otherFeeTotal" placeholder="请输入">
|
||||
<el-input
|
||||
v-model="dispatchItemForm.otherFeeTotal"
|
||||
placeholder="请输入"
|
||||
@input="value => handleDispatchNumberInput('otherFeeTotal', value)"
|
||||
>
|
||||
<template #suffix>{{ dispatchItemFreightCurrencyLabel }}</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
@@ -2547,8 +2552,8 @@
|
||||
</el-button>
|
||||
</div>
|
||||
<div class="map-picker-content">
|
||||
<map-search-results :results="transportMapSearchResults" @select="selectTransportMapSearchResult" />
|
||||
<div ref="transportAmap" class="transport-plan-page__map"></div>
|
||||
<map-search-results :results="transportMapSearchResults" @select="selectTransportMapSearchResult" />
|
||||
</div>
|
||||
<div class="transport-plan-page__map-info">
|
||||
<span>{{ transportMapStatus }}</span>
|
||||
@@ -2729,13 +2734,14 @@ import {
|
||||
getList as getProjectList,
|
||||
} from '@/api/business/project-apply';
|
||||
import { getList as getCommonRouteList } from '@/api/business/common-route';
|
||||
import { getList as getDriverList } from '@/api/transportCapacity/driver';
|
||||
import { fetchDriversByCarrierOrganizations } from '@/utils/carrier-org-resource';
|
||||
import { getDictionary } from '@/api/system/dictbiz';
|
||||
import { getDictionary as getSystemDictionary } from '@/api/system/dict';
|
||||
import { addressTypeOptions } from '@/option/base/common-address';
|
||||
import { packageOptions } from '@/option/business/common';
|
||||
import { formatUpdateUserName } from '@/utils/audit';
|
||||
import { getToken } from '@/utils/auth';
|
||||
import { normalizeMapSearchResults } from '@/utils/map-search';
|
||||
import { applyTableMenuWidth } from '@/utils/table-menu';
|
||||
import { downloadFileByUrl, downloadXls } from '@/utils/util';
|
||||
import { isMobile } from '@/utils/validate';
|
||||
@@ -3284,6 +3290,23 @@ export default {
|
||||
isTransportPlanDetailLayout() {
|
||||
return this.detailBox;
|
||||
},
|
||||
detailStatusTextClass() {
|
||||
const status = this.detailRow?.businessStatus;
|
||||
if (status === 2 || status === '2' || status === 'completed') {
|
||||
return 'status-text-success';
|
||||
}
|
||||
if (status === 1 || status === '1' || status === 'dispatching') {
|
||||
return 'is-dispatching';
|
||||
}
|
||||
if (
|
||||
status === 3 ||
|
||||
status === '3' ||
|
||||
['rejected', 'change_rejected', 'cancelled', 'withdrawn'].includes(status)
|
||||
) {
|
||||
return 'is-danger';
|
||||
}
|
||||
return 'is-info';
|
||||
},
|
||||
transportPlanGoodsText() {
|
||||
const text = this.formatTransportPlanGoodsInfo(this.detailRow);
|
||||
return text && text !== '-' ? text : '暂无货物信息';
|
||||
@@ -3526,17 +3549,19 @@ export default {
|
||||
},
|
||||
dispatchItemFreightTotal() {
|
||||
if (this.dispatchItemForm.taskEntryMode !== 'full') {
|
||||
const otherFeeTotal = Number(this.dispatchItemForm.otherFeeTotal || 0);
|
||||
const otherFeeRaw = this.normalizeDispatchFeeValue(this.dispatchItemForm.otherFeeTotal);
|
||||
const otherFeeTotal = Number(otherFeeRaw || 0);
|
||||
const freight = this.dispatchItemFreightSubtotal;
|
||||
const hasFreight = freight !== '';
|
||||
const hasOtherFee = this.dispatchItemForm.otherFeeTotal !== '';
|
||||
const hasOtherFee = otherFeeRaw !== '';
|
||||
if (!hasFreight && !hasOtherFee) return '';
|
||||
const total = (hasFreight ? Number(freight) : 0) + (hasOtherFee ? otherFeeTotal : 0);
|
||||
return Number.isInteger(total) ? String(total) : String(Number(total.toFixed(2)));
|
||||
}
|
||||
const otherFeeTotal = Number(this.dispatchItemForm.otherFeeTotal || 0);
|
||||
const otherFeeRaw = this.normalizeDispatchFeeValue(this.dispatchItemForm.otherFeeTotal);
|
||||
const otherFeeTotal = Number(otherFeeRaw || 0);
|
||||
const freightSubtotal = this.dispatchItemFreightSubtotal;
|
||||
const hasOtherFee = this.dispatchItemForm.otherFeeTotal !== '';
|
||||
const hasOtherFee = otherFeeRaw !== '';
|
||||
if (freightSubtotal === '' && !hasOtherFee) return '';
|
||||
const total = Number(freightSubtotal || 0) + (hasOtherFee ? otherFeeTotal : 0);
|
||||
return Number.isInteger(total) ? String(total) : String(Number(total.toFixed(2)));
|
||||
@@ -4841,10 +4866,17 @@ export default {
|
||||
},
|
||||
fetchTaskDriverSuggestions(queryString, callback) {
|
||||
const keyword = String(queryString || '').trim();
|
||||
const carrier = this.getDispatchCarrierFilter();
|
||||
if (!carrier.carrierId && !carrier.carrierName) {
|
||||
callback([]);
|
||||
return;
|
||||
}
|
||||
this.taskDriverLoading = true;
|
||||
getDriverList(1, 20, { ...(keyword ? { driverName: keyword } : {}), posts: '司机' })
|
||||
.then(res => {
|
||||
const records = extractRecords(res);
|
||||
fetchDriversByCarrierOrganizations(
|
||||
{ size: 20, ...(keyword ? { driverName: keyword } : {}), posts: '司机' },
|
||||
carrier
|
||||
)
|
||||
.then(records => {
|
||||
this.taskDriverOptions = records;
|
||||
callback(
|
||||
records.map(item => ({
|
||||
@@ -4863,10 +4895,17 @@ export default {
|
||||
},
|
||||
fetchTaskEscortSuggestions(queryString, callback) {
|
||||
const keyword = String(queryString || '').trim();
|
||||
const carrier = this.getDispatchCarrierFilter();
|
||||
if (!carrier.carrierId && !carrier.carrierName) {
|
||||
callback([]);
|
||||
return;
|
||||
}
|
||||
this.taskEscortLoading = true;
|
||||
getDriverList(1, 20, { ...(keyword ? { driverName: keyword } : {}), posts: '押运员' })
|
||||
.then(res => {
|
||||
const records = extractRecords(res);
|
||||
fetchDriversByCarrierOrganizations(
|
||||
{ size: 20, ...(keyword ? { driverName: keyword } : {}), posts: '押运员' },
|
||||
carrier
|
||||
)
|
||||
.then(records => {
|
||||
this.taskEscortOptions = records;
|
||||
callback(records.map(item => ({ ...item, value: item.driverName || item.name || '' })));
|
||||
})
|
||||
@@ -5738,12 +5777,7 @@ export default {
|
||||
this.ensureTransportAmapGeocoder()
|
||||
.then(() => this.runAmapGeocode('location', keyword))
|
||||
.then(result => {
|
||||
this.transportMapSearchResults = (result.geocodes || []).map((item, index) => ({
|
||||
id: item.id || index,
|
||||
name: item.formattedAddress || keyword,
|
||||
address: item.formattedAddress || keyword,
|
||||
location: item.location,
|
||||
}));
|
||||
this.transportMapSearchResults = normalizeMapSearchResults(result, keyword);
|
||||
const point = this.resolveMapPoint(result);
|
||||
if (!point) {
|
||||
this.transportMapStatus = '未找到匹配地址';
|
||||
@@ -7241,11 +7275,13 @@ export default {
|
||||
freightInfo.freightItems?.[0]?.unitPrice ??
|
||||
'';
|
||||
const otherFeeTotal =
|
||||
item.otherFeeTotal ??
|
||||
plan.otherFeeTotal ??
|
||||
freightInfo.otherFeeTotal ??
|
||||
freightInfo.otherFreightAmount ??
|
||||
'';
|
||||
this.normalizeDispatchFeeValue(
|
||||
item.otherFeeTotal ??
|
||||
plan.otherFeeTotal ??
|
||||
freightInfo.otherFeeTotal ??
|
||||
freightInfo.otherFreightAmount ??
|
||||
''
|
||||
);
|
||||
const quantity = Number(
|
||||
item.quantity ||
|
||||
item.cargoQuantity ||
|
||||
@@ -7560,6 +7596,9 @@ export default {
|
||||
...baseRow,
|
||||
...(index >= 0 ? row : {}),
|
||||
};
|
||||
this.dispatchItemForm.otherFeeTotal = this.normalizeDispatchFeeValue(
|
||||
this.dispatchItemForm.otherFeeTotal
|
||||
);
|
||||
this.dispatchItemForm.carrierType = this.dispatchItemForm.carrierType || '承运商';
|
||||
this.loadDispatchCarrierOptions(this.dispatchRow).then(() => {
|
||||
this.applyDispatchSelfCarrierDefaults();
|
||||
@@ -7611,6 +7650,7 @@ export default {
|
||||
this.dispatchItemForm.carrierName = '';
|
||||
this.dispatchItemForm.carrierId = '';
|
||||
this.dispatchItemForm.carrierContractId = '';
|
||||
this.clearDispatchDriverVehicleFields();
|
||||
if (nextValue === '承运商') {
|
||||
this.dispatchItemForm.trailerVehicleNo = '';
|
||||
this.dispatchItemForm.escortName = '';
|
||||
@@ -7798,12 +7838,29 @@ export default {
|
||||
this.dispatchItemForm.carrierContractId = carrier?.carrierContractId || '';
|
||||
this.dispatchItemForm.carrierId = carrier?.carrierId || '';
|
||||
this.dispatchItemForm.carrierName = carrier?.carrierName || '';
|
||||
return;
|
||||
} else {
|
||||
this.dispatchItemForm.carrierContractId = '';
|
||||
this.dispatchItemForm.carrierId = '';
|
||||
this.dispatchItemForm.carrierName = carrier?.carrierName || value || '';
|
||||
}
|
||||
this.dispatchItemForm.carrierContractId = '';
|
||||
this.dispatchItemForm.carrierId =
|
||||
this.dispatchItemForm.carrierType === '自运' ? '' : carrier?.id || '';
|
||||
this.dispatchItemForm.carrierName = carrier?.carrierName || value || '';
|
||||
this.clearDispatchDriverVehicleFields();
|
||||
},
|
||||
clearDispatchDriverVehicleFields() {
|
||||
this.dispatchItemForm.driverId = '';
|
||||
this.dispatchItemForm.driverName = '';
|
||||
this.dispatchItemForm.driverPhone = '';
|
||||
this.dispatchItemForm.vehicleNo = '';
|
||||
this.dispatchItemForm.trailerVehicleNo = '';
|
||||
this.dispatchItemForm.escortName = '';
|
||||
this.dispatchItemForm.escortPhone = '';
|
||||
this.taskDriverOptions = [];
|
||||
this.taskEscortOptions = [];
|
||||
},
|
||||
getDispatchCarrierFilter() {
|
||||
return {
|
||||
carrierId: this.dispatchItemForm.carrierId || '',
|
||||
carrierName: this.dispatchItemForm.carrierName || '',
|
||||
};
|
||||
},
|
||||
handleDispatchCargoQuantityInput(row, value) {
|
||||
const text = String(value || '').replace(/[^\d.]/g, '');
|
||||
@@ -7820,6 +7877,11 @@ export default {
|
||||
this.dispatchItemForm[prop] =
|
||||
parts.length > 1 ? `${parts[0]}.${parts.slice(1).join('').slice(0, 2)}` : parts[0];
|
||||
},
|
||||
normalizeDispatchFeeValue(value) {
|
||||
if (value === undefined || value === null || value === '') return '';
|
||||
if (Number(value) === -1) return '';
|
||||
return value;
|
||||
},
|
||||
addDispatchCargoRow(index = -1) {
|
||||
const nextRow = this.normalizeTransportCargoRow({ priceUnit: '元/吨' });
|
||||
if (index > -1) this.dispatchItemCargoRows.splice(index + 1, 0, nextRow);
|
||||
@@ -7972,6 +8034,12 @@ export default {
|
||||
}, 0)
|
||||
);
|
||||
},
|
||||
/** 精简录入提示:可调度余量扣减当前本次数量后的剩余 */
|
||||
dispatchItemHintRemainingQuantity(row = {}) {
|
||||
const available = this.dispatchItemRemainingQuantity(row);
|
||||
const current = this.parseDispatchQuantity(row.quantity);
|
||||
return Math.max(available - current, 0);
|
||||
},
|
||||
pruneDispatchPendingRows(rows = []) {
|
||||
const planQuantities = new Map();
|
||||
this.dispatchPlanGoodsRows.forEach(goods => {
|
||||
@@ -8220,14 +8288,29 @@ export default {
|
||||
}),
|
||||
];
|
||||
const firstGoods = goodsRows[0] || {};
|
||||
const quantitySum = goodsRows.reduce(
|
||||
(sum, goods) => sum + this.parseDispatchQuantity(goods.quantity),
|
||||
0
|
||||
);
|
||||
const quantityText = quantitySum
|
||||
? this.formatDispatchQuantity(quantitySum)
|
||||
: firstGoods.quantity || this.dispatchItemForm.quantity || '';
|
||||
const otherFeeTotal = this.normalizeDispatchFeeValue(this.dispatchItemForm.otherFeeTotal);
|
||||
const nextRow = {
|
||||
...this.dispatchItemForm,
|
||||
goodsJson: JSON.stringify(goodsRows),
|
||||
cargoType: firstGoods.cargoType || this.dispatchItemForm.cargoType || '',
|
||||
cargoTypeCode: firstGoods.cargoTypeCode || this.dispatchItemForm.cargoTypeCode || '',
|
||||
cargoTypePath: firstGoods.cargoTypePath || this.dispatchItemForm.cargoTypePath || [],
|
||||
cargoName: firstGoods.cargoName || this.dispatchItemForm.cargoName || '',
|
||||
quantity: firstGoods.quantity || this.dispatchItemForm.quantity || '',
|
||||
cargoName:
|
||||
goodsRows
|
||||
.map(goods => goods.cargoName || goods.goodsName || '')
|
||||
.filter(Boolean)
|
||||
.join('.') ||
|
||||
firstGoods.cargoName ||
|
||||
this.dispatchItemForm.cargoName ||
|
||||
'',
|
||||
quantity: quantityText,
|
||||
quantityUnit: firstGoods.quantityUnit || this.dispatchItemForm.quantityUnit || '',
|
||||
unitPrice: firstGoods.unitPrice || '',
|
||||
priceUnit: firstGoods.priceUnit || '',
|
||||
@@ -8235,7 +8318,7 @@ export default {
|
||||
freightJson: JSON.stringify({
|
||||
currency: this.dispatchContractCurrency || this.dispatchItemForm.freightCurrency || 'RMB',
|
||||
totalFreightAmount: this.dispatchItemFreightSubtotal,
|
||||
otherFreightAmount: this.dispatchItemForm.otherFeeTotal || '',
|
||||
otherFreightAmount: otherFeeTotal,
|
||||
freightItems: goodsRows.map((cargo, index) => ({
|
||||
cargoIndex: index,
|
||||
cargoName: cargo.cargoName || '',
|
||||
@@ -8251,7 +8334,7 @@ export default {
|
||||
};
|
||||
nextRow.cargoInfo = this.formatDispatchCargoInfo({}, nextRow);
|
||||
nextRow.freight = this.dispatchItemFreightSubtotal;
|
||||
nextRow.otherFeeTotal = this.dispatchItemForm.otherFeeTotal || '';
|
||||
nextRow.otherFeeTotal = otherFeeTotal;
|
||||
const quantityUnit = this.getDispatchQuantityUnit(nextRow);
|
||||
const otherDispatchedQuantity = this.dispatchRows.reduce((total, row, index) => {
|
||||
if (
|
||||
@@ -9348,19 +9431,36 @@ export default {
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.el-tag) {
|
||||
height: 36px;
|
||||
padding: 0 14px;
|
||||
border: 0;
|
||||
.detail-status-text {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
height: 28px;
|
||||
padding: 0 12px;
|
||||
border-radius: 8px;
|
||||
font-size: 14px;
|
||||
line-height: 36px;
|
||||
font-size: 13px;
|
||||
line-height: 28px;
|
||||
}
|
||||
|
||||
:deep(.is-dispatching) {
|
||||
.status-text-success,
|
||||
.is-dispatching {
|
||||
color: #67c23a;
|
||||
background: #e1f3d8;
|
||||
}
|
||||
|
||||
.detail-transport-type {
|
||||
color: #409eff;
|
||||
background: #ecf5ff;
|
||||
}
|
||||
|
||||
.is-danger {
|
||||
color: #f56c6c;
|
||||
background: #fef0f0;
|
||||
}
|
||||
|
||||
.is-info {
|
||||
color: #909399;
|
||||
background: #f4f4f5;
|
||||
}
|
||||
}
|
||||
|
||||
&__transport-plan-detail-overview {
|
||||
|
||||
@@ -1939,12 +1939,17 @@
|
||||
}}</strong
|
||||
>
|
||||
</div>
|
||||
<div
|
||||
v-if="!waybillIsCarrier(detailRow)"
|
||||
class="waybill-manage-page__waybill-detail-field"
|
||||
>
|
||||
<div class="waybill-manage-page__waybill-detail-field">
|
||||
<span>里程(km)</span
|
||||
><strong>{{ normalizeMileageValue(detailRow.mileage) || '-' }}</strong>
|
||||
><strong>{{ waybillDetailMileage(detailRow) || '-' }}</strong>
|
||||
</div>
|
||||
<div class="waybill-manage-page__waybill-detail-field">
|
||||
<span>计划发货日期</span
|
||||
><strong>{{ waybillDetailEstimatedStartTime(detailRow) || '-' }}</strong>
|
||||
</div>
|
||||
<div class="waybill-manage-page__waybill-detail-field">
|
||||
<span>计划完成日期</span
|
||||
><strong>{{ waybillDetailEstimatedEndTime(detailRow) || '-' }}</strong>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else-if="waybillIsNonRoadTransport(detailRow)">
|
||||
@@ -1966,12 +1971,32 @@
|
||||
<div class="waybill-manage-page__waybill-detail-field">
|
||||
<span>仓位</span><strong>{{ detailRow.cabinNo || '-' }}</strong>
|
||||
</div>
|
||||
<div class="waybill-manage-page__waybill-detail-field">
|
||||
<span>预计发货日期</span
|
||||
><strong>{{ waybillDetailEstimatedStartTime(detailRow) || '-' }}</strong>
|
||||
</div>
|
||||
<div class="waybill-manage-page__waybill-detail-field">
|
||||
<span>预计完成日期</span
|
||||
><strong>{{ waybillDetailEstimatedEndTime(detailRow) || '-' }}</strong>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="waybill-manage-page__waybill-detail-field">
|
||||
<span>{{ waybillVehicleNoLabel(detailRow) }}</span>
|
||||
<strong>{{ detailRow.vehicleNo || '-' }}</strong>
|
||||
</div>
|
||||
<div class="waybill-manage-page__waybill-detail-field">
|
||||
<span>里程(km)</span
|
||||
><strong>{{ waybillDetailMileage(detailRow) || '-' }}</strong>
|
||||
</div>
|
||||
<div class="waybill-manage-page__waybill-detail-field">
|
||||
<span>计划发货日期</span
|
||||
><strong>{{ waybillDetailEstimatedStartTime(detailRow) || '-' }}</strong>
|
||||
</div>
|
||||
<div class="waybill-manage-page__waybill-detail-field">
|
||||
<span>计划完成日期</span
|
||||
><strong>{{ waybillDetailEstimatedEndTime(detailRow) || '-' }}</strong>
|
||||
</div>
|
||||
</template>
|
||||
<div
|
||||
class="waybill-manage-page__waybill-detail-field"
|
||||
@@ -2851,8 +2876,8 @@
|
||||
</el-button>
|
||||
</div>
|
||||
<div class="map-picker-content">
|
||||
<map-search-results :results="transportMapSearchResults" @select="selectTransportMapSearchResult" />
|
||||
<div ref="transportAmap" class="waybill-manage-page__map"></div>
|
||||
<map-search-results :results="transportMapSearchResults" @select="selectTransportMapSearchResult" />
|
||||
</div>
|
||||
<div class="waybill-manage-page__map-info">
|
||||
<span>{{ transportMapStatus }}</span>
|
||||
@@ -3112,6 +3137,9 @@ import {
|
||||
import { getPunchRecords as getWaybillPunchRecords, locateVehicle, trackVehicle } from '@/api/business/waybill-manage';
|
||||
import { getMkPublicDetail } from '@/api/mk-process';
|
||||
import { getList as getDriverList } from '@/api/transportCapacity/driver';
|
||||
import {
|
||||
fetchDriversByCarrierOrganizations,
|
||||
} from '@/utils/carrier-org-resource';
|
||||
import { getDictionary } from '@/api/system/dictbiz';
|
||||
import { getDictionary as getSystemDictionary } from '@/api/system/dict';
|
||||
import { addressTypeOptions } from '@/option/base/common-address';
|
||||
@@ -3120,11 +3148,12 @@ import { formatUpdateUserName } from '@/utils/audit';
|
||||
import { submitMkApprovalFlow } from '@/utils/mk-approval';
|
||||
import { getToken } from '@/utils/auth';
|
||||
import { openImportDialog } from '@/utils/import-excel';
|
||||
import { normalizeMapSearchResults } from '@/utils/map-search';
|
||||
import { applyTableMenuWidth } from '@/utils/table-menu';
|
||||
import { downloadFileByUrl, downloadXls } from '@/utils/util';
|
||||
import { isMobile } from '@/utils/validate';
|
||||
import { InfoFilled, Location, OfficeBuilding, Search, WarnTriangleFilled } from '@element-plus/icons-vue';
|
||||
import { ElImageViewer } from 'element-plus';
|
||||
import { ElAutocomplete, ElImageViewer } from 'element-plus';
|
||||
import { OpenFileViewer } from '@open-file-viewer/vue';
|
||||
import {
|
||||
fallbackPlugin,
|
||||
@@ -3634,6 +3663,7 @@ export default {
|
||||
this.detailPageLocked = this.isStandaloneWaybillDetailPage;
|
||||
this.formModeLocked = this.$route.query.mode || '';
|
||||
if (this.isPublicWaybillView) return;
|
||||
this.setupSearchAutocompletes();
|
||||
if (this.config.enableAllDept && this.isAdmin) {
|
||||
this.allDept = 1;
|
||||
}
|
||||
@@ -4292,8 +4322,12 @@ export default {
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
// 进行中的运单隐藏取消(与 config.canCancel / isInProgressStatus 对齐)
|
||||
if (typeof this.config.canCancel === 'function' && !this.config.canCancel(row)) {
|
||||
return false;
|
||||
}
|
||||
const status = this.statusValue(row);
|
||||
return ['pending', 'processing', 'running'].includes(status);
|
||||
return status === 'pending';
|
||||
},
|
||||
canComplete(row) {
|
||||
if (
|
||||
@@ -4524,6 +4558,42 @@ export default {
|
||||
waybillIsCarrier(row = {}) {
|
||||
return String(row.carrierTypeName || row.carrierType || '').trim() === '承运商';
|
||||
},
|
||||
waybillDetailTaskInfo(row = {}) {
|
||||
return this.parseJsonObject(row.taskInfoJson || row.taskInfo || {});
|
||||
},
|
||||
waybillDetailMileage(row = {}) {
|
||||
const taskInfo = this.waybillDetailTaskInfo(row);
|
||||
const goodsRows = this.parseJsonArray(row.goodsJson);
|
||||
const firstGoods = goodsRows[0] || {};
|
||||
return this.normalizeMileageValue(
|
||||
row.mileage || taskInfo.mileage || firstGoods.mileage || ''
|
||||
);
|
||||
},
|
||||
waybillDetailEstimatedStartTime(row = {}) {
|
||||
const taskInfo = this.waybillDetailTaskInfo(row);
|
||||
const value =
|
||||
row.estimatedStartTime ||
|
||||
row.planStartDate ||
|
||||
taskInfo.estimatedStartTime ||
|
||||
taskInfo.planStartDate ||
|
||||
'';
|
||||
return this.formatWaybillDetailDate(value);
|
||||
},
|
||||
waybillDetailEstimatedEndTime(row = {}) {
|
||||
const taskInfo = this.waybillDetailTaskInfo(row);
|
||||
const value =
|
||||
row.estimatedEndTime ||
|
||||
row.planEndDate ||
|
||||
taskInfo.estimatedEndTime ||
|
||||
taskInfo.planEndDate ||
|
||||
'';
|
||||
return this.formatWaybillDetailDate(value);
|
||||
},
|
||||
formatWaybillDetailDate(value) {
|
||||
const text = String(value || '').trim();
|
||||
if (!text) return '';
|
||||
return text.slice(0, 10);
|
||||
},
|
||||
waybillVehicleNoLabel(row = {}) {
|
||||
return this.waybillIsRoadTransport(row) ? '车牌号' : '船/航/班列号';
|
||||
},
|
||||
@@ -6398,6 +6468,15 @@ export default {
|
||||
this.form.carrierName = '';
|
||||
this.form.carrierId = '';
|
||||
this.form.carrierContractId = '';
|
||||
this.form.driverId = '';
|
||||
this.form.driverName = '';
|
||||
this.form.driverPhone = '';
|
||||
this.form.vehicleNo = '';
|
||||
this.form.trailerVehicleNo = '';
|
||||
this.form.escortName = '';
|
||||
this.form.escortPhone = '';
|
||||
this.taskDriverOptions = [];
|
||||
this.taskEscortOptions = [];
|
||||
if (nextValue === '承运商') {
|
||||
this.form.trailerVehicleNo = '';
|
||||
this.form.escortName = '';
|
||||
@@ -6651,17 +6730,36 @@ export default {
|
||||
this.form.carrierContractId = carrier?.carrierContractId || '';
|
||||
this.form.carrierId = carrier?.carrierId || '';
|
||||
this.form.carrierName = carrier?.carrierName || '';
|
||||
return;
|
||||
} else {
|
||||
this.form.carrierId = carrier?.carrierContractId ? '' : carrier?.id || '';
|
||||
this.form.carrierName = value || '';
|
||||
}
|
||||
this.form.carrierId = carrier?.carrierContractId ? '' : carrier?.id || '';
|
||||
this.form.carrierName = value || '';
|
||||
// 承运商变更后清空司机/车辆,避免跨组织脏数据
|
||||
this.form.driverId = '';
|
||||
this.form.driverName = '';
|
||||
this.form.driverPhone = '';
|
||||
this.form.vehicleNo = '';
|
||||
this.form.trailerVehicleNo = '';
|
||||
this.form.escortName = '';
|
||||
this.form.escortPhone = '';
|
||||
this.taskDriverOptions = [];
|
||||
this.taskEscortOptions = [];
|
||||
},
|
||||
getTaskCarrierFilter() {
|
||||
return {
|
||||
carrierId: this.form.carrierId || '',
|
||||
carrierName: this.form.carrierName || '',
|
||||
};
|
||||
},
|
||||
loadTaskDriverOptions() {
|
||||
if (!this.taskInfoFormEnabled || this.taskDriverLoading) return Promise.resolve([]);
|
||||
this.taskDriverLoading = true;
|
||||
return getDriverList(1, 9999, { posts: '司机' })
|
||||
.then(res => {
|
||||
this.taskDriverOptions = extractRecords(res);
|
||||
return fetchDriversByCarrierOrganizations(
|
||||
{ size: 9999, posts: '司机' },
|
||||
this.getTaskCarrierFilter()
|
||||
)
|
||||
.then(records => {
|
||||
this.taskDriverOptions = records;
|
||||
return this.taskDriverOptions;
|
||||
})
|
||||
.finally(() => {
|
||||
@@ -6670,10 +6768,17 @@ export default {
|
||||
},
|
||||
fetchTaskDriverSuggestions(queryString, callback) {
|
||||
const keyword = String(queryString || '').trim();
|
||||
const carrier = this.getTaskCarrierFilter();
|
||||
if (!carrier.carrierId && !carrier.carrierName) {
|
||||
callback([]);
|
||||
return;
|
||||
}
|
||||
this.taskDriverLoading = true;
|
||||
getDriverList(1, 20, { ...(keyword ? { driverName: keyword } : {}), posts: '司机' })
|
||||
.then(res => {
|
||||
const records = extractRecords(res);
|
||||
fetchDriversByCarrierOrganizations(
|
||||
{ size: 20, ...(keyword ? { driverName: keyword } : {}), posts: '司机' },
|
||||
carrier
|
||||
)
|
||||
.then(records => {
|
||||
this.taskDriverOptions = records;
|
||||
callback(
|
||||
records.map(item => ({
|
||||
@@ -6692,10 +6797,17 @@ export default {
|
||||
},
|
||||
fetchTaskEscortSuggestions(queryString, callback) {
|
||||
const keyword = String(queryString || '').trim();
|
||||
const carrier = this.getTaskCarrierFilter();
|
||||
if (!carrier.carrierId && !carrier.carrierName) {
|
||||
callback([]);
|
||||
return;
|
||||
}
|
||||
this.taskEscortLoading = true;
|
||||
getDriverList(1, 20, { ...(keyword ? { driverName: keyword } : {}), posts: '押运员' })
|
||||
.then(res => {
|
||||
const records = extractRecords(res);
|
||||
fetchDriversByCarrierOrganizations(
|
||||
{ size: 20, ...(keyword ? { driverName: keyword } : {}), posts: '押运员' },
|
||||
carrier
|
||||
)
|
||||
.then(records => {
|
||||
this.taskEscortOptions = records;
|
||||
callback(records.map(item => ({ ...item, value: item.driverName || item.name || '' })));
|
||||
})
|
||||
@@ -7835,12 +7947,7 @@ export default {
|
||||
this.ensureTransportAmapGeocoder()
|
||||
.then(() => this.runAmapGeocode('location', keyword))
|
||||
.then(result => {
|
||||
this.transportMapSearchResults = (result.geocodes || []).map((item, index) => ({
|
||||
id: item.id || index,
|
||||
name: item.formattedAddress || keyword,
|
||||
address: item.formattedAddress || keyword,
|
||||
location: item.location,
|
||||
}));
|
||||
this.transportMapSearchResults = normalizeMapSearchResults(result, keyword);
|
||||
const point = this.resolveMapPoint(result);
|
||||
if (!point) {
|
||||
this.transportMapStatus = '未找到匹配地址';
|
||||
@@ -8463,6 +8570,123 @@ export default {
|
||||
this.page.currentPage = 1;
|
||||
this.onLoad(this.page);
|
||||
},
|
||||
setupSearchAutocompletes() {
|
||||
const fields = [
|
||||
{
|
||||
prop: 'projectName',
|
||||
placeholder: '请选择或输入项目名称',
|
||||
fetch: (queryString, callback) =>
|
||||
this.fetchSearchNameSuggestions(queryString, callback, {
|
||||
request: keyword =>
|
||||
getProjectList(1, 20, keyword ? { projectName: keyword } : {}),
|
||||
getValue: item => item.projectName || item.name || '',
|
||||
}),
|
||||
},
|
||||
{
|
||||
prop: 'customerName',
|
||||
placeholder: '请选择或输入客户名称',
|
||||
fetch: (queryString, callback) =>
|
||||
this.fetchSearchNameSuggestions(queryString, callback, {
|
||||
request: keyword =>
|
||||
getCustomerArchiveList(1, 20, {
|
||||
...(keyword ? { fullName: keyword } : {}),
|
||||
customerType: '客户',
|
||||
}),
|
||||
getValue: item => item.fullName || item.customerName || item.name || '',
|
||||
}),
|
||||
},
|
||||
{
|
||||
prop: 'cargoType',
|
||||
placeholder: '请选择或输入货物类型',
|
||||
fetch: (queryString, callback) =>
|
||||
this.fetchSearchNameSuggestions(queryString, callback, {
|
||||
request: keyword =>
|
||||
getCargoTypeList(1, 20, keyword ? { cargoName: keyword } : {}),
|
||||
getValue: item => item.cargoName || item.typeName || item.name || item.label || '',
|
||||
}),
|
||||
},
|
||||
{
|
||||
prop: 'carrierName',
|
||||
placeholder: '请选择或输入承运商名称',
|
||||
fetch: (queryString, callback) =>
|
||||
this.fetchSearchNameSuggestions(queryString, callback, {
|
||||
request: keyword =>
|
||||
getCustomerArchiveList(1, 20, {
|
||||
...(keyword ? { fullName: keyword } : {}),
|
||||
customerType: '承运商',
|
||||
}),
|
||||
getValue: item =>
|
||||
item.fullName || item.carrierName || item.customerName || item.name || '',
|
||||
}),
|
||||
},
|
||||
{
|
||||
prop: 'driverName',
|
||||
placeholder: '请选择或输入司机名称',
|
||||
fetch: (queryString, callback) =>
|
||||
this.fetchSearchNameSuggestions(queryString, callback, {
|
||||
request: keyword =>
|
||||
getDriverList(1, 20, {
|
||||
...(keyword ? { driverName: keyword } : {}),
|
||||
posts: '司机',
|
||||
}),
|
||||
getValue: item => item.driverName || item.name || '',
|
||||
}),
|
||||
},
|
||||
{
|
||||
prop: 'planName',
|
||||
placeholder: '请选择或输入计划名称',
|
||||
fetch: (queryString, callback) =>
|
||||
this.fetchSearchNameSuggestions(queryString, callback, {
|
||||
request: keyword =>
|
||||
getShippingPlanList(1, 20, keyword ? { planName: keyword } : {}),
|
||||
getValue: item => item.planName || item.name || '',
|
||||
}),
|
||||
},
|
||||
];
|
||||
fields.forEach(field => {
|
||||
const column = this.findColumn?.(this.option.column, field.prop);
|
||||
if (!column) return;
|
||||
column.searchPlaceholder = field.placeholder;
|
||||
column.renderSearch = scope =>
|
||||
this.renderSearchAutocomplete(scope, field.prop, field.placeholder, field.fetch);
|
||||
});
|
||||
},
|
||||
renderSearchAutocomplete(scope, prop, placeholder, fetchSuggestions) {
|
||||
return h(ElAutocomplete, {
|
||||
modelValue: scope.row?.[prop] ?? '',
|
||||
'onUpdate:modelValue': value => {
|
||||
if (scope.row) scope.row[prop] = value ?? '';
|
||||
},
|
||||
fetchSuggestions,
|
||||
debounce: 300,
|
||||
clearable: true,
|
||||
triggerOnFocus: true,
|
||||
placeholder,
|
||||
style: 'width: 100%',
|
||||
valueKey: 'value',
|
||||
fitInputWidth: true,
|
||||
});
|
||||
},
|
||||
fetchSearchNameSuggestions(queryString, callback, { request, getValue }) {
|
||||
const keyword = String(queryString || '').trim();
|
||||
Promise.resolve(request(keyword))
|
||||
.then(res => {
|
||||
const records = extractRecords(res);
|
||||
const seen = new Set();
|
||||
const options = [];
|
||||
records.forEach(item => {
|
||||
const value = String(getValue(item) || '').trim();
|
||||
if (!value || seen.has(value)) return;
|
||||
seen.add(value);
|
||||
options.push({ ...item, value });
|
||||
});
|
||||
callback(options);
|
||||
})
|
||||
.catch(error => {
|
||||
window.console.log(error);
|
||||
callback([]);
|
||||
});
|
||||
},
|
||||
searchChange(params, done) {
|
||||
this.query = {
|
||||
...params,
|
||||
@@ -8728,10 +8952,21 @@ export default {
|
||||
},
|
||||
fetchReassignDriverSuggestions(queryString, callback) {
|
||||
const keyword = String(queryString || '').trim();
|
||||
const row = this.reassignDrawer.row || {};
|
||||
const carrier = {
|
||||
carrierId: row.carrierId || '',
|
||||
carrierName: row.carrierName || '',
|
||||
};
|
||||
if (!carrier.carrierId && !carrier.carrierName) {
|
||||
callback([]);
|
||||
return;
|
||||
}
|
||||
this.reassignDrawer.driverLoading = true;
|
||||
getDriverList(1, 20, { ...(keyword ? { driverName: keyword } : {}), posts: '司机' })
|
||||
.then(res => {
|
||||
const records = extractRecords(res);
|
||||
fetchDriversByCarrierOrganizations(
|
||||
{ size: 20, ...(keyword ? { driverName: keyword } : {}), posts: '司机' },
|
||||
carrier
|
||||
)
|
||||
.then(records => {
|
||||
this.reassignDriverOptions = records;
|
||||
callback(
|
||||
records.map(item => ({
|
||||
|
||||
Reference in New Issue
Block a user