1、新增结算模块

2、调整业务模块
3、调整客商模块
This commit is contained in:
2026-08-20 06:33:01 +08:00
parent 4199255186
commit fd5b1484e6
59 changed files with 11745 additions and 1104 deletions
@@ -63,7 +63,7 @@
<div class="route-node-form">
<el-form-item label="发货地址" required class="route-address-form-item"
><div class="route-address-inputs"
><el-input v-model="form.departureName" class="route-address-name" readonly placeholder="发货地址" @click="openAddressMap('departure')" /><el-input v-model="form.departureAddress" class="route-address-detail" readonly placeholder="请输入详细地址" @click="openAddressMap('departure')" /><el-tooltip content="选择常用地址" placement="top"><el-button class="address-picker-button" type="primary" link :icon="List" @click="openCommonAddress('departure')" /></el-tooltip></div
><el-cascader v-model="addressRegionPaths.departure" class="route-address-name" :options="regionOptions" :props="regionCascaderProps" :loading="regionLoading" placeholder="请选择省市区" clearable filterable @visible-change="visible => visible && loadRegionOptions()" @change="value => handleAddressRegionChange('departure', value)" /><el-input v-model="form.departureAddress" class="route-address-detail" readonly placeholder="请输入详细地址" @click="openAddressMap('departure')" /><el-tooltip content="选择常用地址" placement="top"><el-button class="address-picker-button" type="primary" link :icon="List" @click="openCommonAddress('departure')" /></el-tooltip></div
></el-form-item>
<el-form-item label="联系人"
><el-input v-model="form.departureContact" placeholder="请输入"
@@ -74,9 +74,9 @@
<el-form-item label="计划开始日期"
><el-date-picker
v-model="form.planStartTime"
type="datetime"
format="YYYY-MM-DD HH:mm"
value-format="YYYY-MM-DD HH:mm:ss"
type="date"
format="YYYY-MM-DD"
value-format="YYYY-MM-DD"
placeholder="请选择"
/></el-form-item>
</div>
@@ -84,12 +84,12 @@
</el-step>
<el-step v-for="(route, index) in form.routes" :key="index">
<template #icon><span class="route-step-icon route-step-icon--middle"></span></template>
<template #title><div class="route-step-title"><span class="route-segment-label">{{ index + 1 }}</span><span class="route-title-path">{{ getRouteTitle(index).replace(`${index + 1}`, '') }}</span><div class="route-transport-control"><span>运输方式</span><el-select v-model="route.transportType" placeholder="请选择"><el-option v-for="type in transports" :key="type" :label="type" :value="type" /></el-select></div></div></template>
<template #title><div class="route-step-title"><span class="route-segment-label">{{ index + 1 }}</span><el-tooltip :content="getRouteTitle(index).replace(`段${index + 1}`, '')" placement="top"><span class="route-title-path">{{ formatRouteTitle(getRouteTitle(index), index + 1) }}</span></el-tooltip><div class="route-transport-control"><span>运输方式</span><el-select v-model="route.transportType" placeholder="请选择"><el-option v-for="type in transports" :key="type" :label="type" :value="type" /></el-select></div></div></template>
<template #description>
<div class="route-node-form">
<el-form-item label="途经地" required class="route-address-form-item"
><div class="route-address-inputs"
><el-input v-model="route.departureName" class="route-address-name" readonly :disabled="!route.transportType" placeholder="请选择运输方式" @click="openAddressMap(`route-${index}`)" /><el-input v-model="route.departureAddress" class="route-address-detail" readonly :disabled="!route.transportType" placeholder="请先选择运输方式" @click="openAddressMap(`route-${index}`)" /><el-tooltip content="选择常用地址" placement="top"><el-button class="address-picker-button" type="primary" link :icon="List" :disabled="!route.transportType" @click="openCommonAddress(`route-${index}`)" /></el-tooltip></div
><el-cascader v-model="addressRegionPaths[`route-${index}`]" class="route-address-name" :options="regionOptions" :props="regionCascaderProps" :loading="regionLoading" :disabled="!route.transportType" placeholder="请选择省市区" clearable filterable @visible-change="visible => visible && loadRegionOptions()" @change="value => handleAddressRegionChange(`route-${index}`, value)" /><el-input v-model="route.departureAddress" class="route-address-detail" readonly :disabled="!route.transportType || !isRoadTransportType(route.transportType)" placeholder="请先选择运输方式" @click="openAddressMap(`route-${index}`)" /><el-tooltip content="选择常用地址" placement="top"><el-button class="address-picker-button" type="primary" link :icon="List" :disabled="!route.transportType" @click="openCommonAddress(`route-${index}`)" /></el-tooltip></div
></el-form-item>
<el-form-item label="联系人"><el-input v-model="route.departureContact" placeholder="请输入" /></el-form-item>
<el-form-item label="联系方式"><div class="route-contact-actions"><el-input v-model="route.departurePhone" placeholder="请输入" /><el-tooltip v-if="index === form.routes.length - 1" content="新增途经点" placement="top"><el-button class="route-action-button" type="primary" link @click="addRoute"><el-icon><CirclePlus /></el-icon></el-button></el-tooltip><el-tooltip content="删除途经点" placement="top"><el-button class="route-action-button" type="danger" link @click="removeRoute(index)"><el-icon><Remove /></el-icon></el-button></el-tooltip></div></el-form-item>
@@ -98,12 +98,12 @@
</el-step>
<el-step>
<template #icon><span class="route-step-icon route-step-icon--end"></span></template>
<template #title><div class="route-step-title"><span class="route-segment-label">{{ form.routes.length + 1 }}</span><span class="route-title-path">{{ getFinalRouteTitle().replace(`${form.routes.length + 1}`, '') }}</span><div class="route-transport-control"><span>运输方式</span><el-select v-model="form.finalTransportType" placeholder="请选择"><el-option v-for="type in transports" :key="type" :label="type" :value="type" /></el-select></div></div></template>
<template #title><div class="route-step-title"><span class="route-segment-label">{{ form.routes.length + 1 }}</span><el-tooltip :content="getFinalRouteTitle().replace(`段${form.routes.length + 1}`, '')" placement="top"><span class="route-title-path">{{ formatRouteTitle(getFinalRouteTitle(), form.routes.length + 1) }}</span></el-tooltip><div class="route-transport-control"><span>运输方式</span><el-select v-model="form.finalTransportType" placeholder="请选择"><el-option v-for="type in transports" :key="type" :label="type" :value="type" /></el-select></div></div></template>
<template #description>
<div class="route-node-form">
<el-form-item label="收货地址" required class="route-address-form-item"
><div class="route-address-inputs"
><el-input v-model="form.arrivalName" class="route-address-name" readonly :disabled="!form.finalTransportType" placeholder="请选择运输方式" @click="openAddressMap('arrival')" /><el-input v-model="form.arrivalAddress" class="route-address-detail" readonly :disabled="!form.finalTransportType" placeholder="请选择运输方式" @click="openAddressMap('arrival')" /><el-tooltip content="选择常用地址" placement="top"><el-button class="address-picker-button" type="primary" link :icon="List" :disabled="!form.finalTransportType" @click="openCommonAddress('arrival')" /></el-tooltip></div
><el-cascader v-model="addressRegionPaths.arrival" class="route-address-name" :options="regionOptions" :props="regionCascaderProps" :loading="regionLoading" :disabled="!form.finalTransportType" placeholder="请选择省市区" clearable filterable @visible-change="visible => visible && loadRegionOptions()" @change="value => handleAddressRegionChange('arrival', value)" /><el-input v-model="form.arrivalAddress" class="route-address-detail" readonly :disabled="!form.finalTransportType || !isRoadTransportType(form.finalTransportType)" placeholder="请选择省市区" @click="openAddressMap('arrival')" /><el-tooltip content="选择常用地址" placement="top"><el-button class="address-picker-button" type="primary" link :icon="List" :disabled="!form.finalTransportType" @click="openCommonAddress('arrival')" /></el-tooltip></div
></el-form-item>
<el-form-item label="联系人"
><el-input v-model="form.arrivalContact" placeholder="请输入" /></el-form-item>
@@ -112,9 +112,9 @@
<el-form-item label="计划结束日期"
><el-date-picker
v-model="form.planEndTime"
type="datetime"
format="YYYY-MM-DD HH:mm"
value-format="YYYY-MM-DD HH:mm:ss"
type="date"
format="YYYY-MM-DD"
value-format="YYYY-MM-DD"
placeholder="请选择"
/></el-form-item>
</div>
@@ -299,6 +299,31 @@
width="1100px"
@opened="loadAddressList"
>
<div class="route-dialog__search common-address-dialog__search">
<el-form :model="addressQuery" label-position="right" label-width="auto">
<el-row :gutter="16">
<el-col :span="6">
<el-form-item label="地址名称">
<el-input v-model="addressQuery.addressName" clearable placeholder="请输入" @keyup.enter="searchAddressList" />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="详细地址">
<el-input v-model="addressQuery.detailAddress" clearable placeholder="请输入" @keyup.enter="searchAddressList" />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="行政区划">
<el-input v-model="addressQuery.regionName" clearable placeholder="请输入" @keyup.enter="searchAddressList" />
</el-form-item>
</el-col>
<el-col :span="6" class="route-dialog__search-actions">
<el-button type="primary" @click="searchAddressList">查询</el-button>
<el-button @click="resetAddressList">重置</el-button>
</el-col>
</el-row>
</el-form>
</div>
<el-table v-loading="addressLoading" :data="addressRows" border height="360">
<el-table-column type="index" label="序号" width="70" />
<el-table-column prop="addressName" label="地址名称" min-width="150" />
@@ -386,10 +411,22 @@
<div class="address-pagination"><el-pagination v-model:current-page="stationPage.current" v-model:page-size="stationPage.size" :page-sizes="[10, 20, 50]" layout="total, prev, pager, next, sizes" :total="stationPage.total" @size-change="loadStationList" @current-change="loadStationList" /></div>
</el-dialog>
<el-dialog v-model="commonCargoVisible" title="选择常用货物" append-to-body width="1100px" @opened="loadCommonCargoList">
<el-table v-loading="commonCargoLoading" :data="commonCargoRows" border height="360">
<div class="route-dialog__search common-cargo-dialog__search">
<el-form :model="commonCargoQuery" label-position="right" label-width="86px">
<el-row :gutter="16">
<el-col :span="6"><el-form-item label="货物名称"><el-input v-model="commonCargoQuery.cargoName" clearable placeholder="请输入" /></el-form-item></el-col>
<el-col :span="6"><el-form-item label="货物类型"><el-cascader v-model="commonCargoQuery.cargoTypePath" :options="cargoTypeOptions" :props="cargoTypeCascaderProps" clearable filterable placeholder="请选择" :loading="cargoTypeLoading" @visible-change="visible => visible && loadCargoTypeOptions()" @change="handleCommonCargoTypeChange" /></el-form-item></el-col>
<el-col :span="6"><el-form-item label="规格型号"><el-input v-model="commonCargoQuery.specificationModel" clearable placeholder="请输入" /></el-form-item></el-col>
<el-col :span="6" class="route-dialog__search-actions"><el-button type="primary" @click="searchCommonCargoList">查询</el-button><el-button @click="resetCommonCargoList">重置</el-button></el-col>
</el-row>
</el-form>
</div>
<el-table v-loading="commonCargoLoading" :data="commonCargoRows" border height="360" @selection-change="handleCommonCargoSelectionChange">
<el-table-column type="selection" width="55" align="center" fixed="left" />
<el-table-column type="index" label="序号" width="70" /><el-table-column prop="cargoName" label="货物名称" min-width="150" /><el-table-column prop="cargoCode" label="货物编号" min-width="130" /><el-table-column prop="firstCargoTypeName" label="一级货物类型" min-width="150" /><el-table-column prop="secondCargoTypeName" label="二级货物类型" min-width="150" /><el-table-column prop="packageType" label="包装" min-width="100" /><el-table-column prop="brand" label="品牌" min-width="120" /><el-table-column prop="specification" label="规格" min-width="120" /><el-table-column prop="model" label="型号" min-width="120" /><el-table-column label="操作" width="100" fixed="right"><template #default="{ row }"><el-link type="primary" @click="selectCommonCargo(row)">选择</el-link></template></el-table-column>
</el-table>
<div class="address-pagination"><el-pagination v-model:current-page="commonCargoPage.current" v-model:page-size="commonCargoPage.size" :page-sizes="[10, 20, 50]" layout="total, prev, pager, next, sizes" :total="commonCargoPage.total" @size-change="loadCommonCargoList" @current-change="loadCommonCargoList" /></div>
<template #footer><el-button @click="commonCargoVisible = false">关闭</el-button><el-button type="primary" :disabled="!commonCargoSelected.length" @click="confirmCommonCargoSelection">确定</el-button></template>
</el-dialog>
<el-dialog v-model="cargoImportVisible" title="导入货物" append-to-body width="560px"><div class="cargo-import"><el-upload action="#" accept=".xls,.xlsx" :auto-upload="false" :show-file-list="false" :disabled="cargoImportLoading" :on-change="handleCargoImport"><el-button type="primary" :loading="cargoImportLoading">上传</el-button><template #tip><div class="el-upload__tip">支持 .xlsx、.xls 文件,单个文件不超过 50M</div></template></el-upload><el-button type="primary" plain @click="downloadCargoTemplate">点击下载模板</el-button></div><template #footer><el-button @click="cargoImportVisible = false">关闭</el-button></template></el-dialog>
<el-dialog v-model="routeDialogVisible" title="选择线路" width="1280px" @opened="loadRouteList">
@@ -485,6 +522,7 @@ import { getList as getAirportMasterList } from '@/api/base/airport-master';
import { getList as getPortTerminalList } from '@/api/base/port-terminal';
import { getList as getRailwayStationList } from '@/api/base/railway-station';
import { getList as getCargoTypeList } from '@/api/base/cargo-type';
import { getLazyTree as getRegionLazyTree } from '@/api/base/region';
import { getList as getCommonCargoList } from '@/api/business/common-cargo';
import { exportBlob, importBlob } from '@/api/common';
import { downloadFileByUrl, downloadXls } from '@/utils/util';
@@ -534,7 +572,12 @@ export default {
addressLoading: false,
addressTarget: '',
addressRows: [],
addressQuery: {},
addressPage: { current: 1, size: 10, total: 0 },
addressRegionPaths: { departure: [], arrival: [] },
regionOptions: [],
regionLoading: false,
regionRequest: null,
stationDialogVisible: false,
stationLoading: false,
stationQuery: {},
@@ -552,6 +595,8 @@ export default {
commonCargoVisible: false,
commonCargoLoading: false,
commonCargoRows: [],
commonCargoQuery: {},
commonCargoSelected: [],
commonCargoPage: { current: 1, size: 10, total: 0 },
cargoTypeLoading: false,
cargoTypeOptions: [],
@@ -625,11 +670,19 @@ export default {
emitPath: true,
};
},
regionCascaderProps() {
return {
value: 'id',
label: 'title',
children: 'children',
emitPath: true,
};
},
},
async mounted() {
this.loading = true;
try {
await Promise.all([this.loadProjects(), this.loadCargoTypeOptions()]);
await Promise.all([this.loadProjects(), this.loadCargoTypeOptions(), this.loadRegionOptions()]);
if (this.id) {
await this.loadDetail(this.id);
}
@@ -641,6 +694,118 @@ export default {
responseData(response) {
return response?.data?.data || response?.data || response || {};
},
async loadRegionOptions() {
if (this.regionOptions.length) return this.regionOptions;
if (this.regionRequest) return this.regionRequest;
this.regionLoading = true;
this.regionRequest = getRegionLazyTree()
.then(response => {
const data = response?.data?.data || response?.data || response || [];
this.regionOptions = this.extractChinaRegionOptions(this.buildRegionTree(data));
this.syncAddressRegionPaths();
return this.regionOptions;
})
.finally(() => {
this.regionLoading = false;
this.regionRequest = null;
});
return this.regionRequest;
},
buildRegionTree(regions = []) {
const flat = [];
const collect = list =>
(list || []).forEach(item => {
flat.push({ ...item, children: undefined });
if (item.children?.length) collect(item.children);
});
collect(regions);
const nodeMap = new Map();
flat.forEach(item => {
const id = item.id ?? item.code ?? item.value;
if (id === undefined || id === null) return;
nodeMap.set(String(id), {
...item,
id: String(id),
parentId: String(item.parentId ?? item.parentCode ?? ''),
children: [],
});
});
const roots = [];
nodeMap.forEach(node => {
const parent = nodeMap.get(node.parentId);
if (parent && parent !== node) parent.children.push(node);
else roots.push(node);
});
const normalize = node => {
const children = node.children.map(item => normalize(item));
return { ...node, title: node.title || node.name || '', children: children.length ? children : undefined, leaf: !children.length };
};
return roots.map(item => normalize(item));
},
extractChinaRegionOptions(regions = []) {
const china = regions.find(item => ['中国', '中华人民共和国'].includes(item.title));
if (china?.children?.length) return china.children;
if (regions.length === 1 && regions[0].children?.length) return regions[0].children;
return regions;
},
findRegionPathByName(options = [], regionName = '', parents = []) {
const target = String(regionName || '').replace(/\s+/g, '');
if (!target) return [];
for (const item of options) {
const nextParents = [...parents, item];
const name = nextParents.map(region => region.title || region.name || '').join('');
if (name === target) return nextParents.map(region => region.id);
const path = this.findRegionPathByName(item.children, target, nextParents);
if (path.length) return path;
}
return [];
},
findRegionPathByCode(options = [], regionCode = '', parents = []) {
const target = String(regionCode || '').trim();
if (!target) return [];
for (const item of options) {
const nextParents = [...parents, item];
if (String(item.id) === target) return nextParents.map(region => region.id);
const path = this.findRegionPathByCode(item.children, target, nextParents);
if (path.length) return path;
}
return [];
},
regionLabels(path = []) {
let options = this.regionOptions;
const labels = [];
path.forEach(value => {
const option = options.find(item => String(item.id) === String(value));
if (!option) return;
labels.push(option.title || option.name || '');
options = option.children || [];
});
return labels;
},
handleAddressRegionChange(target, value) {
const field = this.addressModel(target);
const path = Array.isArray(value) ? value.map(item => String(item)) : [];
this.addressRegionPaths = { ...this.addressRegionPaths, [target]: path };
if (!field) return;
const labels = this.regionLabels(path);
field.model[field.name] = labels.join('');
field.model[field.regionCode] = path.at(-1) || '';
},
syncAddressRegionPath(target) {
const field = this.addressModel(target);
if (!field) return;
const codePath = this.findRegionPathByCode(this.regionOptions, field.model[field.regionCode]);
const path = codePath.length
? codePath
: this.findRegionPathByName(this.regionOptions, field.model[field.name]);
this.addressRegionPaths = { ...this.addressRegionPaths, [target]: path };
},
syncAddressRegionPaths() {
if (!this.regionOptions.length) return;
['departure', 'arrival', ...this.form.routes.map((route, index) => `route-${index}`)].forEach(target => {
this.syncAddressRegionPath(target);
});
},
async loadDetail(id) {
const res = await api.getDetail(id);
const detail = this.responseData(res);
@@ -652,6 +817,8 @@ export default {
finalSegment.departureName === detail.arrivalName;
this.form = {
...detail,
planStartTime: this.normalizeDateValue(detail.planStartTime),
planEndTime: this.normalizeDateValue(detail.planEndTime),
routes: hasFinalSegment ? routes.slice(0, -1) : routes.length ? routes : [this.createRoute(0)],
finalTransportType: hasFinalSegment
? finalSegment.transportType
@@ -660,6 +827,7 @@ export default {
attachmentsJson: detail.attachmentsJson || '[]',
transportOrganizationType: '多式联运',
};
this.syncAddressRegionPaths();
this.restoreGoodsCargoTypePaths();
this.attachmentRows = this.parseJsonArray(this.form.attachmentsJson);
this.selectedAttachmentRows = [];
@@ -675,17 +843,54 @@ export default {
return [];
}
},
normalizeDateValue(value) {
if (!value) return '';
if (value instanceof Date) {
const year = value.getFullYear();
const month = String(value.getMonth() + 1).padStart(2, '0');
const day = String(value.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
}
return String(value).replace('T', ' ').slice(0, 10);
},
getRegionDisplay(target, fallback) {
const labels = this.regionLabels(this.addressRegionPaths[target] || []);
return labels.length ? labels.join('/') : fallback;
},
getRouteTitle(index) {
const departureName =
index === 0
? this.form.departureName || '发货地址'
: this.form.routes[index - 1].departureName || '途经地';
return `段${index + 1}${departureName} - ${this.form.routes[index].departureName || '途经地'}`;
? this.getRegionDisplay('departure', this.form.departureName || '发货地址')
: this.getRegionDisplay(
`route-${index - 1}`,
this.form.routes[index - 1].departureName || '途经地'
);
const arrivalName = this.getRegionDisplay(
`route-${index}`,
this.form.routes[index].departureName || '途经地'
);
return `段${index + 1}${departureName} → ${arrivalName}`;
},
truncateRouteAddress(value) {
const text = String(value || '');
return text.length > 6 ? `${text.slice(0, 6)}...` : text;
},
formatRouteTitle(title, segmentNo) {
const fullTitle = String(title || '').replace(`段${segmentNo}`, '');
const separator = ' → ';
const separatorIndex = fullTitle.indexOf(separator);
if (separatorIndex < 0) return this.truncateRouteAddress(fullTitle);
const departure = fullTitle.slice(0, separatorIndex);
const arrival = fullTitle.slice(separatorIndex + separator.length);
return `${this.truncateRouteAddress(departure)}${separator}${this.truncateRouteAddress(arrival)}`;
},
getFinalRouteTitle() {
return `段${this.form.routes.length + 1}${this.lastRouteName} - ${
this.form.arrivalName || '收货地址'
}`;
const departureName = this.getRegionDisplay(
`route-${this.form.routes.length - 1}`,
this.lastRouteName
);
const arrivalName = this.getRegionDisplay('arrival', this.form.arrivalName || '收货地址');
return `段${this.form.routes.length + 1}${departureName} → ${arrivalName}`;
},
responseRecords(response) {
const data = response?.data?.data || response?.data || response;
@@ -730,6 +935,7 @@ export default {
phone: 'departurePhone',
longitude: 'departureLongitude',
latitude: 'departureLatitude',
regionCode: 'departureRegionCode',
siteCode: 'departureSiteCode',
}
: null;
@@ -743,6 +949,7 @@ export default {
phone: `${prefix}Phone`,
longitude: `${prefix}Longitude`,
latitude: `${prefix}Latitude`,
regionCode: `${prefix}RegionCode`,
siteCode: `${prefix}SiteCode`,
};
},
@@ -759,6 +966,10 @@ export default {
if (type.includes('航空') || type.includes('空运')) return 'air';
return 'road';
},
isRoadTransportType(type) {
const value = String(type || '').trim().toLowerCase();
return value === 'road' || value.includes('公路');
},
openCommonAddress(target) {
this.addressTarget = target;
if (this.addressTransportMode(target) !== 'road') {
@@ -767,6 +978,7 @@ export default {
this.stationDialogVisible = true;
return;
}
this.addressQuery = {};
this.addressPage.current = 1;
this.addressDialogVisible = true;
},
@@ -814,12 +1026,14 @@ export default {
const target = this.addressModel(this.addressTarget);
if (!target) return;
const station = this.normalizeStation(row);
target.model[target.name] = station.stationName;
target.model[target.name] = station.regionName || station.stationName;
target.model[target.address] = station.detailAddress;
target.model[target.longitude] = station.longitude || '';
target.model[target.latitude] = station.latitude || '';
target.model[target.regionCode] = station.regionCode || '';
target.model[target.siteCode] = station.stationCode;
this.stationDialogVisible = false;
this.syncAddressRegionPath(this.addressTarget);
},
async loadAddressList() {
this.addressLoading = true;
@@ -827,7 +1041,7 @@ export default {
const response = await getCommonAddressList(
this.addressPage.current,
this.addressPage.size,
{ allDept: 0 }
{ ...this.addressQuery, allDept: 0 }
);
const data = response?.data?.data || response?.data || response || {};
this.addressRows = data.records || [];
@@ -836,18 +1050,33 @@ export default {
this.addressLoading = false;
}
},
searchAddressList() {
this.addressPage.current = 1;
this.loadAddressList();
},
resetAddressList() {
this.addressQuery = {};
this.addressPage.current = 1;
this.loadAddressList();
},
selectAddress(row) {
const target = this.addressModel(this.addressTarget);
if (!target) return;
target.model[target.name] = row.addressName || row.regionName || '';
target.model[target.name] = row.regionName || row.addressName || '';
target.model[target.address] = row.detailAddress || row.address || '';
target.model[target.contact] = row.contactName || target.model[target.contact] || '';
target.model[target.phone] = row.contactPhone || target.model[target.phone] || '';
target.model[target.longitude] = row.longitude || '';
target.model[target.latitude] = row.latitude || '';
target.model[target.regionCode] = row.regionCode || '';
this.addressDialogVisible = false;
this.syncAddressRegionPath(this.addressTarget);
},
openAddressMap(target) {
if ((target.startsWith('route-') || target === 'arrival') && !this.isRoadTransportType(this.addressTransportType(target))) {
this.$message.info('非公路运输地址不能使用地图选择');
return;
}
const field = this.addressModel(target);
if (!field) return;
this.mapTarget = target;
@@ -855,6 +1084,7 @@ export default {
this.mapSelected = {
longitude: field.model[field.longitude] || '',
latitude: field.model[field.latitude] || '',
regionCode: field.model[field.regionCode] || '',
detailAddress: this.mapKeyword,
regionName: field.model[field.name] || '',
};
@@ -935,6 +1165,7 @@ export default {
latitude,
detailAddress: keyword || this.mapKeyword,
regionName: '',
regionCode: '',
};
this.renderAddressMarker([longitude, latitude]);
this.mapStatus = '已选点,可确认回填';
@@ -947,6 +1178,7 @@ export default {
this.mapSelected.regionName = [component.province, component.city, component.district]
.filter(Boolean)
.join('');
this.mapSelected.regionCode = component.adcode || '';
this.mapKeyword = this.mapSelected.detailAddress;
this.mapStatus = '已解析地址,可确认回填';
}
@@ -967,16 +1199,22 @@ export default {
target.model[target.address] = this.mapSelected.detailAddress || this.mapKeyword;
target.model[target.longitude] = this.mapSelected.longitude;
target.model[target.latitude] = this.mapSelected.latitude;
target.model[target.regionCode] = this.mapSelected.regionCode || '';
this.mapDialogVisible = false;
this.syncAddressRegionPath(this.mapTarget);
},
openCommonCargoDialog() {
this.commonCargoQuery = {};
this.commonCargoSelected = [];
this.commonCargoPage.current = 1;
this.commonCargoVisible = true;
},
async loadCommonCargoList() {
this.commonCargoSelected = [];
this.commonCargoLoading = true;
try {
const response = await getCommonCargoList(this.commonCargoPage.current, this.commonCargoPage.size, { allDept: 0 });
const { cargoTypePath, ...query } = this.commonCargoQuery;
const response = await getCommonCargoList(this.commonCargoPage.current, this.commonCargoPage.size, { ...query, allDept: 0 });
const data = response?.data?.data || response?.data || response || {};
this.commonCargoRows = data.records || [];
this.commonCargoPage.total = data.total || 0;
@@ -984,6 +1222,31 @@ export default {
this.commonCargoLoading = false;
}
},
searchCommonCargoList() {
this.commonCargoPage.current = 1;
this.loadCommonCargoList();
},
resetCommonCargoList() {
this.commonCargoQuery = {};
this.commonCargoPage.current = 1;
this.loadCommonCargoList();
},
handleCommonCargoTypeChange(value) {
const path = Array.isArray(value) ? value.map(item => String(item)) : [];
const firstCargoType = this.getCargoTypeByPath(path.slice(0, 1));
const secondCargoType = path.length > 1 ? this.getCargoTypeByPath(path) : null;
this.commonCargoQuery = {
...this.commonCargoQuery,
cargoTypePath: path,
firstCargoTypeName: firstCargoType?.label || '',
firstCargoTypeCode: firstCargoType?.cargoCode || firstCargoType?.code || '',
secondCargoTypeName: secondCargoType?.label || '',
secondCargoTypeCode: secondCargoType?.cargoCode || secondCargoType?.code || '',
};
},
handleCommonCargoSelectionChange(rows) {
this.commonCargoSelected = rows || [];
},
async loadCargoTypeOptions() {
if (this.cargoTypeOptions.length || this.cargoTypeLoading) return;
this.cargoTypeLoading = true;
@@ -1133,7 +1396,16 @@ export default {
});
},
selectCommonCargo(row) {
const cargo = this.normalizeCargoRow(row, this.resolveCargoTypePath(row));
this.appendCommonCargoRows([row]);
this.commonCargoVisible = false;
},
confirmCommonCargoSelection() {
if (!this.commonCargoSelected.length) return;
this.appendCommonCargoRows(this.commonCargoSelected);
this.$message.success(`成功选择${this.commonCargoSelected.length}条常用货物`);
this.commonCargoVisible = false;
},
appendCommonCargoRows(rows = []) {
const firstGoods = this.form.goods[0];
const isFirstGoodsEmpty =
firstGoods &&
@@ -1148,12 +1420,11 @@ export default {
'remark',
].some(key => firstGoods[key]);
if (isFirstGoodsEmpty) {
this.form.goods.splice(0, 1, cargo);
} else {
this.form.goods.push(cargo);
}
this.commonCargoVisible = false;
rows.forEach((row, index) => {
const cargo = this.normalizeCargoRow(row, this.resolveCargoTypePath(row));
if (isFirstGoodsEmpty && index === 0) this.form.goods.splice(0, 1, cargo);
else this.form.goods.push(cargo);
});
},
resolveCargoTypePath(row = {}) {
return (
@@ -1275,6 +1546,7 @@ export default {
this.form.arrivalAddress = route.arrivalAddress || '';
this.form.arrivalContact = route.arrivalContact || '';
this.form.arrivalPhone = route.arrivalPhone || '';
this.syncAddressRegionPaths();
this.form.finalTransportType = '公路运输';
if (!this.form.routes.length) this.addRoute();
this.routeDialogVisible = false;
@@ -1287,6 +1559,7 @@ export default {
},
removeRoute(index) {
this.form.routes.splice(index, 1);
this.syncAddressRegionPaths();
},
addGoods(index) {
this.form.goods.splice(index + 1, 0, { quantityUnit: quantityUnitOptions[0] });
@@ -1416,6 +1689,8 @@ export default {
}
const payload = {
...this.form,
planStartTime: this.normalizeDateValue(this.form.planStartTime),
planEndTime: this.normalizeDateValue(this.form.planEndTime),
attachmentsJson: JSON.stringify(this.attachmentRows),
routes: [
...this.form.routes.map((route, index) => ({ ...route, segmentNo: `段${index + 1}` })),
@@ -1537,6 +1812,9 @@ export default {
padding: 12px 12px 4px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
}
.common-cargo-dialog__search {
background: #fff;
}
.route-dialog__search-actions {
display: flex;
justify-content: flex-end;
@@ -1638,9 +1916,14 @@ export default {
flex: 0 0 auto;
}
.route-title-path {
display: inline-block;
max-width: 24em;
overflow: hidden;
color: #606266;
font-size: 14px;
font-weight: 400;
text-overflow: ellipsis;
white-space: nowrap;
}
.route-transport-control {
display: flex;