修复业务模块bug

This commit is contained in:
2026-08-13 14:07:04 +08:00
parent 6554ad80dd
commit f62e36e846
15 changed files with 941 additions and 186 deletions
+1 -2
View File
@@ -122,8 +122,7 @@ export const createOption = () => ({
maxlength: 20, maxlength: 20,
formslot: true, formslot: true,
rules: [ rules: [
{ max: 20, message: '联系方式不能超过20个字', trigger: 'blur' }, { pattern: /^1[3-9]\d{9}$/, message: '联系方式格式不正确', trigger: 'blur' },
{ pattern: /^\d*$/, message: '联系方式只能输入数字', trigger: 'blur' },
], ],
}, },
{ {
+1 -1
View File
@@ -171,7 +171,7 @@ export const nonNegativeRule = label => ({
}); });
export const phoneRule = label => ({ export const phoneRule = label => ({
pattern: /^(1\d{10}|0\d{2,3}-?\d{7,8})$/, pattern: /^1[3-9]\d{9}$/,
message: `${label}格式不正确`, message: `${label}格式不正确`,
trigger: 'blur', trigger: 'blur',
}); });
+1 -1
View File
@@ -39,7 +39,7 @@ export const loadingManageConfig = {
{ prop: 'transitAddress', label: '途经地' }, { prop: 'transitAddress', label: '途经地' },
{ prop: 'arrivalAddress', label: '到货地' }, { prop: 'arrivalAddress', label: '到货地' },
{ prop: 'carrierName', label: '承运商' }, { prop: 'carrierName', label: '承运商' },
{ prop: 'transportType', label: '运输类型' }, { prop: 'transportType', label: '运输方式' },
{ prop: 'dataSource', label: '数据来源' }, { prop: 'dataSource', label: '数据来源' },
{ prop: 'createTime', label: '创建时间' }, { prop: 'createTime', label: '创建时间' },
{ prop: 'updateTime', label: '更新时间' }, { prop: 'updateTime', label: '更新时间' },
+15 -3
View File
@@ -15,8 +15,6 @@ const templateTypeOptions = [
export const config = { export const config = {
title: '发货模板', title: '发货模板',
permission: 'shipping_template', permission: 'shipping_template',
exportUrl: '/blade-transport/shipping-template/export-shipping-template',
exportName: '发货模板',
tableColumnOrder: [ tableColumnOrder: [
'templateCode', 'templateCode',
'templateName', 'templateName',
@@ -31,7 +29,7 @@ export const config = {
{ prop: 'templateCode', label: '模板编号' }, { prop: 'templateCode', label: '模板编号' },
{ prop: 'templateName', label: '模板名称' }, { prop: 'templateName', label: '模板名称' },
{ prop: 'templateType', label: '模板类型' }, { prop: 'templateType', label: '模板类型' },
{ prop: 'transportType', label: '运输类型' }, { prop: 'transportType', label: '运输方式' },
{ prop: 'createUserName', label: '创建人' }, { prop: 'createUserName', label: '创建人' },
{ prop: 'remark', label: '备注' }, { prop: 'remark', label: '备注' },
{ prop: 'updateTime', label: '更新时间' }, { prop: 'updateTime', label: '更新时间' },
@@ -173,6 +171,19 @@ export const option = {
rules: selectRule('运输方式'), rules: selectRule('运输方式'),
display: true, display: true,
}, },
{
label: '备注',
prop: 'basicRemark',
type: 'textarea',
minRows: 3,
span: 24,
order: 315,
minWidth: 180,
maxlength: 200,
showWordLimit: true,
rules: textRule('备注', 200),
display: true,
},
{ {
label: '', label: '',
prop: 'shippingInfoTitle', prop: 'shippingInfoTitle',
@@ -259,4 +270,5 @@ export const option = {
]) ])
), ),
dialogWidth: '96%', dialogWidth: '96%',
dialogTop: '10px',
}; };
+1
View File
@@ -172,6 +172,7 @@ export const config = {
enableTransportPlanForm: true, enableTransportPlanForm: true,
enableTransportPlanCreateActions: true, enableTransportPlanCreateActions: true,
enableTransportPlanImport: true, enableTransportPlanImport: true,
fixedTransportAddressType: true,
transportPlanImportTitle: '导入发货计划', transportPlanImportTitle: '导入发货计划',
transportPlanTemplateUrl: '/blade-transport/transport-plan/export-template', transportPlanTemplateUrl: '/blade-transport/transport-plan/export-template',
attachmentTitle: '附件', attachmentTitle: '附件',
+11 -23
View File
@@ -18,6 +18,7 @@ const transportTypeDict = {
}; };
const isEmpty = value => value === undefined || value === null || value === ''; const isEmpty = value => value === undefined || value === null || value === '';
const normalizeNumericDisplayValue = value => (Number(value) === -1 ? '' : value);
const parseJsonArray = value => { const parseJsonArray = value => {
if (Array.isArray(value)) return value; if (Array.isArray(value)) return value;
@@ -86,7 +87,9 @@ const formatGoodsInfo = row => {
const name = getFirstValue(item, ['cargoName', 'goodsName', 'name']); const name = getFirstValue(item, ['cargoName', 'goodsName', 'name']);
const type = getFirstValue(item, ['cargoType', 'goodsType', 'typeName']); const type = getFirstValue(item, ['cargoType', 'goodsType', 'typeName']);
const quantity = joinText([ const quantity = joinText([
getFirstValue(item, ['quantity', 'cargoQuantity', 'goodsQuantity']), normalizeNumericDisplayValue(
getFirstValue(item, ['quantity', 'cargoQuantity', 'goodsQuantity'])
),
getFirstValue(item, ['quantityUnit', 'cargoUnit', 'unit']), getFirstValue(item, ['quantityUnit', 'cargoUnit', 'unit']),
]); ]);
return joinText([name, type, quantity]); return joinText([name, type, quantity]);
@@ -102,17 +105,17 @@ const formatGoodsField = (row, props) => {
return goods ? getFirstValue(goods, props) : ''; return goods ? getFirstValue(goods, props) : '';
}; };
const formatTransportMode = row =>
getFirstValue(row, ['transportModeName', 'transportMode', 'transportWayName', 'transportWay']);
const formatUnitPrice = row => { const formatUnitPrice = row => {
const value = getFirstValue(row, ['unitPrice', 'price']); const value = normalizeNumericDisplayValue(getFirstValue(row, ['unitPrice', 'price']));
const unit = getFirstValue(row, ['priceUnit', 'billingUnit', 'unit']); const unit = getFirstValue(row, ['priceUnit', 'billingUnit', 'unit']);
if (!isEmpty(value)) return unit ? `${value}(${unit})` : value; if (!isEmpty(value)) return unit ? `${value}(${unit})` : value;
const billing = getBillingRows(row).find(item => !isEmpty(item.unitPrice)); const billing = getBillingRows(row).find(
item => !isEmpty(normalizeNumericDisplayValue(item.unitPrice))
);
if (!billing) return ''; if (!billing) return '';
const billingUnit = getFirstValue(billing, ['priceUnit', 'billingUnit', 'unit']); const billingUnit = getFirstValue(billing, ['priceUnit', 'billingUnit', 'unit']);
return billingUnit ? `${billing.unitPrice}(${billingUnit})` : billing.unitPrice; const billingPrice = normalizeNumericDisplayValue(billing.unitPrice);
return billingUnit ? `${billingPrice}(${billingUnit})` : billingPrice;
}; };
const formatAmount = (row, props) => getFirstValue(row, props); const formatAmount = (row, props) => getFirstValue(row, props);
@@ -176,8 +179,6 @@ const auditColumn = prop => ({ ...auditColumns.find(item => item.prop === prop)
export const config = { export const config = {
title: '运单管理', title: '运单管理',
permission: 'waybill_manage', permission: 'waybill_manage',
exportUrl: '/blade-transport/waybill-manage/export-waybill-manage',
exportName: '运单管理',
importUrl: '/blade-transport/waybill-manage/import-waybill-manage', importUrl: '/blade-transport/waybill-manage/import-waybill-manage',
defaultForm: { defaultForm: {
carrierType: '承运商', carrierType: '承运商',
@@ -332,19 +333,6 @@ export const option = {
}, },
{ {
label: '运输方式', label: '运输方式',
prop: 'transportMode',
type: 'select',
search: true,
searchOrder: 1,
formatter: row => formatTransportMode(row),
...transportTypeDict,
minWidth: 130,
addDisplay: false,
editDisplay: false,
viewDisplay: false,
},
{
label: '运输类型',
prop: 'transportType', prop: 'transportType',
type: 'select', type: 'select',
search: true, search: true,
@@ -353,7 +341,7 @@ export const option = {
order: 850, order: 850,
...transportTypeDict, ...transportTypeDict,
minWidth: 130, minWidth: 130,
rules: selectRule('运输类型'), rules: selectRule('运输方式'),
}, },
{ {
label: '过程节点', label: '过程节点',
+1 -1
View File
@@ -27,7 +27,7 @@ export function isEmail(s) {
* @param {*} s * @param {*} s
*/ */
export function isMobile(s) { export function isMobile(s) {
return /^1[0-9]{10}$/.test(s); return /^1[3-9]\d{9}$/.test(String(s || '').trim());
} }
/** /**
+3 -6
View File
@@ -267,6 +267,7 @@ import { createOption } from '@/option/base/common-address';
import { mapGetters } from 'vuex'; import { mapGetters } from 'vuex';
import { downloadXls } from '@/utils/util'; import { downloadXls } from '@/utils/util';
import { getToken } from '@/utils/auth'; import { getToken } from '@/utils/auth';
import { isMobile } from '@/utils/validate';
import NProgress from 'nprogress'; import NProgress from 'nprogress';
import 'nprogress/nprogress.css'; import 'nprogress/nprogress.css';
@@ -830,12 +831,8 @@ export default {
this.$message.warning('联系人不能超过50个字'); this.$message.warning('联系人不能超过50个字');
return false; return false;
} }
if (row.contactPhone && row.contactPhone.length > 20) { if (row.contactPhone && !isMobile(row.contactPhone)) {
this.$message.warning('联系方式不能超过20个字'); this.$message.warning('联系方式格式不正确');
return false;
}
if (row.contactPhone && !/^\d+$/.test(row.contactPhone)) {
this.$message.warning('联系方式只能输入数字');
return false; return false;
} }
if (row.remark && row.remark.length > 200) { if (row.remark && row.remark.length > 200) {
+9
View File
@@ -322,6 +322,7 @@ import { config, excelOption, option } from '@/option/business/common-route';
import { getToken } from '@/utils/auth'; import { getToken } from '@/utils/auth';
import { openImportDialog } from '@/utils/import-excel'; import { openImportDialog } from '@/utils/import-excel';
import { downloadXls } from '@/utils/util'; import { downloadXls } from '@/utils/util';
import { isMobile } from '@/utils/validate';
import { List, Search } from '@element-plus/icons-vue'; import { List, Search } from '@element-plus/icons-vue';
import { mapGetters } from 'vuex'; import { mapGetters } from 'vuex';
import NProgress from 'nprogress'; import NProgress from 'nprogress';
@@ -789,6 +790,14 @@ export default {
this.$message.warning('收货地址不能超过255个字符'); this.$message.warning('收货地址不能超过255个字符');
return false; return false;
} }
const invalidPhone = [
[row.departurePhone, '发货联系方式'],
[row.arrivalPhone, '收货联系方式'],
].find(([value]) => value && !isMobile(value));
if (invalidPhone) {
this.$message.warning(`${invalidPhone[1]}格式不正确`);
return false;
}
if (row.remark && row.remark.length > 500) { if (row.remark && row.remark.length > 500) {
this.$message.warning('备注不能超过500个字符'); this.$message.warning('备注不能超过500个字符');
return false; return false;
@@ -109,12 +109,8 @@
</template> </template>
<template #departureAddress="{ row }"> <template #departureAddress="{ row }">
<div v-if="isWaybillDetailLayout" class="business-crud-page__waybill-address-cell">
<span>{{ row.departureName || '-' }}</span>
<span>{{ row.departureAddress || '-' }}</span>
</div>
<el-tooltip <el-tooltip
v-else-if="isTransportPlanPage" v-if="isTransportPlanPage || isWaybillDetailLayout"
:content="row.departureAddress || '-'" :content="row.departureAddress || '-'"
placement="top" placement="top"
> >
@@ -124,12 +120,8 @@
</template> </template>
<template #arrivalAddress="{ row }"> <template #arrivalAddress="{ row }">
<div v-if="isWaybillDetailLayout" class="business-crud-page__waybill-address-cell">
<span>{{ row.arrivalName || '-' }}</span>
<span>{{ row.arrivalAddress || '-' }}</span>
</div>
<el-tooltip <el-tooltip
v-else-if="isTransportPlanPage" v-if="isTransportPlanPage || isWaybillDetailLayout"
:content="row.arrivalAddress || '-'" :content="row.arrivalAddress || '-'"
placement="top" placement="top"
> >
@@ -616,7 +608,7 @@
</el-form-item> </el-form-item>
</div> </div>
<div <div
v-if="isCarrierMode" v-if="hasTransportType && isCarrierMode"
class="business-crud-page__subsection business-crud-page__carrier-section" class="business-crud-page__subsection business-crud-page__carrier-section"
> >
<div class="business-crud-page__subsection-head"> <div class="business-crud-page__subsection-head">
@@ -650,7 +642,11 @@
/> />
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="司机" class="business-crud-page__task-span-1"> <el-form-item
v-if="!isNonRoadTransport"
label="司机"
class="business-crud-page__task-span-1"
>
<el-select <el-select
v-model="form.driverName" v-model="form.driverName"
placeholder="请输入" placeholder="请输入"
@@ -672,7 +668,11 @@
/> />
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="手机号" class="business-crud-page__task-span-1"> <el-form-item
v-if="!isNonRoadTransport"
label="手机号"
class="business-crud-page__task-span-1"
>
<el-input <el-input
v-model="form.driverPhone" v-model="form.driverPhone"
placeholder="请输入" placeholder="请输入"
@@ -681,9 +681,52 @@
:disabled="dialogReadonly" :disabled="dialogReadonly"
/> />
</el-form-item> </el-form-item>
<el-form-item label="车牌号" required class="business-crud-page__task-span-1"> <el-form-item
:label="transportVehicleNoLabel"
required
class="business-crud-page__task-span-1"
>
<el-input <el-input
v-model="form.vehicleNo" v-model="form.vehicleNo"
:placeholder="`请输入${transportVehicleNoLabel}`"
clearable
maxlength="30"
:disabled="dialogReadonly"
/>
</el-form-item>
<el-form-item
v-if="isNonRoadTransport"
label="船长"
class="business-crud-page__task-span-1"
>
<el-input
v-model="form.captainName"
placeholder="请输入"
clearable
maxlength="20"
:disabled="dialogReadonly"
/>
</el-form-item>
<el-form-item
v-if="isNonRoadTransport"
label="联系电话"
class="business-crud-page__task-span-1"
>
<el-input
v-model="form.driverPhone"
placeholder="请输入"
clearable
maxlength="20"
:disabled="dialogReadonly"
/>
</el-form-item>
<el-form-item
v-if="isNonRoadTransport"
label="箱号"
class="business-crud-page__task-span-1"
>
<el-input
v-model="form.containerNo"
placeholder="请输入" placeholder="请输入"
clearable clearable
maxlength="30" maxlength="30"
@@ -693,7 +736,20 @@
</div> </div>
<div class="business-crud-page__task-row"> <div class="business-crud-page__task-row">
<el-form-item <el-form-item
v-if="isRoadTransport" v-if="isNonRoadTransport"
label="舱位"
class="business-crud-page__task-span-1"
>
<el-input
v-model="form.cabinNo"
placeholder="请输入"
clearable
maxlength="30"
:disabled="dialogReadonly"
/>
</el-form-item>
<el-form-item
v-if="showRoadEscortFields"
label="挂车车牌号" label="挂车车牌号"
class="business-crud-page__task-span-1" class="business-crud-page__task-span-1"
> >
@@ -706,7 +762,7 @@
/> />
</el-form-item> </el-form-item>
<el-form-item <el-form-item
v-if="isRoadTransport" v-if="showRoadEscortFields"
label="押运人" label="押运人"
class="business-crud-page__task-span-1" class="business-crud-page__task-span-1"
> >
@@ -719,7 +775,7 @@
/> />
</el-form-item> </el-form-item>
<el-form-item <el-form-item
v-if="isRoadTransport" v-if="showRoadEscortFields"
label="押运人手机号" label="押运人手机号"
class="business-crud-page__task-span-1" class="business-crud-page__task-span-1"
> >
@@ -731,7 +787,11 @@
:disabled="dialogReadonly" :disabled="dialogReadonly"
/> />
</el-form-item> </el-form-item>
<el-form-item label="里程(km)" class="business-crud-page__task-span-1"> <el-form-item
v-if="!isNonRoadTransport"
label="里程(km)"
class="business-crud-page__task-span-1"
>
<el-input <el-input
v-model="form.mileage" v-model="form.mileage"
placeholder="请输入" placeholder="请输入"
@@ -742,7 +802,7 @@
/> />
</el-form-item> </el-form-item>
</div> </div>
<div class="business-crud-page__task-row"> <div v-if="!isNonRoadTransport" class="business-crud-page__task-row">
<el-form-item <el-form-item
:label="isRoadTransport ? '计划发货日期' : '预计发货日期'" :label="isRoadTransport ? '计划发货日期' : '预计发货日期'"
class="business-crud-page__task-span-1" class="business-crud-page__task-span-1"
@@ -786,8 +846,8 @@
</el-form-item> </el-form-item>
</div> </div>
</div> </div>
<template v-else> <template v-else-if="hasTransportType">
<div class="business-crud-page__task-row"> <div v-if="isRoadTransport" class="business-crud-page__task-row">
<el-form-item <el-form-item
label="司机" label="司机"
:required="isRoadTransport" :required="isRoadTransport"
@@ -842,7 +902,7 @@
/> />
</el-form-item> </el-form-item>
</div> </div>
<div class="business-crud-page__task-row"> <div v-if="isRoadTransport" class="business-crud-page__task-row">
<el-form-item label="押运人" class="business-crud-page__task-span-1"> <el-form-item label="押运人" class="business-crud-page__task-span-1">
<el-input <el-input
v-model="form.escortName" v-model="form.escortName"
@@ -872,7 +932,7 @@
/> />
</el-form-item> </el-form-item>
</div> </div>
<div class="business-crud-page__task-row"> <div v-if="isRoadTransport" class="business-crud-page__task-row">
<el-form-item <el-form-item
:label="isRoadTransport ? '计划发货日期' : '预计发货日期'" :label="isRoadTransport ? '计划发货日期' : '预计发货日期'"
class="business-crud-page__task-span-1" class="business-crud-page__task-span-1"
@@ -900,7 +960,7 @@
/> />
</el-form-item> </el-form-item>
</div> </div>
<div class="business-crud-page__task-row"> <div v-if="isRoadTransport" class="business-crud-page__task-row">
<el-form-item <el-form-item
label="备注" label="备注"
class="business-crud-page__task-note business-crud-page__task-span-4" class="business-crud-page__task-note business-crud-page__task-span-4"
@@ -915,6 +975,32 @@
/> />
</el-form-item> </el-form-item>
</div> </div>
<template v-if="isNonRoadTransport">
<div class="business-crud-page__task-row">
<el-form-item :label="transportVehicleNoLabel" required class="business-crud-page__task-span-1">
<el-input v-model="form.vehicleNo" :placeholder="`请输入${transportVehicleNoLabel}`" clearable maxlength="30" :disabled="dialogReadonly" />
</el-form-item>
<el-form-item label="船长" class="business-crud-page__task-span-1">
<el-input v-model="form.captainName" placeholder="请输入" clearable maxlength="20" :disabled="dialogReadonly" />
</el-form-item>
<el-form-item label="联系电话" class="business-crud-page__task-span-1">
<el-input v-model="form.driverPhone" placeholder="请输入" clearable maxlength="20" :disabled="dialogReadonly" />
</el-form-item>
<el-form-item label="箱号" class="business-crud-page__task-span-1">
<el-input v-model="form.containerNo" placeholder="请输入" clearable maxlength="30" :disabled="dialogReadonly" />
</el-form-item>
</div>
<div class="business-crud-page__task-row">
<el-form-item label="舱位" class="business-crud-page__task-span-1">
<el-input v-model="form.cabinNo" placeholder="请输入" clearable maxlength="30" :disabled="dialogReadonly" />
</el-form-item>
</div>
<div class="business-crud-page__task-row">
<el-form-item label="备注" class="business-crud-page__task-note business-crud-page__task-span-4">
<el-input v-model="form.taskRemark" placeholder="请输入,200字以内" clearable maxlength="200" show-word-limit :disabled="dialogReadonly" />
</el-form-item>
</div>
</template>
</template> </template>
</el-form> </el-form>
<div class="business-crud-page__subsection"> <div class="business-crud-page__subsection">
@@ -1041,7 +1127,8 @@
/> />
</el-radio-group> </el-radio-group>
</el-form-item> </el-form-item>
<el-form-item v-if="isRoadTransport && isCarrierMode" label="承运商" required> <template v-if="hasTransportType">
<el-form-item v-if="isCarrierMode" label="承运商" required>
<el-select <el-select
v-model="form.carrierName" v-model="form.carrierName"
placeholder="请选择承运商" placeholder="请选择承运商"
@@ -1123,12 +1210,12 @@
:disabled="dialogReadonly" :disabled="dialogReadonly"
/> />
</el-form-item> </el-form-item>
<el-form-item v-if="isNonRoadTransport" label="舱位"> <el-form-item v-if="isNonRoadTransport && isCarrierMode" label="联系电话">
<el-input <el-input
v-model="form.cabinNo" v-model="form.driverPhone"
placeholder="请输入舱位" placeholder="请输入联系电话"
clearable clearable
maxlength="30" maxlength="20"
:disabled="dialogReadonly" :disabled="dialogReadonly"
/> />
</el-form-item> </el-form-item>
@@ -1141,7 +1228,16 @@
:disabled="dialogReadonly" :disabled="dialogReadonly"
/> />
</el-form-item> </el-form-item>
<el-form-item v-if="isRoadTransport" label="挂车车牌号"> <el-form-item v-if="isNonRoadTransport" label="舱位">
<el-input
v-model="form.cabinNo"
placeholder="请输入舱位"
clearable
maxlength="30"
:disabled="dialogReadonly"
/>
</el-form-item>
<el-form-item v-if="showRoadEscortFields" label="挂车车牌号">
<el-input <el-input
v-model="form.trailerVehicleNo" v-model="form.trailerVehicleNo"
placeholder="请输入挂车车牌号" placeholder="请输入挂车车牌号"
@@ -1150,7 +1246,7 @@
:disabled="dialogReadonly" :disabled="dialogReadonly"
/> />
</el-form-item> </el-form-item>
<el-form-item v-if="isRoadTransport" label="押运人"> <el-form-item v-if="showRoadEscortFields" label="押运人">
<el-input <el-input
v-model="form.escortName" v-model="form.escortName"
placeholder="请输入押运人" placeholder="请输入押运人"
@@ -1159,7 +1255,7 @@
:disabled="dialogReadonly" :disabled="dialogReadonly"
/> />
</el-form-item> </el-form-item>
<el-form-item v-if="isRoadTransport" label="押运人手机号"> <el-form-item v-if="showRoadEscortFields" label="押运人手机号">
<el-input <el-input
v-model="form.escortPhone" v-model="form.escortPhone"
placeholder="请输入押运人手机号" placeholder="请输入押运人手机号"
@@ -1185,8 +1281,10 @@
<el-form-item label="货物名称"> <el-form-item label="货物名称">
<el-select <el-select
v-model="form.cargoName" v-model="form.cargoName"
placeholder="请选择货物" placeholder="请选择或输入货物"
filterable filterable
allow-create
default-first-option
clearable clearable
:disabled="dialogReadonly || !form.cargoType" :disabled="dialogReadonly || !form.cargoType"
:loading="taskCargoLoading" :loading="taskCargoLoading"
@@ -1299,6 +1397,15 @@
</template> </template>
</el-input> </el-input>
</el-form-item> </el-form-item>
<el-form-item label="其他费用合计">
<el-input
v-model="form.otherFeeTotal"
placeholder="请输入其他费用合计"
clearable
:disabled="dialogReadonly"
@input="value => handleTaskNumberInput('otherFeeTotal', value)"
/>
</el-form-item>
<el-form-item label="备注" class="business-crud-page__task-remark"> <el-form-item label="备注" class="business-crud-page__task-remark">
<el-input <el-input
v-model="form.taskRemark" v-model="form.taskRemark"
@@ -1310,6 +1417,7 @@
:disabled="dialogReadonly" :disabled="dialogReadonly"
/> />
</el-form-item> </el-form-item>
</template>
</el-form> </el-form>
</div> </div>
</template> </template>
@@ -1338,7 +1446,27 @@
<el-table-column type="index" label="序号" width="70" align="center" fixed="left" /> <el-table-column type="index" label="序号" width="70" align="center" fixed="left" />
<el-table-column label="货物名称" min-width="150" align="center"> <el-table-column label="货物名称" min-width="150" align="center">
<template #default="{ row }"> <template #default="{ row }">
<el-input v-model="row.cargoName" placeholder="请输入" :disabled="dialogReadonly" /> <el-select
v-model="row.cargoName"
placeholder="请选择或输入"
filterable
allow-create
default-first-option
clearable
:disabled="dialogReadonly"
:loading="isTaskCargoOptionsLoading(row.cargoTypePath)"
@visible-change="
visible => visible && loadTaskCargoOptionsByPath(row.cargoTypePath)
"
@change="value => handleTaskCargoRowNameChange(row, value)"
>
<el-option
v-for="item in getTaskCargoRowOptions(row)"
:key="item.id || item.cargoCode || item.cargoName"
:label="formatBillingCargoTypeLabel(item)"
:value="formatBillingCargoTypeLabel(item)"
/>
</el-select>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column min-width="220" align="center"> <el-table-column min-width="220" align="center">
@@ -2887,7 +3015,7 @@
{{ dispatchDialogStatusText }} {{ dispatchDialogStatusText }}
</el-tag> </el-tag>
<el-tag type="primary" effect="light"> <el-tag type="primary" effect="light">
{{ dispatchRow.transportType || '公路整车' }} {{ formatDispatchTransportType(dispatchRow.transportType) || '公路整车' }}
</el-tag> </el-tag>
</div> </div>
</div> </div>
@@ -3014,12 +3142,15 @@
> >
<el-table-column type="index" label="序号" width="70" align="center" fixed="left" /> <el-table-column type="index" label="序号" width="70" align="center" fixed="left" />
<el-table-column <el-table-column
prop="transportType"
label="运输方式" label="运输方式"
min-width="180" min-width="180"
align="center" align="center"
show-overflow-tooltip show-overflow-tooltip
/> >
<template #default="{ row }">
{{ formatDispatchTransportType(row.transportType) || '-' }}
</template>
</el-table-column>
<el-table-column <el-table-column
prop="carrierType" prop="carrierType"
label="承运类型" label="承运类型"
@@ -4556,6 +4687,7 @@ import { getToken } from '@/utils/auth';
import { openImportDialog } from '@/utils/import-excel'; import { openImportDialog } from '@/utils/import-excel';
import { applyTableMenuWidth } from '@/utils/table-menu'; import { applyTableMenuWidth } from '@/utils/table-menu';
import { downloadFileByUrl, downloadXls } from '@/utils/util'; import { downloadFileByUrl, downloadXls } from '@/utils/util';
import { isMobile } from '@/utils/validate';
import { List, Location, Rank, Search } from '@element-plus/icons-vue'; import { List, Location, Rank, Search } from '@element-plus/icons-vue';
import { mapGetters } from 'vuex'; import { mapGetters } from 'vuex';
import NProgress from 'nprogress'; import NProgress from 'nprogress';
@@ -4927,6 +5059,8 @@ export default {
taskQuantityUnitLoading: false, taskQuantityUnitLoading: false,
taskFeeUnitOptions: [], taskFeeUnitOptions: [],
taskFeeUnitLoading: false, taskFeeUnitLoading: false,
dispatchTransportTypeOptions: [],
dispatchTransportTypeLoading: false,
cargoImportBox: false, cargoImportBox: false,
cargoImportLoading: false, cargoImportLoading: false,
cargoImportRows: [], cargoImportRows: [],
@@ -5141,6 +5275,9 @@ export default {
isCarrierMode() { isCarrierMode() {
return this.form.carrierType === '承运商'; return this.form.carrierType === '承运商';
}, },
showRoadEscortFields() {
return this.isRoadTransport && !this.isCarrierMode;
},
taskCargoOptions() { taskCargoOptions() {
return this.getTaskCargoOptionsByPath(this.form.cargoTypePath); return this.getTaskCargoOptionsByPath(this.form.cargoTypePath);
}, },
@@ -5186,9 +5323,12 @@ export default {
row.cargoQuantity || row.cargoQuantity ||
row.goodsQuantity || row.goodsQuantity ||
row.totalQuantity || row.totalQuantity ||
row.totalCargoQuantity row.totalCargoQuantity
); );
}); });
this.parseJsonArray(this.dispatchRow.dispatchRows).forEach(row => {
addQuantity(this.getDispatchQuantityUnit(row), 'assigned', row.quantity);
});
this.dispatchRows.forEach(row => { this.dispatchRows.forEach(row => {
addQuantity(this.getDispatchQuantityUnit(row), 'assigned', row.quantity); addQuantity(this.getDispatchQuantityUnit(row), 'assigned', row.quantity);
}); });
@@ -5467,6 +5607,9 @@ export default {
transportMode() { transportMode() {
return this.resolveTransportMode(this.form.transportType); return this.resolveTransportMode(this.form.transportType);
}, },
hasTransportType() {
return !this.isBillingFieldEmpty(this.form.transportType);
},
transportStationMode() { transportStationMode() {
return ['water', 'rail', 'air'].includes(this.transportMode); return ['water', 'rail', 'air'].includes(this.transportMode);
}, },
@@ -5544,14 +5687,16 @@ export default {
const text = String(value || '').trim(); const text = String(value || '').trim();
if (!text) return '-'; if (!text) return '-';
const cityIndex = text.indexOf('市'); const cityIndex = text.indexOf('市');
const districtStart = cityIndex > -1 ? cityIndex + 1 : 0; const provinceIndex = Math.max(text.lastIndexOf('省', cityIndex), text.lastIndexOf('自治区', cityIndex));
const cityStart = provinceIndex > -1 ? provinceIndex + 1 : 0;
const districtStart = cityIndex > -1 ? cityIndex + 1 : cityStart;
const districtMatch = text const districtMatch = text
.slice(districtStart) .slice(districtStart)
.match(/(?:自治县|自治旗|林区|矿区|新区|开发区|区|县|旗)/); .match(/(?:自治县|自治旗|林区|矿区|新区|开发区|区|县|旗)/);
if (districtMatch) { if (districtMatch) {
return text.slice(0, districtStart + districtMatch.index + districtMatch[0].length); return text.slice(cityStart, districtStart + districtMatch.index + districtMatch[0].length);
} }
return cityIndex > -1 ? text.slice(0, cityIndex + 1) : text; return cityIndex > -1 ? text.slice(cityStart, cityIndex + 1) : text;
}, },
hasPermission(code) { hasPermission(code) {
return this.isAdmin || this.validData(this.permission && this.permission[code], false); return this.isAdmin || this.validData(this.permission && this.permission[code], false);
@@ -5734,6 +5879,7 @@ export default {
].forEach(key => delete row[key]); ].forEach(key => delete row[key]);
this.suppressTransportTypeClear = true; this.suppressTransportTypeClear = true;
Object.assign(this.form, row); Object.assign(this.form, row);
this.applyWaybillTemplateTaskData(row);
this.$nextTick(() => { this.$nextTick(() => {
this.suppressTransportTypeClear = false; this.suppressTransportTypeClear = false;
}); });
@@ -5752,6 +5898,44 @@ export default {
}); });
}); });
}, },
applyWaybillTemplateTaskData(template = {}) {
if (this.config.permission !== 'waybill_manage') return;
const goodsRows = this.parseJsonArray(template.goodsJson);
if (!goodsRows.length) return;
const freight = this.parseJsonObject(template.freightJson);
const freightItems = Array.isArray(freight.freightItems) ? freight.freightItems : [];
const enrichedGoodsRows = goodsRows.map((goods, index) => {
const freightItem = freightItems[index] || {};
return {
...goods,
unitPrice: freightItem.unitPrice ?? goods.unitPrice ?? '',
priceUnit: freightItem.priceUnit || goods.priceUnit || '',
};
});
this.form.goodsJson = JSON.stringify(enrichedGoodsRows);
this.taskFreightCurrency = freight.currency || this.taskFreightCurrency || 'CNY';
this.form.otherFeeTotal =
freight.otherFreightAmount ?? freight.otherFeeTotal ?? this.form.otherFeeTotal ?? '';
if (enrichedGoodsRows.length > 1) {
this.form.taskEntryMode = 'full';
return;
}
const [goods] = enrichedGoodsRows;
this.form.taskEntryMode = 'simple';
Object.assign(this.form, {
cargoType: goods.cargoType || goods.goodsType || '',
cargoTypeCode: goods.cargoTypeCode || goods.goodsTypeCode || '',
cargoName: goods.cargoName || goods.goodsName || '',
specification: goods.specification || goods.spec || '',
model: goods.model || goods.modelName || '',
quantity: goods.quantity ?? goods.cargoQuantity ?? goods.goodsQuantity ?? '',
quantityUnit: goods.quantityUnit || goods.goodsQuantityUnit || goods.unit || '吨',
unitPrice: goods.unitPrice,
priceUnit: goods.priceUnit || '元/吨',
});
},
canOperation(operation, row) { canOperation(operation, row) {
const permission = operation.permission || `${this.config.permission}_${operation.action}`; const permission = operation.permission || `${this.config.permission}_${operation.action}`;
if (!this.hasPermission(permission) || this.readonlyScope || row.readonly) { if (!this.hasPermission(permission) || this.readonlyScope || row.readonly) {
@@ -6208,6 +6392,19 @@ export default {
normalizeRow(row, saveAsDraft = false) { normalizeRow(row, saveAsDraft = false) {
const submitRow = { ...row }; const submitRow = { ...row };
const skipValidation = saveAsDraft && this.config.skipDraftValidation === true; const skipValidation = saveAsDraft && this.config.skipDraftValidation === true;
if (
['shipping_template', 'transport_plan', 'waybill_manage'].includes(
this.config.permission
) &&
!this.validateMobileFields([
[submitRow.departurePhone, '发货联系方式'],
[submitRow.arrivalPhone, '收货联系方式'],
[submitRow.driverPhone, this.isNonRoadTransport ? '联系电话' : '手机号'],
[submitRow.escortPhone, '押运人手机号'],
])
) {
return false;
}
if (this.config.enableTransportPlanForm) { if (this.config.enableTransportPlanForm) {
if (!skipValidation && !this.validateTransportPlanForm(submitRow)) return false; if (!skipValidation && !this.validateTransportPlanForm(submitRow)) return false;
submitRow.goodsJson = JSON.stringify(this.transportCargoRows); submitRow.goodsJson = JSON.stringify(this.transportCargoRows);
@@ -6265,6 +6462,14 @@ export default {
}); });
return submitRow; return submitRow;
}, },
validateMobileFields(fields = []) {
const invalidField = fields.find(
([value]) => String(value || '').trim() && !isMobile(value)
);
if (!invalidField) return true;
this.$message.warning(`${invalidField[1]}格式不正确`);
return false;
},
validateTransportPlanForm(row) { validateTransportPlanForm(row) {
const requiredFields = this.config.transportFormRequiredFields || [ const requiredFields = this.config.transportFormRequiredFields || [
['projectName', '项目'], ['projectName', '项目'],
@@ -6289,6 +6494,10 @@ export default {
this.$message.warning('备注不能超过200个字符'); this.$message.warning('备注不能超过200个字符');
return false; return false;
} }
if (row.basicRemark && row.basicRemark.length > 200) {
this.$message.warning('基本信息备注不能超过200个字符');
return false;
}
for (const [index, cargo] of this.transportCargoRows.entries()) { for (const [index, cargo] of this.transportCargoRows.entries()) {
if (!cargo.cargoType) { if (!cargo.cargoType) {
this.$message.warning(`${index + 1}行货物类型不能为空`); this.$message.warning(`${index + 1}行货物类型不能为空`);
@@ -6808,7 +7017,10 @@ export default {
}, },
applyFormDetail(row) { applyFormDetail(row) {
this.suppressTransportTypeClear = true; this.suppressTransportTypeClear = true;
this.form = row || {}; this.form = { ...(row || {}) };
['quantity', 'mileage', 'unitPrice'].forEach(prop => {
if (Number(this.form[prop]) === -1) this.form[prop] = '';
});
this.$nextTick(() => { this.$nextTick(() => {
this.suppressTransportTypeClear = false; this.suppressTransportTypeClear = false;
}); });
@@ -7204,8 +7416,12 @@ export default {
this.form.quantity = this.form.quantity || taskInfo.quantity || goodsInfo.quantity || ''; this.form.quantity = this.form.quantity || taskInfo.quantity || goodsInfo.quantity || '';
this.form.quantityUnit = this.form.quantityUnit =
this.form.quantityUnit || taskInfo.quantityUnit || goodsInfo.quantityUnit || '吨'; this.form.quantityUnit || taskInfo.quantityUnit || goodsInfo.quantityUnit || '吨';
this.form.mileage = this.form.mileage || taskInfo.mileage || goodsInfo.mileage || ''; const mileage = this.form.mileage || taskInfo.mileage || goodsInfo.mileage || '';
this.form.mileage = Number(mileage) === -1 ? '' : mileage;
this.form.unitPrice = this.form.unitPrice || taskInfo.unitPrice || goodsInfo.unitPrice || ''; this.form.unitPrice = this.form.unitPrice || taskInfo.unitPrice || goodsInfo.unitPrice || '';
['quantity', 'mileage', 'unitPrice'].forEach(prop => {
if (Number(this.form[prop]) === -1) this.form[prop] = '';
});
this.form.priceUnit = this.form.priceUnit =
this.form.priceUnit || taskInfo.priceUnit || goodsInfo.priceUnit || '元/吨'; this.form.priceUnit || taskInfo.priceUnit || goodsInfo.priceUnit || '元/吨';
this.form.otherFeeTotal = this.form.otherFeeTotal =
@@ -7541,7 +7757,7 @@ export default {
['carrierType', '承运类型'], ['carrierType', '承运类型'],
['vehicleNo', this.transportVehicleNoLabel], ['vehicleNo', this.transportVehicleNoLabel],
]; ];
if (this.isRoadTransport && row.carrierType === '承运商') { if (row.carrierType === '承运商') {
requiredFields.push(['carrierName', '承运商']); requiredFields.push(['carrierName', '承运商']);
} }
if (this.isRoadTransport && row.carrierType === '自运') { if (this.isRoadTransport && row.carrierType === '自运') {
@@ -7556,11 +7772,9 @@ export default {
const numberFields = [ const numberFields = [
['quantity', '数量'], ['quantity', '数量'],
['mileage', '里程'], ['mileage', '里程'],
['otherFeeTotal', '其他费用合计'],
]; ];
if (!this.isTaskFullMode) numberFields.push(['unitPrice', '单价']); if (!this.isTaskFullMode) numberFields.push(['unitPrice', '单价']);
if (this.isTaskFullMode) {
numberFields.push(['otherFeeTotal', '其他运费合计']);
}
const invalidField = numberFields.find(([prop]) => { const invalidField = numberFields.find(([prop]) => {
const value = row[prop]; const value = row[prop];
return value !== undefined && value !== null && value !== '' && Number(value) < 0; return value !== undefined && value !== null && value !== '' && Number(value) < 0;
@@ -7614,6 +7828,9 @@ export default {
}, },
syncTaskInfoToRow(row) { syncTaskInfoToRow(row) {
row.taskEntryMode = row.taskEntryMode || 'simple'; row.taskEntryMode = row.taskEntryMode || 'simple';
['quantity', 'mileage', 'unitPrice'].forEach(prop => {
if (this.isBillingFieldEmpty(row[prop]) || Number(row[prop]) === -1) row[prop] = '';
});
const taskInfo = { const taskInfo = {
taskEntryMode: row.taskEntryMode, taskEntryMode: row.taskEntryMode,
carrierType: row.carrierType, carrierType: row.carrierType,
@@ -7728,6 +7945,9 @@ export default {
this.transportCargoRows = this.parseJsonArray(this.form.goodsJson).map(row => this.transportCargoRows = this.parseJsonArray(this.form.goodsJson).map(row =>
this.normalizeTransportCargoRow(row) this.normalizeTransportCargoRow(row)
); );
if (!this.transportCargoRows.length) {
this.transportCargoRows.push(this.normalizeTransportCargoRow());
}
this.initShippingTemplateFreight(); this.initShippingTemplateFreight();
this.syncTransportCargoJson(); this.syncTransportCargoJson();
this.syncCurrentContractOption(); this.syncCurrentContractOption();
@@ -7752,7 +7972,7 @@ export default {
}); });
}, },
normalizeTransportCargoRow(row = {}) { normalizeTransportCargoRow(row = {}) {
return { const cargo = {
...defaultTransportCargo(), ...defaultTransportCargo(),
...row, ...row,
cargoTypePath: this.normalizeBillingCargoTypePath(row.cargoTypePath), cargoTypePath: this.normalizeBillingCargoTypePath(row.cargoTypePath),
@@ -7768,6 +7988,10 @@ export default {
packageType: row.packageType || row.package || '', packageType: row.packageType || row.package || '',
specification: row.specification || row.spec || '', specification: row.specification || row.spec || '',
}; };
['quantity', 'mileage', 'unitPrice'].forEach(prop => {
if (Number(cargo[prop]) === -1) cargo[prop] = '';
});
return cargo;
}, },
syncTransportCargoJson() { syncTransportCargoJson() {
if (!this.config.enableTransportPlanForm && !this.isTaskFullMode) return; if (!this.config.enableTransportPlanForm && !this.isTaskFullMode) return;
@@ -7780,7 +8004,13 @@ export default {
priceUnit: this.form.priceUnit || '元/吨', priceUnit: this.form.priceUnit || '元/吨',
...row, ...row,
}); });
if (index > -1) { if (
index < 0 &&
this.transportCargoRows.length === 1 &&
this.isEmptyTransportCargoRow(this.transportCargoRows[0])
) {
this.transportCargoRows.splice(0, 1, nextRow);
} else if (index > -1) {
this.transportCargoRows.splice(index + 1, 0, nextRow); this.transportCargoRows.splice(index + 1, 0, nextRow);
} else { } else {
this.transportCargoRows.push(nextRow); this.transportCargoRows.push(nextRow);
@@ -7788,6 +8018,22 @@ export default {
this.syncShippingTemplateFreightItems(); this.syncShippingTemplateFreightItems();
this.syncTransportCargoJson(); this.syncTransportCargoJson();
}, },
isEmptyTransportCargoRow(row = {}) {
return ![
row.cargoName,
row.cargoType,
row.cargoTypeCode,
row.quantity,
row.quantityUnit,
row.packageType,
row.brand,
row.specification,
row.model,
row.materialCode,
row.deviceCode,
row.remark,
].some(value => !this.isBillingFieldEmpty(value));
},
removeTransportCargoRow(index) { removeTransportCargoRow(index) {
if (this.config.enableShippingTemplateFreight && this.transportMode === 'road') { if (this.config.enableShippingTemplateFreight && this.transportMode === 'road') {
this.shippingTemplateFreight.freightItems.splice(index, 1); this.shippingTemplateFreight.freightItems.splice(index, 1);
@@ -8128,7 +8374,10 @@ export default {
this.transportAddressBox = true; this.transportAddressBox = true;
}, },
openTransportAddressPicker(target) { openTransportAddressPicker(target) {
if (this.transportStationMode) { if (
this.transportStationMode &&
!(this.isTransportPlanPage && this.transportMode === 'water')
) {
this.openTransportStationDialog(target); this.openTransportStationDialog(target);
return; return;
} }
@@ -10126,6 +10375,7 @@ export default {
this.dispatchBox = true; this.dispatchBox = true;
this.dispatchLoading = true; this.dispatchLoading = true;
this.dispatchRows = []; this.dispatchRows = [];
this.loadDispatchTransportTypeOptions();
const request = const request =
this.api && typeof this.api.getDetail === 'function' this.api && typeof this.api.getDetail === 'function'
? this.api.getDetail(row.id) ? this.api.getDetail(row.id)
@@ -10143,6 +10393,26 @@ export default {
this.dispatchLoading = false; this.dispatchLoading = false;
}); });
}, },
loadDispatchTransportTypeOptions() {
if (this.dispatchTransportTypeOptions.length || this.dispatchTransportTypeLoading) {
return Promise.resolve(this.dispatchTransportTypeOptions);
}
this.dispatchTransportTypeLoading = true;
return getDictionary({ code: 'transport_type' })
.then(res => {
this.dispatchTransportTypeOptions = this.normalizeDictOptions(res.data?.data || []);
return this.dispatchTransportTypeOptions;
})
.finally(() => {
this.dispatchTransportTypeLoading = false;
});
},
formatDispatchTransportType(value) {
const item = this.dispatchTransportTypeOptions.find(
option => String(option.value) === String(value ?? '')
);
return item?.label || value || '';
},
resetDispatchDialog() { resetDispatchDialog() {
this.dispatchRow = {}; this.dispatchRow = {};
this.dispatchRows = []; this.dispatchRows = [];
@@ -10364,7 +10634,17 @@ export default {
}, 0); }, 0);
const currentQuantity = this.parseDispatchQuantity(nextRow.quantity); const currentQuantity = this.parseDispatchQuantity(nextRow.quantity);
const totalQuantity = this.getDispatchSummaryItem(quantityUnit)?.total || 0; const totalQuantity = this.getDispatchSummaryItem(quantityUnit)?.total || 0;
if (totalQuantity > 0 && otherDispatchedQuantity + currentQuantity > totalQuantity) { const persistedDispatchedQuantity = this.parseJsonArray(this.dispatchRow.dispatchRows).reduce(
(total, row) => {
if (this.getDispatchQuantityUnit(row) !== quantityUnit) return total;
return total + this.parseDispatchQuantity(row.quantity);
},
0
);
if (
totalQuantity > 0 &&
persistedDispatchedQuantity + otherDispatchedQuantity + currentQuantity > totalQuantity
) {
this.$message.warning( this.$message.warning(
`已调度${quantityUnit}合计不能超过总数量${this.formatDispatchQuantity( `已调度${quantityUnit}合计不能超过总数量${this.formatDispatchQuantity(
totalQuantity totalQuantity
@@ -10445,6 +10725,18 @@ export default {
this.$message.warning('请先添加调度明细'); this.$message.warning('请先添加调度明细');
return false; return false;
} }
for (const [index, row] of this.dispatchRows.entries()) {
const invalidField = [
[row.departurePhone, '发货联系方式'],
[row.arrivalPhone, '收货联系方式'],
[row.driverPhone, '手机号'],
[row.escortPhone, '押运人手机号'],
].find(([value]) => String(value || '').trim() && !isMobile(value));
if (invalidField) {
this.$message.warning(`${index + 1}条调度明细的${invalidField[1]}格式不正确`);
return false;
}
}
if (mode === 'draft') return true; if (mode === 'draft') return true;
const invalidRow = this.dispatchRows.find(row => { const invalidRow = this.dispatchRows.find(row => {
const selfTransport = row.carrierType !== '承运商'; const selfTransport = row.carrierType !== '承运商';
@@ -11438,7 +11730,7 @@ export default {
&__dispatch-shell { &__dispatch-shell {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
min-height: calc(100vh - 140px); min-height: calc(100vh - 240px);
} }
&__dispatch-top { &__dispatch-top {
@@ -14,7 +14,7 @@
<div class="segment-stats"><div><span>总量</span><strong>{{ quantity(master.totalQuantity) }}<small></small></strong></div><div><span>已调度</span><strong class="dispatched">{{ quantity(route.dispatchedQuantity) }}<small></small></strong></div><div><span>剩余</span><strong class="remaining">{{ quantity(remainingQuantity(route)) }}<small></small></strong></div></div> <div class="segment-stats"><div><span>总量</span><strong>{{ quantity(master.totalQuantity) }}<small></small></strong></div><div><span>已调度</span><strong class="dispatched">{{ quantity(route.dispatchedQuantity) }}<small></small></strong></div><div><span>剩余</span><strong class="remaining">{{ quantity(remainingQuantity(route)) }}<small></small></strong></div></div>
<el-table :data="route.waybills || []" border class="waybill-table"><el-table-column label="运单号" min-width="180"><template #default="{ row }"><el-link type="primary" @click="openWaybill(row)">{{ row.waybillNo }}</el-link></template></el-table-column><el-table-column prop="carrierName" label="承运商" min-width="190" /><el-table-column label="司机/车牌" min-width="180"><template #default="{ row }">{{ driverVehicle(row) }}</template></el-table-column><el-table-column prop="cargoType" label="货物类型" min-width="120" /><el-table-column label="数量(吨)" width="130"><template #default="{ row }">{{ quantity(row.quantity) }}</template></el-table-column><el-table-column label="状态" width="130"><template #default="{ row }"><el-tag :type="waybillStatusType(row.businessStatus)" size="small">{{ waybillStatusName(row.businessStatus) }}</el-tag></template></el-table-column><el-table-column prop="createTime" label="创建时间" min-width="180" /></el-table> <el-table :data="route.waybills || []" border class="waybill-table"><el-table-column label="运单号" min-width="180"><template #default="{ row }"><el-link type="primary" @click="openWaybill(row)">{{ row.waybillNo }}</el-link></template></el-table-column><el-table-column prop="carrierName" label="承运商" min-width="190" /><el-table-column label="司机/车牌" min-width="180"><template #default="{ row }">{{ driverVehicle(row) }}</template></el-table-column><el-table-column prop="cargoType" label="货物类型" min-width="120" /><el-table-column label="数量(吨)" width="130"><template #default="{ row }">{{ quantity(row.quantity) }}</template></el-table-column><el-table-column label="状态" width="130"><template #default="{ row }"><el-tag :type="waybillStatusType(row.businessStatus)" size="small">{{ waybillStatusName(row.businessStatus) }}</el-tag></template></el-table-column><el-table-column prop="createTime" label="创建时间" min-width="180" /></el-table>
<el-empty v-if="!(route.waybills || []).length" description="暂无运单明细" :image-size="56" /> <el-empty v-if="!(route.waybills || []).length" description="暂无运单明细" :image-size="56" />
<div v-if="(route.transportPlans || []).length" class="transport-plan-detail"><h4>关联计划单</h4><el-table :data="route.transportPlans" border><el-table-column label="计划单号" min-width="160"><template #default="{ row }"><el-link type="primary" @click="openPlan(row)">{{ row.planNo }}</el-link></template></el-table-column><el-table-column prop="planName" label="计划名称" min-width="180" /><el-table-column prop="transportType" label="运输类型" width="130" /><el-table-column prop="planStartDate" label="计划开始日期" width="130" /><el-table-column prop="planEndDate" label="计划结束日期" width="130" /><el-table-column prop="businessStatus" label="状态" width="110" /></el-table></div> <div v-if="(route.transportPlans || []).length" class="transport-plan-detail"><h4>关联计划单</h4><el-table :data="route.transportPlans" border><el-table-column label="计划单号" min-width="160"><template #default="{ row }"><el-link type="primary" @click="openPlan(row)">{{ row.planNo }}</el-link></template></el-table-column><el-table-column prop="planName" label="计划名称" min-width="180" /><el-table-column prop="transportType" label="运输方式" width="130" /><el-table-column prop="planStartDate" label="计划开始日期" width="130" /><el-table-column prop="planEndDate" label="计划结束日期" width="130" /><el-table-column prop="businessStatus" label="状态" width="110" /></el-table></div>
</div> </div>
</section> </section>
</section> </section>
@@ -41,13 +41,13 @@
<div> <div>
<el-checkbox v-model="route.selected" :label="`${route.segmentNo}${route.departureName} → ${route.arrivalName}`" /> <el-checkbox v-model="route.selected" :label="`${route.segmentNo}${route.departureName} → ${route.arrivalName}`" />
</div> </div>
<div>总量 {{ formatQuantity(totalQuantity) }}已调度 <em>{{ formatQuantity(route.dispatchedQuantity) }}</em>剩余 <em>{{ formatQuantity(remaining(route)) }}</em></div> <div>总量 {{ formatQuantity(totalQuantity) }}已调度 <em>{{ formatQuantity(dispatchedQuantity(route)) }}</em>剩余 <em>{{ formatQuantity(remaining(route)) }}</em></div>
</header> </header>
<div v-show="route.selected" class="segment-content"> <div v-show="route.selected" class="segment-content">
<el-form :model="route" label-position="right" label-width="auto" class="dispatch-form"> <el-form :model="route" label-position="right" label-width="auto" class="dispatch-form">
<el-row :gutter="16"> <el-row :gutter="16">
<el-col :span="24"> <el-col :span="24">
<el-form-item label="运输类型" required class="transport-type-field"><el-input :model-value="route.transportType" readonly /></el-form-item> <el-form-item label="运输方式" required class="transport-type-field"><el-input :model-value="route.transportType" readonly /></el-form-item>
</el-col> </el-col>
<el-col v-if="isRoad(route)" :span="8"> <el-col v-if="isRoad(route)" :span="8">
<el-form-item label="单据类型" required> <el-form-item label="单据类型" required>
@@ -74,10 +74,10 @@
<div class="goods-heading"><h3>货物信息</h3><div><el-link type="primary" @click="addGoods(route)">新增货物</el-link></div></div> <div class="goods-heading"><h3>货物信息</h3><div><el-link type="primary" @click="addGoods(route)">新增货物</el-link></div></div>
<el-table :data="route.goods" border class="goods-table"> <el-table :data="route.goods" border class="goods-table">
<el-table-column type="index" label="序号" width="64" /> <el-table-column type="index" label="序号" width="64" />
<el-table-column label="货物名称" min-width="180"><template #default="{ row }"><el-select v-model="row.sourceIndex" placeholder="请选择总单货物" @change="selectGoods(route, row)"><el-option v-for="item in masterGoods" :key="item.sourceIndex" :label="item.label" :value="item.sourceIndex" /></el-select></template></el-table-column> <el-table-column label="货物类型" min-width="180"><template #default="{ row }"><el-cascader v-model="row.cargoTypePath" :options="cargoTypeOptions" :props="cargoTypeCascaderProps" placeholder="请选择货物类型" clearable filterable @change="value => handleCargoTypeChange(route, row, value)" /></template></el-table-column>
<el-table-column label="货物类型" min-width="130"><template #default="{ row }"><el-input :model-value="row.cargoType" readonly /></template></el-table-column> <el-table-column label="货物名称" min-width="180"><template #default="{ row }"><el-select v-model="row.sourceIndex" placeholder="请选择总单货物" @change="selectGoods(route, row)"><el-option v-for="item in masterGoodsByCargoType(row)" :key="item.sourceIndex" :label="item.cargoName" :value="item.sourceIndex" /></el-select></template></el-table-column>
<el-table-column label="剩余数量" width="120"><template #default="{ row }">{{ formatQuantity(goodsRemainingQuantity(route, row)) }}</template></el-table-column> <el-table-column prop="remainingQuantity" column-key="remainingQuantity" label="剩余数量" width="150" class-name="goods-table__remaining"><template #default="{ row }">{{ formatQuantity(goodsRemainingQuantity(route, row)) }}</template></el-table-column>
<el-table-column label="本次数量" width="160"><template #default="{ row }"><el-input :model-value="row.dispatchQuantity" inputmode="decimal" placeholder="请输入" @input="value => handleDispatchQuantityInput(route, row, value)" /></template></el-table-column> <el-table-column prop="dispatchQuantity" column-key="dispatchQuantity" label="本次数量" width="200" class-name="goods-table__dispatch"><template #default="{ row }"><el-input :model-value="row.dispatchQuantity" inputmode="decimal" placeholder="请输入" @input="value => handleDispatchQuantityInput(route, row, value)" /></template></el-table-column>
<el-table-column prop="quantityUnit" label="数量单位" width="110" /> <el-table-column prop="quantityUnit" label="数量单位" width="110" />
<el-table-column prop="packageType" label="包装" min-width="110" /> <el-table-column prop="packageType" label="包装" min-width="110" />
<el-table-column prop="brand" label="品牌" min-width="110" /> <el-table-column prop="brand" label="品牌" min-width="110" />
@@ -89,11 +89,31 @@
<div class="freight-heading"><h3>运费信息</h3></div> <div class="freight-heading"><h3>运费信息</h3></div>
<el-form :model="route" label-position="right" label-width="auto" class="dispatch-form freight-form"> <el-form :model="route" label-position="right" label-width="auto" class="dispatch-form freight-form">
<el-row :gutter="16"> <el-row v-for="(item, index) in route.freightItems" :key="item.sourceIndex" :gutter="16">
<el-col :span="6"><el-form-item label="单价"><el-input :model-value="route.unitPrice" inputmode="decimal" placeholder="请输入" @input="value => handleUnitPriceInput(route, value)" /></el-form-item></el-col> <el-col :span="6">
<el-col :span="6"><el-form-item label="计价单位"><el-select v-model="route.priceUnit"><el-option label="元/吨" value="元/吨" /><el-option label="元/件" value="元/件" /></el-select></el-form-item></el-col> <el-form-item :label="`单价${index + 1}`" required>
<el-col :span="6"><el-form-item label="重量合计"><el-input :model-value="formatQuantity(dispatchWeight(route))" readonly><template #append></template></el-input></el-form-item></el-col> <el-input :model-value="item.unitPrice" inputmode="decimal" placeholder="请输入" @input="value => handleFreightUnitPriceInput(item, value)">
<el-col :span="6"><el-form-item label="运费"><el-input :model-value="formatAmount(freightAmount(route))" readonly><template #append></template></el-input></el-form-item></el-col> <template #append><el-select v-model="item.priceUnit" class="freight-unit-select"><el-option label="元/吨" value="元/吨" /><el-option label="元/件" value="元/件" /><el-option label="元/方" value="元/方" /></el-select></template>
</el-input>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item :label="`数量${index + 1}`" required>
<el-input :model-value="formatQuantity(item.quantity)" readonly>
<template #append><el-select v-model="item.quantityUnit" class="freight-unit-select" @change="value => handleFreightQuantityUnitChange(route, item, value)"><el-option v-for="unit in quantityUnitOptions" :key="unit" :label="unit" :value="unit" /></el-select></template>
</el-input>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item :label="`运费${index + 1}`">
<el-input :model-value="formatAmount(freightAmount(item))" readonly><template #suffix>{{ currencyLabel(route.currency) }}</template></el-input>
</el-form-item>
</el-col>
</el-row>
<el-row v-if="route.freightItems.length" :gutter="16">
<el-col :span="6"><el-form-item label="运费合计"><el-input :model-value="formatAmount(freightTotal(route))" readonly><template #suffix>{{ currencyLabel(route.currency) }}</template></el-input></el-form-item></el-col>
<el-col :span="6"><el-form-item label="其他费用合计"><el-input :model-value="route.otherFeeTotal" inputmode="decimal" placeholder="请输入" @input="value => handleOtherFeeTotalInput(route, value)" /></el-form-item></el-col>
<el-col :span="6"><el-form-item label="币种"><el-select v-model="route.currency"><el-option label="人民币" value="CNY" /><el-option label="美元" value="USD" /><el-option label="欧元" value="EUR" /></el-select></el-form-item></el-col>
</el-row> </el-row>
</el-form> </el-form>
@@ -142,6 +162,7 @@
import * as api from '@/api/business/master-order'; import * as api from '@/api/business/master-order';
import { getList as getCustomerList } from '@/api/vehicle/customer-archive'; import { getList as getCustomerList } from '@/api/vehicle/customer-archive';
import { getList as getDriverList } from '@/api/transportCapacity/driver'; import { getList as getDriverList } from '@/api/transportCapacity/driver';
import { isMobile } from '@/utils/validate';
const unwrapRecords = res => { const unwrapRecords = res => {
const data = res?.data || res; const data = res?.data || res;
@@ -156,7 +177,7 @@ export default {
props: { id: [String, Number] }, props: { id: [String, Number] },
emits: ['back'], emits: ['back'],
data() { data() {
return { loading: false, submitting: false, master: null, routes: [], pending: [], pendingExpanded: false, editingId: null, driverInputs: {}, carrierOptions: [], driverOptions: [], carrierLoading: false, driverLoading: false }; return { loading: false, submitting: false, master: null, routes: [], pending: [], pendingExpanded: false, editingId: null, driverInputs: {}, carrierOptions: [], driverOptions: [], carrierLoading: false, driverLoading: false, cargoTypeOptions: [], cargoTypeCascaderProps: { label: 'cargoName', value: 'id', children: 'children', emitPath: true }, quantityUnitOptions: ['吨', '千克', '立方米', '件', '车', '箱', '托盘'] };
}, },
computed: { computed: {
totalQuantity() { return (this.master?.goods || []).reduce((sum, item) => sum + Number(item.quantity || 0), 0); }, totalQuantity() { return (this.master?.goods || []).reduce((sum, item) => sum + Number(item.quantity || 0), 0); },
@@ -186,6 +207,7 @@ export default {
try { try {
const res = await api.getDetail(this.id); const res = await api.getDetail(this.id);
this.master = res.data?.data || res.data || res; this.master = res.data?.data || res.data || res;
this.cargoTypeOptions = this.buildMasterCargoTypeOptions(this.master.goods || []);
this.routes = (this.master.routes || []).map((node, index) => this.createRoute(node, index)); this.routes = (this.master.routes || []).map((node, index) => this.createRoute(node, index));
} finally { this.loading = false; } } finally { this.loading = false; }
}, },
@@ -196,26 +218,88 @@ export default {
selected: false, selected: false,
documentType: '运单', documentType: '运单',
carrierType: '承运商', carrierType: '承运商',
unitPrice: undefined, currency: 'CNY',
priceUnit: '元/吨', otherFeeTotal: '',
freightItems: [],
departureName: previous.departureName || '', departureAddress: previous.departureAddress || '', departureContact: previous.departureContact || '', departurePhone: previous.departurePhone || '', departureName: previous.departureName || '', departureAddress: previous.departureAddress || '', departureContact: previous.departureContact || '', departurePhone: previous.departurePhone || '',
arrivalName: node.departureName || this.master.arrivalName || '', arrivalAddress: node.departureAddress || this.master.arrivalAddress || '', arrivalContact: node.departureContact || '', arrivalPhone: node.departurePhone || '', arrivalName: node.departureName || this.master.arrivalName || '', arrivalAddress: node.departureAddress || this.master.arrivalAddress || '', arrivalContact: node.departureContact || '', arrivalPhone: node.departurePhone || '',
goods: [], goods: [],
}; };
}, },
buildMasterCargoTypeOptions(goods = []) {
return [...new Set(goods.map(item => item.cargoType).filter(Boolean))].map(cargoType => ({
id: cargoType,
cargoName: cargoType,
}));
},
masterGoodsByCargoType(row = {}) {
return this.masterGoods.filter(item => !row.cargoType || item.cargoType === row.cargoType);
},
findCargoTypeByPath(path = []) {
let options = this.cargoTypeOptions;
let selected;
(path || []).forEach(id => {
selected = (options || []).find(item => String(item.id) === String(id));
options = selected?.children || [];
});
return selected;
},
findCargoTypePath(cargoType, options = this.cargoTypeOptions, parentPath = []) {
for (const option of options || []) {
const path = [...parentPath, option.id];
if (option.cargoName === cargoType) return path;
const result = this.findCargoTypePath(cargoType, option.children, path);
if (result.length) return result;
}
return [];
},
handleCargoTypeChange(route, row, value) {
const path = Array.isArray(value) ? value : [];
const cargoType = this.findCargoTypeByPath(path);
row.cargoTypePath = path;
row.cargoType = cargoType?.cargoName || '';
const firstGoods = this.masterGoodsByCargoType(row)[0];
if (firstGoods) {
row.sourceIndex = firstGoods.sourceIndex;
this.selectGoods(route, row);
}
else Object.assign(row, { sourceIndex: undefined, cargoName: '', quantity: '', quantityUnit: '' });
this.syncFreightItems(route);
},
isRoad(route) { return String(route.transportType || '').includes('公路'); }, isRoad(route) { return String(route.transportType || '').includes('公路'); },
dispatchedQuantity(route) {
return this.quantityNumber(route.dispatchedQuantity) + this.pendingSegmentQuantity(route);
},
remaining(route) { remaining(route) {
return Math.max( return Math.max(
0, 0,
this.totalQuantity - this.totalQuantity -
Number(route.dispatchedQuantity || 0) - this.dispatchedQuantity(route)
this.pendingSegmentQuantity(route)
); );
}, },
formatQuantity(value) { const number = Number(value || 0); return Number.isInteger(number) ? number : number.toFixed(3).replace(/\.?0+$/, ''); }, formatQuantity(value) { const number = Number(value || 0); return Number.isInteger(number) ? number : number.toFixed(3).replace(/\.?0+$/, ''); },
formatAmount(value) { return Number(value || 0).toFixed(2); }, formatAmount(value) { return Number(value || 0).toFixed(2); },
dispatchWeight(route) { return (route.goods || []).reduce((sum, item) => sum + Number(item.dispatchQuantity || 0), 0); }, dispatchWeight(route) { return (route.goods || []).reduce((sum, item) => sum + Number(item.dispatchQuantity || 0), 0); },
freightAmount(route) { return Number(route.unitPrice || 0) * this.dispatchWeight(route); }, freightAmount(item = {}) { return Number(item.unitPrice || 0) * Number(item.quantity || 0); },
freightTotal(route) {
const goodsFreight = (route.freightItems || []).reduce(
(sum, item) => sum + this.freightAmount(item),
0
);
return goodsFreight + Number(route.otherFeeTotal || 0);
},
buildFreightJson(route) {
return JSON.stringify({
currency: route.currency || 'CNY',
totalFreightAmount: this.freightTotal(route),
otherFreightAmount: route.otherFeeTotal || '',
freightItems: (route.freightItems || []).map(item => ({
...item,
freightAmount: this.freightAmount(item),
})),
});
},
currencyLabel(value) { return ({ CNY: '人民币', USD: '美元', EUR: '欧元' })[value] || value || '人民币'; },
quantityNumber(value) { return Number(value || 0); }, quantityNumber(value) { return Number(value || 0); },
goodsKey(goods = {}, includeUnit = true) { goodsKey(goods = {}, includeUnit = true) {
return [ return [
@@ -224,8 +308,20 @@ export default {
includeUnit ? goods.quantityUnit || '' : '', includeUnit ? goods.quantityUnit || '' : '',
].join('\u0000'); ].join('\u0000');
}, },
masterGoodsIndex(goods = {}) {
if (goods.sourceIndex !== undefined && goods.sourceIndex !== null && goods.sourceIndex !== '') {
return Number(goods.sourceIndex);
}
return (this.master?.goods || []).findIndex(item =>
item.cargoName === goods.cargoName &&
item.cargoType === goods.cargoType &&
(!goods.quantityUnit || !item.quantityUnit || item.quantityUnit === goods.quantityUnit)
);
},
isSameGoods(left = {}, right = {}) { isSameGoods(left = {}, right = {}) {
if (left.sourceIndex !== undefined && right.sourceIndex !== undefined) return Number(left.sourceIndex) === Number(right.sourceIndex); const leftIndex = this.masterGoodsIndex(left);
const rightIndex = this.masterGoodsIndex(right);
if (leftIndex >= 0 && rightIndex >= 0) return leftIndex === rightIndex;
return left.cargoName === right.cargoName && left.cargoType === right.cargoType && left.quantityUnit === right.quantityUnit; return left.cargoName === right.cargoName && left.cargoType === right.cargoType && left.quantityUnit === right.quantityUnit;
}, },
dispatchedGoodsQuantity(route, row) { dispatchedGoodsQuantity(route, row) {
@@ -239,16 +335,29 @@ export default {
}, },
pendingSegmentQuantity(route) { pendingSegmentQuantity(route) {
return this.pending.reduce((sum, item) => { return this.pending.reduce((sum, item) => {
if (item.segmentNo !== route.segmentNo) return sum; if (!this.isSameRouteSegment(route, item)) return sum;
return sum + this.quantityNumber(item.quantity); return sum + this.quantityNumber(item.quantity);
}, 0); }, 0);
}, },
pendingGoodsQuantity(route, row) { pendingGoodsQuantity(route, row) {
const goodsIndex = this.masterGoodsIndex(row);
return this.pending.reduce((sum, item) => { return this.pending.reduce((sum, item) => {
if (item.segmentNo !== route.segmentNo || !this.isSameGoods(item, row)) return sum; if (!this.isSameRouteSegment(route, item)) return sum;
const pendingGoodsIndex = this.masterGoodsIndex(item);
if (
(goodsIndex >= 0 && pendingGoodsIndex >= 0 && pendingGoodsIndex !== goodsIndex) ||
(goodsIndex < 0 && !this.isSameGoods(item, row))
) {
return sum;
}
return sum + this.quantityNumber(item.quantity); return sum + this.quantityNumber(item.quantity);
}, 0); }, 0);
}, },
isSameRouteSegment(route = {}, item = {}) {
const routeSegment = route.segmentNo || route.relationNo || '';
const itemSegment = item.segmentNo || item.relationNo || '';
return String(routeSegment) === String(itemSegment);
},
editingGoodsQuantity(route, row) { editingGoodsQuantity(route, row) {
return (route.goods || []).reduce((sum, item) => { return (route.goods || []).reduce((sum, item) => {
if (item === row || !this.isSameGoods(item, row)) return sum; if (item === row || !this.isSameGoods(item, row)) return sum;
@@ -270,14 +379,42 @@ export default {
const availableQuantity = this.availableDispatchQuantity(route, row); const availableQuantity = this.availableDispatchQuantity(route, row);
if (Number(nextValue || 0) > availableQuantity) { if (Number(nextValue || 0) > availableQuantity) {
row.dispatchQuantity = this.formatQuantity(availableQuantity); row.dispatchQuantity = this.formatQuantity(availableQuantity);
this.syncFreightItems(route);
this.$message.warning('本次数量不能超过剩余数量'); this.$message.warning('本次数量不能超过剩余数量');
return; return;
} }
row.dispatchQuantity = nextValue; row.dispatchQuantity = nextValue;
this.syncFreightItems(route);
}, },
handleUnitPriceInput(route, value) { handleFreightUnitPriceInput(item, value) {
const [integer = '', decimal = ''] = String(value || '').replace(/[^\d.]/g, '').split('.'); const [integer = '', decimal = ''] = String(value || '').replace(/[^\d.]/g, '').split('.');
route.unitPrice = decimal || String(value || '').includes('.') ? `${integer}.${decimal.slice(0, 2)}` : integer; item.unitPrice = decimal || String(value || '').includes('.') ? `${integer}.${decimal.slice(0, 2)}` : integer;
},
handleOtherFeeTotalInput(route, value) {
const [integer = '', decimal = ''] = String(value || '').replace(/[^\d.]/g, '').split('.');
route.otherFeeTotal = decimal || String(value || '').includes('.') ? `${integer}.${decimal.slice(0, 2)}` : integer;
},
validateFreightItems(route) {
for (const [index, item] of (route.freightItems || []).entries()) {
const fields = [
['unitPrice', '单价'],
['priceUnit', '单价单位'],
['quantity', '数量'],
['quantityUnit', '数量单位'],
];
const emptyField = fields.find(
([prop]) => item[prop] === undefined || item[prop] === null || item[prop] === ''
);
if (emptyField) {
this.$message.warning(`${index + 1}条货物的${emptyField[1]}不能为空`);
return false;
}
}
return true;
},
handleFreightQuantityUnitChange(route, freightItem, value) {
const goods = (route.goods || []).find(item => String(item.sourceIndex) === String(freightItem.sourceIndex));
if (goods) goods.quantityUnit = value;
}, },
handleMileageInput(route, value) { handleMileageInput(route, value) {
const [integer = '', decimal = ''] = String(value || '').replace(/[^\d.]/g, '').split('.'); const [integer = '', decimal = ''] = String(value || '').replace(/[^\d.]/g, '').split('.');
@@ -291,16 +428,46 @@ export default {
return { return {
...goods, ...goods,
sourceIndex, sourceIndex,
cargoTypePath:
Array.isArray(goods.cargoTypePath) && goods.cargoTypePath.length
? goods.cargoTypePath
: this.findCargoTypePath(goods.cargoType),
dispatchQuantity: undefined, dispatchQuantity: undefined,
}; };
}, },
addGoods(route) { route.goods.push(this.createGoodsRow()); }, addGoods(route) {
const firstGoods = this.masterGoods[0];
route.goods.push(
firstGoods
? this.createGoodsRow(firstGoods, firstGoods.sourceIndex)
: this.createGoodsRow()
);
this.syncFreightItems(route);
},
selectGoods(route, row) { selectGoods(route, row) {
const source = (this.master.goods || [])[Number(row.sourceIndex)]; const source = (this.master.goods || [])[Number(row.sourceIndex)];
if (!source) return; if (!source) return;
Object.assign(row, this.createGoodsRow(source, Number(row.sourceIndex))); Object.assign(row, this.createGoodsRow(source, Number(row.sourceIndex)));
this.syncFreightItems(route);
},
removeGoods(route, index) { route.goods.splice(index, 1); this.syncFreightItems(route); },
syncFreightItems(route) {
const oldItems = route.freightItems || [];
route.freightItems = (route.goods || [])
.filter(item => item.sourceIndex !== undefined && item.sourceIndex !== '')
.map(item => {
const old = oldItems.find(entry => String(entry.sourceIndex) === String(item.sourceIndex)) || {};
return {
...old,
sourceIndex: item.sourceIndex,
cargoName: item.cargoName || '',
quantity: item.dispatchQuantity || '',
quantityUnit: item.quantityUnit || '',
unitPrice: old.unitPrice || '',
priceUnit: old.priceUnit || `元/${item.quantityUnit || '吨'}`,
};
});
}, },
removeGoods(route, index) { route.goods.splice(index, 1); },
handleCarrierTypeChange(route, value) { handleCarrierTypeChange(route, value) {
route.carrierType = value; route.carrierType = value;
if (value === '承运商') { if (value === '承运商') {
@@ -356,8 +523,11 @@ export default {
const goods = route.goods.filter(item => Number(item.dispatchQuantity) > 0); const goods = route.goods.filter(item => Number(item.dispatchQuantity) > 0);
if (!goods.length) return this.$message.warning('请填写本次数量'); if (!goods.length) return this.$message.warning('请填写本次数量');
if (goods.some(item => Number(item.dispatchQuantity) > this.availableDispatchQuantity(route, item))) return this.$message.warning('本次数量不能超过剩余数量'); if (goods.some(item => Number(item.dispatchQuantity) > this.availableDispatchQuantity(route, item))) return this.$message.warning('本次数量不能超过剩余数量');
this.syncFreightItems(route);
if (!this.validateFreightItems(route)) return;
if (!route.estimatedStartTime) return this.$message.warning(`请选择${this.dateStartLabel(route)}`); if (!route.estimatedStartTime) return this.$message.warning(`请选择${this.dateStartLabel(route)}`);
if (!route.estimatedEndTime) return this.$message.warning(`请选择${this.dateEndLabel(route)}`); if (!route.estimatedEndTime) return this.$message.warning(`请选择${this.dateEndLabel(route)}`);
if (!this.validateRoutePhones(route)) return;
if (route.documentType === '运单') { if (route.documentType === '运单') {
if (route.carrierType === '承运商' && (!route.carrierName || !route.vehicleNo)) return this.$message.warning('请填写承运商和车牌号'); if (route.carrierType === '承运商' && (!route.carrierName || !route.vehicleNo)) return this.$message.warning('请填写承运商和车牌号');
if (route.carrierType !== '承运商' && (!route.driverName || !route.driverPhone || !route.vehicleNo || !route.trailerVehicleNo || !route.escortName || !route.escortPhone || route.mileage === undefined || route.mileage === null || route.mileage === '')) return this.$message.warning('请补全自运或网货平台的车辆与人员信息'); if (route.carrierType !== '承运商' && (!route.driverName || !route.driverPhone || !route.vehicleNo || !route.trailerVehicleNo || !route.escortName || !route.escortPhone || route.mileage === undefined || route.mileage === null || route.mileage === '')) return this.$message.warning('请补全自运或网货平台的车辆与人员信息');
@@ -367,11 +537,11 @@ export default {
id: `${route.segmentNo}-${item.cargoName}-${Date.now()}-${Math.random()}`, id: `${route.segmentNo}-${item.cargoName}-${Date.now()}-${Math.random()}`,
batchNo, batchNo,
segmentNo: route.segmentNo, relationNo: route.segmentNo, documentType: route.documentType, transportType: route.transportType, carrierType: route.carrierType, segmentNo: route.segmentNo, relationNo: route.segmentNo, documentType: route.documentType, transportType: route.transportType, carrierType: route.carrierType,
carrierName: route.carrierName, driverName: route.driverName, driverPhone: route.driverPhone, vehicleNo: route.vehicleNo, trailerVehicleNo: route.trailerVehicleNo, escortName: route.escortName, escortPhone: route.escortPhone, mileage: route.mileage, unitPrice: route.unitPrice, priceUnit: route.priceUnit, remark: route.remark, carrierName: route.carrierName, driverName: route.driverName, driverPhone: route.driverPhone, vehicleNo: route.vehicleNo, trailerVehicleNo: route.trailerVehicleNo, escortName: route.escortName, escortPhone: route.escortPhone, mileage: route.mileage, unitPrice: this.freightItemsForGoods(route, item).unitPrice || '', priceUnit: this.freightItemsForGoods(route, item).priceUnit || '', currency: route.currency || 'CNY', freightAmount: this.freightAmount(this.freightItemsForGoods(route, item)), freightTotal: this.freightTotal(route), otherFeeTotal: route.otherFeeTotal || '', freightJson: this.buildFreightJson(route), remark: route.remark,
departureName: route.departureName, departureAddress: route.departureAddress, departureContact: route.departureContact, departurePhone: route.departurePhone, departureName: route.departureName, departureAddress: route.departureAddress, departureContact: route.departureContact, departurePhone: route.departurePhone,
arrivalName: route.arrivalName, arrivalAddress: route.arrivalAddress, arrivalContact: route.arrivalContact, arrivalPhone: route.arrivalPhone, arrivalName: route.arrivalName, arrivalAddress: route.arrivalAddress, arrivalContact: route.arrivalContact, arrivalPhone: route.arrivalPhone,
estimatedStartTime: route.estimatedStartTime, estimatedEndTime: route.estimatedEndTime, estimatedStartTime: route.estimatedStartTime, estimatedEndTime: route.estimatedEndTime,
sourceIndex: item.sourceIndex, cargoName: item.cargoName, cargoType: item.cargoType, quantity: item.dispatchQuantity, quantityUnit: item.quantityUnit, packageType: item.packageType, brand: item.brand, specification: item.specification, model: item.model, materialCode: item.materialCode, sourceIndex: this.masterGoodsIndex(item), cargoName: item.cargoName, cargoType: item.cargoType, quantity: item.dispatchQuantity, quantityUnit: item.quantityUnit, packageType: item.packageType, brand: item.brand, specification: item.specification, model: item.model, materialCode: item.materialCode,
})); }));
this.editingId = null; this.editingId = null;
this.pendingExpanded = true; this.pendingExpanded = true;
@@ -393,13 +563,31 @@ export default {
} }
if (goods) goods.dispatchQuantity = item.quantity; if (goods) goods.dispatchQuantity = item.quantity;
Object.assign(route, item); Object.assign(route, item);
this.syncFreightItems(route);
const freightItem = this.freightItemsForGoods(route, goods);
if (freightItem) { freightItem.unitPrice = item.unitPrice || ''; freightItem.priceUnit = item.priceUnit || freightItem.priceUnit; }
this.editingId = item.id; this.editingId = item.id;
this.removePending(item.id); this.removePending(item.id);
this.$nextTick(() => document.querySelector(`[data-segment="${item.segmentNo}"]`)?.scrollIntoView({ behavior: 'smooth', block: 'start' })); this.$nextTick(() => document.querySelector(`[data-segment="${item.segmentNo}"]`)?.scrollIntoView({ behavior: 'smooth', block: 'start' }));
}, },
freightItemsForGoods(route, goods) {
return (route.freightItems || []).find(item => String(item.sourceIndex) === String(goods?.sourceIndex)) || {};
},
removePending(id) { this.pending = this.pending.filter(item => item.id !== id); if (this.editingId === id) this.editingId = null; }, removePending(id) { this.pending = this.pending.filter(item => item.id !== id); if (this.editingId === id) this.editingId = null; },
validateRoutePhones(route = {}) {
const invalidPhone = [
[route.departurePhone, '发货联系方式'],
[route.arrivalPhone, '收货联系方式'],
[route.driverPhone, '手机号'],
[route.escortPhone, '押运人手机号'],
].find(([value]) => String(value || '').trim() && !isMobile(value));
if (!invalidPhone) return true;
this.$message.warning(`${invalidPhone[1]}格式不正确`);
return false;
},
async submit() { async submit() {
if (!this.pending.length) return this.$message.warning('请加入待提交调度清单'); if (!this.pending.length) return this.$message.warning('请加入待提交调度清单');
if (this.pending.some(item => !this.validateRoutePhones(item))) return;
this.submitting = true; this.submitting = true;
try { await api.dispatch({ id: this.master.id, dispatches: this.pending.map(({ id, ...item }) => item) }); this.$message.success('调度成功'); this.$emit('back'); } finally { this.submitting = false; } try { await api.dispatch({ id: this.master.id, dispatches: this.pending.map(({ id, ...item }) => item) }); this.$message.success('调度成功'); this.$emit('back'); } finally { this.submitting = false; }
}, },
@@ -426,7 +614,8 @@ export default {
.transport-type-field :deep(.el-input) { width: 240px; } .transport-type-field :deep(.el-input) { width: 240px; }
.goods-heading { display: flex; align-items: center; justify-content: space-between; margin: 0 -24px 12px; padding: 14px 24px; border-top: 1px solid #eff1f7; border-bottom: 1px solid #eff1f7; h3 { margin: 0; padding-left: 12px; border-left: 4px solid #409eff; font-size: 16px; } span { color: #909399; font-size: 13px; } } .goods-heading { display: flex; align-items: center; justify-content: space-between; margin: 0 -24px 12px; padding: 14px 24px; border-top: 1px solid #eff1f7; border-bottom: 1px solid #eff1f7; h3 { margin: 0; padding-left: 12px; border-left: 4px solid #409eff; font-size: 16px; } span { color: #909399; font-size: 13px; } }
.freight-heading { margin: 16px 0 12px; h3 { margin: 0; padding-left: 12px; border-left: 4px solid #409eff; font-size: 16px; } } .freight-heading { margin: 16px 0 12px; h3 { margin: 0; padding-left: 12px; border-left: 4px solid #409eff; font-size: 16px; } }
.goods-table { :deep(th.el-table__cell), :deep(td.el-table__cell) { border-color: #eff1f7; } :deep(.el-table__row--striped td.el-table__cell) { background: #fafafa; } } .goods-table { :deep(th.el-table__cell), :deep(td.el-table__cell) { border-color: #eff1f7; } :deep(.el-table__row--striped td.el-table__cell) { background: #fafafa; } :deep(.goods-table__remaining .cell) { white-space: nowrap; } :deep(.goods-table__dispatch .el-input) { width: 100%; min-width: 0; } }
.freight-form { :deep(.freight-unit-select) { width: 92px; } }
.carrier-type-form { margin-top: 16px; } .carrier-type-form { margin-top: 16px; }
.carrier-form { padding-top: 8px; } .carrier-form { padding-top: 8px; }
.segment-actions { display: flex; justify-content: flex-end; gap: 12px; padding-top: 4px; } .segment-actions { display: flex; justify-content: flex-end; gap: 12px; padding-top: 4px; }
@@ -84,12 +84,12 @@
</el-step> </el-step>
<el-step v-for="(route, index) in form.routes" :key="index"> <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 #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><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 #description> <template #description>
<div class="route-node-form"> <div class="route-node-form">
<el-form-item label="途经地" required class="route-address-form-item" <el-form-item label="途经地" required class="route-address-form-item"
><div class="route-address-inputs" ><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-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-form-item> ></el-form-item>
<el-form-item label="联系人"><el-input v-model="route.departureContact" placeholder="请输入" /></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> <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>
<el-step> <el-step>
<template #icon><span class="route-step-icon route-step-icon--end"></span></template> <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><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 #description> <template #description>
<div class="route-node-form"> <div class="route-node-form">
<el-form-item label="收货地址" required class="route-address-form-item" <el-form-item label="收货地址" required class="route-address-form-item"
><div class="route-address-inputs" ><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-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-form-item> ></el-form-item>
<el-form-item label="联系人" <el-form-item label="联系人"
><el-input v-model="form.arrivalContact" placeholder="请输入" /></el-form-item> ><el-input v-model="form.arrivalContact" placeholder="请输入" /></el-form-item>
@@ -140,25 +140,33 @@
><el-select ><el-select
v-model="row.cargoName" v-model="row.cargoName"
filterable filterable
remote allow-create
default-first-option
clearable clearable
placeholder="请输入并选择" placeholder="请选择或输入"
:loading="cargoNameLoading" :loading="isCargoOptionsLoading(row.cargoTypePath)"
:remote-method="searchCargoOptions" @visible-change="visible => visible && loadCargoOptionsByType(row.cargoTypePath)"
@visible-change="loadCargoOptions"
@change="selectCargoName(row, $event)" @change="selectCargoName(row, $event)"
><el-option ><el-option
v-for="cargo in cargoOptions" v-for="cargo in getCargoOptionsByType(row.cargoTypePath)"
:key="cargo.id" :key="cargo.id"
:label="cargo.cargoName" :label="cargo.cargoName"
:value="cargo.cargoName" /></el-select :value="cargo.cargoName" /></el-select
></template></el-table-column ></template></el-table-column
><el-table-column label="货物类型" min-width="130" ><el-table-column label="货物类型" min-width="130"
><template #default="{ row }" ><template #default="{ row }"
><el-input ><el-cascader
v-model="row.cargoType" v-model="row.cargoTypePath"
placeholder="从货物类型获取" /></template></el-table-column :options="cargoTypeOptions"
><el-table-column label="数量" width="132" class-name="master-editor__quantity-cell" :props="cargoTypeCascaderProps"
:placeholder="row.cargoType || '请选择'"
clearable
filterable
:loading="cargoTypeLoading"
@visible-change="visible => visible && loadCargoTypeOptions()"
@change="value => handleCargoTypeChange(row, value)"
/></template></el-table-column
><el-table-column label="数量" width="180" class-name="master-editor__quantity-cell"
><template #default="{ row }" ><template #default="{ row }"
><el-input ><el-input
v-model="row.quantity" v-model="row.quantity"
@@ -232,7 +240,11 @@
<el-table-column type="selection" width="55" align="center" /> <el-table-column type="selection" width="55" align="center" />
<el-table-column type="index" label="序号" width="70" align="center" /> <el-table-column type="index" label="序号" width="70" align="center" />
<el-table-column label="文件名" min-width="240" align="center" show-overflow-tooltip> <el-table-column label="文件名" min-width="240" align="center" show-overflow-tooltip>
<template #default="{ row }">{{ row.originalName || row.name }}</template> <template #default="{ row }">
<el-link type="primary" @click="previewAttachment(row)">
{{ attachmentName(row) }}
</el-link>
</template>
</el-table-column> </el-table-column>
<el-table-column label="文件大小" width="120" align="center"> <el-table-column label="文件大小" width="120" align="center">
<template #default="{ row }">{{ formatFileSize(row.size) }}</template> <template #default="{ row }">{{ formatFileSize(row.size) }}</template>
@@ -248,6 +260,37 @@
</el-table> </el-table>
</div> </div>
</section> </section>
<el-dialog
v-model="attachmentDocumentPreviewVisible"
:title="attachmentPreviewFile.name || '附件预览'"
append-to-body
destroy-on-close
width="90%"
top="4vh"
class="master-editor__attachment-viewer-dialog"
>
<open-file-viewer
v-if="attachmentDocumentPreviewVisible && attachmentPreviewFile.url"
:file="attachmentPreviewFile.url"
:file-name="attachmentPreviewFile.name"
:mime-type="attachmentPreviewFile.mimeType"
width="100%"
height="72vh"
fit="contain"
theme="auto"
locale="zh-CN"
:toolbar="attachmentViewerToolbar"
:plugins="attachmentViewerPlugins"
@unsupported="handleAttachmentPreviewUnsupported"
@error="handleAttachmentPreviewError"
/>
</el-dialog>
<el-image-viewer
v-if="attachmentImagePreviewVisible"
:url-list="attachmentImagePreviewUrls"
:initial-index="attachmentImagePreviewIndex"
@close="attachmentImagePreviewVisible = false"
/>
<el-dialog <el-dialog
v-model="addressDialogVisible" v-model="addressDialogVisible"
title="选择常用地址" title="选择常用地址"
@@ -440,20 +483,38 @@ import { getList as getCommonAddressList } from '@/api/base/common-address';
import { getList as getAirportMasterList } from '@/api/base/airport-master'; import { getList as getAirportMasterList } from '@/api/base/airport-master';
import { getList as getPortTerminalList } from '@/api/base/port-terminal'; import { getList as getPortTerminalList } from '@/api/base/port-terminal';
import { getList as getRailwayStationList } from '@/api/base/railway-station'; import { getList as getRailwayStationList } from '@/api/base/railway-station';
import { getList as getCargoTypeList } from '@/api/base/cargo-type';
import { getList as getCommonCargoList } from '@/api/business/common-cargo'; import { getList as getCommonCargoList } from '@/api/business/common-cargo';
import { exportBlob, importBlob } from '@/api/common'; import { exportBlob, importBlob } from '@/api/common';
import { downloadFileByUrl, downloadXls } from '@/utils/util'; import { downloadFileByUrl, downloadXls } from '@/utils/util';
import { List, Search } from '@element-plus/icons-vue'; import { List, Search } from '@element-plus/icons-vue';
import { ElImageViewer } from 'element-plus';
import { OpenFileViewer } from '@open-file-viewer/vue';
import { fallbackPlugin, imagePlugin, officePlugin, pdfPlugin, textPlugin } from '@open-file-viewer/core';
import '@open-file-viewer/core/style.css';
import pdfWorkerSrc from 'pdfjs-dist/build/pdf.worker.mjs?url';
import { mapGetters } from 'vuex'; import { mapGetters } from 'vuex';
const AMAP_KEY = '653b7cf105ad7fb8ec9b2f5198ade315'; const AMAP_KEY = '653b7cf105ad7fb8ec9b2f5198ade315';
const AMAP_SECURITY_CODE = '5aab65c632e48ebae0d0e28f5b28e21a'; const AMAP_SECURITY_CODE = '5aab65c632e48ebae0d0e28f5b28e21a';
const quantityUnitOptions = ['吨', '件', '方']; const quantityUnitOptions = ['吨', '件', '方'];
const attachmentViewerPlugins = [
imagePlugin(),
pdfPlugin({ workerSrc: pdfWorkerSrc, useFetchData: true }),
officePlugin({ pdf: { workerSrc: pdfWorkerSrc, useFetchData: true } }),
textPlugin(),
fallbackPlugin(),
];
let amapLoader; let amapLoader;
import { getList as getCommonRouteList } from '@/api/business/common-route'; import { getList as getCommonRouteList } from '@/api/business/common-route';
import * as api from '@/api/business/master-order'; import * as api from '@/api/business/master-order';
import { formatUpdateUserName } from '@/utils/audit'; import { formatUpdateUserName } from '@/utils/audit';
import { isMobile } from '@/utils/validate';
export default { export default {
components: {
ElImageViewer,
OpenFileViewer,
},
props: { id: [String, Number] }, props: { id: [String, Number] },
emits: ['back', 'dispatch'], emits: ['back', 'dispatch'],
data() { data() {
@@ -491,12 +552,28 @@ export default {
commonCargoLoading: false, commonCargoLoading: false,
commonCargoRows: [], commonCargoRows: [],
commonCargoPage: { current: 1, size: 10, total: 0 }, commonCargoPage: { current: 1, size: 10, total: 0 },
cargoNameLoading: false, cargoTypeLoading: false,
cargoOptions: [], cargoTypeOptions: [],
cargoTypeFlatOptions: [],
cargoOptionsMap: {},
cargoOptionsLoadingMap: {},
cargoImportVisible: false, cargoImportVisible: false,
cargoImportLoading: false, cargoImportLoading: false,
attachmentRows: [], attachmentRows: [],
selectedAttachmentRows: [], selectedAttachmentRows: [],
attachmentImagePreviewVisible: false,
attachmentImagePreviewUrls: [],
attachmentImagePreviewIndex: 0,
attachmentDocumentPreviewVisible: false,
attachmentPreviewFile: {},
attachmentViewerPlugins,
attachmentViewerToolbar: {
download: true,
fullscreen: true,
print: true,
rotate: true,
zoom: true,
},
attachmentFileTypes: [ attachmentFileTypes: [
'pdf', 'pdf',
'bmp', 'bmp',
@@ -539,11 +616,19 @@ export default {
air: '空港机场', air: '空港机场',
}[this.addressTransportMode(this.addressTarget)] || '站点'; }[this.addressTransportMode(this.addressTarget)] || '站点';
}, },
cargoTypeCascaderProps() {
return {
value: 'id',
label: 'label',
children: 'children',
emitPath: true,
};
},
}, },
async mounted() { async mounted() {
this.loading = true; this.loading = true;
try { try {
await this.loadProjects(); await Promise.all([this.loadProjects(), this.loadCargoTypeOptions()]);
if (this.id) { if (this.id) {
await this.loadDetail(this.id); await this.loadDetail(this.id);
} }
@@ -574,6 +659,7 @@ export default {
attachmentsJson: detail.attachmentsJson || '[]', attachmentsJson: detail.attachmentsJson || '[]',
transportOrganizationType: '多式联运', transportOrganizationType: '多式联运',
}; };
this.restoreGoodsCargoTypePaths();
this.attachmentRows = this.parseJsonArray(this.form.attachmentsJson); this.attachmentRows = this.parseJsonArray(this.form.attachmentsJson);
this.selectedAttachmentRows = []; this.selectedAttachmentRows = [];
if (this.form.projectId) await this.loadContracts(this.form.projectId, false); if (this.form.projectId) await this.loadContracts(this.form.projectId, false);
@@ -897,31 +983,117 @@ export default {
this.commonCargoLoading = false; this.commonCargoLoading = false;
} }
}, },
async loadCargoOptions(visible) { async loadCargoTypeOptions() {
if (!visible || this.cargoOptions.length) return; if (this.cargoTypeOptions.length || this.cargoTypeLoading) return;
await this.searchCargoOptions(''); this.cargoTypeLoading = true;
},
async searchCargoOptions(keyword) {
this.cargoNameLoading = true;
try { try {
const response = await getCommonCargoList(1, 50, { const response = await getCargoTypeList(1, 9999, { allDept: 0 });
allDept: 0, const rows = this.responseRecords(response);
cargoName: keyword || undefined, this.cargoTypeOptions = this.buildCargoTypeTree(rows);
});
const data = response?.data?.data || response?.data || response || {};
this.cargoOptions = data.records || [];
} finally { } finally {
this.cargoNameLoading = false; this.cargoTypeLoading = false;
}
},
buildCargoTypeTree(rows = []) {
const nodes = new Map();
rows.forEach(row => {
const id = String(row.id);
nodes.set(id, {
...row,
id,
label: row.cargoName || row.cargoTypeName || row.name || row.cargoCode || '',
children: [],
});
});
const roots = [];
nodes.forEach(node => {
const parentId = node.parentId == null ? '' : String(node.parentId);
if (parentId && parentId !== '0' && nodes.has(parentId)) {
nodes.get(parentId).children.push(node);
} else {
roots.push(node);
}
});
this.cargoTypeFlatOptions = [];
const normalize = (node, path = []) => {
const nextPath = [...path, node.id];
const children = node.children.map(child => normalize(child, nextPath));
const normalized = { ...node, children };
this.cargoTypeFlatOptions.push({ ...normalized, path: nextPath });
return normalized;
};
return roots.map(node => normalize(node));
},
getCargoTypeByPath(path = []) {
const normalizedPath = Array.isArray(path) ? path.map(value => String(value)) : [];
const id = normalizedPath.at(-1);
return this.cargoTypeFlatOptions.find(item => item.id === id) || null;
},
getCargoOptionsKey(path = []) {
const cargoType = this.getCargoTypeByPath(path);
return String(cargoType?.cargoCode || cargoType?.code || cargoType?.id || '');
},
getCargoOptionsByType(path = []) {
const key = this.getCargoOptionsKey(path);
return key ? this.cargoOptionsMap[key] || [] : [];
},
isCargoOptionsLoading(path = []) {
const key = this.getCargoOptionsKey(path);
return key ? Boolean(this.cargoOptionsLoadingMap[key]) : false;
},
async loadCargoOptionsByType(path = []) {
const cargoType = this.getCargoTypeByPath(path);
const key = this.getCargoOptionsKey(path);
if (!key || !cargoType) return [];
if (this.cargoOptionsMap[key] || this.cargoOptionsLoadingMap[key]) {
return this.cargoOptionsMap[key] || [];
}
this.cargoOptionsLoadingMap = { ...this.cargoOptionsLoadingMap, [key]: true };
try {
const response = await getCommonCargoList(1, 500, {
allDept: 0,
secondCargoTypeName: cargoType.label,
secondCargoTypeCode: cargoType.cargoCode || cargoType.code || '',
});
const options = this.responseRecords(response);
this.cargoOptionsMap = { ...this.cargoOptionsMap, [key]: options };
return options;
} finally {
this.cargoOptionsLoadingMap = { ...this.cargoOptionsLoadingMap, [key]: false };
} }
}, },
selectCargoName(goods, cargoName) { selectCargoName(goods, cargoName) {
if (!cargoName) return; if (!cargoName) return;
const cargo = this.cargoOptions.find(item => item.cargoName === cargoName); const cargo = this.getCargoOptionsByType(goods.cargoTypePath).find(
item => item.cargoName === cargoName
);
if (!cargo) return; if (!cargo) return;
Object.assign(goods, this.normalizeCargoRow(cargo)); Object.assign(goods, this.normalizeCargoRow(cargo, goods.cargoTypePath));
},
handleCargoTypeChange(goods, value) {
const path = Array.isArray(value) ? value.map(item => String(item)) : [];
const cargoType = this.getCargoTypeByPath(path);
goods.cargoTypePath = path;
goods.cargoType = cargoType?.label || '';
goods.cargoTypeCode = cargoType?.cargoCode || cargoType?.code || cargoType?.id || '';
goods.cargoName = '';
goods.specification = '';
goods.model = '';
this.loadCargoOptionsByType(path);
},
restoreGoodsCargoTypePaths() {
this.form.goods.forEach(goods => {
if (Array.isArray(goods.cargoTypePath) && goods.cargoTypePath.length) return;
const cargoType = this.cargoTypeFlatOptions.find(
item =>
String(item.cargoCode || item.code || '') === String(goods.cargoTypeCode || '') ||
item.label === goods.cargoType
);
if (cargoType) goods.cargoTypePath = cargoType.path;
});
}, },
selectCommonCargo(row) { selectCommonCargo(row) {
const cargo = this.normalizeCargoRow(row); const cargo = this.normalizeCargoRow(row, this.resolveCargoTypePath(row));
const firstGoods = this.form.goods[0]; const firstGoods = this.form.goods[0];
const isFirstGoodsEmpty = const isFirstGoodsEmpty =
firstGoods && firstGoods &&
@@ -943,12 +1115,35 @@ export default {
} }
this.commonCargoVisible = false; this.commonCargoVisible = false;
}, },
normalizeCargoRow(row = {}) { resolveCargoTypePath(row = {}) {
return (
this.cargoTypeFlatOptions.find(
item =>
String(item.cargoCode || item.code || '') ===
String(row.secondCargoTypeCode || row.cargoTypeCode || '') ||
item.label === (row.secondCargoTypeName || row.cargoType || row.goodsType)
)?.path || []
);
},
normalizeCargoRow(row = {}, cargoTypePath = this.resolveCargoTypePath(row)) {
const cargoType = this.getCargoTypeByPath(cargoTypePath);
return { return {
...row, ...row,
cargoName: row.cargoName || row.goodsName || '', cargoName: row.cargoName || row.goodsName || '',
cargoType: cargoType:
row.cargoType || row.goodsType || row.secondCargoTypeName || row.firstCargoTypeName || '', cargoType?.label ||
row.cargoType ||
row.goodsType ||
row.secondCargoTypeName ||
row.firstCargoTypeName ||
'',
cargoTypeCode:
cargoType?.cargoCode ||
cargoType?.code ||
row.cargoTypeCode ||
row.secondCargoTypeCode ||
'',
cargoTypePath,
quantity: row.quantity || '', quantity: row.quantity || '',
quantityUnit: row.quantityUnit || quantityUnitOptions[0], quantityUnit: row.quantityUnit || quantityUnitOptions[0],
packageType: row.packageType || row.package || '', packageType: row.packageType || row.package || '',
@@ -1096,13 +1291,54 @@ export default {
if (number < 1024 * 1024) return `${(number / 1024).toFixed(1)}KB`; if (number < 1024 * 1024) return `${(number / 1024).toFixed(1)}KB`;
return `${(number / 1024 / 1024).toFixed(1)}MB`; return `${(number / 1024 / 1024).toFixed(1)}MB`;
}, },
attachmentUrl(row = {}) {
return row.url || row.link || row.fileUrl || row.domain || '';
},
attachmentName(row = {}) {
return row.originalName || row.name || row.fileName || '附件';
},
attachmentExtension(row = {}) {
const source = String(this.attachmentName(row) || this.attachmentUrl(row)).split('?')[0];
const index = source.lastIndexOf('.');
return index > -1 ? source.slice(index + 1).toLowerCase() : '';
},
isAttachmentImage(row) {
return ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp'].includes(this.attachmentExtension(row));
},
previewAttachment(row) {
const url = this.attachmentUrl(row);
if (!url) {
this.$message.warning('附件地址为空,无法预览');
return;
}
if (this.isAttachmentImage(row)) {
this.attachmentImagePreviewUrls = this.attachmentRows
.filter(item => this.isAttachmentImage(item) && this.attachmentUrl(item))
.map(item => this.attachmentUrl(item));
this.attachmentImagePreviewIndex = Math.max(this.attachmentImagePreviewUrls.indexOf(url), 0);
this.attachmentImagePreviewVisible = true;
return;
}
this.attachmentPreviewFile = {
name: this.attachmentName(row),
url,
mimeType: row.mimeType || row.contentType || '',
};
this.attachmentDocumentPreviewVisible = true;
},
handleAttachmentPreviewUnsupported() {
this.$message.warning('当前文件暂不支持在线预览');
},
handleAttachmentPreviewError() {
this.$message.error('附件预览失败');
},
downloadAttachment(row) { downloadAttachment(row) {
const url = row.url || row.link; const url = this.attachmentUrl(row);
if (!url) { if (!url) {
this.$message.warning('附件地址为空'); this.$message.warning('附件地址为空');
return; return;
} }
downloadFileByUrl(url, row.originalName || row.name || '附件'); downloadFileByUrl(url, this.attachmentName(row));
}, },
handleAttachmentBatchDownload() { handleAttachmentBatchDownload() {
const rows = this.selectedAttachmentRows.length const rows = this.selectedAttachmentRows.length
@@ -1111,6 +1347,21 @@ export default {
rows.forEach(row => this.downloadAttachment(row)); rows.forEach(row => this.downloadAttachment(row));
}, },
async save(draft = false) { async save(draft = false) {
const phoneFields = [
[this.form.departurePhone, '发货联系方式'],
...this.form.routes.map((route, index) => [
route.departurePhone,
`第${index + 1}个途经地联系方式`,
]),
[this.form.arrivalPhone, '收货联系方式'],
];
const invalidPhone = phoneFields.find(
([value]) => String(value || '').trim() && !isMobile(value)
);
if (invalidPhone) {
this.$message.warning(`${invalidPhone[1]}格式不正确`);
return false;
}
if (!draft) { if (!draft) {
await this.$refs.form.validate(); await this.$refs.form.validate();
if (!this.form.departureAddress || !this.form.arrivalAddress) if (!this.form.departureAddress || !this.form.arrivalAddress)
@@ -1121,7 +1372,7 @@ export default {
this.form.routes.some(item => !item.departureName || !item.transportType) || this.form.routes.some(item => !item.departureName || !item.transportType) ||
!this.form.finalTransportType !this.form.finalTransportType
) )
return this.$message.warning('请完整填写途经地及运输类型'); return this.$message.warning('请完整填写途经地及运输方式');
} }
const payload = { const payload = {
...this.form, ...this.form,
@@ -1145,15 +1396,18 @@ export default {
return res?.data?.data || res?.data || res; return res?.data?.data || res?.data || res;
}, },
async saveDraft() { async saveDraft() {
await this.save(true); const result = await this.save(true);
if (result === false) return;
this.$emit('back'); this.$emit('back');
}, },
async confirmCreate() { async confirmCreate() {
await this.save(); const result = await this.save();
if (result === false) return;
this.$emit('back'); this.$emit('back');
}, },
async createAndDispatch() { async createAndDispatch() {
const data = await this.save(); const data = await this.save();
if (data === false) return;
const id = data?.id || data?.masterOrderId || data?.masterId; const id = data?.id || data?.masterOrderId || data?.masterId;
if (!id) { if (!id) {
this.$message.error('保存成功但未获取到总单ID,无法进入调度'); this.$message.error('保存成功但未获取到总单ID,无法进入调度');
@@ -1223,7 +1477,7 @@ export default {
} }
} }
:deep(.master-editor__quantity-cell .el-input) { :deep(.master-editor__quantity-cell .el-input) {
width: 108px; width: 156px;
} }
.section-head { .section-head {
display: flex; display: flex;
@@ -27,7 +27,7 @@
<el-dialog v-model="detailVisible" title="导入运单详情" width="90%" append-to-body> <el-dialog v-model="detailVisible" title="导入运单详情" width="90%" append-to-body>
<el-form :inline="true" :model="detailQuery"><el-form-item label="车牌号/船号"><el-input v-model="detailQuery.vehicleNo" clearable /></el-form-item><el-form-item label="货物名称"><el-input v-model="detailQuery.cargoName" clearable /></el-form-item><el-button type="primary" @click="loadDetails">查询</el-button></el-form> <el-form :inline="true" :model="detailQuery"><el-form-item label="车牌号/船号"><el-input v-model="detailQuery.vehicleNo" clearable /></el-form-item><el-form-item label="货物名称"><el-input v-model="detailQuery.cargoName" clearable /></el-form-item><el-button type="primary" @click="loadDetails">查询</el-button></el-form>
<el-table :data="details" border><el-table-column type="index" label="序号" width="65" /><el-table-column prop="batchNo" label="运单批次号" min-width="140" /><el-table-column prop="originalNo" label="原始单号" min-width="130" /><el-table-column prop="vehicleNo" label="车牌号/航班号/船号/班列号" min-width="190" /><el-table-column prop="driverName" label="司机/船长" min-width="120" /><el-table-column prop="transportType" label="运输类型" width="110" /><el-table-column prop="cargoName" label="货物名称" width="130" /><el-table-column prop="cargoType" label="货物类型" width="120" /><el-table-column prop="quantity" label="重量" width="90" /><el-table-column prop="departureAddress" label="发货地址" min-width="220" show-overflow-tooltip /><el-table-column prop="departureContact" label="发货联系人" width="110" /><el-table-column prop="departurePhone" label="发货联系人电话" width="140" /><el-table-column prop="arrivalAddress" label="到货地址" min-width="220" show-overflow-tooltip /><el-table-column prop="arrivalContact" label="收货联系人" width="110" /><el-table-column prop="arrivalPhone" label="收货联系人电话" width="140" /><el-table-column prop="startDate" label="开始时间" width="160" sortable/><el-table-column prop="endDate" label="结束时间" width="160" sortable/><el-table-column prop="unitPrice" label="单价" width="90" /><el-table-column prop="freight" label="运费" width="100" /><el-table-column prop="otherFeeTotal" label="其他费用合计" width="120" /><el-table-column prop="freightTotal" label="运费合计" width="100" /><el-table-column prop="remark" label="备注" min-width="160" /><el-table-column label="操作" width="80">-</el-table-column></el-table> <el-table :data="details" border><el-table-column type="index" label="序号" width="65" /><el-table-column prop="batchNo" label="运单批次号" min-width="140" /><el-table-column prop="originalNo" label="原始单号" min-width="130" /><el-table-column prop="vehicleNo" label="车牌号/航班号/船号/班列号" min-width="190" /><el-table-column prop="driverName" label="司机/船长" min-width="120" /><el-table-column prop="transportType" label="运输方式" width="110" /><el-table-column prop="cargoName" label="货物名称" width="130" /><el-table-column prop="cargoType" label="货物类型" width="120" /><el-table-column prop="quantity" label="重量" width="90" /><el-table-column prop="departureAddress" label="发货地址" min-width="220" show-overflow-tooltip /><el-table-column prop="departureContact" label="发货联系人" width="110" /><el-table-column prop="departurePhone" label="发货联系人电话" width="140" /><el-table-column prop="arrivalAddress" label="到货地址" min-width="220" show-overflow-tooltip /><el-table-column prop="arrivalContact" label="收货联系人" width="110" /><el-table-column prop="arrivalPhone" label="收货联系人电话" width="140" /><el-table-column prop="startDate" label="开始时间" width="160" sortable/><el-table-column prop="endDate" label="结束时间" width="160" sortable/><el-table-column prop="unitPrice" label="单价" width="90" /><el-table-column prop="freight" label="运费" width="100" /><el-table-column prop="otherFeeTotal" label="其他费用合计" width="120" /><el-table-column prop="freightTotal" label="运费合计" width="100" /><el-table-column prop="remark" label="备注" min-width="160" /><el-table-column label="操作" width="80">-</el-table-column></el-table>
<el-pagination v-model:current-page="detailPage.current" v-model:page-size="detailPage.size" layout="total, prev, pager, next, sizes" :page-sizes="[10, 20, 50]" :total="detailPage.total" @current-change="loadDetails" @size-change="loadDetails" /> <el-pagination v-model:current-page="detailPage.current" v-model:page-size="detailPage.size" layout="total, prev, pager, next, sizes" :page-sizes="[10, 20, 50]" :total="detailPage.total" @current-change="loadDetails" @size-change="loadDetails" />
</el-dialog> </el-dialog>
@@ -74,7 +74,7 @@ const detailColumns = [
{ prop: 'originalNo', label: '原始单号', editor: 'input', minWidth: 150 }, { prop: 'originalNo', label: '原始单号', editor: 'input', minWidth: 150 },
{ prop: 'vehicleNo', label: '车牌号/航班号/船号/班列号', editor: 'input', minWidth: 220 }, { prop: 'vehicleNo', label: '车牌号/航班号/船号/班列号', editor: 'input', minWidth: 220 },
{ prop: 'driverName', label: '司机/船长', editor: 'driver', minWidth: 170 }, { prop: 'driverName', label: '司机/船长', editor: 'driver', minWidth: 170 },
{ prop: 'transportType', label: '运输类型', editor: 'transportType', minWidth: 150 }, { prop: 'transportType', label: '运输方式', editor: 'transportType', minWidth: 150 },
{ prop: 'cargoType', label: '货物类型', editor: 'cargoType', minWidth: 220 }, { prop: 'cargoType', label: '货物类型', editor: 'cargoType', minWidth: 220 },
{ prop: 'cargoName', label: '货物名称', editor: 'cargoName', minWidth: 220 }, { prop: 'cargoName', label: '货物名称', editor: 'cargoName', minWidth: 220 },
{ prop: 'quantity', label: '重量', editor: 'number', minWidth: 130 }, { prop: 'quantity', label: '重量', editor: 'number', minWidth: 130 },
@@ -92,7 +92,7 @@ const detailColumns = [
{ prop: 'freightTotal', label: '运费合计', editor: 'number', minWidth: 130 }, { prop: 'freightTotal', label: '运费合计', editor: 'number', minWidth: 130 },
{ prop: 'remark', label: '备注', editor: 'textarea', minWidth: 180 }, { prop: 'remark', label: '备注', editor: 'textarea', minWidth: 180 },
]; ];
const requiredDetailFields = [{ prop: 'vehicleNo', label: '车牌号/航班号/船号/班列号' }, { prop: 'driverName', label: '司机/船长' }, { prop: 'transportType', label: '运输类型' }, { prop: 'cargoName', label: '货物名称' }, { prop: 'cargoType', label: '货物类型' }, { prop: 'departureAddress', label: '发货地址' }, { prop: 'departureContact', label: '发货联系人' }, { prop: 'departurePhone', label: '发货联系人电话' }, { prop: 'arrivalAddress', label: '到货地址' }, { prop: 'arrivalContact', label: '到货联系人' }, { prop: 'arrivalPhone', label: '收货联系人电话' }, { prop: 'startDate', label: '开始时间' }, { prop: 'endDate', label: '结束时间' }, { prop: 'unitPrice', label: '单价' }, { prop: 'freight', label: '运费' }]; const requiredDetailFields = [{ prop: 'vehicleNo', label: '车牌号/航班号/船号/班列号' }, { prop: 'driverName', label: '司机/船长' }, { prop: 'transportType', label: '运输方式' }, { prop: 'cargoName', label: '货物名称' }, { prop: 'cargoType', label: '货物类型' }, { prop: 'departureAddress', label: '发货地址' }, { prop: 'departureContact', label: '发货联系人' }, { prop: 'departurePhone', label: '发货联系人电话' }, { prop: 'arrivalAddress', label: '到货地址' }, { prop: 'arrivalContact', label: '到货联系人' }, { prop: 'arrivalPhone', label: '收货联系人电话' }, { prop: 'startDate', label: '开始时间' }, { prop: 'endDate', label: '结束时间' }, { prop: 'unitPrice', label: '单价' }, { prop: 'freight', label: '运费' }];
const projectQueryParams = { approvalStatuses: 'approved,change_approved' }; const projectQueryParams = { approvalStatuses: 'approved,change_approved' };
const extractRecords = res => { const extractRecords = res => {
const data = res?.data || res; const data = res?.data || res;
+35 -21
View File
@@ -46,7 +46,7 @@
/> />
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="运输类型"> <el-form-item label="运输方式">
<el-select <el-select
v-model="searchForm.transportType" v-model="searchForm.transportType"
clearable clearable
@@ -300,7 +300,7 @@
</el-table-column> </el-table-column>
<el-table-column label="承运商" prop="carrierName" min-width="160" show-overflow-tooltip /> <el-table-column label="承运商" prop="carrierName" min-width="160" show-overflow-tooltip />
<el-table-column <el-table-column
label="运输类型" label="运输方式"
prop="transportType" prop="transportType"
min-width="150" min-width="150"
show-overflow-tooltip show-overflow-tooltip
@@ -555,7 +555,7 @@
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="6"> <el-col :span="6">
<el-form-item label="运输类型"> <el-form-item label="运输方式">
<el-select <el-select
v-model="candidateQuery.transportType" v-model="candidateQuery.transportType"
clearable clearable
@@ -621,7 +621,7 @@
/> />
<el-table-column <el-table-column
prop="transportType" prop="transportType"
label="运输类型" label="运输方式"
min-width="130" min-width="130"
show-overflow-tooltip show-overflow-tooltip
/> />
@@ -947,6 +947,7 @@ import {
import { ElMessage, ElMessageBox } from 'element-plus'; import { ElMessage, ElMessageBox } from 'element-plus';
import { Location, Rank, Refresh, Setting } from '@element-plus/icons-vue'; import { Location, Rank, Refresh, Setting } from '@element-plus/icons-vue';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import { isMobile } from '@/utils/validate';
const defaultTransportType = ''; const defaultTransportType = '';
const defaultCandidateTransportType = ''; const defaultCandidateTransportType = '';
@@ -1253,26 +1254,30 @@ export default {
formatRouteAddress(row, type) { formatRouteAddress(row, type) {
const prefix = type === 'departure' ? 'departure' : type === 'arrival' ? 'arrival' : 'transit'; const prefix = type === 'departure' ? 'departure' : type === 'arrival' ? 'arrival' : 'transit';
const values = [ const values = [
row[`${prefix}Name`],
row[`${prefix}RegionName`], row[`${prefix}RegionName`],
row[`${prefix}DistrictName`], row[`${prefix}DistrictName`],
row[`${prefix}Address`], row[`${prefix}Address`],
] ]
.filter(Boolean) .filter(Boolean)
.flatMap(value => this.splitText(value)); .flatMap(value => this.splitText(value))
const addressType = [ .map(value => this.formatCityDistrict(value))
row[`${prefix}AddressType`], .filter(Boolean);
row[`${prefix}Type`], return [...new Set(values)].join(',') || '-';
row.addressType, },
] formatCityDistrict(value) {
.filter(Boolean) const text = String(value || '').trim();
.join(''); if (!text) return '';
const isStation = /港口|码头|机场|铁路|车站|空港/.test(`${addressType}${values[0] || ''}`); const cityIndex = text.indexOf('市');
const displayValues = values.filter( const provinceIndex = Math.max(text.lastIndexOf('省', cityIndex), text.lastIndexOf('自治区', cityIndex));
(value, index) => index === 0 || isStation || !row[`${prefix}Name`] || value !== row[`${prefix}Address`] const cityStart = provinceIndex > -1 ? provinceIndex + 1 : 0;
); const districtStart = cityIndex > -1 ? cityIndex + 1 : cityStart;
if (type === 'transit') return displayValues.join(',') || '-'; const districtMatch = text
return (isStation ? displayValues[0] : row[`${prefix}Name`] || displayValues[0]) || '-'; .slice(districtStart)
.match(/(?:自治县|自治旗|林区|矿区|新区|开发区|区|县|旗)/);
if (districtMatch) {
return text.slice(cityStart, districtStart + districtMatch.index + districtMatch[0].length);
}
return cityIndex > -1 ? text.slice(cityStart, cityIndex + 1) : text;
}, },
rowActions(row) { rowActions(row) {
const status = row.businessStatus; const status = row.businessStatus;
@@ -1804,7 +1809,8 @@ export default {
planDate: item.planDate || waybill.startDate || '', planDate: item.planDate || waybill.startDate || '',
unitPrice: item.unitPrice || waybill.unitPrice || '', unitPrice: item.unitPrice || waybill.unitPrice || '',
materialCode: item.materialCode || '', materialCode: item.materialCode || '',
equipmentCode: item.equipmentCode || '', equipmentCode:
item.equipmentCode || item.deviceCode || waybill.equipmentCode || waybill.deviceCode || '',
brand: item.brand || item.brandName || waybill.brand || '', brand: item.brand || item.brandName || waybill.brand || '',
specificationModel: specificationModel:
item.specificationModel || item.specModel || item.specification || waybill.specificationModel || '', item.specificationModel || item.specModel || item.specification || waybill.specificationModel || '',
@@ -1840,7 +1846,15 @@ export default {
return false; return false;
} }
if (!this.dialogForm.transportType) { if (!this.dialogForm.transportType) {
ElMessage.warning('请选择运输类型'); ElMessage.warning('请选择运输方式');
return false;
}
const invalidPhone = [
[this.dialogForm.driverPhone, '手机号'],
[this.dialogForm.escortPhone, '押运人手机号'],
].find(([value]) => String(value || '').trim() && !isMobile(value));
if (invalidPhone) {
ElMessage.warning(`${invalidPhone[1]}格式不正确`);
return false; return false;
} }
if (draftMode) { if (draftMode) {