修复业务模块bug
This commit is contained in:
@@ -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' },
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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',
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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,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',
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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: '附件',
|
||||||
|
|||||||
@@ -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: '过程节点',
|
||||||
|
|||||||
@@ -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());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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);
|
||||||
},
|
},
|
||||||
@@ -5189,6 +5326,9 @@ export default {
|
|||||||
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;
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
Reference in New Issue
Block a user