Compare commits
4 Commits
23b23dc8e5
...
96f2fa0eb5
| Author | SHA1 | Date | |
|---|---|---|---|
| 96f2fa0eb5 | |||
| e789f1647a | |||
| c63c7c3e33 | |||
| 024e77801e |
@@ -59,14 +59,14 @@ export const withdraw = id =>
|
|||||||
params: { id },
|
params: { id },
|
||||||
});
|
});
|
||||||
|
|
||||||
export const startChange = (id, reason) =>
|
export const startChange = (id, changeContent, changeReason = changeContent) =>
|
||||||
request({
|
request({
|
||||||
url: `${baseUrl}/start-change`,
|
url: `${baseUrl}/start-change`,
|
||||||
method: 'post',
|
method: 'post',
|
||||||
params: {
|
params: {
|
||||||
id,
|
id,
|
||||||
changeContent: reason,
|
changeContent: changeContent || '',
|
||||||
changeReason: reason,
|
changeReason,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -86,3 +86,10 @@ export const getDictionary = params => {
|
|||||||
params,
|
params,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getDictionaryAll = () => {
|
||||||
|
return request({
|
||||||
|
url: '/blade-system/dict-biz/select-all',
|
||||||
|
method: 'get',
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|||||||
@@ -15,10 +15,12 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import VuePdfEmbed from 'vue-pdf-embed';
|
import { defineAsyncComponent } from 'vue';
|
||||||
import 'vue-pdf-embed/dist/styles/annotationLayer.css';
|
import 'vue-pdf-embed/dist/styles/annotationLayer.css';
|
||||||
import 'vue-pdf-embed/dist/styles/textLayer.css';
|
import 'vue-pdf-embed/dist/styles/textLayer.css';
|
||||||
|
|
||||||
|
const VuePdfEmbed = defineAsyncComponent(() => import('vue-pdf-embed'));
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'PdfPreview',
|
name: 'PdfPreview',
|
||||||
components: { VuePdfEmbed },
|
components: { VuePdfEmbed },
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ export const getCargoTypeOption = ctx => ({
|
|||||||
height: 'auto',
|
height: 'auto',
|
||||||
calcHeight: 22,
|
calcHeight: 22,
|
||||||
dialogWidth: 760,
|
dialogWidth: 760,
|
||||||
|
// 货物类型弹窗单独使用样式,避免全局 Avue footer 上移遮挡底部备注字段
|
||||||
|
dialogCustomClass: 'cargo-type-dialog',
|
||||||
labelPosition: 'right',
|
labelPosition: 'right',
|
||||||
labelWidth: 'auto',
|
labelWidth: 'auto',
|
||||||
tip: false,
|
tip: false,
|
||||||
@@ -172,6 +174,7 @@ export const getCargoTypeOption = ctx => ({
|
|||||||
label: '备注',
|
label: '备注',
|
||||||
prop: 'remark',
|
prop: 'remark',
|
||||||
type: 'textarea',
|
type: 'textarea',
|
||||||
|
formslot: true,
|
||||||
minRows: 2,
|
minRows: 2,
|
||||||
span: 24,
|
span: 24,
|
||||||
minWidth: 180,
|
minWidth: 180,
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import {
|
|||||||
auditColumns,
|
auditColumns,
|
||||||
createCrudOption,
|
createCrudOption,
|
||||||
packageOptions,
|
packageOptions,
|
||||||
priceUnitOptions,
|
|
||||||
textRule,
|
textRule,
|
||||||
} from './common';
|
} from './common';
|
||||||
|
|
||||||
@@ -25,10 +24,42 @@ export const config = {
|
|||||||
enableAllDept: false,
|
enableAllDept: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 单价统一按两位小数展示;格式非法时保留原值,交由表单校验提示。
|
||||||
|
export const formatCargoValue = value => {
|
||||||
|
if (value === undefined || value === null) return '';
|
||||||
|
const raw = String(value).trim();
|
||||||
|
if (!raw || raw === '-1') return '';
|
||||||
|
if (!/^\d+(?:\.\d+)?$/.test(raw)) return raw;
|
||||||
|
const number = Number(raw);
|
||||||
|
return Number.isFinite(number) ? number.toFixed(2) : raw;
|
||||||
|
};
|
||||||
|
|
||||||
export const option = createCrudOption([
|
export const option = createCrudOption([
|
||||||
|
{
|
||||||
|
label: '一级货物类型',
|
||||||
|
prop: 'firstCargoTypeName',
|
||||||
|
formslot: true,
|
||||||
|
order: 60,
|
||||||
|
span: 12,
|
||||||
|
minWidth: 150,
|
||||||
|
editDisabled: true,
|
||||||
|
rules: [{ required: true, message: '请选择一级货物类型', trigger: 'change' }],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '二级货物类型',
|
||||||
|
prop: 'secondCargoTypeName',
|
||||||
|
formslot: true,
|
||||||
|
order: 59,
|
||||||
|
span: 12,
|
||||||
|
minWidth: 150,
|
||||||
|
editDisabled: true,
|
||||||
|
rules: [{ required: true, message: '请选择二级货物类型', trigger: 'change' }],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: '货物名称',
|
label: '货物名称',
|
||||||
prop: 'cargoName',
|
prop: 'cargoName',
|
||||||
|
order: 50,
|
||||||
|
span: 12,
|
||||||
minWidth: 150,
|
minWidth: 150,
|
||||||
placeholder: '请输入',
|
placeholder: '请输入',
|
||||||
rules: textRule('货物名称', 100, true),
|
rules: textRule('货物名称', 100, true),
|
||||||
@@ -37,6 +68,8 @@ export const option = createCrudOption([
|
|||||||
label: '货物编号',
|
label: '货物编号',
|
||||||
prop: 'cargoCode',
|
prop: 'cargoCode',
|
||||||
formslot: true,
|
formslot: true,
|
||||||
|
order: 49,
|
||||||
|
span: 12,
|
||||||
minWidth: 120,
|
minWidth: 120,
|
||||||
maxlength: 20,
|
maxlength: 20,
|
||||||
showWordLimit: true,
|
showWordLimit: true,
|
||||||
@@ -45,14 +78,6 @@ export const option = createCrudOption([
|
|||||||
{ max: 20, message: '货物编号不能超过20个字符', trigger: 'blur' },
|
{ max: 20, message: '货物编号不能超过20个字符', trigger: 'blur' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
|
||||||
label: '一级货物类型',
|
|
||||||
prop: 'firstCargoTypeName',
|
|
||||||
formslot: true,
|
|
||||||
minWidth: 150,
|
|
||||||
editDisabled: true,
|
|
||||||
rules: [{ required: true, message: '请选择一级货物类型', trigger: 'change' }],
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
label: '一级货物类型编码',
|
label: '一级货物类型编码',
|
||||||
prop: 'firstCargoTypeCode',
|
prop: 'firstCargoTypeCode',
|
||||||
@@ -61,14 +86,6 @@ export const option = createCrudOption([
|
|||||||
addDisplay: false,
|
addDisplay: false,
|
||||||
editDisabled: true,
|
editDisabled: true,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
label: '二级货物类型',
|
|
||||||
prop: 'secondCargoTypeName',
|
|
||||||
formslot: true,
|
|
||||||
minWidth: 150,
|
|
||||||
editDisabled: true,
|
|
||||||
rules: [{ required: true, message: '请选择二级货物类型', trigger: 'change' }],
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
label: '二级货物类型编码',
|
label: '二级货物类型编码',
|
||||||
prop: 'secondCargoTypeCode',
|
prop: 'secondCargoTypeCode',
|
||||||
@@ -174,12 +191,16 @@ export const option = createCrudOption([
|
|||||||
prop: 'packageType',
|
prop: 'packageType',
|
||||||
type: 'select',
|
type: 'select',
|
||||||
dicData: packageOptions,
|
dicData: packageOptions,
|
||||||
|
order: 39,
|
||||||
|
span: 12,
|
||||||
minWidth: 100,
|
minWidth: 100,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '品牌',
|
label: '品牌',
|
||||||
prop: 'brand',
|
prop: 'brand',
|
||||||
search: false,
|
search: false,
|
||||||
|
order: 40,
|
||||||
|
span: 12,
|
||||||
minWidth: 120,
|
minWidth: 120,
|
||||||
placeholder: '请输入',
|
placeholder: '请输入',
|
||||||
rules: textRule('品牌', 50),
|
rules: textRule('品牌', 50),
|
||||||
@@ -187,22 +208,18 @@ export const option = createCrudOption([
|
|||||||
{
|
{
|
||||||
label: '规格',
|
label: '规格',
|
||||||
prop: 'specification',
|
prop: 'specification',
|
||||||
|
order: 29,
|
||||||
|
span: 12,
|
||||||
minWidth: 120,
|
minWidth: 120,
|
||||||
placeholder: '请输入',
|
placeholder: '请输入',
|
||||||
rules: textRule('规格', 50),
|
rules: textRule('规格', 50),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '型号',
|
label: '货值',
|
||||||
prop: 'model',
|
|
||||||
minWidth: 120,
|
|
||||||
placeholder: '请输入',
|
|
||||||
rules: textRule('型号', 50),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '单价',
|
|
||||||
prop: 'cargoValue',
|
prop: 'cargoValue',
|
||||||
formslot: true,
|
formslot: true,
|
||||||
formatter: row => (String(row.cargoValue) === '-1' ? '' : row.cargoValue),
|
order: 30,
|
||||||
|
formatter: row => formatCargoValue(row.cargoValue),
|
||||||
minWidth: 120,
|
minWidth: 120,
|
||||||
rules: [
|
rules: [
|
||||||
{
|
{
|
||||||
@@ -225,10 +242,19 @@ export const option = createCrudOption([
|
|||||||
label: '计价单位',
|
label: '计价单位',
|
||||||
prop: 'priceUnit',
|
prop: 'priceUnit',
|
||||||
type: 'select',
|
type: 'select',
|
||||||
dicData: priceUnitOptions,
|
dicData: [],
|
||||||
display: false,
|
display: false,
|
||||||
minWidth: 120,
|
minWidth: 120,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: '型号',
|
||||||
|
prop: 'model',
|
||||||
|
order: 19,
|
||||||
|
span: 12,
|
||||||
|
minWidth: 120,
|
||||||
|
placeholder: '请输入',
|
||||||
|
rules: textRule('型号', 50, true),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: '尺寸',
|
label: '尺寸',
|
||||||
prop: 'sizeText',
|
prop: 'sizeText',
|
||||||
@@ -238,23 +264,19 @@ export const option = createCrudOption([
|
|||||||
rules: textRule('尺寸', 100),
|
rules: textRule('尺寸', 100),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '其他说明1',
|
label: '说明',
|
||||||
prop: 'descriptionOne',
|
prop: 'descriptionOne',
|
||||||
|
order: 20,
|
||||||
|
span: 12,
|
||||||
minWidth: 160,
|
minWidth: 160,
|
||||||
placeholder: '请输入',
|
placeholder: '请输入',
|
||||||
rules: textRule('其他说明1', 100),
|
rules: textRule('说明', 100),
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '其他说明2',
|
|
||||||
prop: 'descriptionTwo',
|
|
||||||
minWidth: 160,
|
|
||||||
placeholder: '请输入',
|
|
||||||
rules: textRule('其他说明2', 100),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '备注',
|
label: '备注',
|
||||||
prop: 'remark',
|
prop: 'remark',
|
||||||
type: 'textarea',
|
type: 'textarea',
|
||||||
|
order: 10,
|
||||||
minRows: 2,
|
minRows: 2,
|
||||||
span: 24,
|
span: 24,
|
||||||
maxlength: 200,
|
maxlength: 200,
|
||||||
|
|||||||
@@ -138,7 +138,6 @@ export const option = {
|
|||||||
searchPlaceholder: '请选择',
|
searchPlaceholder: '请选择',
|
||||||
dicData: processStatusOptions,
|
dicData: processStatusOptions,
|
||||||
slot: true,
|
slot: true,
|
||||||
hide: true,
|
|
||||||
fixed: 'right',
|
fixed: 'right',
|
||||||
minWidth: 100,
|
minWidth: 100,
|
||||||
addDisplay: false,
|
addDisplay: false,
|
||||||
|
|||||||
@@ -268,6 +268,19 @@ export const option = {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const exportColumns = [
|
||||||
|
{ prop: 'vehicleType', label: '车船类型' },
|
||||||
|
{ prop: 'vehicleNo', label: '车牌号/船号' },
|
||||||
|
{ prop: 'accidentDate', label: '事故发生日期' },
|
||||||
|
{ prop: 'accidentLocation', label: '事故发生地点' },
|
||||||
|
{ prop: 'accidentNature', label: '事故性质' },
|
||||||
|
{ prop: 'accidentResponsibility', label: '事故责任' },
|
||||||
|
{ prop: 'directEconomicLoss', label: '直接经济损失' },
|
||||||
|
{ prop: 'insuranceClaimAmount', label: '保险理赔金额' },
|
||||||
|
{ prop: 'accidentReasonDamage', label: '事故原因及损坏情况' },
|
||||||
|
{ prop: 'remark', label: '备注' },
|
||||||
|
];
|
||||||
|
|
||||||
export const excelOption = {
|
export const excelOption = {
|
||||||
labelPosition: 'right',
|
labelPosition: 'right',
|
||||||
submitBtn: false,
|
submitBtn: false,
|
||||||
|
|||||||
@@ -278,6 +278,20 @@ export const option = {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const exportColumns = [
|
||||||
|
{ prop: 'vehicleType', label: '车船类型' },
|
||||||
|
{ prop: 'vehicleNo', label: '车牌号/船号' },
|
||||||
|
{ prop: 'inspectionAssessmentDate', label: '检测评定日期' },
|
||||||
|
{ prop: 'validUntilDate', label: '有效期截止日' },
|
||||||
|
{ prop: 'vehicleTechnicalLevel', label: '车辆技术等级' },
|
||||||
|
{ prop: 'shipInspectionType', label: '船舶检验类型' },
|
||||||
|
{ prop: 'passengerTypeLevel', label: '客车类型及等级' },
|
||||||
|
{ prop: 'inspectionUnit', label: '检测评定单位' },
|
||||||
|
{ prop: 'fee', label: '费用(元)' },
|
||||||
|
{ prop: 'assessmentUnit', label: '评定(复核)单位' },
|
||||||
|
{ prop: 'remark', label: '备注' },
|
||||||
|
];
|
||||||
|
|
||||||
export const excelOption = {
|
export const excelOption = {
|
||||||
labelPosition: 'right',
|
labelPosition: 'right',
|
||||||
submitBtn: false,
|
submitBtn: false,
|
||||||
|
|||||||
@@ -211,6 +211,19 @@ export const option = {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const exportColumns = [
|
||||||
|
{ prop: 'vehicleType', label: '车船类型' },
|
||||||
|
{ prop: 'vehicleNo', label: '车牌号/船号' },
|
||||||
|
{ prop: 'equipmentCode', label: '设备号' },
|
||||||
|
{ prop: 'equipmentName', label: '设备名称' },
|
||||||
|
{ prop: 'factoryDate', label: '出厂日期' },
|
||||||
|
{ prop: 'equipmentBrand', label: '设备品牌' },
|
||||||
|
{ prop: 'equipmentType', label: '设备类型' },
|
||||||
|
{ prop: 'specificationModel', label: '规格型号' },
|
||||||
|
{ prop: 'remark', label: '备注' },
|
||||||
|
{ prop: 'originalEquipmentNo', label: '原厂设备号' },
|
||||||
|
];
|
||||||
|
|
||||||
export const excelOption = {
|
export const excelOption = {
|
||||||
labelPosition: 'right',
|
labelPosition: 'right',
|
||||||
submitBtn: false,
|
submitBtn: false,
|
||||||
|
|||||||
@@ -206,6 +206,18 @@ export const option = {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const exportColumns = [
|
||||||
|
{ prop: 'vehicleNo', label: '车牌号' },
|
||||||
|
{ prop: 'entryTime', label: '入口时间' },
|
||||||
|
{ prop: 'etcCardNo', label: 'ETC卡号' },
|
||||||
|
{ prop: 'exitTime', label: '出口时间' },
|
||||||
|
{ prop: 'entryStation', label: '入口站' },
|
||||||
|
{ prop: 'transactionAmount', label: '交易金额' },
|
||||||
|
{ prop: 'exitStation', label: '出口站' },
|
||||||
|
{ prop: 'dataSource', label: '数据来源' },
|
||||||
|
{ prop: 'remark', label: '备注' },
|
||||||
|
];
|
||||||
|
|
||||||
export const excelOption = {
|
export const excelOption = {
|
||||||
labelPosition: 'right',
|
labelPosition: 'right',
|
||||||
submitBtn: false,
|
submitBtn: false,
|
||||||
|
|||||||
@@ -240,6 +240,20 @@ export const option = {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const exportColumns = [
|
||||||
|
{ prop: 'vehicleType', label: '车船类型' },
|
||||||
|
{ prop: 'vehicleNo', label: '车牌号/船号' },
|
||||||
|
{ prop: 'insuranceType', label: '保险类型' },
|
||||||
|
{ prop: 'policyNo', label: '保单号' },
|
||||||
|
{ prop: 'startDate', label: '开始日期' },
|
||||||
|
{ prop: 'endDate', label: '结束日期' },
|
||||||
|
{ prop: 'insuredAmount', label: '保额' },
|
||||||
|
{ prop: 'premium', label: '保费' },
|
||||||
|
{ prop: 'invoiceNo', label: '发票号' },
|
||||||
|
{ prop: 'invoiceDate', label: '开票日期' },
|
||||||
|
{ prop: 'remark', label: '备注' },
|
||||||
|
];
|
||||||
|
|
||||||
export const excelOption = {
|
export const excelOption = {
|
||||||
labelPosition: 'right',
|
labelPosition: 'right',
|
||||||
submitBtn: false,
|
submitBtn: false,
|
||||||
|
|||||||
@@ -199,6 +199,15 @@ export const option = {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const exportColumns = [
|
||||||
|
{ prop: 'vehicleNo', label: '车牌号' },
|
||||||
|
{ prop: 'previousMonthMileage', label: '上月统计里程数' },
|
||||||
|
{ prop: 'currentMonthMileage', label: '本月统计里程数' },
|
||||||
|
{ prop: 'monthlyMileage', label: '本月行驶里程数' },
|
||||||
|
{ prop: 'totalMileage', label: '累计行驶里程数' },
|
||||||
|
{ prop: 'remark', label: '备注' },
|
||||||
|
];
|
||||||
|
|
||||||
export const excelOption = {
|
export const excelOption = {
|
||||||
labelPosition: 'right',
|
labelPosition: 'right',
|
||||||
submitBtn: false,
|
submitBtn: false,
|
||||||
|
|||||||
@@ -287,6 +287,23 @@ export const option = {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const exportColumns = [
|
||||||
|
{ prop: 'cardNo', label: '卡号' },
|
||||||
|
{ prop: 'transactionTime', label: '交易时间' },
|
||||||
|
{ prop: 'vehicleType', label: '车船类型' },
|
||||||
|
{ prop: 'feeType', label: '费用类型' },
|
||||||
|
{ prop: 'oilProduct', label: '油品' },
|
||||||
|
{ prop: 'vehicleNo', label: '车牌号/船号' },
|
||||||
|
{ prop: 'cardHolder', label: '持卡人' },
|
||||||
|
{ prop: 'dataSource', label: '数据来源' },
|
||||||
|
{ prop: 'quantity', label: '数量' },
|
||||||
|
{ prop: 'unitPrice', label: '单价' },
|
||||||
|
{ prop: 'transactionAmount', label: '交易金额' },
|
||||||
|
{ prop: 'balance', label: '余额' },
|
||||||
|
{ prop: 'station', label: '站点' },
|
||||||
|
{ prop: 'remark', label: '备注' },
|
||||||
|
];
|
||||||
|
|
||||||
export const excelOption = {
|
export const excelOption = {
|
||||||
labelPosition: 'right',
|
labelPosition: 'right',
|
||||||
submitBtn: false,
|
submitBtn: false,
|
||||||
|
|||||||
@@ -209,6 +209,16 @@ export const option = {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const exportColumns = [
|
||||||
|
{ prop: 'vehicleType', label: '车船类型' },
|
||||||
|
{ prop: 'vehicleNo', label: '车牌号/船号' },
|
||||||
|
{ prop: 'expenseDate', label: '费用日期' },
|
||||||
|
{ prop: 'expenseType', label: '费用类型' },
|
||||||
|
{ prop: 'amount', label: '金额' },
|
||||||
|
{ prop: 'dataSource', label: '数据来源' },
|
||||||
|
{ prop: 'remark', label: '备注' },
|
||||||
|
];
|
||||||
|
|
||||||
export const excelOption = {
|
export const excelOption = {
|
||||||
labelPosition: 'right',
|
labelPosition: 'right',
|
||||||
submitBtn: false,
|
submitBtn: false,
|
||||||
|
|||||||
@@ -206,6 +206,17 @@ export const option = {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const exportColumns = [
|
||||||
|
{ prop: 'vehicleNo', label: '车牌号' },
|
||||||
|
{ prop: 'handler', label: '处理人' },
|
||||||
|
{ prop: 'replacementTime', label: '换胎时间' },
|
||||||
|
{ prop: 'tireBrand', label: '轮胎品牌' },
|
||||||
|
{ prop: 'tireQuantity', label: '换胎数量' },
|
||||||
|
{ prop: 'replacementCost', label: '换胎费用' },
|
||||||
|
{ prop: 'replacementDescription', label: '换胎说明' },
|
||||||
|
{ prop: 'remark', label: '备注' },
|
||||||
|
];
|
||||||
|
|
||||||
export const excelOption = {
|
export const excelOption = {
|
||||||
labelPosition: 'right',
|
labelPosition: 'right',
|
||||||
submitBtn: false,
|
submitBtn: false,
|
||||||
|
|||||||
@@ -161,6 +161,14 @@ export const option = {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const exportColumns = [
|
||||||
|
{ prop: 'vehicleType', label: '车船类型' },
|
||||||
|
{ prop: 'vehicleNo', label: '车牌号/船号' },
|
||||||
|
{ prop: 'changeItem', label: '变更事项' },
|
||||||
|
{ prop: 'changeContent', label: '变更内容' },
|
||||||
|
{ prop: 'remark', label: '备注' },
|
||||||
|
];
|
||||||
|
|
||||||
export const excelOption = {
|
export const excelOption = {
|
||||||
labelPosition: 'right',
|
labelPosition: 'right',
|
||||||
submitBtn: false,
|
submitBtn: false,
|
||||||
|
|||||||
@@ -173,7 +173,7 @@ export const option = {
|
|||||||
prop: 'processStatus',
|
prop: 'processStatus',
|
||||||
type: 'radio',
|
type: 'radio',
|
||||||
dicData: processStatusDic,
|
dicData: processStatusDic,
|
||||||
value: '未处理',
|
value: '已处理',
|
||||||
search: true,
|
search: true,
|
||||||
clearable: true,
|
clearable: true,
|
||||||
minWidth: 120,
|
minWidth: 120,
|
||||||
@@ -278,6 +278,22 @@ export const option = {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const exportColumns = [
|
||||||
|
{ prop: 'vehicleType', label: '车船类型' },
|
||||||
|
{ prop: 'vehicleNo', label: '车牌号/船号' },
|
||||||
|
{ prop: 'driverName', label: '驾驶人' },
|
||||||
|
{ prop: 'violationType', label: '类型' },
|
||||||
|
{ prop: 'violationItem', label: '事项' },
|
||||||
|
{ prop: 'violationTime', label: '日期' },
|
||||||
|
{ prop: 'location', label: '地点' },
|
||||||
|
{ prop: 'fineAmount', label: '罚款' },
|
||||||
|
{ prop: 'deductPoints', label: '被扣分数' },
|
||||||
|
{ prop: 'penaltyUnit', label: '被罚单位' },
|
||||||
|
{ prop: 'processStatus', label: '状态' },
|
||||||
|
{ prop: 'processDescription', label: '过程描述' },
|
||||||
|
{ prop: 'processResult', label: '处理结果' },
|
||||||
|
];
|
||||||
|
|
||||||
export const excelOption = {
|
export const excelOption = {
|
||||||
labelPosition: 'right',
|
labelPosition: 'right',
|
||||||
submitBtn: false,
|
submitBtn: false,
|
||||||
|
|||||||
@@ -55,6 +55,19 @@
|
|||||||
{{ row.createUserName || row.createUser || '' }}
|
{{ row.createUserName || row.createUser || '' }}
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<template #remark-form>
|
||||||
|
<el-input
|
||||||
|
v-model="form.remark"
|
||||||
|
class="remark-input"
|
||||||
|
type="textarea"
|
||||||
|
:rows="2"
|
||||||
|
:disabled="dialogType === 'view'"
|
||||||
|
maxlength="200"
|
||||||
|
show-word-limit
|
||||||
|
placeholder="请输入"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
<template #parentId-form>
|
<template #parentId-form>
|
||||||
<el-select
|
<el-select
|
||||||
v-model="form.parentId"
|
v-model="form.parentId"
|
||||||
@@ -644,3 +657,18 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
/* 货物类型新增/编辑弹窗:footer 不上移,避免覆盖备注文本域底部内容 */
|
||||||
|
.cargo-type-dialog.avue-dialog.avue-crud__dialog .avue-dialog__footer {
|
||||||
|
margin-top: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cargo-type-dialog.avue-dialog.avue-crud__dialog .el-dialog__body {
|
||||||
|
padding-bottom: 16px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cargo-type-dialog .remark-input {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -30,6 +30,9 @@
|
|||||||
{{ row.status === 1 ? '启用' : '停用' }}
|
{{ row.status === 1 ? '启用' : '停用' }}
|
||||||
</el-tag>
|
</el-tag>
|
||||||
</template>
|
</template>
|
||||||
|
<template #feeCategory="{ row }">
|
||||||
|
{{ formatFeeCategory(row.feeCategory) }}
|
||||||
|
</template>
|
||||||
<template #feeCategory-form>
|
<template #feeCategory-form>
|
||||||
<el-select
|
<el-select
|
||||||
v-model="form.feeCategory"
|
v-model="form.feeCategory"
|
||||||
@@ -47,6 +50,19 @@
|
|||||||
/>
|
/>
|
||||||
</el-select>
|
</el-select>
|
||||||
</template>
|
</template>
|
||||||
|
<template #taxRate="{ row }">{{ formatTaxRate(row.taxRate) }}</template>
|
||||||
|
<template #taxRate-form>
|
||||||
|
<el-input
|
||||||
|
v-model="form.taxRate"
|
||||||
|
inputmode="decimal"
|
||||||
|
maxlength="6"
|
||||||
|
clearable
|
||||||
|
placeholder="请输入税率"
|
||||||
|
@input="handleTaxRateInput"
|
||||||
|
>
|
||||||
|
<template #append>%</template>
|
||||||
|
</el-input>
|
||||||
|
</template>
|
||||||
<template #englishName-form>
|
<template #englishName-form>
|
||||||
<el-input
|
<el-input
|
||||||
v-model="form.englishName"
|
v-model="form.englishName"
|
||||||
@@ -78,7 +94,7 @@
|
|||||||
<script>
|
<script>
|
||||||
import { getList, getDetail, submit, remove, changeStatus } from '@/api/base/fee-item';
|
import { getList, getDetail, submit, remove, changeStatus } from '@/api/base/fee-item';
|
||||||
import { exportBlob } from '@/api/common';
|
import { exportBlob } from '@/api/common';
|
||||||
import { getDictionary } from '@/api/system/dictbiz';
|
import { getDictionaryAll } from '@/api/system/dictbiz';
|
||||||
import { getDeptTree } from '@/api/system/dept';
|
import { getDeptTree } from '@/api/system/dept';
|
||||||
import { downloadXls } from '@/utils/util';
|
import { downloadXls } from '@/utils/util';
|
||||||
import { getToken } from '@/utils/auth';
|
import { getToken } from '@/utils/auth';
|
||||||
@@ -134,10 +150,11 @@ export default {
|
|||||||
searchOrder: 3,
|
searchOrder: 3,
|
||||||
filterable: true,
|
filterable: true,
|
||||||
formslot: true,
|
formslot: true,
|
||||||
|
slot: true,
|
||||||
span: 12,
|
span: 12,
|
||||||
dicData: [],
|
dicData: [],
|
||||||
props: {
|
props: {
|
||||||
label: 'dictValue',
|
label: 'displayLabel',
|
||||||
value: 'dictKey',
|
value: 'dictKey',
|
||||||
},
|
},
|
||||||
minWidth: 120,
|
minWidth: 120,
|
||||||
@@ -152,6 +169,30 @@ export default {
|
|||||||
maxlength: 100,
|
maxlength: 100,
|
||||||
rules: [{ required: true, message: '请输入费用项代码', trigger: 'blur' }],
|
rules: [{ required: true, message: '请输入费用项代码', trigger: 'blur' }],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: '税率',
|
||||||
|
prop: 'taxRate',
|
||||||
|
type: 'input',
|
||||||
|
formslot: true,
|
||||||
|
slot: true,
|
||||||
|
minWidth: 100,
|
||||||
|
span: 12,
|
||||||
|
rules: [
|
||||||
|
{ required: true, message: '请输入税率', trigger: 'blur' },
|
||||||
|
{
|
||||||
|
validator: (rule, value, callback) => {
|
||||||
|
const text = String(value ?? '').trim();
|
||||||
|
const rate = Number(text);
|
||||||
|
if (!/^\d+(\.\d{1,2})?$/.test(text) || rate < 0 || rate > 100) {
|
||||||
|
callback(new Error('税率必须是0到100之间且最多保留2位小数的数字'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
callback();
|
||||||
|
},
|
||||||
|
trigger: 'blur',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: '费用项',
|
label: '费用项',
|
||||||
prop: 'name',
|
prop: 'name',
|
||||||
@@ -168,7 +209,8 @@ export default {
|
|||||||
type: 'textarea',
|
type: 'textarea',
|
||||||
minRows: 2,
|
minRows: 2,
|
||||||
span: 24,
|
span: 24,
|
||||||
hide: true,
|
minWidth: 200,
|
||||||
|
overHidden: true,
|
||||||
maxlength: 200,
|
maxlength: 200,
|
||||||
showWordLimit: true,
|
showWordLimit: true,
|
||||||
rules: [{ max: 200, message: '备注不能超过200个字', trigger: 'blur' }],
|
rules: [{ max: 200, message: '备注不能超过200个字', trigger: 'blur' }],
|
||||||
@@ -273,10 +315,17 @@ export default {
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
initFeeCategoryOptions() {
|
initFeeCategoryOptions() {
|
||||||
getDictionary({ code: 'fee_category' }).then(res => {
|
getDictionaryAll().then(res => {
|
||||||
this.feeCategoryOptions = res.data.data || [];
|
const options = (res.data.data || [])
|
||||||
|
.filter(item => item.code === 'fee_category' && Number(item.parentId) > 0)
|
||||||
|
.sort((first, second) => Number(first.sort || 0) - Number(second.sort || 0))
|
||||||
|
.map(item => ({
|
||||||
|
...item,
|
||||||
|
displayLabel: `${item.dictValue}/${item.dictKey}`,
|
||||||
|
}));
|
||||||
|
this.feeCategoryOptions = options;
|
||||||
const column = this.findColumn(this.option.column, 'feeCategory');
|
const column = this.findColumn(this.option.column, 'feeCategory');
|
||||||
column.dicData = this.feeCategoryOptions;
|
column.dicData = options;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
initDeptTree() {
|
initDeptTree() {
|
||||||
@@ -305,6 +354,18 @@ export default {
|
|||||||
);
|
);
|
||||||
return String((option && option.dictKey) || feeCategory);
|
return String((option && option.dictKey) || feeCategory);
|
||||||
},
|
},
|
||||||
|
formatFeeCategory(value) {
|
||||||
|
const feeCategory = String(value || '').trim();
|
||||||
|
if (!feeCategory) return '';
|
||||||
|
const option = this.feeCategoryOptions.find(
|
||||||
|
item =>
|
||||||
|
String(item.dictKey || '') === feeCategory || String(item.dictValue || '') === feeCategory
|
||||||
|
);
|
||||||
|
return option ? `${option.dictValue}/${option.dictKey}` : feeCategory;
|
||||||
|
},
|
||||||
|
formatTaxRate(value) {
|
||||||
|
return value === undefined || value === null || value === '' ? '' : `${value}%`;
|
||||||
|
},
|
||||||
getFeeItemCodeSuffix(code, feeCategory) {
|
getFeeItemCodeSuffix(code, feeCategory) {
|
||||||
const value = String(code || '').trim();
|
const value = String(code || '').trim();
|
||||||
const prefix = this.getFeeCategoryPrefix(feeCategory);
|
const prefix = this.getFeeCategoryPrefix(feeCategory);
|
||||||
@@ -326,6 +387,7 @@ export default {
|
|||||||
const code = this.getFeeItemCodeSuffix(values.englishName, values.feeCategory);
|
const code = this.getFeeItemCodeSuffix(values.englishName, values.feeCategory);
|
||||||
values.feeCategory = String(values.feeCategory || '').trim();
|
values.feeCategory = String(values.feeCategory || '').trim();
|
||||||
values.name = String(values.name || '').trim();
|
values.name = String(values.name || '').trim();
|
||||||
|
values.taxRate = Number(values.taxRate);
|
||||||
values.englishName = prefix ? `${prefix}${code}` : code;
|
values.englishName = prefix ? `${prefix}${code}` : code;
|
||||||
values.remark = String(values.remark || '').trim() || undefined;
|
values.remark = String(values.remark || '').trim() || undefined;
|
||||||
if (!values.status) values.status = 1;
|
if (!values.status) values.status = 1;
|
||||||
@@ -434,6 +496,15 @@ export default {
|
|||||||
this.form.englishName = '';
|
this.form.englishName = '';
|
||||||
done();
|
done();
|
||||||
},
|
},
|
||||||
|
handleTaxRateInput(value) {
|
||||||
|
let normalized = String(value || '').replace(/[^\d.]/g, '');
|
||||||
|
normalized = normalized.replace(/(\..*)\./g, '$1');
|
||||||
|
if (normalized.startsWith('.')) normalized = `0${normalized}`;
|
||||||
|
const [integerPart, decimalPart] = normalized.split('.');
|
||||||
|
normalized =
|
||||||
|
decimalPart === undefined ? integerPart : `${integerPart}.${decimalPart.slice(0, 2)}`;
|
||||||
|
this.form.taxRate = normalized;
|
||||||
|
},
|
||||||
searchReset() {
|
searchReset() {
|
||||||
this.query = {};
|
this.query = {};
|
||||||
this.onLoad(this.page);
|
this.onLoad(this.page);
|
||||||
|
|||||||
@@ -93,6 +93,24 @@
|
|||||||
<el-radio label="码头">码头</el-radio>
|
<el-radio label="码头">码头</el-radio>
|
||||||
</el-radio-group>
|
</el-radio-group>
|
||||||
</template>
|
</template>
|
||||||
|
<template #provinceCode-form>
|
||||||
|
<el-select
|
||||||
|
v-model="form.provinceCode"
|
||||||
|
clearable
|
||||||
|
filterable
|
||||||
|
:disabled="isProvinceDisabled"
|
||||||
|
placeholder="请选择"
|
||||||
|
style="width: 100%"
|
||||||
|
@change="handleProvinceChange"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="item in provinceOptions"
|
||||||
|
:key="item.id"
|
||||||
|
:label="item.title"
|
||||||
|
:value="item.id"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</template>
|
||||||
<template #code-form>
|
<template #code-form>
|
||||||
<el-input
|
<el-input
|
||||||
v-if="form.category !== '码头'"
|
v-if="form.category !== '码头'"
|
||||||
@@ -300,6 +318,8 @@ export default {
|
|||||||
excelForm: {},
|
excelForm: {},
|
||||||
portOptions: [],
|
portOptions: [],
|
||||||
countryOptions: [],
|
countryOptions: [],
|
||||||
|
provinceOptions: [],
|
||||||
|
provinceOptionCache: {},
|
||||||
cityOptions: [],
|
cityOptions: [],
|
||||||
cityLoading: false,
|
cityLoading: false,
|
||||||
cityOptionCache: {},
|
cityOptionCache: {},
|
||||||
@@ -436,13 +456,30 @@ export default {
|
|||||||
change: ({ value }) => this.handleCountryNameChange(value),
|
change: ({ value }) => this.handleCountryNameChange(value),
|
||||||
rules: [{ required: true, message: '请选择国家', trigger: 'change' }],
|
rules: [{ required: true, message: '请选择国家', trigger: 'change' }],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: '所属省份',
|
||||||
|
prop: 'provinceCode',
|
||||||
|
formslot: true,
|
||||||
|
hide: true,
|
||||||
|
span: 12,
|
||||||
|
order: 88,
|
||||||
|
placeholder: '请选择',
|
||||||
|
rules: [{ required: true, message: '请选择所属省份', trigger: 'change' }],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '所属省份',
|
||||||
|
prop: 'provinceName',
|
||||||
|
addDisplay: false,
|
||||||
|
editDisplay: false,
|
||||||
|
minWidth: 120,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: '城市',
|
label: '城市',
|
||||||
prop: 'cityCode',
|
prop: 'cityCode',
|
||||||
formslot: true,
|
formslot: true,
|
||||||
hide: true,
|
hide: true,
|
||||||
span: 12,
|
span: 12,
|
||||||
order: 88,
|
order: 87,
|
||||||
placeholder: '请选择',
|
placeholder: '请选择',
|
||||||
rules: [{ required: true, message: '请选择所属城市', trigger: 'change' }],
|
rules: [{ required: true, message: '请选择所属城市', trigger: 'change' }],
|
||||||
},
|
},
|
||||||
@@ -465,7 +502,7 @@ export default {
|
|||||||
type: 'select',
|
type: 'select',
|
||||||
formslot: true,
|
formslot: true,
|
||||||
hide: true,
|
hide: true,
|
||||||
order: 87,
|
order: 86,
|
||||||
props: {
|
props: {
|
||||||
label: 'title',
|
label: 'title',
|
||||||
value: 'id',
|
value: 'id',
|
||||||
@@ -534,7 +571,7 @@ export default {
|
|||||||
span: 12,
|
span: 12,
|
||||||
minWidth: 220,
|
minWidth: 220,
|
||||||
overHidden: false,
|
overHidden: false,
|
||||||
order: 86,
|
order: 85,
|
||||||
placeholder: '请输入',
|
placeholder: '请输入',
|
||||||
maxlength: 255,
|
maxlength: 255,
|
||||||
showWordLimit: true,
|
showWordLimit: true,
|
||||||
@@ -667,6 +704,9 @@ export default {
|
|||||||
return this.form.category === '码头';
|
return this.form.category === '码头';
|
||||||
},
|
},
|
||||||
isCityDisabled() {
|
isCityDisabled() {
|
||||||
|
return this.isParentRegionLocked || !this.form.provinceCode;
|
||||||
|
},
|
||||||
|
isProvinceDisabled() {
|
||||||
return this.isParentRegionLocked || !this.form.countryCode;
|
return this.isParentRegionLocked || !this.form.countryCode;
|
||||||
},
|
},
|
||||||
isDistrictDisabled() {
|
isDistrictDisabled() {
|
||||||
@@ -755,7 +795,7 @@ export default {
|
|||||||
this.form.parentName = '';
|
this.form.parentName = '';
|
||||||
this.form.countryCode = '';
|
this.form.countryCode = '';
|
||||||
this.form.country = '';
|
this.form.country = '';
|
||||||
this.clearCityValue();
|
this.clearProvinceValue();
|
||||||
},
|
},
|
||||||
handleCategoryChange(value, reset = true) {
|
handleCategoryChange(value, reset = true) {
|
||||||
const required = value === '码头';
|
const required = value === '码头';
|
||||||
@@ -779,13 +819,15 @@ export default {
|
|||||||
handleCountryChange(value) {
|
handleCountryChange(value) {
|
||||||
const country = this.countryOptions.find(item => item.id === value);
|
const country = this.countryOptions.find(item => item.id === value);
|
||||||
this.form.country = country ? country.title : '';
|
this.form.country = country ? country.title : '';
|
||||||
this.clearCityValue();
|
this.clearProvinceValue();
|
||||||
this.loadCityOptions(value);
|
this.loadProvinceOptions(value);
|
||||||
},
|
},
|
||||||
handleCountryNameChange(value) {
|
handleCountryNameChange(value) {
|
||||||
const country = this.countryOptions.find(item => item.title === value);
|
const country = this.countryOptions.find(item => item.title === value);
|
||||||
|
this.setProvinceOptions([]);
|
||||||
|
this.setCityOptions([]);
|
||||||
this.setDistrictOptions([]);
|
this.setDistrictOptions([]);
|
||||||
this.loadCityOptions(country ? country.id : '');
|
this.loadProvinceOptions(country ? country.id : '');
|
||||||
},
|
},
|
||||||
syncCountryName(countryCode) {
|
syncCountryName(countryCode) {
|
||||||
const country = this.countryOptions.find(item => item.id === countryCode);
|
const country = this.countryOptions.find(item => item.id === countryCode);
|
||||||
@@ -818,6 +860,18 @@ export default {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
setProvinceOptions(provinceOptions) {
|
||||||
|
this.provinceOptions = provinceOptions;
|
||||||
|
const provinceCodeColumn = this.findColumn(this.option.column, 'provinceCode');
|
||||||
|
if (provinceCodeColumn) {
|
||||||
|
provinceCodeColumn.dicData = provinceOptions;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
clearProvinceValue() {
|
||||||
|
this.form.provinceCode = undefined;
|
||||||
|
this.form.provinceName = '';
|
||||||
|
this.clearCityValue();
|
||||||
|
},
|
||||||
clearCityValue() {
|
clearCityValue() {
|
||||||
this.form.cityCode = undefined;
|
this.form.cityCode = undefined;
|
||||||
this.form.city = '';
|
this.form.city = '';
|
||||||
@@ -828,36 +882,38 @@ export default {
|
|||||||
this.form.districtName = '';
|
this.form.districtName = '';
|
||||||
this.form.regionCode = '';
|
this.form.regionCode = '';
|
||||||
},
|
},
|
||||||
loadCityOptions(countryCode) {
|
loadProvinceOptions(countryCode) {
|
||||||
if (!countryCode) {
|
if (!countryCode) {
|
||||||
|
this.setProvinceOptions([]);
|
||||||
|
return Promise.resolve([]);
|
||||||
|
}
|
||||||
|
if (this.provinceOptionCache[countryCode]) {
|
||||||
|
this.setProvinceOptions(this.provinceOptionCache[countryCode]);
|
||||||
|
return Promise.resolve(this.provinceOptionCache[countryCode]);
|
||||||
|
}
|
||||||
|
return getLazyTree(countryCode).then(res => {
|
||||||
|
const options = (res.data.data || []).map(this.normalizeRegionOption);
|
||||||
|
this.provinceOptionCache[countryCode] = options;
|
||||||
|
this.setProvinceOptions(options);
|
||||||
|
return options;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
loadCityOptions(provinceCode) {
|
||||||
|
if (!provinceCode) {
|
||||||
this.setCityOptions([]);
|
this.setCityOptions([]);
|
||||||
return Promise.resolve([]);
|
return Promise.resolve([]);
|
||||||
}
|
}
|
||||||
if (this.cityOptionCache[countryCode]) {
|
if (this.cityOptionCache[provinceCode]) {
|
||||||
this.setCityOptions(this.cityOptionCache[countryCode]);
|
this.setCityOptions(this.cityOptionCache[provinceCode]);
|
||||||
return Promise.resolve(this.cityOptionCache[countryCode]);
|
return Promise.resolve(this.cityOptionCache[provinceCode]);
|
||||||
}
|
}
|
||||||
this.cityLoading = true;
|
this.cityLoading = true;
|
||||||
return getLazyTree(countryCode)
|
return getLazyTree(provinceCode)
|
||||||
.then(res => {
|
.then(res => {
|
||||||
const firstLevelList = (res.data.data || []).map(this.normalizeRegionOption);
|
const options = (res.data.data || []).map(this.normalizeRegionOption);
|
||||||
return Promise.all(
|
this.cityOptionCache[provinceCode] = options;
|
||||||
firstLevelList.map(parent => {
|
|
||||||
return getLazyTree(parent.id).then(childRes => {
|
|
||||||
const childList = (childRes.data.data || []).map(this.normalizeRegionOption);
|
|
||||||
return childList.map(child => ({
|
|
||||||
...child,
|
|
||||||
parentCode: child.parentCode || parent.id,
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
})
|
|
||||||
).then(cityGroups => {
|
|
||||||
const cityList = cityGroups.flat();
|
|
||||||
const options = cityList.length > 0 ? cityList : firstLevelList;
|
|
||||||
this.cityOptionCache[countryCode] = options;
|
|
||||||
this.setCityOptions(options);
|
this.setCityOptions(options);
|
||||||
return options;
|
return options;
|
||||||
});
|
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
this.cityLoading = false;
|
this.cityLoading = false;
|
||||||
@@ -867,6 +923,12 @@ export default {
|
|||||||
const city = this.cityOptions.find(item => item.title === cityName || item.id === regionCode);
|
const city = this.cityOptions.find(item => item.title === cityName || item.id === regionCode);
|
||||||
return city ? city.id : '';
|
return city ? city.id : '';
|
||||||
},
|
},
|
||||||
|
resolveProvinceCode(provinceName) {
|
||||||
|
const province = this.provinceOptions.find(
|
||||||
|
item => item.title === provinceName || item.id === provinceName
|
||||||
|
);
|
||||||
|
return province ? province.id : '';
|
||||||
|
},
|
||||||
setDistrictOptions(districtOptions) {
|
setDistrictOptions(districtOptions) {
|
||||||
this.districtOptions = districtOptions;
|
this.districtOptions = districtOptions;
|
||||||
const districtCodeColumn = this.findColumn(this.option.column, 'districtCode');
|
const districtCodeColumn = this.findColumn(this.option.column, 'districtCode');
|
||||||
@@ -927,6 +989,8 @@ export default {
|
|||||||
this.form.terminalCode = terminalCode;
|
this.form.terminalCode = terminalCode;
|
||||||
this.handleTerminalCodeChange(terminalCode);
|
this.handleTerminalCodeChange(terminalCode);
|
||||||
this.form.country = parent.country;
|
this.form.country = parent.country;
|
||||||
|
this.form.provinceCode = parent.provinceCode;
|
||||||
|
this.form.provinceName = parent.provinceName;
|
||||||
this.form.city = parent.city;
|
this.form.city = parent.city;
|
||||||
this.form.districtCode = parent.districtCode;
|
this.form.districtCode = parent.districtCode;
|
||||||
this.form.districtName = parent.districtName;
|
this.form.districtName = parent.districtName;
|
||||||
@@ -935,7 +999,12 @@ export default {
|
|||||||
.then(countryCode => {
|
.then(countryCode => {
|
||||||
this.form.countryCode = countryCode;
|
this.form.countryCode = countryCode;
|
||||||
this.syncCountryName(countryCode);
|
this.syncCountryName(countryCode);
|
||||||
return this.loadCityOptions(countryCode);
|
return this.loadProvinceOptions(countryCode);
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
this.form.provinceCode =
|
||||||
|
String(parent.provinceCode || '') || this.resolveProvinceCode(parent.provinceName);
|
||||||
|
return this.loadCityOptions(this.form.provinceCode);
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.form.cityCode = this.resolveCityCode(parent.city, parent.regionCode);
|
this.form.cityCode = this.resolveCityCode(parent.city, parent.regionCode);
|
||||||
@@ -952,6 +1021,12 @@ export default {
|
|||||||
this.clearDistrictValue();
|
this.clearDistrictValue();
|
||||||
this.loadDistrictOptions(value);
|
this.loadDistrictOptions(value);
|
||||||
},
|
},
|
||||||
|
handleProvinceChange(value) {
|
||||||
|
const province = this.provinceOptions.find(item => item.id === value);
|
||||||
|
this.form.provinceName = province ? province.title : '';
|
||||||
|
this.clearCityValue();
|
||||||
|
this.loadCityOptions(value);
|
||||||
|
},
|
||||||
handleCityNameChange(value) {
|
handleCityNameChange(value) {
|
||||||
const city = this.cityOptions.find(item => item.title === value);
|
const city = this.cityOptions.find(item => item.title === value);
|
||||||
this.setDistrictOptions([]);
|
this.setDistrictOptions([]);
|
||||||
@@ -988,6 +1063,8 @@ export default {
|
|||||||
submitRow.regionCode = String(submitRow.regionCode || '').trim();
|
submitRow.regionCode = String(submitRow.regionCode || '').trim();
|
||||||
submitRow.districtCode = String(submitRow.districtCode || '').trim();
|
submitRow.districtCode = String(submitRow.districtCode || '').trim();
|
||||||
submitRow.districtName = String(submitRow.districtName || '').trim();
|
submitRow.districtName = String(submitRow.districtName || '').trim();
|
||||||
|
submitRow.provinceCode = String(submitRow.provinceCode || '').trim();
|
||||||
|
submitRow.provinceName = String(submitRow.provinceName || '').trim();
|
||||||
submitRow.detailAddress = String(submitRow.detailAddress || '').trim();
|
submitRow.detailAddress = String(submitRow.detailAddress || '').trim();
|
||||||
submitRow.longitude = normalizeCoordinateInput(submitRow.longitude);
|
submitRow.longitude = normalizeCoordinateInput(submitRow.longitude);
|
||||||
submitRow.latitude = normalizeCoordinateInput(submitRow.latitude);
|
submitRow.latitude = normalizeCoordinateInput(submitRow.latitude);
|
||||||
@@ -1021,6 +1098,10 @@ export default {
|
|||||||
this.$message.warning('请选择上级港口');
|
this.$message.warning('请选择上级港口');
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (isBlank(row.provinceCode)) {
|
||||||
|
this.$message.warning('请选择所属省份');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
if (isBlank(row.regionCode)) {
|
if (isBlank(row.regionCode)) {
|
||||||
this.$message.warning('请选择区县');
|
this.$message.warning('请选择区县');
|
||||||
return false;
|
return false;
|
||||||
@@ -1153,7 +1234,13 @@ export default {
|
|||||||
.then(countryCode => {
|
.then(countryCode => {
|
||||||
this.form.countryCode = countryCode;
|
this.form.countryCode = countryCode;
|
||||||
this.syncCountryName(countryCode);
|
this.syncCountryName(countryCode);
|
||||||
return this.loadCityOptions(countryCode);
|
return this.loadProvinceOptions(countryCode);
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
this.form.provinceCode =
|
||||||
|
String(this.form.provinceCode || '') ||
|
||||||
|
this.resolveProvinceCode(this.form.provinceName);
|
||||||
|
return this.loadCityOptions(this.form.provinceCode);
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.form.cityCode = this.resolveCityCode(this.form.city, this.form.regionCode);
|
this.form.cityCode = this.resolveCityCode(this.form.city, this.form.regionCode);
|
||||||
@@ -1179,7 +1266,13 @@ export default {
|
|||||||
.then(countryCode => {
|
.then(countryCode => {
|
||||||
this.form.countryCode = countryCode;
|
this.form.countryCode = countryCode;
|
||||||
this.syncCountryName(countryCode);
|
this.syncCountryName(countryCode);
|
||||||
return this.loadCityOptions(countryCode);
|
return this.loadProvinceOptions(countryCode);
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
this.form.provinceCode =
|
||||||
|
String(this.form.provinceCode || '') ||
|
||||||
|
this.resolveProvinceCode(this.form.provinceName);
|
||||||
|
return this.loadCityOptions(this.form.provinceCode);
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.form.cityCode = this.resolveCityCode(this.form.city, this.form.regionCode);
|
this.form.cityCode = this.resolveCityCode(this.form.city, this.form.regionCode);
|
||||||
@@ -1211,6 +1304,7 @@ export default {
|
|||||||
},
|
},
|
||||||
searchReset() {
|
searchReset() {
|
||||||
this.query = {};
|
this.query = {};
|
||||||
|
this.setProvinceOptions([]);
|
||||||
this.setCityOptions([]);
|
this.setCityOptions([]);
|
||||||
this.setDistrictOptions([]);
|
this.setDistrictOptions([]);
|
||||||
this.onLoad(this.page);
|
this.onLoad(this.page);
|
||||||
@@ -1251,14 +1345,10 @@ export default {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
handleImport() {
|
handleImport() {
|
||||||
openImportDialog(
|
openImportDialog(this, '港口码头主数据', () => {
|
||||||
this,
|
|
||||||
'港口码头主数据',
|
|
||||||
() => {
|
|
||||||
this.loadPortOptions();
|
this.loadPortOptions();
|
||||||
this.onLoad(this.page, this.query);
|
this.onLoad(this.page, this.query);
|
||||||
}
|
});
|
||||||
);
|
|
||||||
},
|
},
|
||||||
handleExport() {
|
handleExport() {
|
||||||
this.$confirm('是否导出港口码头主数据?', '提示', {
|
this.$confirm('是否导出港口码头主数据?', '提示', {
|
||||||
@@ -1271,7 +1361,10 @@ export default {
|
|||||||
feedback: true,
|
feedback: true,
|
||||||
})
|
})
|
||||||
.then(res => {
|
.then(res => {
|
||||||
downloadXls(res.data, `港口码头主数据${this.$dayjs().format('YYYY-MM-DD HH:mm:ss')}.xlsx`);
|
downloadXls(
|
||||||
|
res.data,
|
||||||
|
`港口码头主数据${this.$dayjs().format('YYYY-MM-DD HH:mm:ss')}.xlsx`
|
||||||
|
);
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
NProgress.done();
|
NProgress.done();
|
||||||
|
|||||||
@@ -411,9 +411,7 @@ export default {
|
|||||||
computed: {
|
computed: {
|
||||||
...mapGetters(['permission']),
|
...mapGetters(['permission']),
|
||||||
isCountryRegion() {
|
isCountryRegion() {
|
||||||
return (
|
return Number(this.regionForm.regionLevel) === 0;
|
||||||
Number(this.regionForm.regionLevel) === 0 || this.regionForm.parentCode === this.topCode
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
isProvinceRegion() {
|
isProvinceRegion() {
|
||||||
return Number(this.regionForm.regionLevel) === 1;
|
return Number(this.regionForm.regionLevel) === 1;
|
||||||
@@ -641,7 +639,7 @@ export default {
|
|||||||
() => {
|
() => {
|
||||||
this.initTree();
|
this.initTree();
|
||||||
},
|
},
|
||||||
{ timeout: 180000 }
|
{ timeout: 300000 }
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
syncImportAction() {
|
syncImportAction() {
|
||||||
|
|||||||
@@ -113,7 +113,7 @@
|
|||||||
:maxlength="getCargoCodeMaxLength()"
|
:maxlength="getCargoCodeMaxLength()"
|
||||||
show-word-limit
|
show-word-limit
|
||||||
placeholder="请输入"
|
placeholder="请输入"
|
||||||
:disabled="dialogReadonly || dialogType === 'edit'"
|
:disabled="dialogReadonly"
|
||||||
@input="handleCargoCodeInput"
|
@input="handleCargoCodeInput"
|
||||||
>
|
>
|
||||||
<template v-if="getCargoCodePrefix()" #prepend>
|
<template v-if="getCargoCodePrefix()" #prepend>
|
||||||
@@ -130,11 +130,13 @@
|
|||||||
placeholder="请输入"
|
placeholder="请输入"
|
||||||
:disabled="dialogReadonly"
|
:disabled="dialogReadonly"
|
||||||
@input="handleCargoValueInput"
|
@input="handleCargoValueInput"
|
||||||
|
@blur="handleCargoValueBlur"
|
||||||
/>
|
/>
|
||||||
<el-select
|
<el-select
|
||||||
v-model="form.priceUnit"
|
v-model="form.priceUnit"
|
||||||
clearable
|
clearable
|
||||||
:disabled="dialogReadonly"
|
:disabled="dialogReadonly"
|
||||||
|
:loading="priceUnitLoading"
|
||||||
placeholder="请选择"
|
placeholder="请选择"
|
||||||
>
|
>
|
||||||
<el-option
|
<el-option
|
||||||
@@ -147,6 +149,10 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<template #cargoValue="{ row }">
|
||||||
|
{{ formatCargoValue(row.cargoValue) }}
|
||||||
|
</template>
|
||||||
|
|
||||||
<template #menu="{ row, index }">
|
<template #menu="{ row, index }">
|
||||||
<el-link
|
<el-link
|
||||||
type="primary"
|
type="primary"
|
||||||
@@ -196,8 +202,8 @@ import * as api from '@/api/business/common-cargo';
|
|||||||
import { getList as getCargoTypeList } from '@/api/base/cargo-type';
|
import { getList as getCargoTypeList } from '@/api/base/cargo-type';
|
||||||
import { exportBlob } from '@/api/common';
|
import { exportBlob } from '@/api/common';
|
||||||
import { getDeptTree } from '@/api/system/dept';
|
import { getDeptTree } from '@/api/system/dept';
|
||||||
import { config, excelOption, option } from '@/option/business/common-cargo';
|
import { config, excelOption, formatCargoValue, option } from '@/option/business/common-cargo';
|
||||||
import { priceUnitOptions } from '@/option/business/common';
|
import { getDictionary } from '@/api/system/dictbiz';
|
||||||
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';
|
||||||
@@ -211,7 +217,8 @@ export default {
|
|||||||
api,
|
api,
|
||||||
config,
|
config,
|
||||||
option,
|
option,
|
||||||
priceUnitOptions,
|
priceUnitOptions: [],
|
||||||
|
priceUnitLoading: false,
|
||||||
form: {},
|
form: {},
|
||||||
query: {},
|
query: {},
|
||||||
loading: true,
|
loading: true,
|
||||||
@@ -254,8 +261,10 @@ export default {
|
|||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
this.initDeptTree();
|
this.initDeptTree();
|
||||||
|
this.loadPriceUnitOptions();
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
formatCargoValue,
|
||||||
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);
|
||||||
},
|
},
|
||||||
@@ -273,6 +282,28 @@ export default {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
loadPriceUnitOptions() {
|
||||||
|
this.priceUnitLoading = true;
|
||||||
|
getDictionary({ code: 'unit_fee' })
|
||||||
|
.then(res => {
|
||||||
|
const payload = res?.data?.data || res?.data || [];
|
||||||
|
const list = Array.isArray(payload) ? payload : payload.records || payload.data || [];
|
||||||
|
this.priceUnitOptions = list
|
||||||
|
.map(item => ({
|
||||||
|
label: item.dictValue || item.label || item.name || item.dictKey || item.value,
|
||||||
|
value: item.dictKey || item.value || item.dictValue || item.name,
|
||||||
|
}))
|
||||||
|
.filter(item => item.label && item.value);
|
||||||
|
if (this.dialogType === 'add' && !this.form.priceUnit && this.priceUnitOptions.length) {
|
||||||
|
this.form.priceUnit = this.priceUnitOptions[0].value;
|
||||||
|
}
|
||||||
|
const priceUnitColumn = this.findColumn(this.option.column, 'priceUnit');
|
||||||
|
if (priceUnitColumn) priceUnitColumn.dicData = this.priceUnitOptions;
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
this.priceUnitLoading = false;
|
||||||
|
});
|
||||||
|
},
|
||||||
flattenDept(tree, level = 0) {
|
flattenDept(tree, level = 0) {
|
||||||
const result = [];
|
const result = [];
|
||||||
tree.forEach(item => {
|
tree.forEach(item => {
|
||||||
@@ -389,6 +420,9 @@ export default {
|
|||||||
const decimal = parts.slice(1).join('').slice(0, 2);
|
const decimal = parts.slice(1).join('').slice(0, 2);
|
||||||
this.form.cargoValue = parts.length > 1 ? `${integer}.${decimal}` : integer;
|
this.form.cargoValue = parts.length > 1 ? `${integer}.${decimal}` : integer;
|
||||||
},
|
},
|
||||||
|
handleCargoValueBlur() {
|
||||||
|
this.form.cargoValue = formatCargoValue(this.form.cargoValue);
|
||||||
|
},
|
||||||
normalizeRow(row) {
|
normalizeRow(row) {
|
||||||
const submitRow = { ...row };
|
const submitRow = { ...row };
|
||||||
Object.keys(submitRow).forEach(key => {
|
Object.keys(submitRow).forEach(key => {
|
||||||
@@ -399,14 +433,23 @@ export default {
|
|||||||
if (submitRow.cargoValue !== undefined && submitRow.cargoValue !== null) {
|
if (submitRow.cargoValue !== undefined && submitRow.cargoValue !== null) {
|
||||||
submitRow.cargoValue = String(submitRow.cargoValue).trim();
|
submitRow.cargoValue = String(submitRow.cargoValue).trim();
|
||||||
}
|
}
|
||||||
if (!submitRow.cargoValue || String(submitRow.cargoValue) === '-1') {
|
const cargoValue = submitRow.cargoValue;
|
||||||
|
if (
|
||||||
|
cargoValue === undefined ||
|
||||||
|
cargoValue === null ||
|
||||||
|
String(cargoValue).trim() === '' ||
|
||||||
|
String(cargoValue) === '-1'
|
||||||
|
) {
|
||||||
submitRow.cargoValue = null;
|
submitRow.cargoValue = null;
|
||||||
|
} else {
|
||||||
|
submitRow.cargoValue = formatCargoValue(submitRow.cargoValue);
|
||||||
}
|
}
|
||||||
return submitRow;
|
return submitRow;
|
||||||
},
|
},
|
||||||
normalizeCargoValue(row) {
|
normalizeCargoValue(row) {
|
||||||
if (row && String(row.cargoValue) === '-1') {
|
if (row && Object.prototype.hasOwnProperty.call(row, 'cargoValue')) {
|
||||||
row.cargoValue = null;
|
const formatted = formatCargoValue(row.cargoValue);
|
||||||
|
row.cargoValue = formatted || null;
|
||||||
}
|
}
|
||||||
return row;
|
return row;
|
||||||
},
|
},
|
||||||
@@ -445,6 +488,14 @@ export default {
|
|||||||
this.$message.warning('货值只能输入数字,最多保留2位小数');
|
this.$message.warning('货值只能输入数字,最多保留2位小数');
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (!row.model) {
|
||||||
|
this.$message.warning('请输入型号');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (row.model.length > 50) {
|
||||||
|
this.$message.warning('型号不能超过50个字符');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
if (row.remark && row.remark.length > 200) {
|
if (row.remark && row.remark.length > 200) {
|
||||||
this.$message.warning('备注不能超过200个字符');
|
this.$message.warning('备注不能超过200个字符');
|
||||||
return false;
|
return false;
|
||||||
@@ -539,8 +590,8 @@ export default {
|
|||||||
this.dialogType = type || 'add';
|
this.dialogType = type || 'add';
|
||||||
if (type === 'add') {
|
if (type === 'add') {
|
||||||
this.form = {
|
this.form = {
|
||||||
priceUnit: '元/吨',
|
|
||||||
...this.form,
|
...this.form,
|
||||||
|
priceUnit: this.priceUnitOptions[0]?.value || this.form.priceUnit || '',
|
||||||
};
|
};
|
||||||
this.loadFirstCargoTypeOptions();
|
this.loadFirstCargoTypeOptions();
|
||||||
done();
|
done();
|
||||||
|
|||||||
@@ -52,7 +52,7 @@
|
|||||||
:value="item.value" /></el-select></el-form-item
|
:value="item.value" /></el-select></el-form-item
|
||||||
></el-col>
|
></el-col>
|
||||||
<el-col :span="8"
|
<el-col :span="8"
|
||||||
><el-form-item label="默认方案"
|
><el-form-item
|
||||||
><el-checkbox v-model="draft.defaultPlan" :disabled="readonly">默认方案</el-checkbox
|
><el-checkbox v-model="draft.defaultPlan" :disabled="readonly">默认方案</el-checkbox
|
||||||
><el-tooltip content="同一运输方式仅支持配置一个默认计费方案" placement="top">
|
><el-tooltip content="同一运输方式仅支持配置一个默认计费方案" placement="top">
|
||||||
<el-icon class="billing-plan-editor__default-tip"
|
<el-icon class="billing-plan-editor__default-tip"
|
||||||
@@ -103,12 +103,28 @@
|
|||||||
filterable
|
filterable
|
||||||
:disabled="!row.feeType"
|
:disabled="!row.feeType"
|
||||||
:loading="feeItemLoadingMap[feeTypeKey(row)]"
|
:loading="feeItemLoadingMap[feeTypeKey(row)]"
|
||||||
|
@change="value => handleFeeItemChange(row, value)"
|
||||||
><el-option
|
><el-option
|
||||||
v-for="item in feeItemOptions(row)"
|
v-for="item in feeItemOptions(row)"
|
||||||
:key="item.id || item.name || item.englishName"
|
:key="item.id || item.name || item.englishName"
|
||||||
:label="item.name || item.englishName"
|
:label="item.name || item.englishName"
|
||||||
:value="item.name || item.englishName" /></el-select></template
|
:value="item.name || item.englishName" /></el-select></template
|
||||||
></el-table-column>
|
></el-table-column>
|
||||||
|
<el-table-column label="税率" width="180"
|
||||||
|
><template #default="{ row }"
|
||||||
|
><span v-if="readonly">{{ formatTaxRate(row.taxRate) }}</span
|
||||||
|
><el-input
|
||||||
|
v-else
|
||||||
|
v-model="row.taxRate"
|
||||||
|
inputmode="decimal"
|
||||||
|
maxlength="6"
|
||||||
|
clearable
|
||||||
|
placeholder="请输入"
|
||||||
|
@input="value => taxRateInput(row, value)"
|
||||||
|
><template #append>%</template></el-input
|
||||||
|
></template
|
||||||
|
></el-table-column
|
||||||
|
>
|
||||||
<el-table-column label="计费要素" width="170"
|
<el-table-column label="计费要素" width="170"
|
||||||
><template #default="{ row }"
|
><template #default="{ row }"
|
||||||
><span v-if="readonly">{{ displayValue(row.billingElement) }}</span
|
><span v-if="readonly">{{ displayValue(row.billingElement) }}</span
|
||||||
@@ -372,6 +388,7 @@ const clone = value => JSON.parse(JSON.stringify(value));
|
|||||||
const defaultRule = () => ({
|
const defaultRule = () => ({
|
||||||
feeType: '',
|
feeType: '',
|
||||||
feeItem: '',
|
feeItem: '',
|
||||||
|
taxRate: '',
|
||||||
billingElement: '',
|
billingElement: '',
|
||||||
billingType: '',
|
billingType: '',
|
||||||
billingUnit: '',
|
billingUnit: '',
|
||||||
@@ -467,7 +484,6 @@ export default {
|
|||||||
label: 'cargoName',
|
label: 'cargoName',
|
||||||
value: 'id',
|
value: 'id',
|
||||||
children: 'children',
|
children: 'children',
|
||||||
disabled: (data, node) => node.level === 1,
|
|
||||||
leaf: 'leaf',
|
leaf: 'leaf',
|
||||||
checkStrictly: true,
|
checkStrictly: true,
|
||||||
emitPath: true,
|
emitPath: true,
|
||||||
@@ -622,27 +638,64 @@ export default {
|
|||||||
feeItemOptions(row) {
|
feeItemOptions(row) {
|
||||||
return this.feeItems[this.feeTypeKey(row)] || [];
|
return this.feeItems[this.feeTypeKey(row)] || [];
|
||||||
},
|
},
|
||||||
|
formatTaxRate(value) {
|
||||||
|
return value === undefined || value === null || value === '' ? '-' : `${value}%`;
|
||||||
|
},
|
||||||
loadRuleItems() {
|
loadRuleItems() {
|
||||||
(this.draft.rules || []).forEach(row => this.loadFeeItems(row.feeType));
|
(this.draft.rules || []).forEach(row => this.loadFeeItems(row.feeType));
|
||||||
},
|
},
|
||||||
loadFeeItems(value) {
|
loadFeeItems(value) {
|
||||||
const key = this.feeTypeKey({ feeType: value });
|
const key = this.feeTypeKey({ feeType: value });
|
||||||
if (!key || this.feeItems[key]) return;
|
if (!key) return;
|
||||||
|
if (this.feeItems[key]) {
|
||||||
|
this.fillLoadedRuleTaxRates(key);
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.feeItemLoadingMap[key] = true;
|
this.feeItemLoadingMap[key] = true;
|
||||||
getFeeItemList(1, 9999, { feeCategory: key })
|
getFeeItemList(1, 9999, { feeCategory: key })
|
||||||
.then(res => {
|
.then(res => {
|
||||||
const data = res?.data?.data || res?.data || {};
|
const data = res?.data?.data || res?.data || {};
|
||||||
this.feeItems[key] = Array.isArray(data) ? data : data.records || [];
|
this.feeItems[key] = Array.isArray(data) ? data : data.records || [];
|
||||||
|
this.fillLoadedRuleTaxRates(key);
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
this.feeItemLoadingMap[key] = false;
|
this.feeItemLoadingMap[key] = false;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
fillLoadedRuleTaxRates(key) {
|
||||||
|
(this.draft.rules || [])
|
||||||
|
.filter(row => this.feeTypeKey(row) === key)
|
||||||
|
.forEach(row => this.fillRuleTaxRate(row));
|
||||||
|
},
|
||||||
handleFeeTypeChange(row, value) {
|
handleFeeTypeChange(row, value) {
|
||||||
row.feeType = this.feeTypeKey({ feeType: value });
|
row.feeType = this.feeTypeKey({ feeType: value });
|
||||||
row.feeItem = '';
|
row.feeItem = '';
|
||||||
|
row.taxRate = '';
|
||||||
this.loadFeeItems(row.feeType);
|
this.loadFeeItems(row.feeType);
|
||||||
},
|
},
|
||||||
|
handleFeeItemChange(row, value) {
|
||||||
|
row.feeItem = value;
|
||||||
|
const feeItem = this.feeItemOptions(row).find(
|
||||||
|
item => String(item.name || item.englishName || '') === String(value || '')
|
||||||
|
);
|
||||||
|
row.taxRate = this.hasTaxRate(feeItem?.taxRate) ? String(feeItem.taxRate) : '';
|
||||||
|
},
|
||||||
|
fillRuleTaxRate(row) {
|
||||||
|
if (this.hasTaxRate(row.taxRate) || !row.feeItem) return;
|
||||||
|
const feeItem = this.feeItemOptions(row).find(
|
||||||
|
item => String(item.name || item.englishName || '') === String(row.feeItem)
|
||||||
|
);
|
||||||
|
if (this.hasTaxRate(feeItem?.taxRate)) row.taxRate = String(feeItem.taxRate);
|
||||||
|
},
|
||||||
|
hasTaxRate(value) {
|
||||||
|
return value !== undefined && value !== null && String(value).trim() !== '';
|
||||||
|
},
|
||||||
|
taxRateInput(row, value) {
|
||||||
|
const text = String(value || '').replace(/[^\d.]/g, '');
|
||||||
|
const parts = text.split('.');
|
||||||
|
row.taxRate =
|
||||||
|
parts.length > 1 ? `${parts[0]}.${parts.slice(1).join('').slice(0, 2)}` : parts[0];
|
||||||
|
},
|
||||||
billingTypes(row) {
|
billingTypes(row) {
|
||||||
return this.typeMap[row.billingElement] || [];
|
return this.typeMap[row.billingElement] || [];
|
||||||
},
|
},
|
||||||
@@ -771,6 +824,7 @@ export default {
|
|||||||
const required = [
|
const required = [
|
||||||
['feeType', '费用类型'],
|
['feeType', '费用类型'],
|
||||||
['feeItem', '费用项'],
|
['feeItem', '费用项'],
|
||||||
|
['taxRate', '税率'],
|
||||||
['billingElement', '计费要素'],
|
['billingElement', '计费要素'],
|
||||||
['billingType', '计费类型'],
|
['billingType', '计费类型'],
|
||||||
['billingUnit', '计费单位'],
|
['billingUnit', '计费单位'],
|
||||||
@@ -789,6 +843,13 @@ export default {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
feeItemSet.add(feeItem);
|
feeItemSet.add(feeItem);
|
||||||
|
if (this.hasTaxRate(row.taxRate)) {
|
||||||
|
const taxRate = Number(row.taxRate);
|
||||||
|
if (!/^\d+(\.\d{1,2})?$/.test(String(row.taxRate)) || taxRate < 0 || taxRate > 100) {
|
||||||
|
this.$message.warning(`第${i + 1}行税率必须在0到100之间且最多保留2位小数`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
if (
|
if (
|
||||||
!this.usesRangeUnitPrice(row) &&
|
!this.usesRangeUnitPrice(row) &&
|
||||||
(row.unitPrice === undefined ||
|
(row.unitPrice === undefined ||
|
||||||
@@ -803,8 +864,9 @@ export default {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!this.canEditLimit(row)) continue;
|
if (!this.canEditLimit(row)) continue;
|
||||||
const key = row.billingElement;
|
const billingElement = String(row.billingElement).trim();
|
||||||
groups[key] = groups[key] || [];
|
const key = JSON.stringify([feeItem, billingElement]);
|
||||||
|
groups[key] = groups[key] || { feeItem, billingElement, ranges: [] };
|
||||||
for (const range of this.getRanges(row)) {
|
for (const range of this.getRanges(row)) {
|
||||||
const lower = Number(range.lowerLimit);
|
const lower = Number(range.lowerLimit);
|
||||||
const upper = Number(range.upperLimit);
|
const upper = Number(range.upperLimit);
|
||||||
@@ -830,22 +892,24 @@ export default {
|
|||||||
this.$message.warning('计费要素下限不能大于上限');
|
this.$message.warning('计费要素下限不能大于上限');
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
groups[key].push({ lower, upper });
|
groups[key].ranges.push({ lower, upper });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return this.validateRangeGroups(groups);
|
return this.validateRangeGroups(groups);
|
||||||
},
|
},
|
||||||
validateRangeGroups(groups) {
|
validateRangeGroups(groups) {
|
||||||
const precision = 0.000001;
|
const precision = 0.000001;
|
||||||
for (const [element, ranges] of Object.entries(groups)) {
|
for (const { feeItem, billingElement, ranges } of Object.values(groups)) {
|
||||||
const sorted = [...ranges].sort((a, b) => a.lower - b.lower || a.upper - b.upper);
|
const sorted = [...ranges].sort((a, b) => a.lower - b.lower || a.upper - b.upper);
|
||||||
for (let i = 1; i < sorted.length; i += 1) {
|
for (let i = 1; i < sorted.length; i += 1) {
|
||||||
if (sorted[i].lower < sorted[i - 1].upper - precision) {
|
if (sorted[i].lower < sorted[i - 1].upper - precision) {
|
||||||
this.$message.warning(`${element}的计费要素区间不能重叠`);
|
this.$message.warning(`费用项“${feeItem}”${billingElement}的计费要素区间不能重叠`);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (sorted[i].lower > sorted[i - 1].upper + precision) {
|
if (sorted[i].lower > sorted[i - 1].upper + precision) {
|
||||||
this.$message.warning(`${element}的计费要素区间必须连续,不能存在间隙`);
|
this.$message.warning(
|
||||||
|
`费用项“${feeItem}”${billingElement}的计费要素区间必须连续,不能存在间隙`
|
||||||
|
);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1078,25 +1142,27 @@ export default {
|
|||||||
},
|
},
|
||||||
matchCargoChange(value) {
|
matchCargoChange(value) {
|
||||||
const path =
|
const path =
|
||||||
Array.isArray(value) && value.length >= 2
|
Array.isArray(value) && value.length
|
||||||
? value.slice(0, 2).map(item => String(item))
|
? value.slice(0, 2).map(item => String(item))
|
||||||
: [];
|
: [];
|
||||||
const item = this.cargoFlatOptions.find(
|
const item = this.cargoFlatOptions.find(
|
||||||
option => String(option.id) === String(path[1]) && option.path?.length >= 2
|
option =>
|
||||||
|
option.path?.length === path.length &&
|
||||||
|
option.path.every((itemValue, index) => String(itemValue) === path[index])
|
||||||
);
|
);
|
||||||
this.matchForm.cargoTypePath = path;
|
this.matchForm.cargoTypePath = path;
|
||||||
this.matchForm.cargoType = item ? this.cargoLabels(path).join('/') : '';
|
this.matchForm.cargoType = item ? this.cargoLabels(path).join('/') : '';
|
||||||
this.matchForm.cargoTypeCode = item ? item.cargoCode || item.code || item.id : '';
|
this.matchForm.cargoTypeCode = item ? item.cargoCode || item.code || item.id : '';
|
||||||
},
|
},
|
||||||
resolveCargoPath(condition) {
|
resolveCargoPath(condition) {
|
||||||
if (Array.isArray(condition.cargoTypePath) && condition.cargoTypePath.length >= 2)
|
if (Array.isArray(condition.cargoTypePath) && condition.cargoTypePath.length)
|
||||||
return condition.cargoTypePath;
|
return condition.cargoTypePath.slice(0, 2).map(item => String(item));
|
||||||
const item = this.cargoFlatOptions.find(
|
const item = this.cargoFlatOptions.find(
|
||||||
option =>
|
option =>
|
||||||
option.path?.length >= 2 &&
|
|
||||||
(String(option.cargoCode || option.code || option.id) ===
|
(String(option.cargoCode || option.code || option.id) ===
|
||||||
String(condition.cargoTypeCode) ||
|
String(condition.cargoTypeCode) ||
|
||||||
option.cargoName === condition.cargoType)
|
option.cargoName === condition.cargoType ||
|
||||||
|
this.cargoLabels(option.path).join('/') === condition.cargoType)
|
||||||
);
|
);
|
||||||
return item?.path || [];
|
return item?.path || [];
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
<!-- 该文件已废弃 -->
|
||||||
<template>
|
<template>
|
||||||
<basic-container
|
<basic-container
|
||||||
:class="[
|
:class="[
|
||||||
@@ -1089,7 +1090,7 @@
|
|||||||
class="business-crud-page__freight-form business-crud-page__freight-form--road"
|
class="business-crud-page__freight-form business-crud-page__freight-form--road"
|
||||||
>
|
>
|
||||||
<template v-for="(cargo, index) in transportCargoRows" :key="index">
|
<template v-for="(cargo, index) in transportCargoRows" :key="index">
|
||||||
<el-form-item :label="`单价${index + 1}`">
|
<el-form-item label="单价">
|
||||||
<el-input
|
<el-input
|
||||||
v-model="cargo.unitPrice"
|
v-model="cargo.unitPrice"
|
||||||
placeholder="请输入"
|
placeholder="请输入"
|
||||||
@@ -1115,7 +1116,7 @@
|
|||||||
</template>
|
</template>
|
||||||
</el-input>
|
</el-input>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item :label="`数量合计${index + 1}`">
|
<el-form-item label="数量合计">
|
||||||
<el-input :model-value="taskFullFreightQuantity(index)" disabled>
|
<el-input :model-value="taskFullFreightQuantity(index)" disabled>
|
||||||
<template #append>
|
<template #append>
|
||||||
<el-select
|
<el-select
|
||||||
@@ -1132,7 +1133,7 @@
|
|||||||
</template>
|
</template>
|
||||||
</el-input>
|
</el-input>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item :label="`运费${index + 1}`">
|
<el-form-item label="运费">
|
||||||
<el-input
|
<el-input
|
||||||
:model-value="taskFullFreightAmount(cargo)"
|
:model-value="taskFullFreightAmount(cargo)"
|
||||||
placeholder="请输入运费"
|
placeholder="请输入运费"
|
||||||
@@ -1674,7 +1675,7 @@
|
|||||||
class="business-crud-page__freight-form business-crud-page__freight-form--road"
|
class="business-crud-page__freight-form business-crud-page__freight-form--road"
|
||||||
>
|
>
|
||||||
<template v-for="(row, index) in shippingTemplateFreight.freightItems" :key="index">
|
<template v-for="(row, index) in shippingTemplateFreight.freightItems" :key="index">
|
||||||
<el-form-item :label="`单价${index + 1}`">
|
<el-form-item label="单价">
|
||||||
<el-input
|
<el-input
|
||||||
v-model="row.unitPrice"
|
v-model="row.unitPrice"
|
||||||
placeholder="请输入"
|
placeholder="请输入"
|
||||||
@@ -1701,7 +1702,7 @@
|
|||||||
</template>
|
</template>
|
||||||
</el-input>
|
</el-input>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item :label="`数量合计${index + 1}`">
|
<el-form-item label="数量合计">
|
||||||
<el-input :model-value="shippingTemplateRoadFreightQuantity(index)" disabled>
|
<el-input :model-value="shippingTemplateRoadFreightQuantity(index)" disabled>
|
||||||
<template #append>
|
<template #append>
|
||||||
<el-select
|
<el-select
|
||||||
@@ -1718,7 +1719,7 @@
|
|||||||
</template>
|
</template>
|
||||||
</el-input>
|
</el-input>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item :label="`运费${index + 1}`">
|
<el-form-item label="运费">
|
||||||
<el-input :model-value="shippingTemplateRoadFreightAmount(row, index)" disabled>
|
<el-input :model-value="shippingTemplateRoadFreightAmount(row, index)" disabled>
|
||||||
<template #suffix>{{ shippingTemplateCurrencyLabel }}</template>
|
<template #suffix>{{ shippingTemplateCurrencyLabel }}</template>
|
||||||
</el-input>
|
</el-input>
|
||||||
@@ -3267,7 +3268,11 @@
|
|||||||
</template>
|
</template>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
|
|
||||||
<waybill-import-dialog v-if="config.enableWaybillImport" v-model="waybillImportBox" />
|
<waybill-import-dialog
|
||||||
|
v-if="config.enableWaybillImport"
|
||||||
|
v-model="waybillImportBox"
|
||||||
|
@closed="refreshChange"
|
||||||
|
/>
|
||||||
|
|
||||||
<flow-design
|
<flow-design
|
||||||
v-if="website.design.designMode"
|
v-if="website.design.designMode"
|
||||||
@@ -3799,7 +3804,7 @@
|
|||||||
class="business-crud-page__dispatch-item-grid business-crud-page__dispatch-item-grid--full"
|
class="business-crud-page__dispatch-item-grid business-crud-page__dispatch-item-grid--full"
|
||||||
>
|
>
|
||||||
<template v-for="(cargo, index) in dispatchItemCargoRows" :key="cargo._key || index">
|
<template v-for="(cargo, index) in dispatchItemCargoRows" :key="cargo._key || index">
|
||||||
<el-form-item :label="`单价${index + 1}`">
|
<el-form-item label="单价">
|
||||||
<el-input
|
<el-input
|
||||||
v-model="cargo.unitPrice"
|
v-model="cargo.unitPrice"
|
||||||
placeholder="请输入"
|
placeholder="请输入"
|
||||||
@@ -3819,12 +3824,12 @@
|
|||||||
></template>
|
></template>
|
||||||
</el-input>
|
</el-input>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item :label="`数量合计${index + 1}`"
|
<el-form-item label="数量合计"
|
||||||
><el-input :model-value="formatDispatchQuantity(cargo.quantity)" disabled
|
><el-input :model-value="formatDispatchQuantity(cargo.quantity)" disabled
|
||||||
><template #append>{{ cargo.quantityUnit || '吨' }}</template></el-input
|
><template #append>{{ cargo.quantityUnit || '吨' }}</template></el-input
|
||||||
></el-form-item
|
></el-form-item
|
||||||
>
|
>
|
||||||
<el-form-item :label="`运费${index + 1}`"
|
<el-form-item label="运费"
|
||||||
><el-input :model-value="dispatchCargoFreightAmount(cargo)" disabled
|
><el-input :model-value="dispatchCargoFreightAmount(cargo)" disabled
|
||||||
><template #suffix>{{ dispatchItemFreightCurrencyLabel }}</template></el-input
|
><template #suffix>{{ dispatchItemFreightCurrencyLabel }}</template></el-input
|
||||||
></el-form-item
|
></el-form-item
|
||||||
@@ -15498,7 +15503,6 @@ export default {
|
|||||||
&__waybill-route-addr {
|
&__waybill-route-addr {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
color: #606266;
|
color: #606266;
|
||||||
font-size: 12px;
|
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<section v-if="master" class="detail-overview">
|
<section v-if="master" class="detail-overview">
|
||||||
<div class="detail-heading"><div><h2>多联总单详情 <span>|</span> {{ master.masterNo }}</h2><el-tag :type="statusType(master.businessStatus)" class="status-text">{{ statusName(master.businessStatus) }}</el-tag><el-tag v-if="transportFlowLabel" type="info" class="transport-flow-tag">{{ transportFlowLabel }}</el-tag></div><el-button @click="$emit('back')">取消</el-button></div>
|
<div class="detail-heading"><div><h2>多联总单详情 <span>|</span> {{ master.masterNo }}</h2><el-tag :type="statusType(master.businessStatus)" class="status-text">{{ statusName(master.businessStatus) }}</el-tag><el-tag v-if="transportFlowLabel" type="info" class="transport-flow-tag">{{ transportFlowLabel }}</el-tag></div><el-button @click="$emit('back')">取消</el-button></div>
|
||||||
<dl class="detail-meta"><div><dt>客户</dt><dd>{{ master.customerName || '-' }}</dd></div><div><dt>合同编号</dt><dd>{{ master.contractNo || '-' }}</dd></div><div><dt>项目</dt><dd>{{ master.projectName || '-' }}</dd></div><div class="detail-meta__attachments"><dt>附件</dt><dd><template v-if="attachments.length"><el-link v-for="file in attachments" :key="file.url || file.link || file.name || file.originalName" type="primary" @click="previewAttachment(file)">{{ attachmentName(file) }}</el-link></template><span v-else>-</span></dd></div></dl>
|
<dl class="detail-meta"><div><dt>客户</dt><dd>{{ master.customerName || '-' }}</dd></div><div><dt>合同编号</dt><dd>{{ master.contractNo || '-' }}</dd></div><div><dt>项目</dt><dd>{{ master.projectName || '-' }}</dd></div><div class="detail-meta__attachments"><dt>附件</dt><dd><template v-if="attachments.length"><el-link v-for="file in attachments" :key="file.url || file.link || file.name || file.originalName" type="primary" @click="previewAttachment(file)">{{ attachmentName(file) }}</el-link></template><span v-else>-</span></dd></div></dl>
|
||||||
<div class="route-map"><template v-for="(route, index) in segments" :key="route.segmentNo"><div class="route-map__node"><span :class="['route-badge', index ? 'middle' : 'start']">{{ index ? '经' : '起' }}</span><strong>{{ route.departureName || '-' }}</strong><small>{{ route.departureAddress || '-' }}</small><small class="route-map__progress">{{ index ? `已到达 ${quantity(segments[index - 1].arrivedQuantity)}/${quantity(master.totalQuantity)}` : `已调度 ${quantity(route.dispatchedQuantity)}/${quantity(master.totalQuantity)}` }}</small><small v-if="index" class="route-map__progress">已调度 {{ quantity(route.dispatchedQuantity) }}/{{ quantity(master.totalQuantity) }}</small></div><div class="route-map__line"></div><div v-if="index === segments.length - 1" class="route-map__node"><span class="route-badge end">终</span><strong>{{ route.arrivalName || '-' }}</strong><small>{{ route.arrivalAddress || '-' }}</small><small class="route-map__progress">已到达 {{ quantity(route.arrivedQuantity) }}/{{ quantity(master.totalQuantity) }}</small></div></template></div>
|
<div class="route-map"><template v-for="(route, index) in segments" :key="route.segmentNo"><div class="route-map__node"><span :class="['route-badge', index ? 'middle' : 'start']">{{ index ? '经' : '起' }}</span><strong>{{ segmentLocationName(route, 'departure') }}</strong><small>{{ route.departureAddress || '-' }}</small><small class="route-map__progress">{{ index ? `已到达 ${quantity(segments[index - 1].arrivedQuantity)}/${quantity(master.totalQuantity)} ${plannedUnit(segments[index - 1])}` : `已调度 ${quantity(route.dispatchedQuantity)}/${quantity(master.totalQuantity)} ${plannedUnit(route)}` }}</small><small v-if="index" class="route-map__progress">已调度 {{ quantity(route.dispatchedQuantity) }}/{{ quantity(master.totalQuantity) }} {{ plannedUnit(route) }}</small></div><div class="route-map__line"></div><div v-if="index === segments.length - 1" class="route-map__node"><span class="route-badge end">终</span><strong>{{ segmentLocationName(route, 'arrival') }}</strong><small>{{ route.arrivalAddress || '-' }}</small><small class="route-map__progress">已到达 {{ quantity(route.arrivedQuantity) }}/{{ quantity(master.totalQuantity) }} {{ plannedUnit(route) }}</small></div></template></div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<el-dialog v-model="documentPreviewVisible" :title="previewFile.name || '附件预览'" append-to-body destroy-on-close width="90%" top="4vh">
|
<el-dialog v-model="documentPreviewVisible" :title="previewFile.name || '附件预览'" append-to-body destroy-on-close width="90%" top="4vh">
|
||||||
@@ -14,10 +14,10 @@
|
|||||||
<section v-if="master" class="execution-detail-card">
|
<section v-if="master" class="execution-detail-card">
|
||||||
<div class="execution-detail-heading"><h3>分段执行明细</h3></div>
|
<div class="execution-detail-heading"><h3>分段执行明细</h3></div>
|
||||||
<section v-for="(route, index) in segments" :key="route.segmentNo" class="segment-detail">
|
<section v-for="(route, index) in segments" :key="route.segmentNo" class="segment-detail">
|
||||||
<header @click="toggleSegment(route.segmentNo)"><h3><span class="segment-index">{{ segmentNumber(route, index) }}</span><span>{{ route.departureName || '-' }} → {{ route.arrivalName || '-' }}</span><span class="segment-detail__transport-type">{{ segmentTransportType(route) || '-' }}</span><span class="segment-detail__progress-bar"><span :style="{ width: `${segmentProgress(route)}%` }"></span></span><span class="segment-detail__progress">已调度 <em>{{ quantity(route.dispatchedQuantity) }}</em> / {{ quantity(plannedQuantity(route)) }} {{ plannedUnit(route) }}</span></h3><div class="segment-detail__header-right"><el-tooltip :content="isSegmentExpanded(route.segmentNo) ? '折叠' : '展开'" placement="top"><el-button circle :icon="isSegmentExpanded(route.segmentNo) ? ArrowUp : ArrowDown" @click.stop="toggleSegment(route.segmentNo)" /></el-tooltip></div></header>
|
<header @click="toggleSegment(route.segmentNo)"><h3><span class="segment-index">{{ segmentNumber(route, index) }}</span><span>{{ segmentLocationName(route, 'departure') }} → {{ segmentLocationName(route, 'arrival') }}</span><span class="segment-detail__transport-type">{{ segmentTransportType(route) || '-' }}</span><span class="segment-detail__progress-bar"><span :style="{ width: `${segmentProgress(route)}%` }"></span></span><span class="segment-detail__progress">已调度 <em>{{ quantity(route.dispatchedQuantity) }}</em> / {{ quantity(plannedQuantity(route)) }} {{ plannedUnit(route) }}</span><el-button text class="segment-toggle" @click.stop="toggleSegment(route.segmentNo)"><span>{{ isSegmentExpanded(route.segmentNo) ? '收起' : '展开' }}</span><el-icon class="segment-toggle__icon"><component :is="isSegmentExpanded(route.segmentNo) ? CaretTop : CaretBottom" /></el-icon></el-button></h3></header>
|
||||||
<div v-show="isSegmentExpanded(route.segmentNo)" class="segment-execution">
|
<div v-show="isSegmentExpanded(route.segmentNo)" class="segment-execution">
|
||||||
<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" class="status-text">{{ 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="isRoadTransport(route) ? '司机/车牌' : '船长/船号'" min-width="180"><template #default="{ row }">{{ driverVehicle(row, route) }}</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)" effect="dark" 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>
|
||||||
@@ -26,20 +26,14 @@
|
|||||||
<section v-if="master" class="master-goods-card">
|
<section v-if="master" class="master-goods-card">
|
||||||
<div class="master-goods-card__heading"><h3>货物信息</h3></div>
|
<div class="master-goods-card__heading"><h3>货物信息</h3></div>
|
||||||
<el-table :data="master.goods || []" border>
|
<el-table :data="master.goods || []" border>
|
||||||
<el-table-column type="index" label="序号" width="64" />
|
|
||||||
<el-table-column prop="cargoName" label="货物名称" min-width="150" />
|
<el-table-column prop="cargoName" label="货物名称" min-width="150" />
|
||||||
<el-table-column prop="cargoType" label="货物类型" min-width="130" />
|
<el-table-column prop="cargoType" label="类型" min-width="130" />
|
||||||
<el-table-column prop="packageType" label="包装" min-width="120" />
|
<el-table-column prop="packageType" label="包装" min-width="120" />
|
||||||
<el-table-column label="重量(吨)" min-width="110"><template #default="{ row }">{{ row.weight ?? row.weightTon ?? (row.quantityUnit === '吨' ? row.quantity : '-') }}</template></el-table-column>
|
|
||||||
<el-table-column label="体积(方)" min-width="110"><template #default="{ row }">{{ row.volume ?? row.volumeCubic ?? '-' }}</template></el-table-column>
|
|
||||||
<el-table-column label="数量" min-width="100"><template #default="{ row }">{{ row.quantity ?? '-' }}{{ row.quantityUnit ? ` ${row.quantityUnit}` : '' }}</template></el-table-column>
|
<el-table-column label="数量" min-width="100"><template #default="{ row }">{{ row.quantity ?? '-' }}{{ row.quantityUnit ? ` ${row.quantityUnit}` : '' }}</template></el-table-column>
|
||||||
<el-table-column prop="materialCode" label="物料编码" min-width="130" />
|
|
||||||
<el-table-column prop="deviceCode" label="设备编码" min-width="130" />
|
|
||||||
<el-table-column prop="brand" label="品牌" min-width="120" />
|
<el-table-column prop="brand" label="品牌" min-width="120" />
|
||||||
<el-table-column label="规格型号" min-width="150"><template #default="{ row }">{{ [row.specification, row.model].filter(Boolean).join('/') || '-' }}</template></el-table-column>
|
<el-table-column prop="specification" label="规格" min-width="150" />
|
||||||
<el-table-column prop="remark" label="备注" min-width="160" />
|
<el-table-column prop="model" label="型号" min-width="150" />
|
||||||
<el-table-column label="货物单价(元)" min-width="130"><template #default="{ row }">{{ row.unitPrice ?? '-' }}</template></el-table-column>
|
<el-table-column prop="materialCode" label="物料编码" min-width="130" />
|
||||||
<el-table-column label="计划日期" min-width="130"><template #default="{ row }">{{ row.planDate || row.planStartDate || '-' }}</template></el-table-column>
|
|
||||||
</el-table>
|
</el-table>
|
||||||
<el-empty v-if="!(master.goods || []).length" description="暂无货物信息" :image-size="56" />
|
<el-empty v-if="!(master.goods || []).length" description="暂无货物信息" :image-size="56" />
|
||||||
</section>
|
</section>
|
||||||
@@ -48,7 +42,7 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import * as api from '@/api/business/master-order';
|
import * as api from '@/api/business/master-order';
|
||||||
import { ArrowDown, ArrowUp } from '@element-plus/icons-vue';
|
import { CaretBottom, CaretTop } from '@element-plus/icons-vue';
|
||||||
import { ElImageViewer } from 'element-plus';
|
import { ElImageViewer } from 'element-plus';
|
||||||
import { OpenFileViewer } from '@open-file-viewer/vue';
|
import { OpenFileViewer } from '@open-file-viewer/vue';
|
||||||
import { fallbackPlugin, imagePlugin, officePlugin, pdfPlugin, textPlugin } from '@open-file-viewer/core';
|
import { fallbackPlugin, imagePlugin, officePlugin, pdfPlugin, textPlugin } from '@open-file-viewer/core';
|
||||||
@@ -61,7 +55,7 @@ export default {
|
|||||||
components: { ElImageViewer, OpenFileViewer },
|
components: { ElImageViewer, OpenFileViewer },
|
||||||
props: { id: [String, Number] },
|
props: { id: [String, Number] },
|
||||||
emits: ['back'],
|
emits: ['back'],
|
||||||
data() { return { loading: false, master: null, expandedSegments: {}, ArrowDown, ArrowUp, imagePreviewVisible: false, imagePreviewUrls: [], imagePreviewIndex: 0, documentPreviewVisible: false, previewFile: {}, viewerPlugins, viewerToolbar: { download: true, fullscreen: true, print: true, rotate: true, zoom: true } }; },
|
data() { return { loading: false, master: null, expandedSegments: {}, CaretBottom, CaretTop, imagePreviewVisible: false, imagePreviewUrls: [], imagePreviewIndex: 0, documentPreviewVisible: false, previewFile: {}, viewerPlugins, viewerToolbar: { download: true, fullscreen: true, print: true, rotate: true, zoom: true } }; },
|
||||||
computed: {
|
computed: {
|
||||||
segments() {
|
segments() {
|
||||||
const routes = this.master?.routeProgress || this.master?.routes || [];
|
const routes = this.master?.routeProgress || this.master?.routes || [];
|
||||||
@@ -94,6 +88,36 @@ export default {
|
|||||||
toggleSegment(segmentNo) { this.expandedSegments = { ...this.expandedSegments, [segmentNo]: !this.isSegmentExpanded(segmentNo) }; },
|
toggleSegment(segmentNo) { this.expandedSegments = { ...this.expandedSegments, [segmentNo]: !this.isSegmentExpanded(segmentNo) }; },
|
||||||
remainingQuantity(route) { return Math.max(0, Number(this.master?.totalQuantity || 0) - Number(route.dispatchedQuantity || 0)); },
|
remainingQuantity(route) { return Math.max(0, Number(this.master?.totalQuantity || 0) - Number(route.dispatchedQuantity || 0)); },
|
||||||
segmentTransportType(route = {}) { return route.transportTypeName || route.transportType || ''; },
|
segmentTransportType(route = {}) { return route.transportTypeName || route.transportType || ''; },
|
||||||
|
isRoadTransport(route = {}) {
|
||||||
|
const type = String(this.segmentTransportType(route)).trim().toLowerCase();
|
||||||
|
return type === 'road' || type.includes('公路');
|
||||||
|
},
|
||||||
|
formatRoadLocation(value) {
|
||||||
|
const text = String(value || '').replace(/\s+/g, '');
|
||||||
|
if (!text) return '';
|
||||||
|
const withoutProvince = text.replace(/^.*?(?:省|自治区|特别行政区)/, '');
|
||||||
|
const cityMatch = withoutProvince.match(/.+?(?:市|州(?!市)|盟(?!市))/);
|
||||||
|
if (!cityMatch) return text;
|
||||||
|
const city = cityMatch[0];
|
||||||
|
const districtSource = withoutProvince.slice(city.length);
|
||||||
|
const districtMatch = districtSource.match(/^[^市州盟]*?(?:自治县|自治旗|林区|矿区|新区|开发区|区|县|旗)/);
|
||||||
|
if (!districtMatch) return city;
|
||||||
|
const district = `${districtMatch[0]}${districtSource.slice(districtMatch[0].length).startsWith('区') ? '区' : ''}`;
|
||||||
|
return `${city} ${district}`;
|
||||||
|
},
|
||||||
|
segmentLocationName(route = {}, field) {
|
||||||
|
const name = String(route[`${field}Name`] || '').trim();
|
||||||
|
const address = String(route[`${field}Address`] || '').trim();
|
||||||
|
const formattedName = this.formatRoadLocation(name);
|
||||||
|
if (/(?:市|州|盟)/.test(formattedName)) return formattedName;
|
||||||
|
const regionalName = /(?:省|自治区|特别行政区|市|州|盟|区|县|旗)/.test(name);
|
||||||
|
if (name && !regionalName) return name;
|
||||||
|
const formattedAddress = this.formatRoadLocation(address);
|
||||||
|
if (/(?:市|州|盟)/.test(formattedAddress)) return formattedAddress;
|
||||||
|
const city = String(route[`${field}CityName`] || '').trim();
|
||||||
|
const district = String(route[`${field}DistrictName`] || '').trim();
|
||||||
|
return [city, district].filter(Boolean).join(' ') || formattedName || formattedAddress || '-';
|
||||||
|
},
|
||||||
plannedQuantity(route = {}) { return route.planQuantity || route.totalQuantity || this.master?.totalQuantity || 0; },
|
plannedQuantity(route = {}) { return route.planQuantity || route.totalQuantity || this.master?.totalQuantity || 0; },
|
||||||
plannedUnit(route = {}) { return route.quantityUnit || this.master?.goods?.[0]?.quantityUnit || '吨'; },
|
plannedUnit(route = {}) { return route.quantityUnit || this.master?.goods?.[0]?.quantityUnit || '吨'; },
|
||||||
segmentProgress(route = {}) { const planned = Number(this.plannedQuantity(route)); return planned > 0 ? Math.min(100, Math.max(0, (Number(route.dispatchedQuantity || 0) / planned) * 100)) : 0; },
|
segmentProgress(route = {}) { const planned = Number(this.plannedQuantity(route)); return planned > 0 ? Math.min(100, Math.max(0, (Number(route.dispatchedQuantity || 0) / planned) * 100)) : 0; },
|
||||||
@@ -113,11 +137,17 @@ export default {
|
|||||||
this.previewFile = { name: this.attachmentName(file), url, mimeType: file.mimeType || file.contentType || '' };
|
this.previewFile = { name: this.attachmentName(file), url, mimeType: file.mimeType || file.contentType || '' };
|
||||||
this.documentPreviewVisible = true;
|
this.documentPreviewVisible = true;
|
||||||
},
|
},
|
||||||
driverVehicle(row) { return [row.driverName, row.vehicleNo].filter(Boolean).join(' / ') || '-'; },
|
driverVehicle(row = {}, route = {}) {
|
||||||
|
const vehicle = row.vehicleNo || row.vehicleNumber || row.shipNo || row.flightNo || row.trainNo;
|
||||||
|
const values = this.isRoadTransport(route)
|
||||||
|
? [row.driverName, vehicle]
|
||||||
|
: [row.captainName, vehicle];
|
||||||
|
return values.filter(Boolean).join(' / ') || '-';
|
||||||
|
},
|
||||||
openWaybill(row) { this.$router.push({ path: '/business/waybill-manage', query: { detailId: row.id } }); },
|
openWaybill(row) { this.$router.push({ path: '/business/waybill-manage', query: { detailId: row.id } }); },
|
||||||
openPlan(row) { this.$router.push({ path: '/business/transport-plan', query: { detailId: row.id } }); },
|
openPlan(row) { this.$router.push({ path: '/business/transport-plan', query: { detailId: row.id } }); },
|
||||||
waybillStatusName(value) { return ({ pending: '待执行', running: '进行中', completed: '已完成', cancelled: '已取消' })[value] || value || '-'; },
|
waybillStatusName(value) { return ({ pending: '待执行', waiting: '待执行', running: '进行中', processing: '进行中', completed: '已完成', cancelled: '已取消' })[value] || value || '-'; },
|
||||||
waybillStatusType(value) { return ({ pending: 'info', running: 'warning', completed: 'success', cancelled: 'danger' })[value] || 'info'; },
|
waybillStatusType(value) { return ({ pending: 'info', waiting: 'info', running: 'warning', processing: 'warning', completed: 'success', cancelled: 'danger' })[value] || 'info'; },
|
||||||
statusName(value) { return ({ waiting_dispatch: '待调度', dispatching: '调度中', completed: '调度完成', closed: '调度关闭' })[value] || value || '-'; },
|
statusName(value) { return ({ waiting_dispatch: '待调度', dispatching: '调度中', completed: '调度完成', closed: '调度关闭' })[value] || value || '-'; },
|
||||||
statusType(value) { return ({ waiting_dispatch: 'warning', dispatching: 'success', completed: 'primary', closed: 'danger' })[value] || 'info'; },
|
statusType(value) { return ({ waiting_dispatch: 'warning', dispatching: 'success', completed: 'primary', closed: 'danger' })[value] || 'info'; },
|
||||||
},
|
},
|
||||||
@@ -132,19 +162,20 @@ export default {
|
|||||||
.detail-meta { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 18px 24px; margin: 0; padding: 0 24px 20px; dt { margin-bottom: 6px; color: #909399; font-size: 13px; } dd { display: flex; flex-wrap: wrap; gap: 8px 16px; margin: 0; color: #409eff; font-size: 14px; word-break: break-all; } }
|
.detail-meta { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 18px 24px; margin: 0; padding: 0 24px 20px; dt { margin-bottom: 6px; color: #909399; font-size: 13px; } dd { display: flex; flex-wrap: wrap; gap: 8px 16px; margin: 0; color: #409eff; font-size: 14px; word-break: break-all; } }
|
||||||
.route-map { display: flex; align-items: flex-start; padding: 16px 24px 20px; border-top: 1px solid #eff1f7; overflow-x: auto; }
|
.route-map { display: flex; align-items: flex-start; padding: 16px 24px 20px; border-top: 1px solid #eff1f7; overflow-x: auto; }
|
||||||
.route-map__node { display: grid; flex: 0 0 150px; justify-items: center; gap: 6px; text-align: center; strong { font-size: 16px; white-space: nowrap; } small { color: #606266; white-space: nowrap; } }
|
.route-map__node { display: grid; flex: 0 0 150px; justify-items: center; gap: 6px; text-align: center; strong { font-size: 16px; white-space: nowrap; } small { color: #606266; white-space: nowrap; } }
|
||||||
.route-map__progress { color: #409eff !important; }
|
.route-map__progress { color: #303133 !important; }
|
||||||
.route-badge { display: inline-flex; width: 32px; height: 32px; align-items: center; justify-content: center; border-radius: 4px; background: #67c23a; color: #fff; font-weight: 600; &.start { background: #409eff; } &.end { background: #e6a23c; } }
|
.route-badge { display: inline-flex; width: 32px; height: 32px; align-items: center; justify-content: center; border-radius: 4px; background: #67c23a; color: #fff; font-weight: 600; &.start { background: #409eff; } &.end { background: #e6a23c; } }
|
||||||
.route-map__line { flex: 1 0 64px; min-width: 64px; height: 32px; border-bottom: 6px solid #e4e7ed; }
|
.route-map__line { flex: 1 0 64px; min-width: 64px; height: 32px; border-bottom: 6px solid #e4e7ed; }
|
||||||
.execution-detail-heading { padding: 20px 24px 12px; h3 { margin: 0; padding-left: 12px; border-left: 4px solid #409eff; font-size: 16px; } }
|
.execution-detail-heading { padding: 20px 24px 12px; h3 { margin: 0; padding-left: 12px; border-left: 4px solid #409eff; font-size: 16px; } }
|
||||||
.segment-detail { padding: 16px 24px; border-top: 1px solid #eff1f7; header { display: flex; align-items: center; justify-content: space-between; margin: -16px -24px 8px; padding: 12px 24px; cursor: pointer; background: #fafbfc; h3 { display: flex; min-width: 0; align-items: center; flex-wrap: wrap; gap: 12px; margin: 0; font-size: 16px; } em { color: #409eff; font-style: normal; } } }
|
.segment-detail { padding: 16px 24px; border-top: 1px solid #eff1f7; header { display: flex; align-items: center; justify-content: space-between; margin: -16px -24px 8px; padding: 12px 24px; cursor: pointer; background: #fafbfc; h3 { display: flex; min-width: 0; align-items: center; flex-wrap: wrap; gap: 12px; margin: 0; font-size: 16px; } em { color: #67c23a; font-style: normal; } } }
|
||||||
.segment-detail__progress { color: #606266; font-size: 14px; font-weight: 400; }
|
.segment-detail__progress { color: #606266; font-size: 14px; font-weight: 400; }
|
||||||
.segment-detail__transport-type { color: #409eff; font-size: 14px; font-weight: 500; }
|
.segment-detail__transport-type { color: #409eff; font-size: 14px; font-weight: 500; }
|
||||||
.segment-detail__progress-bar { display: inline-flex; width: 144px; height: 6px; overflow: hidden; border-radius: 3px; background: #e4e7ed; }
|
.segment-detail__progress-bar { display: inline-flex; width: 144px; height: 6px; overflow: hidden; border-radius: 3px; background: #e4e7ed; }
|
||||||
.segment-detail__progress-bar span { display: block; height: 100%; background: #409eff; transition: width 0.2s ease; }
|
.segment-detail__progress-bar span { display: block; height: 100%; background: #409eff; transition: width 0.2s ease; }
|
||||||
.segment-detail__header-right { display: flex; align-items: center; gap: 12px; }
|
.segment-toggle { gap: 4px; padding: 4px 0; }
|
||||||
|
.segment-toggle__icon { font-size: 12px; }
|
||||||
.segment-index { display: inline-flex; width: 36px; height: 36px; align-items: center; justify-content: center; border-radius: 50%; background: #2088ee; color: #fff; font-size: 20px; font-weight: 500; }
|
.segment-index { display: inline-flex; width: 36px; height: 36px; align-items: center; justify-content: center; border-radius: 50%; background: #2088ee; color: #fff; font-size: 20px; font-weight: 500; }
|
||||||
.segment-execution { padding-top: 12px; }
|
.segment-execution { padding-top: 12px; }
|
||||||
.segment-stats { display: flex; gap: 72px; padding: 4px 0 24px; div { display: flex; flex-direction: column; gap: 8px; } span { color: #8a9bb8; font-size: 14px; } strong { color: #1f2d3d; font-size: 32px; line-height: 1; } small { margin-left: 6px; color: #8a9bb8; font-size: 16px; font-weight: 400; } .dispatched { color: #409eff; } .remaining { color: #f56c6c; } }
|
.segment-stats { display: flex; gap: 72px; padding: 4px 0 24px; div { display: flex; flex-direction: column; gap: 8px; } span { color: #8a9bb8; font-size: 14px; } strong { color: #1f2d3d; font-size: 32px; line-height: 1; } small { margin-left: 6px; color: #8a9bb8; font-size: 16px; font-weight: 400; } .dispatched { color: #67c23a; } .remaining { color: #e6a23c; } }
|
||||||
.waybill-table { :deep(th.el-table__cell) { background: #f5f7fa; color: #60738f; } }
|
.waybill-table { :deep(th.el-table__cell) { background: #f5f7fa; color: #60738f; } }
|
||||||
.transport-plan-detail { margin-top: 20px; h4 { margin: 0 0 12px; padding-left: 12px; border-left: 4px solid #409eff; font-size: 15px; } }
|
.transport-plan-detail { margin-top: 20px; h4 { margin: 0 0 12px; padding-left: 12px; border-left: 4px solid #409eff; font-size: 15px; } }
|
||||||
.master-goods-card { margin-bottom: 8px; border: 1px solid #eff1f7; background: #fff; }
|
.master-goods-card { margin-bottom: 8px; border: 1px solid #eff1f7; background: #fff; }
|
||||||
|
|||||||
@@ -55,17 +55,17 @@
|
|||||||
<span class="segment-checkbox-label">
|
<span class="segment-checkbox-label">
|
||||||
<span>{{ route.segmentNo }}:</span>
|
<span>{{ route.segmentNo }}:</span>
|
||||||
<el-tooltip
|
<el-tooltip
|
||||||
:content="`${route.departureName || '-'} → ${route.arrivalName || '-'}`"
|
:content="`${dispatchLocationName(route, 'departure')} → ${dispatchLocationName(route, 'arrival')}`"
|
||||||
placement="top"
|
placement="top"
|
||||||
>
|
>
|
||||||
<span class="segment-checkbox-path"
|
<span class="segment-checkbox-path"
|
||||||
>{{ route.departureName || '-' }} → {{ route.arrivalName || '-' }}</span
|
>{{ dispatchLocationName(route, 'departure') }} → {{ dispatchLocationName(route, 'arrival') }}</span
|
||||||
>
|
>
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
</span>
|
</span>
|
||||||
</el-checkbox>
|
</el-checkbox>
|
||||||
</div>
|
</div>
|
||||||
<div>总量 {{ formatQuantity(totalQuantity) }},已调度 <em>{{ formatQuantity(dispatchedQuantity(route)) }}</em>,剩余 <em>{{ formatQuantity(remaining(route)) }}</em></div>
|
<div>总量 {{ formatQuantity(totalQuantity) }} {{ quantityUnitLabel }},已调度 <em>{{ formatQuantity(dispatchedQuantity(route)) }} {{ quantityUnitLabel }}</em>,剩余 <em>{{ formatQuantity(remaining(route)) }} {{ quantityUnitLabel }}</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">
|
||||||
@@ -95,11 +95,11 @@
|
|||||||
<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-cascader v-model="row.cargoTypePath" class="goods-table__cargo-type" :options="cargoTypeOptions" :props="cargoTypeCascaderProps" placeholder="请选择货物类型" clearable filterable @change="value => handleCargoTypeChange(route, row, value)" /></template></el-table-column>
|
<el-table-column min-width="180"><template #header><span>货物类型<span class="goods-required-mark">*</span></span></template><template #default="{ row }"><el-cascader v-model="row.cargoTypePath" class="goods-table__cargo-type" :options="cargoTypeOptions" :props="cargoTypeCascaderProps" placeholder="请选择货物类型" clearable filterable @change="value => handleCargoTypeChange(route, row, value)" /></template></el-table-column>
|
||||||
<el-table-column label="货物名称" min-width="180"><template #default="{ row }"><el-select v-model="row.sourceIndex" class="goods-table__cargo-name" 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 min-width="180"><template #header><span>货物名称<span class="goods-required-mark">*</span></span></template><template #default="{ row }"><el-select v-model="row.sourceIndex" class="goods-table__cargo-name" 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 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 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 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="dispatchQuantity" column-key="dispatchQuantity" width="200" class-name="goods-table__dispatch"><template #header><span>本次数量<span class="goods-required-mark">*</span></span></template><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" width="110"><template #header><span>数量单位<span class="goods-required-mark">*</span></span></template></el-table-column>
|
||||||
<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" />
|
||||||
<el-table-column prop="specification" label="规格" min-width="110" />
|
<el-table-column prop="specification" label="规格" min-width="110" />
|
||||||
@@ -108,35 +108,6 @@
|
|||||||
<el-table-column label="操作" width="90" fixed="right"><template #default="{ $index }"><el-link type="danger" @click="removeGoods(route, $index)">删除</el-link></template></el-table-column>
|
<el-table-column label="操作" width="90" fixed="right"><template #default="{ $index }"><el-link type="danger" @click="removeGoods(route, $index)">删除</el-link></template></el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
|
|
||||||
<div class="freight-heading"><h3>运费信息</h3></div>
|
|
||||||
<el-form :model="route" label-position="right" label-width="auto" class="dispatch-form freight-form">
|
|
||||||
<el-row v-for="(item, index) in route.freightItems" :key="item.sourceIndex" :gutter="16">
|
|
||||||
<el-col :span="6">
|
|
||||||
<el-form-item :label="`单价${index + 1}`">
|
|
||||||
<el-input :model-value="item.unitPrice" inputmode="decimal" placeholder="请输入" @input="value => handleFreightUnitPriceInput(item, value)">
|
|
||||||
<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}`">
|
|
||||||
<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-row>
|
|
||||||
</el-form>
|
|
||||||
|
|
||||||
<template v-if="route.documentType === '运单'">
|
<template v-if="route.documentType === '运单'">
|
||||||
<el-form :model="route" label-position="right" label-width="auto" class="dispatch-form carrier-type-form"><el-form-item label="承运类型" required><el-radio-group v-model="route.carrierType" @change="value => handleCarrierTypeChange(route, value)"><el-radio-button label="承运商" /><el-radio-button label="自运" /><el-radio-button label="网货平台" /></el-radio-group></el-form-item></el-form>
|
<el-form :model="route" label-position="right" label-width="auto" class="dispatch-form carrier-type-form"><el-form-item label="承运类型" required><el-radio-group v-model="route.carrierType" @change="value => handleCarrierTypeChange(route, value)"><el-radio-button label="承运商" /><el-radio-button label="自运" /><el-radio-button label="网货平台" /></el-radio-group></el-form-item></el-form>
|
||||||
<el-form :model="route" label-position="right" label-width="auto" class="dispatch-form carrier-form">
|
<el-form :model="route" label-position="right" label-width="auto" class="dispatch-form carrier-form">
|
||||||
@@ -163,6 +134,35 @@
|
|||||||
</el-row>
|
</el-row>
|
||||||
</el-form>
|
</el-form>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<div class="freight-heading"><h3>运费信息</h3></div>
|
||||||
|
<el-form :model="route" label-position="right" label-width="auto" class="dispatch-form freight-form">
|
||||||
|
<el-row v-for="(group, index) in freightGroups(route)" :key="group.key" :gutter="16">
|
||||||
|
<el-col :span="6">
|
||||||
|
<el-form-item label="单价">
|
||||||
|
<el-input :model-value="freightGroupUnitPrice(group)" inputmode="decimal" placeholder="请输入" @input="value => handleFreightGroupUnitPriceInput(group, value)" @clear="() => handleFreightGroupUnitPriceInput(group, '')">
|
||||||
|
<template #append><el-select :model-value="freightGroupPriceUnit(group)" class="freight-unit-select" @change="value => handleFreightGroupPriceUnitChange(group, value)"><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="数量">
|
||||||
|
<el-input :model-value="formatQuantity(freightGroupQuantity(group))" readonly>
|
||||||
|
<template #append><el-select :model-value="group.quantityUnit" class="freight-unit-select" @change="value => handleFreightGroupQuantityUnitChange(route, group, 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="运费">
|
||||||
|
<el-input :model-value="formatAmount(freightGroupAmount(group))" 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-row>
|
||||||
|
</el-form>
|
||||||
<div class="segment-actions"><el-button plain @click="addToPending(route, true)">加入并继续调度</el-button><el-button plain @click="addToPending(route, false)">加入调度清单</el-button></div>
|
<div class="segment-actions"><el-button plain @click="addToPending(route, true)">加入并继续调度</el-button><el-button plain @click="addToPending(route, false)">加入调度清单</el-button></div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
@@ -178,7 +178,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div v-show="pendingExpanded" class="pending-list">
|
<div v-show="pendingExpanded" class="pending-list">
|
||||||
<template v-for="(items, segmentNo) in pendingGroups" :key="segmentNo">
|
<template v-for="(items, segmentNo) in pendingGroups" :key="segmentNo">
|
||||||
<h3>{{ segmentNo }}:{{ items[0].departureName }} → {{ items[0].arrivalName }}</h3>
|
<h3>{{ segmentNo }}:{{ dispatchLocationName(items[0], 'departure') }} → {{ dispatchLocationName(items[0], 'arrival') }}</h3>
|
||||||
<el-table :data="items" border><el-table-column type="index" label="序号" width="70" /><el-table-column label="单据类型" prop="documentType" width="100" /><el-table-column label="货物" min-width="220"><template #default="{ row }">{{ row.cargoName }}{{ row.cargoType ? `,${row.cargoType}` : '' }}</template></el-table-column><el-table-column label="本次数量" width="150"><template #default="{ row }">{{ formatQuantity(row.quantity) }} {{ row.quantityUnit }}</template></el-table-column><el-table-column prop="carrierName" label="承运商" min-width="180" /><el-table-column prop="driverName" label="司机" width="130" /><el-table-column prop="vehicleNo" label="车牌" width="130" /><el-table-column label="操作" width="120" fixed="right"><template #default="{ row }"><div class="table-actions"><el-link type="primary" @click="editPending(row)">编辑</el-link><el-link type="danger" @click="removePending(row.id)">删除</el-link></div></template></el-table-column></el-table>
|
<el-table :data="items" border><el-table-column type="index" label="序号" width="70" /><el-table-column label="单据类型" prop="documentType" width="100" /><el-table-column label="货物" min-width="220"><template #default="{ row }">{{ row.cargoName }}{{ row.cargoType ? `,${row.cargoType}` : '' }}</template></el-table-column><el-table-column label="本次数量" width="150"><template #default="{ row }">{{ formatQuantity(row.quantity) }} {{ row.quantityUnit }}</template></el-table-column><el-table-column prop="carrierName" label="承运商" min-width="180" /><el-table-column prop="driverName" label="司机" width="130" /><el-table-column prop="vehicleNo" label="车牌" width="130" /><el-table-column label="操作" width="120" fixed="right"><template #default="{ row }"><div class="table-actions"><el-link type="primary" @click="editPending(row)">编辑</el-link><el-link type="danger" @click="removePending(row.id)">删除</el-link></div></template></el-table-column></el-table>
|
||||||
</template>
|
</template>
|
||||||
<el-empty v-if="!pending.length" description="暂无待提交调度条目" :image-size="60" />
|
<el-empty v-if="!pending.length" description="暂无待提交调度条目" :image-size="60" />
|
||||||
@@ -211,6 +211,7 @@ export default {
|
|||||||
},
|
},
|
||||||
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); },
|
||||||
|
quantityUnitLabel() { return this.master?.goods?.[0]?.quantityUnit || '吨'; },
|
||||||
goodsNames() { return (this.master?.goods || []).map(item => item.cargoName).filter(Boolean).join('、') || '-'; },
|
goodsNames() { return (this.master?.goods || []).map(item => item.cargoName).filter(Boolean).join('、') || '-'; },
|
||||||
goodsTypes() { return [...new Set((this.master?.goods || []).map(item => item.cargoType).filter(Boolean))].join('、') || '-'; },
|
goodsTypes() { return [...new Set((this.master?.goods || []).map(item => item.cargoType).filter(Boolean))].join('、') || '-'; },
|
||||||
masterGoods() { return (this.master?.goods || []).map((item, sourceIndex) => ({ ...item, sourceIndex, label: [item.cargoName, item.cargoType].filter(Boolean).join(' / ') })); },
|
masterGoods() { return (this.master?.goods || []).map((item, sourceIndex) => ({ ...item, sourceIndex, label: [item.cargoName, item.cargoType].filter(Boolean).join(' / ') })); },
|
||||||
@@ -293,7 +294,9 @@ export default {
|
|||||||
otherFeeTotal: '',
|
otherFeeTotal: '',
|
||||||
freightItems: [],
|
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 || '',
|
||||||
|
departureCityName: previous.departureCityName || previous.arrivalCityName || '', departureDistrictName: previous.departureDistrictName || previous.arrivalDistrictName || '', departureSiteCode: previous.departureSiteCode || previous.arrivalSiteCode || '',
|
||||||
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 || '',
|
||||||
|
arrivalCityName: node.departureCityName || node.arrivalCityName || this.master.arrivalCityName || '', arrivalDistrictName: node.departureDistrictName || node.arrivalDistrictName || this.master.arrivalDistrictName || '', arrivalSiteCode: node.departureSiteCode || node.arrivalSiteCode || this.master.arrivalSiteCode || '',
|
||||||
goods: (this.master.goods || []).map((goods, sourceIndex) => this.createGoodsRow(goods, sourceIndex)),
|
goods: (this.master.goods || []).map((goods, sourceIndex) => this.createGoodsRow(goods, sourceIndex)),
|
||||||
};
|
};
|
||||||
this.syncFreightItems(route);
|
this.syncFreightItems(route);
|
||||||
@@ -341,7 +344,52 @@ export default {
|
|||||||
else Object.assign(row, { sourceIndex: undefined, cargoName: '', quantity: '', quantityUnit: '' });
|
else Object.assign(row, { sourceIndex: undefined, cargoName: '', quantity: '', quantityUnit: '' });
|
||||||
this.syncFreightItems(route);
|
this.syncFreightItems(route);
|
||||||
},
|
},
|
||||||
isRoad(route) { const type = String(route.transportType || '').toLowerCase(); return type.includes('公路') || type === 'road'; },
|
isRoad(route) {
|
||||||
|
const values = [
|
||||||
|
route.transportTypeName,
|
||||||
|
route.transportTypeLabel,
|
||||||
|
route.transportModeName,
|
||||||
|
route.transportMode,
|
||||||
|
route.transportType,
|
||||||
|
];
|
||||||
|
const type = values
|
||||||
|
.map(value => {
|
||||||
|
if (value && typeof value === 'object') {
|
||||||
|
return value.label || value.name || value.dictValue || value.value || value.code || '';
|
||||||
|
}
|
||||||
|
return value || '';
|
||||||
|
})
|
||||||
|
.join(' ')
|
||||||
|
.trim()
|
||||||
|
.toLowerCase();
|
||||||
|
return type.includes('公路') || type.includes('道路') || type.includes('陆运') || type.includes('汽车') || type === 'road';
|
||||||
|
},
|
||||||
|
formatRoadLocation(value) {
|
||||||
|
const text = String(value || '').replace(/\s+/g, '');
|
||||||
|
if (!text) return '';
|
||||||
|
const withoutProvince = text.replace(/^.*?(?:省|自治区|特别行政区)/, '');
|
||||||
|
const cityMatch = withoutProvince.match(/.+?(?:市|州(?!市)|盟(?!市))/);
|
||||||
|
if (!cityMatch) return text;
|
||||||
|
const city = cityMatch[0];
|
||||||
|
const districtSource = withoutProvince.slice(city.length);
|
||||||
|
const districtMatch = districtSource.match(/^[^市州盟]*?(?:自治县|自治旗|林区|矿区|新区|开发区|区|县|旗)/);
|
||||||
|
if (!districtMatch) return city;
|
||||||
|
const district = `${districtMatch[0]}${districtSource.slice(districtMatch[0].length).startsWith('区') ? '区' : ''}`;
|
||||||
|
return `${city} ${district}`;
|
||||||
|
},
|
||||||
|
dispatchLocationName(route = {}, field) {
|
||||||
|
const name = String(route[`${field}Name`] || '').trim();
|
||||||
|
const address = String(route[`${field}Address`] || '').trim();
|
||||||
|
const formattedName = this.formatRoadLocation(name);
|
||||||
|
if (/(?:市|州|盟)/.test(formattedName)) return formattedName;
|
||||||
|
const regionalName = /(?:省|自治区|特别行政区|市|州|盟|区|县|旗)/.test(name);
|
||||||
|
if (name && !regionalName) return name;
|
||||||
|
const city = String(route[`${field}CityName`] || '').trim();
|
||||||
|
const district = String(route[`${field}DistrictName`] || '').trim();
|
||||||
|
const formattedAddress = this.formatRoadLocation(address);
|
||||||
|
if (/(?:市|州|盟)/.test(formattedAddress)) return formattedAddress;
|
||||||
|
return [city, district].filter(Boolean).join(' ') || formattedName || formattedAddress || '-';
|
||||||
|
},
|
||||||
isWater(route) {
|
isWater(route) {
|
||||||
const type = String(route.transportType || '').trim().toLowerCase();
|
const type = String(route.transportType || '').trim().toLowerCase();
|
||||||
return type === 'river' || type === 'water' || type === 'sl' || type.includes('水路') || type.includes('水运');
|
return type === 'river' || type === 'water' || type === 'sl' || type.includes('水路') || type.includes('水运');
|
||||||
@@ -360,6 +408,42 @@ export default {
|
|||||||
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(item = {}) { return Number(item.unitPrice || 0) * Number(item.quantity || 0); },
|
freightAmount(item = {}) { return Number(item.unitPrice || 0) * Number(item.quantity || 0); },
|
||||||
|
freightGroups(route = {}) {
|
||||||
|
const groups = [];
|
||||||
|
const groupMap = new Map();
|
||||||
|
(route.freightItems || []).forEach((item, index) => {
|
||||||
|
const quantityUnit = String(item.quantityUnit || '').trim();
|
||||||
|
const key = quantityUnit || `__empty_${index}`;
|
||||||
|
let group = groupMap.get(key);
|
||||||
|
if (!group) {
|
||||||
|
group = { key, quantityUnit, rows: [] };
|
||||||
|
groupMap.set(key, group);
|
||||||
|
groups.push(group);
|
||||||
|
}
|
||||||
|
group.rows.push(item);
|
||||||
|
});
|
||||||
|
return groups;
|
||||||
|
},
|
||||||
|
freightGroupQuantity(group = {}) { return (group.rows || []).reduce((sum, item) => sum + Number(item.quantity || 0), 0); },
|
||||||
|
freightGroupUnitPrice(group = {}) {
|
||||||
|
const prices = (group.rows || []).map(item => String(item.unitPrice ?? '').trim()).filter(Boolean);
|
||||||
|
return !prices.length || prices.some(price => price !== prices[0]) ? '' : prices[0];
|
||||||
|
},
|
||||||
|
freightGroupPriceUnit(group = {}) { return group.rows?.[0]?.priceUnit || (group.quantityUnit ? `元/${group.quantityUnit}` : ''); },
|
||||||
|
freightGroupAmount(group = {}) { return (group.rows || []).reduce((sum, item) => sum + this.freightAmount(item), 0); },
|
||||||
|
handleFreightGroupUnitPriceInput(group, value) {
|
||||||
|
const [integer = '', decimal = ''] = String(value || '').replace(/[^\d.]/g, '').split('.');
|
||||||
|
const normalized = decimal || String(value || '').includes('.') ? `${integer}.${decimal.slice(0, 2)}` : integer;
|
||||||
|
(group.rows || []).forEach(item => { item.unitPrice = normalized; });
|
||||||
|
},
|
||||||
|
handleFreightGroupPriceUnitChange(group, value) { (group.rows || []).forEach(item => { item.priceUnit = value || ''; }); },
|
||||||
|
handleFreightGroupQuantityUnitChange(route, group, value) {
|
||||||
|
(group.rows || []).forEach(item => {
|
||||||
|
item.quantityUnit = value || '';
|
||||||
|
const goods = (route.goods || []).find(row => String(row.sourceIndex) === String(item.sourceIndex));
|
||||||
|
if (goods) goods.quantityUnit = value || '';
|
||||||
|
});
|
||||||
|
},
|
||||||
freightTotal(route) {
|
freightTotal(route) {
|
||||||
const goodsFreight = (route.freightItems || []).reduce(
|
const goodsFreight = (route.freightItems || []).reduce(
|
||||||
(sum, item) => sum + this.freightAmount(item),
|
(sum, item) => sum + this.freightAmount(item),
|
||||||
@@ -493,6 +577,23 @@ export default {
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
|
validateDispatchGoods(route, goods) {
|
||||||
|
for (const item of goods) {
|
||||||
|
const index = (route.goods || []).indexOf(item) + 1;
|
||||||
|
const fields = [
|
||||||
|
[!String(item.cargoName || '').trim() || item.sourceIndex === undefined || item.sourceIndex === null || item.sourceIndex === '', '货物名称'],
|
||||||
|
[!String(item.cargoType || '').trim() && !(item.cargoTypePath || []).length, '货物类型'],
|
||||||
|
[item.dispatchQuantity === undefined || item.dispatchQuantity === null || item.dispatchQuantity === '' || Number(item.dispatchQuantity) <= 0, '本次数量'],
|
||||||
|
[!String(item.quantityUnit || '').trim(), '数量单位'],
|
||||||
|
];
|
||||||
|
const emptyField = fields.find(([isEmpty]) => isEmpty);
|
||||||
|
if (emptyField) {
|
||||||
|
this.$message.warning(`第${index}条货物的${emptyField[1]}不能为空`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
},
|
||||||
handleFreightQuantityUnitChange(route, freightItem, value) {
|
handleFreightQuantityUnitChange(route, freightItem, value) {
|
||||||
const goods = (route.goods || []).find(item => String(item.sourceIndex) === String(freightItem.sourceIndex));
|
const goods = (route.goods || []).find(item => String(item.sourceIndex) === String(freightItem.sourceIndex));
|
||||||
if (goods) goods.quantityUnit = value;
|
if (goods) goods.quantityUnit = value;
|
||||||
@@ -695,6 +796,7 @@ export default {
|
|||||||
addToPending(route, continueDispatch) {
|
addToPending(route, continueDispatch) {
|
||||||
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 (!this.validateDispatchGoods(route, goods)) return;
|
||||||
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);
|
this.syncFreightItems(route);
|
||||||
if (!this.validateFreightItems(route)) return;
|
if (!this.validateFreightItems(route)) return;
|
||||||
@@ -712,11 +814,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,
|
||||||
carrierContractId: route.carrierContractId, carrierName: route.carrierName, driverName: route.driverName, driverPhone: route.driverPhone, vehicleNo: route.vehicleNo, captainName: route.captainName, cabinNo: route.cabinNo, containerNo: route.containerNo, trailerVehicleNo: route.trailerVehicleNo, escortName: route.escortName, escortPhone: route.escortPhone, mileage: this.normalizeMileage(route.mileage), unitPrice: this.freightItemsForGoods(route, item).unitPrice || '', priceUnit: this.freightItemsForGoods(route, item).priceUnit || '', currency: route.currency || 'RMB', freightAmount: this.freightAmount(this.freightItemsForGoods(route, item)), freightTotal: this.freightTotal(route), otherFeeTotal: route.otherFeeTotal || '', freightJson: this.buildFreightJson(route), remark: route.remark,
|
carrierContractId: route.carrierContractId, carrierName: route.carrierName, driverName: route.driverName, driverPhone: route.driverPhone, vehicleNo: route.vehicleNo, captainName: route.captainName, cabinNo: route.cabinNo, containerNo: route.containerNo, trailerVehicleNo: route.trailerVehicleNo, escortName: route.escortName, escortPhone: route.escortPhone, mileage: this.normalizeMileage(route.mileage), unitPrice: this.freightItemsForGoods(route, item).unitPrice || '', priceUnit: this.freightItemsForGoods(route, item).priceUnit || '', currency: route.currency || 'RMB', freightAmount: this.freightAmount(this.freightItemsForGoods(route, item)), freightTotal: this.freightTotal(route), otherFeeTotal: route.otherFeeTotal || '', freightJson: this.buildFreightJson(route), routeRemark: 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: 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,
|
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, deviceCode: item.deviceCode, remark: item.remark,
|
||||||
}));
|
}));
|
||||||
this.editingId = null;
|
this.editingId = null;
|
||||||
this.pendingExpanded = true;
|
this.pendingExpanded = true;
|
||||||
@@ -802,6 +904,7 @@ export default {
|
|||||||
.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; } :deep(.goods-table__remaining .cell) { white-space: nowrap; } :deep(.goods-table__dispatch .el-input) { width: 100%; min-width: 0; } :deep(.goods-table__cargo-type .el-input__inner), :deep(.goods-table__cargo-name .el-select__selected-item) { color: #303133; } }
|
.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; } :deep(.goods-table__cargo-type .el-input__inner), :deep(.goods-table__cargo-name .el-select__selected-item) { color: #303133; } }
|
||||||
|
.goods-required-mark { margin-left: 2px; color: #f56c6c; }
|
||||||
.freight-form { :deep(.freight-unit-select) { width: 92px; flex: 0 0 92px; min-width: 0; } }
|
.freight-form { :deep(.freight-unit-select) { width: 92px; flex: 0 0 92px; min-width: 0; } }
|
||||||
.carrier-type-form { margin-top: 16px; }
|
.carrier-type-form { margin-top: 16px; }
|
||||||
.carrier-form { padding-top: 8px; }
|
.carrier-form { padding-top: 8px; }
|
||||||
|
|||||||
@@ -34,7 +34,7 @@
|
|||||||
:model-value="form.masterNo || '系统自动生成'"
|
:model-value="form.masterNo || '系统自动生成'"
|
||||||
readonly /></el-form-item></el-col
|
readonly /></el-form-item></el-col
|
||||||
><el-col :span="6"
|
><el-col :span="6"
|
||||||
><el-form-item label="运输组织类型" required
|
><el-form-item label="运输类型" required
|
||||||
><el-select v-model="form.transportOrganizationType" disabled
|
><el-select v-model="form.transportOrganizationType" disabled
|
||||||
><el-option label="多式联运" value="多式联运" /></el-select></el-form-item></el-col
|
><el-option label="多式联运" value="多式联运" /></el-select></el-form-item></el-col
|
||||||
><el-col :span="24"
|
><el-col :span="24"
|
||||||
@@ -54,75 +54,77 @@
|
|||||||
<el-link type="primary" @click="openRouteDialog">选择线路</el-link>
|
<el-link type="primary" @click="openRouteDialog">选择线路</el-link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<el-form :model="form" label-position="right" label-width="auto"
|
<el-form class="route-info-form" :model="form" label-position="right" label-width="96px"
|
||||||
><div class="route-steps">
|
><div class="route-flow">
|
||||||
<el-steps direction="vertical" :active="form.routes.length + 1">
|
<div class="route-flow-row">
|
||||||
<el-step>
|
<span class="route-flow-marker route-flow-marker--start">起</span>
|
||||||
<template #icon><span class="route-step-icon route-step-icon--start">起</span></template>
|
<div class="route-flow-body">
|
||||||
<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-cascader v-model="addressRegionPaths.departure" class="route-address-name" :options="regionOptions" :props="regionCascaderProps" :loading="regionLoading" placeholder="请选择省市区" clearable filterable @visible-change="visible => visible && loadRegionOptions()" @change="value => handleAddressRegionChange('departure', value)" /><el-input v-model="form.departureAddress" class="route-address-detail" readonly placeholder="请输入详细地址" @click="openAddressMap('departure')" /><el-tooltip content="选择常用地址" placement="top"><el-button class="address-picker-button" type="primary" link :icon="OfficeBuilding" @click="openCommonAddress('departure')" /></el-tooltip></div
|
><el-input v-if="isNonRoadAddress('departure')" v-model="form.departureName" class="route-address-name" readonly placeholder="请选择站点" /><el-cascader v-else v-model="addressRegionPaths.departure" class="route-address-name" :options="regionOptions" :props="regionCascaderProps" :loading="regionLoading" placeholder="请选择省市区" clearable filterable @visible-change="visible => visible && loadRegionOptions()" @change="value => handleAddressRegionChange('departure', value)" /><el-input v-model="form.departureAddress" class="route-address-detail" readonly placeholder="请输入详细地址" @click="handleAddressDetailClick('departure')" /><el-tooltip content="选择常用地址" placement="top"><el-button class="address-picker-button" type="primary" link :icon="OfficeBuilding" @click="openCommonAddress('departure')" /></el-tooltip></div
|
||||||
></el-form-item>
|
></el-form-item>
|
||||||
<el-form-item label="联系人"
|
<el-form-item label="联系人" class="route-contact-form-item"
|
||||||
><el-input v-model="form.departureContact" placeholder="请输入"
|
><el-input v-model="form.departureContact" placeholder="请输入"
|
||||||
/></el-form-item>
|
/></el-form-item>
|
||||||
<el-form-item label="联系方式"
|
<el-form-item label="联系方式" class="route-phone-form-item"
|
||||||
><el-input v-model="form.departurePhone" placeholder="请输入"
|
><el-input v-model="form.departurePhone" placeholder="请输入"
|
||||||
/></el-form-item>
|
/></el-form-item>
|
||||||
<el-form-item label="计划开始日期"
|
<el-form-item label="计划开始" class="route-date-form-item"
|
||||||
><el-date-picker
|
><el-date-picker
|
||||||
v-model="form.planStartTime"
|
v-model="form.planStartTime"
|
||||||
type="date"
|
type="date"
|
||||||
format="YYYY-MM-DD"
|
format="YYYY-MM-DD"
|
||||||
value-format="YYYY-MM-DD"
|
value-format="YYYY-MM-DD"
|
||||||
placeholder="请选择"
|
placeholder="请选择"
|
||||||
style="width: 200px"
|
|
||||||
/></el-form-item>
|
/></el-form-item>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</div>
|
||||||
</el-step>
|
</div>
|
||||||
<el-step v-for="(route, index) in form.routes" :key="index">
|
<template v-for="(route, index) in form.routes" :key="index">
|
||||||
<template #icon><span class="route-step-icon route-step-icon--middle">经</span></template>
|
<div class="route-flow-segment">
|
||||||
<template #title><div class="route-step-title"><span class="route-segment-label">段{{ index + 1 }}</span><el-tooltip :content="getRouteTitle(index).replace(`段${index + 1}:`, '')" placement="top"><span class="route-title-path">{{ formatRouteTitle(getRouteTitle(index), index + 1) }}</span></el-tooltip><div class="route-transport-control"><span>运输方式</span><el-select v-model="route.transportType" placeholder="请选择"><el-option v-for="type in transports" :key="type" :label="type" :value="type" /></el-select></div></div></template>
|
<div class="route-step-title"><span class="route-segment-label">{{ circledSegmentNumber(index + 1) }} 段{{ index + 1 }}</span><el-tooltip :content="getRouteTitle(index).replace(`段${index + 1}:`, '')" placement="top"><span class="route-title-path">{{ formatRouteTitle(getRouteTitle(index), index + 1) }}</span></el-tooltip><div class="route-transport-control"><span>运输方式<span class="required-asterisk">*</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 #description>
|
</div>
|
||||||
|
<div class="route-flow-row">
|
||||||
|
<span class="route-flow-marker route-flow-marker--middle">经</span>
|
||||||
|
<div class="route-flow-body">
|
||||||
<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-cascader v-model="addressRegionPaths[`route-${index}`]" class="route-address-name" :options="regionOptions" :props="regionCascaderProps" :loading="regionLoading" :disabled="!route.transportType" placeholder="请选择省市区" clearable filterable @visible-change="visible => visible && loadRegionOptions()" @change="value => handleAddressRegionChange(`route-${index}`, value)" /><el-input v-model="route.departureAddress" class="route-address-detail" readonly :disabled="!route.transportType || !isRoadTransportType(route.transportType)" placeholder="请先选择运输方式" @click="openAddressMap(`route-${index}`)" /><el-tooltip content="选择常用地址" placement="top"><el-button class="address-picker-button" type="primary" link :icon="OfficeBuilding" :disabled="!route.transportType" @click="openCommonAddress(`route-${index}`)" /></el-tooltip></div
|
><el-input v-if="isNonRoadAddress(`route-${index}`)" v-model="route.departureName" class="route-address-name" readonly :disabled="!route.transportType" placeholder="请先选择运输方式" /><el-cascader v-else v-model="addressRegionPaths[`route-${index}`]" class="route-address-name" :options="regionOptions" :props="regionCascaderProps" :loading="regionLoading" :disabled="!route.transportType" placeholder="请选择省市区" clearable filterable @visible-change="visible => visible && loadRegionOptions()" @change="value => handleAddressRegionChange(`route-${index}`, value)" /><el-input v-model="route.departureAddress" class="route-address-detail" readonly :disabled="!route.transportType" placeholder="请先选择运输方式" @click="handleAddressDetailClick(`route-${index}`)" /><el-tooltip content="选择常用地址" placement="top"><el-button class="address-picker-button" type="primary" link :icon="OfficeBuilding" :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="联系人" class="route-contact-form-item"><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="联系方式" class="route-phone-form-item"><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>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</el-step>
|
<div class="route-flow-segment">
|
||||||
<el-step>
|
<div class="route-step-title"><span class="route-segment-label">{{ circledSegmentNumber(form.routes.length + 1) }} 段{{ form.routes.length + 1 }}</span><el-tooltip :content="getFinalRouteTitle().replace(`段${form.routes.length + 1}:`, '')" placement="top"><span class="route-title-path">{{ formatRouteTitle(getFinalRouteTitle(), form.routes.length + 1) }}</span></el-tooltip><div class="route-transport-control"><span>运输方式<span class="required-asterisk">*</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 #icon><span class="route-step-icon route-step-icon--end">终</span></template>
|
</div>
|
||||||
<template #title><div class="route-step-title"><span class="route-segment-label">段{{ form.routes.length + 1 }}</span><el-tooltip :content="getFinalRouteTitle().replace(`段${form.routes.length + 1}:`, '')" placement="top"><span class="route-title-path">{{ formatRouteTitle(getFinalRouteTitle(), form.routes.length + 1) }}</span></el-tooltip><div class="route-transport-control"><span>运输方式</span><el-select v-model="form.finalTransportType" placeholder="请选择"><el-option v-for="type in transports" :key="type" :label="type" :value="type" /></el-select></div></div></template>
|
<div class="route-flow-row">
|
||||||
<template #description>
|
<span class="route-flow-marker route-flow-marker--end">终</span>
|
||||||
|
<div class="route-flow-body">
|
||||||
<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-cascader v-model="addressRegionPaths.arrival" class="route-address-name" :options="regionOptions" :props="regionCascaderProps" :loading="regionLoading" :disabled="!form.finalTransportType" placeholder="请选择省市区" clearable filterable @visible-change="visible => visible && loadRegionOptions()" @change="value => handleAddressRegionChange('arrival', value)" /><el-input v-model="form.arrivalAddress" class="route-address-detail" readonly :disabled="!form.finalTransportType || !isRoadTransportType(form.finalTransportType)" placeholder="请选择省市区" @click="openAddressMap('arrival')" /><el-tooltip content="选择常用地址" placement="top"><el-button class="address-picker-button" type="primary" link :icon="OfficeBuilding" :disabled="!form.finalTransportType" @click="openCommonAddress('arrival')" /></el-tooltip></div
|
><el-input v-if="isNonRoadAddress('arrival')" v-model="form.arrivalName" class="route-address-name" readonly :disabled="!form.finalTransportType" placeholder="请先选择运输方式" /><el-cascader v-else v-model="addressRegionPaths.arrival" class="route-address-name" :options="regionOptions" :props="regionCascaderProps" :loading="regionLoading" :disabled="!form.finalTransportType" placeholder="请选择省市区" clearable filterable @visible-change="visible => visible && loadRegionOptions()" @change="value => handleAddressRegionChange('arrival', value)" /><el-input v-model="form.arrivalAddress" class="route-address-detail" readonly :disabled="!form.finalTransportType" placeholder="请先选择运输方式" @click="handleAddressDetailClick('arrival')" /><el-tooltip content="选择常用地址" placement="top"><el-button class="address-picker-button" type="primary" link :icon="OfficeBuilding" :disabled="!form.finalTransportType" @click="openCommonAddress('arrival')" /></el-tooltip></div
|
||||||
></el-form-item>
|
></el-form-item>
|
||||||
<el-form-item label="联系人"
|
<el-form-item label="联系人" class="route-contact-form-item"
|
||||||
><el-input v-model="form.arrivalContact" placeholder="请输入" /></el-form-item>
|
><el-input v-model="form.arrivalContact" placeholder="请输入" /></el-form-item>
|
||||||
<el-form-item label="联系方式"
|
<el-form-item label="联系方式" class="route-phone-form-item"
|
||||||
><el-input v-model="form.arrivalPhone" placeholder="请输入" /></el-form-item>
|
><el-input v-model="form.arrivalPhone" placeholder="请输入" /></el-form-item>
|
||||||
<el-form-item label="计划结束日期"
|
<el-form-item label="计划结束" class="route-date-form-item"
|
||||||
><el-date-picker
|
><el-date-picker
|
||||||
v-model="form.planEndTime"
|
v-model="form.planEndTime"
|
||||||
type="date"
|
type="date"
|
||||||
format="YYYY-MM-DD"
|
format="YYYY-MM-DD"
|
||||||
value-format="YYYY-MM-DD"
|
value-format="YYYY-MM-DD"
|
||||||
placeholder="请选择"
|
placeholder="请选择"
|
||||||
style="width: 200px"
|
|
||||||
/></el-form-item>
|
/></el-form-item>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</div>
|
||||||
</el-step>
|
</div>
|
||||||
</el-steps>
|
|
||||||
</div></el-form
|
</div></el-form
|
||||||
>
|
>
|
||||||
</section>
|
</section>
|
||||||
@@ -157,7 +159,8 @@
|
|||||||
</template></el-autocomplete
|
</template></el-autocomplete
|
||||||
></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 #header><span>货物类型<span class="required-asterisk">*</span></span></template>
|
||||||
|
<template #default="{ row }"
|
||||||
><el-cascader
|
><el-cascader
|
||||||
v-model="row.cargoTypePath"
|
v-model="row.cargoTypePath"
|
||||||
:options="cargoTypeOptions"
|
:options="cargoTypeOptions"
|
||||||
@@ -170,14 +173,16 @@
|
|||||||
@change="value => handleCargoTypeChange(row, value)"
|
@change="value => handleCargoTypeChange(row, value)"
|
||||||
/></template></el-table-column
|
/></template></el-table-column
|
||||||
><el-table-column label="数量" width="180" class-name="master-editor__quantity-cell"
|
><el-table-column label="数量" width="180" class-name="master-editor__quantity-cell"
|
||||||
><template #default="{ row }"
|
><template #header><span>数量<span class="required-asterisk">*</span></span></template>
|
||||||
|
<template #default="{ row }"
|
||||||
><el-input
|
><el-input
|
||||||
v-model="row.quantity"
|
v-model="row.quantity"
|
||||||
inputmode="decimal"
|
inputmode="decimal"
|
||||||
placeholder="请输入"
|
placeholder="请输入"
|
||||||
@input="value => handleQuantityInput(row, value)" /></template></el-table-column
|
@input="value => handleQuantityInput(row, value)" /></template></el-table-column
|
||||||
><el-table-column label="数量单位" width="120"
|
><el-table-column label="数量单位" width="120"
|
||||||
><template #default="{ row }"
|
><template #header><span>数量单位<span class="required-asterisk">*</span></span></template>
|
||||||
|
<template #default="{ row }"
|
||||||
><el-select v-model="row.quantityUnit"
|
><el-select v-model="row.quantityUnit"
|
||||||
><el-option v-for="unit in quantityUnitOptions" :key="unit" :label="unit" :value="unit" /></el-select></template></el-table-column
|
><el-option v-for="unit in quantityUnitOptions" :key="unit" :label="unit" :value="unit" /></el-select></template></el-table-column
|
||||||
><el-table-column label="包装" width="130"
|
><el-table-column label="包装" width="130"
|
||||||
@@ -194,6 +199,15 @@
|
|||||||
><el-table-column label="型号" min-width="120"
|
><el-table-column label="型号" min-width="120"
|
||||||
><template #default="{ row }"
|
><template #default="{ row }"
|
||||||
><el-input v-model="row.model" placeholder="请输入" /></template></el-table-column
|
><el-input v-model="row.model" placeholder="请输入" /></template></el-table-column
|
||||||
|
><el-table-column label="物料编码" min-width="140"
|
||||||
|
><template #default="{ row }"
|
||||||
|
><el-input v-model="row.materialCode" placeholder="请输入" /></template></el-table-column
|
||||||
|
><el-table-column label="设备编码" min-width="140"
|
||||||
|
><template #default="{ row }"
|
||||||
|
><el-input v-model="row.deviceCode" placeholder="请输入" /></template></el-table-column
|
||||||
|
><el-table-column label="备注" min-width="180"
|
||||||
|
><template #default="{ row }"
|
||||||
|
><el-input v-model="row.remark" maxlength="200" show-word-limit placeholder="请输入" /></template></el-table-column
|
||||||
><el-table-column label="操作" width="140"
|
><el-table-column label="操作" width="140"
|
||||||
><template #default="{ $index }"
|
><template #default="{ $index }"
|
||||||
><div class="goods-actions"
|
><div class="goods-actions"
|
||||||
@@ -717,6 +731,11 @@ export default {
|
|||||||
responseData(response) {
|
responseData(response) {
|
||||||
return response?.data?.data || response?.data || response || {};
|
return response?.data?.data || response?.data || response || {};
|
||||||
},
|
},
|
||||||
|
circledSegmentNumber(number) {
|
||||||
|
const value = Number(number);
|
||||||
|
const circledNumbers = '①②③④⑤⑥⑦⑧⑨⑩⑪⑫⑬⑭⑮⑯⑰⑱⑲⑳';
|
||||||
|
return circledNumbers[value - 1] || String(value);
|
||||||
|
},
|
||||||
async loadRegionOptions() {
|
async loadRegionOptions() {
|
||||||
if (this.regionOptions.length) return this.regionOptions;
|
if (this.regionOptions.length) return this.regionOptions;
|
||||||
if (this.regionRequest) return this.regionRequest;
|
if (this.regionRequest) return this.regionRequest;
|
||||||
@@ -767,9 +786,19 @@ export default {
|
|||||||
},
|
},
|
||||||
extractChinaRegionOptions(regions = []) {
|
extractChinaRegionOptions(regions = []) {
|
||||||
const china = regions.find(item => ['中国', '中华人民共和国'].includes(item.title));
|
const china = regions.find(item => ['中国', '中华人民共和国'].includes(item.title));
|
||||||
if (china?.children?.length) return china.children;
|
const chinaChildren = china?.children || [];
|
||||||
if (regions.length === 1 && regions[0].children?.length) return regions[0].children;
|
// 部分行政区划数据的省级节点 parentId 为 0,另一部分挂在“中国(+86)”下,需合并两类根节点。
|
||||||
return regions;
|
const rootRegions = regions.filter(
|
||||||
|
item => item !== china && ['0', '', 'null', 'undefined'].includes(String(item.parentId ?? ''))
|
||||||
|
);
|
||||||
|
const merged = [...chinaChildren, ...rootRegions];
|
||||||
|
const seen = new Set();
|
||||||
|
return merged.filter(item => {
|
||||||
|
const key = String(item.id ?? item.value ?? item.title ?? '');
|
||||||
|
if (seen.has(key)) return false;
|
||||||
|
seen.add(key);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
},
|
},
|
||||||
findRegionPathByName(options = [], regionName = '', parents = []) {
|
findRegionPathByName(options = [], regionName = '', parents = []) {
|
||||||
const target = String(regionName || '').replace(/\s+/g, '');
|
const target = String(regionName || '').replace(/\s+/g, '');
|
||||||
@@ -805,6 +834,32 @@ export default {
|
|||||||
});
|
});
|
||||||
return labels;
|
return labels;
|
||||||
},
|
},
|
||||||
|
setAddressRegionNames(field, labels = []) {
|
||||||
|
if (!field) return;
|
||||||
|
const normalized = labels.map(label => String(label || '').trim()).filter(Boolean);
|
||||||
|
const province = normalized.find(label => /省|自治区|特别行政区/.test(label)) || normalized[0] || '';
|
||||||
|
const city = normalized.filter(label => /市|州|盟/.test(label)).at(-1) || '';
|
||||||
|
const district = normalized.filter(label => /区|县|旗/.test(label)).at(-1) || '';
|
||||||
|
if (field.provinceName) field.model[field.provinceName] = province;
|
||||||
|
if (field.cityName) field.model[field.cityName] = city;
|
||||||
|
if (field.districtName) field.model[field.districtName] = district;
|
||||||
|
},
|
||||||
|
setAddressRegionNamesFromRow(field, row = {}) {
|
||||||
|
const labels = [row.provinceName, row.cityName, row.districtName].filter(Boolean);
|
||||||
|
if (labels.length) {
|
||||||
|
this.setAddressRegionNames(field, labels);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const text = String(row.regionName || '').replace(/\s+/g, '');
|
||||||
|
if (!text) return;
|
||||||
|
const provinceMatch = text.match(/^.*?(?:省|自治区|特别行政区)/);
|
||||||
|
const rest = provinceMatch ? text.slice(provinceMatch[0].length) : text;
|
||||||
|
const cityMatch = rest.match(/.+?(?:市|州|盟)/);
|
||||||
|
const districtMatch = cityMatch
|
||||||
|
? rest.slice(cityMatch[0].length).match(/^[^市州盟]*?(?:区|县|旗)/)
|
||||||
|
: rest.match(/.+?(?:区|县|旗)/);
|
||||||
|
this.setAddressRegionNames(field, [provinceMatch?.[0], cityMatch?.[0], districtMatch?.[0]]);
|
||||||
|
},
|
||||||
handleAddressRegionChange(target, value) {
|
handleAddressRegionChange(target, value) {
|
||||||
const field = this.addressModel(target);
|
const field = this.addressModel(target);
|
||||||
const path = Array.isArray(value) ? value.map(item => String(item)) : [];
|
const path = Array.isArray(value) ? value.map(item => String(item)) : [];
|
||||||
@@ -813,6 +868,8 @@ export default {
|
|||||||
const labels = this.regionLabels(path);
|
const labels = this.regionLabels(path);
|
||||||
field.model[field.name] = labels.join('');
|
field.model[field.name] = labels.join('');
|
||||||
field.model[field.regionCode] = path.at(-1) || '';
|
field.model[field.regionCode] = path.at(-1) || '';
|
||||||
|
field.model[field.siteCode] = '';
|
||||||
|
this.setAddressRegionNames(field, labels);
|
||||||
},
|
},
|
||||||
syncAddressRegionPath(target) {
|
syncAddressRegionPath(target) {
|
||||||
const field = this.addressModel(target);
|
const field = this.addressModel(target);
|
||||||
@@ -822,6 +879,7 @@ export default {
|
|||||||
? codePath
|
? codePath
|
||||||
: this.findRegionPathByName(this.regionOptions, field.model[field.name]);
|
: this.findRegionPathByName(this.regionOptions, field.model[field.name]);
|
||||||
this.addressRegionPaths = { ...this.addressRegionPaths, [target]: path };
|
this.addressRegionPaths = { ...this.addressRegionPaths, [target]: path };
|
||||||
|
if (path.length) this.setAddressRegionNames(field, this.regionLabels(path));
|
||||||
},
|
},
|
||||||
syncAddressRegionPaths() {
|
syncAddressRegionPaths() {
|
||||||
if (!this.regionOptions.length) return;
|
if (!this.regionOptions.length) return;
|
||||||
@@ -880,19 +938,27 @@ export default {
|
|||||||
const date = this.normalizeDateValue(value);
|
const date = this.normalizeDateValue(value);
|
||||||
return date ? `${date} ${endOfDay ? '23:59:59' : '00:00:00'}` : '';
|
return date ? `${date} ${endOfDay ? '23:59:59' : '00:00:00'}` : '';
|
||||||
},
|
},
|
||||||
getRegionDisplay(target, fallback) {
|
getRoadRegionDisplay(target, fallback) {
|
||||||
const labels = this.regionLabels(this.addressRegionPaths[target] || []);
|
const labels = this.regionLabels(this.addressRegionPaths[target] || []);
|
||||||
return labels.length ? labels.join('/') : fallback;
|
return labels.at(-1) || fallback;
|
||||||
|
},
|
||||||
|
getAddressDisplay(target, fallback) {
|
||||||
|
const field = this.addressModel(target);
|
||||||
|
const name = field?.model?.[field.name] || fallback;
|
||||||
|
return this.isNonRoadAddress(target) ? name : this.getRoadRegionDisplay(target, name);
|
||||||
},
|
},
|
||||||
getRouteTitle(index) {
|
getRouteTitle(index) {
|
||||||
const departureName =
|
const departureName =
|
||||||
index === 0
|
index === 0
|
||||||
? this.getRegionDisplay('departure', this.form.departureName || '发货地址')
|
? this.getAddressDisplay(
|
||||||
: this.getRegionDisplay(
|
'departure',
|
||||||
|
this.form.departureName || '发货地址'
|
||||||
|
)
|
||||||
|
: this.getAddressDisplay(
|
||||||
`route-${index - 1}`,
|
`route-${index - 1}`,
|
||||||
this.form.routes[index - 1].departureName || '途经地'
|
this.form.routes[index - 1].departureName || '途经地'
|
||||||
);
|
);
|
||||||
const arrivalName = this.getRegionDisplay(
|
const arrivalName = this.getAddressDisplay(
|
||||||
`route-${index}`,
|
`route-${index}`,
|
||||||
this.form.routes[index].departureName || '途经地'
|
this.form.routes[index].departureName || '途经地'
|
||||||
);
|
);
|
||||||
@@ -912,11 +978,14 @@ export default {
|
|||||||
return `${this.truncateRouteAddress(departure)}${separator}${this.truncateRouteAddress(arrival)}`;
|
return `${this.truncateRouteAddress(departure)}${separator}${this.truncateRouteAddress(arrival)}`;
|
||||||
},
|
},
|
||||||
getFinalRouteTitle() {
|
getFinalRouteTitle() {
|
||||||
const departureName = this.getRegionDisplay(
|
const departureName = this.getAddressDisplay(
|
||||||
`route-${this.form.routes.length - 1}`,
|
`route-${this.form.routes.length - 1}`,
|
||||||
this.lastRouteName
|
this.lastRouteName
|
||||||
);
|
);
|
||||||
const arrivalName = this.getRegionDisplay('arrival', this.form.arrivalName || '收货地址');
|
const arrivalName = this.getAddressDisplay(
|
||||||
|
'arrival',
|
||||||
|
this.form.arrivalName || '收货地址'
|
||||||
|
);
|
||||||
return `段${this.form.routes.length + 1}:${departureName} → ${arrivalName}`;
|
return `段${this.form.routes.length + 1}:${departureName} → ${arrivalName}`;
|
||||||
},
|
},
|
||||||
responseRecords(response) {
|
responseRecords(response) {
|
||||||
@@ -965,6 +1034,9 @@ export default {
|
|||||||
latitude: 'departureLatitude',
|
latitude: 'departureLatitude',
|
||||||
regionCode: 'departureRegionCode',
|
regionCode: 'departureRegionCode',
|
||||||
siteCode: 'departureSiteCode',
|
siteCode: 'departureSiteCode',
|
||||||
|
provinceName: 'departureProvinceName',
|
||||||
|
cityName: 'departureCityName',
|
||||||
|
districtName: 'departureDistrictName',
|
||||||
}
|
}
|
||||||
: null;
|
: null;
|
||||||
}
|
}
|
||||||
@@ -979,13 +1051,16 @@ export default {
|
|||||||
latitude: `${prefix}Latitude`,
|
latitude: `${prefix}Latitude`,
|
||||||
regionCode: `${prefix}RegionCode`,
|
regionCode: `${prefix}RegionCode`,
|
||||||
siteCode: `${prefix}SiteCode`,
|
siteCode: `${prefix}SiteCode`,
|
||||||
|
provinceName: `${prefix}ProvinceName`,
|
||||||
|
cityName: `${prefix}CityName`,
|
||||||
|
districtName: `${prefix}DistrictName`,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
addressTransportType(target) {
|
addressTransportType(target) {
|
||||||
if (target.startsWith('route-')) {
|
if (target.startsWith('route-')) {
|
||||||
return this.form.routes[Number(target.slice(6))]?.transportType || '';
|
return this.form.routes[Number(target.slice(6))]?.transportType || '';
|
||||||
}
|
}
|
||||||
return target === 'arrival' ? this.form.finalTransportType : '';
|
return target === 'arrival' ? this.form.finalTransportType : this.form.routes[0]?.transportType || '';
|
||||||
},
|
},
|
||||||
addressTransportMode(target) {
|
addressTransportMode(target) {
|
||||||
const type = this.addressTransportType(target);
|
const type = this.addressTransportType(target);
|
||||||
@@ -998,6 +1073,17 @@ export default {
|
|||||||
const value = String(type || '').trim().toLowerCase();
|
const value = String(type || '').trim().toLowerCase();
|
||||||
return value === 'road' || value.includes('公路');
|
return value === 'road' || value.includes('公路');
|
||||||
},
|
},
|
||||||
|
isNonRoadAddress(target) {
|
||||||
|
const field = this.addressModel(target);
|
||||||
|
if (!field) return false;
|
||||||
|
const siteCode = String(field.model[field.siteCode] || '').trim();
|
||||||
|
// 地址展示模式由实际选择的数据决定:有站点编码表示非公路站点名称,
|
||||||
|
// 没有站点编码则保留省市区级联,避免切换运输方式时改写已有地址。
|
||||||
|
return Boolean(siteCode && siteCode !== '/');
|
||||||
|
},
|
||||||
|
resolveAddressRegionCode(row = {}) {
|
||||||
|
return row.regionCode || row.districtCode || row.areaCode || row.adcode || '';
|
||||||
|
},
|
||||||
openCommonAddress(target) {
|
openCommonAddress(target) {
|
||||||
this.addressTarget = target;
|
this.addressTarget = target;
|
||||||
if (this.addressTransportMode(target) !== 'road') {
|
if (this.addressTransportMode(target) !== 'road') {
|
||||||
@@ -1054,14 +1140,15 @@ export default {
|
|||||||
const target = this.addressModel(this.addressTarget);
|
const target = this.addressModel(this.addressTarget);
|
||||||
if (!target) return;
|
if (!target) return;
|
||||||
const station = this.normalizeStation(row);
|
const station = this.normalizeStation(row);
|
||||||
target.model[target.name] = station.regionName || station.stationName;
|
target.model[target.name] = station.stationName || station.regionName;
|
||||||
target.model[target.address] = station.detailAddress;
|
target.model[target.address] = station.detailAddress;
|
||||||
target.model[target.longitude] = station.longitude || '';
|
target.model[target.longitude] = station.longitude || '';
|
||||||
target.model[target.latitude] = station.latitude || '';
|
target.model[target.latitude] = station.latitude || '';
|
||||||
target.model[target.regionCode] = station.regionCode || '';
|
target.model[target.regionCode] = this.resolveAddressRegionCode(station);
|
||||||
target.model[target.siteCode] = station.stationCode;
|
target.model[target.siteCode] = station.stationCode;
|
||||||
|
this.setAddressRegionNamesFromRow(target, station);
|
||||||
this.stationDialogVisible = false;
|
this.stationDialogVisible = false;
|
||||||
this.syncAddressRegionPath(this.addressTarget);
|
this.loadRegionOptions().then(() => this.syncAddressRegionPath(this.addressTarget));
|
||||||
},
|
},
|
||||||
async loadAddressList() {
|
async loadAddressList() {
|
||||||
this.addressLoading = true;
|
this.addressLoading = true;
|
||||||
@@ -1096,12 +1183,21 @@ export default {
|
|||||||
target.model[target.phone] = row.contactPhone || target.model[target.phone] || '';
|
target.model[target.phone] = row.contactPhone || target.model[target.phone] || '';
|
||||||
target.model[target.longitude] = row.longitude || '';
|
target.model[target.longitude] = row.longitude || '';
|
||||||
target.model[target.latitude] = row.latitude || '';
|
target.model[target.latitude] = row.latitude || '';
|
||||||
target.model[target.regionCode] = row.regionCode || '';
|
target.model[target.regionCode] = this.resolveAddressRegionCode(row);
|
||||||
|
target.model[target.siteCode] = '';
|
||||||
|
this.setAddressRegionNamesFromRow(target, row);
|
||||||
this.addressDialogVisible = false;
|
this.addressDialogVisible = false;
|
||||||
this.syncAddressRegionPath(this.addressTarget);
|
this.loadRegionOptions().then(() => this.syncAddressRegionPath(this.addressTarget));
|
||||||
|
},
|
||||||
|
handleAddressDetailClick(target) {
|
||||||
|
if (this.addressTransportMode(target) !== 'road') {
|
||||||
|
this.openCommonAddress(target);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.openAddressMap(target);
|
||||||
},
|
},
|
||||||
openAddressMap(target) {
|
openAddressMap(target) {
|
||||||
if ((target.startsWith('route-') || target === 'arrival') && !this.isRoadTransportType(this.addressTransportType(target))) {
|
if (this.addressTransportMode(target) !== 'road') {
|
||||||
this.$message.info('非公路运输地址不能使用地图选择');
|
this.$message.info('非公路运输地址不能使用地图选择');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1203,6 +1299,9 @@ export default {
|
|||||||
const component = result.regeocode?.addressComponent || {};
|
const component = result.regeocode?.addressComponent || {};
|
||||||
this.mapSelected.detailAddress =
|
this.mapSelected.detailAddress =
|
||||||
result.regeocode?.formattedAddress || this.mapSelected.detailAddress;
|
result.regeocode?.formattedAddress || this.mapSelected.detailAddress;
|
||||||
|
this.mapSelected.provinceName = component.province || '';
|
||||||
|
this.mapSelected.cityName = component.city || '';
|
||||||
|
this.mapSelected.districtName = component.district || '';
|
||||||
this.mapSelected.regionName = [component.province, component.city, component.district]
|
this.mapSelected.regionName = [component.province, component.city, component.district]
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
.join('');
|
.join('');
|
||||||
@@ -1228,6 +1327,8 @@ export default {
|
|||||||
target.model[target.longitude] = this.mapSelected.longitude;
|
target.model[target.longitude] = this.mapSelected.longitude;
|
||||||
target.model[target.latitude] = this.mapSelected.latitude;
|
target.model[target.latitude] = this.mapSelected.latitude;
|
||||||
target.model[target.regionCode] = this.mapSelected.regionCode || '';
|
target.model[target.regionCode] = this.mapSelected.regionCode || '';
|
||||||
|
target.model[target.siteCode] = '';
|
||||||
|
this.setAddressRegionNamesFromRow(target, this.mapSelected);
|
||||||
this.mapDialogVisible = false;
|
this.mapDialogVisible = false;
|
||||||
this.syncAddressRegionPath(this.mapTarget);
|
this.syncAddressRegionPath(this.mapTarget);
|
||||||
},
|
},
|
||||||
@@ -1445,6 +1546,8 @@ export default {
|
|||||||
'brand',
|
'brand',
|
||||||
'specification',
|
'specification',
|
||||||
'model',
|
'model',
|
||||||
|
'materialCode',
|
||||||
|
'deviceCode',
|
||||||
'remark',
|
'remark',
|
||||||
].some(key => firstGoods[key]);
|
].some(key => firstGoods[key]);
|
||||||
|
|
||||||
@@ -1489,6 +1592,8 @@ export default {
|
|||||||
brand: row.brand || '',
|
brand: row.brand || '',
|
||||||
specification: row.specification || row.spec || '',
|
specification: row.specification || row.spec || '',
|
||||||
model: row.model || '',
|
model: row.model || '',
|
||||||
|
materialCode: row.materialCode || '',
|
||||||
|
deviceCode: row.deviceCode || '',
|
||||||
remark: row.remark || row.descriptionOne || '',
|
remark: row.remark || row.descriptionOne || '',
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@@ -1590,7 +1695,12 @@ export default {
|
|||||||
this.syncAddressRegionPaths();
|
this.syncAddressRegionPaths();
|
||||||
},
|
},
|
||||||
addGoods(index) {
|
addGoods(index) {
|
||||||
this.form.goods.splice(index + 1, 0, { quantityUnit: quantityUnitOptions[0] });
|
this.form.goods.splice(index + 1, 0, {
|
||||||
|
quantityUnit: quantityUnitOptions[0],
|
||||||
|
materialCode: '',
|
||||||
|
deviceCode: '',
|
||||||
|
remark: '',
|
||||||
|
});
|
||||||
},
|
},
|
||||||
removeGoods(index) {
|
removeGoods(index) {
|
||||||
if (this.form.goods.length > 1) this.form.goods.splice(index, 1);
|
if (this.form.goods.length > 1) this.form.goods.splice(index, 1);
|
||||||
@@ -1707,13 +1817,20 @@ export default {
|
|||||||
await this.$refs.form.validate();
|
await this.$refs.form.validate();
|
||||||
if (!this.form.departureAddress || !this.form.arrivalAddress)
|
if (!this.form.departureAddress || !this.form.arrivalAddress)
|
||||||
return this.$message.warning('请填写收发货地址');
|
return this.$message.warning('请填写收发货地址');
|
||||||
if (!this.form.goods.some(item => item.cargoType && item.quantity > 0 && item.quantityUnit))
|
const invalidGoodsIndex = this.form.goods.findIndex(
|
||||||
return this.$message.warning('请至少填写一条有效货物');
|
item =>
|
||||||
if (
|
!String(item.cargoType || '').trim() ||
|
||||||
this.form.routes.some(item => !item.departureName || !item.transportType) ||
|
!(Number(item.quantity) > 0) ||
|
||||||
!this.form.finalTransportType
|
!String(item.quantityUnit || '').trim()
|
||||||
)
|
);
|
||||||
return this.$message.warning('请完整填写途经地及运输方式');
|
if (invalidGoodsIndex >= 0)
|
||||||
|
return this.$message.warning(`请完整填写第${invalidGoodsIndex + 1}条货物的类型、数量和数量单位`);
|
||||||
|
const invalidRouteIndex = this.form.routes.findIndex(
|
||||||
|
item => !item.departureName || !item.transportType
|
||||||
|
);
|
||||||
|
if (invalidRouteIndex >= 0)
|
||||||
|
return this.$message.warning(`请完整填写第${invalidRouteIndex + 1}段途经地及运输方式`);
|
||||||
|
if (!this.form.finalTransportType) return this.$message.warning('请填写最后一段运输方式');
|
||||||
}
|
}
|
||||||
const payload = {
|
const payload = {
|
||||||
...this.form,
|
...this.form,
|
||||||
@@ -1721,7 +1838,17 @@ export default {
|
|||||||
planEndTime: this.normalizeDateTimeValue(this.form.planEndTime, true),
|
planEndTime: this.normalizeDateTimeValue(this.form.planEndTime, true),
|
||||||
attachmentsJson: JSON.stringify(this.attachmentRows),
|
attachmentsJson: JSON.stringify(this.attachmentRows),
|
||||||
routes: [
|
routes: [
|
||||||
...this.form.routes.map((route, index) => ({ ...route, segmentNo: `段${index + 1}` })),
|
...this.form.routes.map((route, index) => ({
|
||||||
|
...route,
|
||||||
|
segmentNo: `段${index + 1}`,
|
||||||
|
...(index === 0
|
||||||
|
? {
|
||||||
|
departureProvinceName: this.form.departureProvinceName,
|
||||||
|
departureCityName: this.form.departureCityName,
|
||||||
|
departureDistrictName: this.form.departureDistrictName,
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
|
})),
|
||||||
{
|
{
|
||||||
segmentNo: `段${this.form.routes.length + 1}`,
|
segmentNo: `段${this.form.routes.length + 1}`,
|
||||||
departureName: this.form.arrivalName,
|
departureName: this.form.arrivalName,
|
||||||
@@ -1732,6 +1859,9 @@ export default {
|
|||||||
departureLatitude: this.form.arrivalLatitude,
|
departureLatitude: this.form.arrivalLatitude,
|
||||||
departureRegionCode: this.form.arrivalRegionCode,
|
departureRegionCode: this.form.arrivalRegionCode,
|
||||||
departureSiteCode: this.form.arrivalSiteCode,
|
departureSiteCode: this.form.arrivalSiteCode,
|
||||||
|
departureProvinceName: this.form.arrivalProvinceName,
|
||||||
|
departureCityName: this.form.arrivalCityName,
|
||||||
|
departureDistrictName: this.form.arrivalDistrictName,
|
||||||
transportType: this.form.finalTransportType,
|
transportType: this.form.finalTransportType,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -1887,38 +2017,32 @@ export default {
|
|||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
margin-top: 12px;
|
margin-top: 12px;
|
||||||
}
|
}
|
||||||
.route-steps {
|
.route-flow {
|
||||||
position: relative;
|
position: relative;
|
||||||
.el-steps {
|
padding: 4px 0;
|
||||||
--el-text-color-placeholder: #a8abb2;
|
&::before {
|
||||||
|
position: absolute;
|
||||||
|
top: 20px;
|
||||||
|
bottom: 20px;
|
||||||
|
left: 16px;
|
||||||
|
width: 2px;
|
||||||
|
background: #dcdfe6;
|
||||||
|
content: '';
|
||||||
}
|
}
|
||||||
:deep(.el-step__main) {
|
}
|
||||||
padding-bottom: 16px;
|
.route-flow-row {
|
||||||
}
|
position: relative;
|
||||||
:deep(.el-step__title) {
|
display: grid;
|
||||||
font-size: 16px;
|
grid-template-columns: 32px minmax(0, 1fr);
|
||||||
color: #303133;
|
column-gap: 16px;
|
||||||
}
|
align-items: start;
|
||||||
:deep(.el-step__description) {
|
}
|
||||||
padding: 12px 0 0;
|
.route-flow-marker {
|
||||||
}
|
position: relative;
|
||||||
:deep(.el-step__line) {
|
z-index: 1;
|
||||||
background-color: #dcdfe6;
|
|
||||||
}
|
|
||||||
:deep(.el-step__icon) {
|
|
||||||
width: 32px;
|
|
||||||
height: 32px;
|
|
||||||
flex-shrink: 0;
|
|
||||||
border: 0;
|
|
||||||
background: transparent;
|
|
||||||
}
|
|
||||||
.route-step-icon {
|
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
width: 32px;
|
width: 32px;
|
||||||
height: 32px;
|
height: 32px;
|
||||||
min-width: 32px;
|
|
||||||
min-height: 32px;
|
|
||||||
flex-shrink: 0;
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
@@ -1933,11 +2057,19 @@ export default {
|
|||||||
&--end {
|
&--end {
|
||||||
background: #e6a23c;
|
background: #e6a23c;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.route-flow-body {
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
.route-flow-segment {
|
||||||
|
min-height: 48px;
|
||||||
|
padding-left: 48px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
.route-node-form {
|
.route-node-form {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(2, minmax(420px, 1fr)) repeat(2, minmax(260px, 1fr));
|
grid-template-columns: 300px 360px repeat(3, 300px);
|
||||||
gap: 0 16px;
|
gap: 0 16px;
|
||||||
align-items: start;
|
align-items: start;
|
||||||
:deep(.el-form-item) {
|
:deep(.el-form-item) {
|
||||||
@@ -1963,9 +2095,24 @@ export default {
|
|||||||
.route-address-name {
|
.route-address-name {
|
||||||
flex: 0 0 220px;
|
flex: 0 0 220px;
|
||||||
}
|
}
|
||||||
.route-address-detail,
|
.route-address-detail {
|
||||||
|
flex: 0 0 300px;
|
||||||
|
width: 300px;
|
||||||
|
}
|
||||||
.route-contact-actions :deep(.el-input) {
|
.route-contact-actions :deep(.el-input) {
|
||||||
flex: 1;
|
flex: 0 0 200px;
|
||||||
|
width: 200px;
|
||||||
|
}
|
||||||
|
:deep(.route-contact-form-item .el-input),
|
||||||
|
:deep(.route-phone-form-item .el-input),
|
||||||
|
:deep(.route-date-form-item .el-date-editor) {
|
||||||
|
width: 200px;
|
||||||
|
}
|
||||||
|
:deep(.route-contact-actions) {
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
:deep(.el-form-item__label) {
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
.address-picker-button {
|
.address-picker-button {
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
@@ -1985,6 +2132,7 @@ export default {
|
|||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
.route-segment-label {
|
.route-segment-label {
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
.route-title-path {
|
.route-title-path {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
@@ -2010,6 +2158,10 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.required-asterisk {
|
||||||
|
margin-left: 4px;
|
||||||
|
color: #f56c6c;
|
||||||
|
}
|
||||||
.route-action-button {
|
.route-action-button {
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -152,10 +152,7 @@
|
|||||||
@click="handleTransportSecondaryAddressInputClick('departure')"
|
@click="handleTransportSecondaryAddressInputClick('departure')"
|
||||||
>
|
>
|
||||||
<template #suffix>
|
<template #suffix>
|
||||||
<el-icon
|
<el-icon @click.stop="openTransportMapDialog('departure')">
|
||||||
v-if="!transportStationMode"
|
|
||||||
@click.stop="openTransportMapDialog('departure')"
|
|
||||||
>
|
|
||||||
<Location />
|
<Location />
|
||||||
</el-icon>
|
</el-icon>
|
||||||
</template>
|
</template>
|
||||||
@@ -198,7 +195,7 @@
|
|||||||
@click="handleTransportSecondaryAddressInputClick('arrival')"
|
@click="handleTransportSecondaryAddressInputClick('arrival')"
|
||||||
>
|
>
|
||||||
<template #suffix>
|
<template #suffix>
|
||||||
<el-icon v-if="!transportStationMode" @click.stop="openTransportMapDialog('arrival')">
|
<el-icon @click.stop="openTransportMapDialog('arrival')">
|
||||||
<Location />
|
<Location />
|
||||||
</el-icon>
|
</el-icon>
|
||||||
</template>
|
</template>
|
||||||
@@ -366,7 +363,7 @@
|
|||||||
v-for="(item, index) in shippingTemplateFreight.freightItems"
|
v-for="(item, index) in shippingTemplateFreight.freightItems"
|
||||||
:key="item.quantityUnit || `empty-${index}`"
|
:key="item.quantityUnit || `empty-${index}`"
|
||||||
>
|
>
|
||||||
<el-form-item :label="`单价${index + 1}`">
|
<el-form-item label="单价">
|
||||||
<el-input
|
<el-input
|
||||||
v-model="item.unitPrice"
|
v-model="item.unitPrice"
|
||||||
:disabled="dialogReadonly"
|
:disabled="dialogReadonly"
|
||||||
@@ -387,7 +384,7 @@
|
|||||||
></template>
|
></template>
|
||||||
</el-input>
|
</el-input>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item :label="`运费${index + 1}`"
|
<el-form-item label="运费"
|
||||||
><el-input
|
><el-input
|
||||||
:model-value="freightAmount(item)"
|
:model-value="freightAmount(item)"
|
||||||
placeholder="请输入"
|
placeholder="请输入"
|
||||||
@@ -559,9 +556,15 @@
|
|||||||
label-width="auto"
|
label-width="auto"
|
||||||
class="shipping-template-page__detail-form shipping-template-page__detail-grid"
|
class="shipping-template-page__detail-form shipping-template-page__detail-grid"
|
||||||
>
|
>
|
||||||
<el-form-item label="模板编号"><span>{{ detailRow.templateCode || '-' }}</span></el-form-item>
|
<el-form-item label="模板编号"
|
||||||
<el-form-item label="模板名称"><span>{{ detailRow.templateName || '-' }}</span></el-form-item>
|
><span>{{ detailRow.templateCode || '-' }}</span></el-form-item
|
||||||
<el-form-item label="模板类型"><span>{{ detailRow.templateType || '-' }}</span></el-form-item>
|
>
|
||||||
|
<el-form-item label="模板名称"
|
||||||
|
><span>{{ detailRow.templateName || '-' }}</span></el-form-item
|
||||||
|
>
|
||||||
|
<el-form-item label="模板类型"
|
||||||
|
><span>{{ detailRow.templateType || '-' }}</span></el-form-item
|
||||||
|
>
|
||||||
<el-form-item label="备注" class="shipping-template-page__detail-form-wide">
|
<el-form-item label="备注" class="shipping-template-page__detail-form-wide">
|
||||||
<span>{{ detailRow.remark || '-' }}</span>
|
<span>{{ detailRow.remark || '-' }}</span>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
@@ -575,10 +578,16 @@
|
|||||||
label-width="auto"
|
label-width="auto"
|
||||||
class="shipping-template-page__detail-form shipping-template-page__detail-grid"
|
class="shipping-template-page__detail-form shipping-template-page__detail-grid"
|
||||||
>
|
>
|
||||||
<el-form-item label="项目"><span>{{ detailRow.projectName || '-' }}</span></el-form-item>
|
<el-form-item label="项目"
|
||||||
<el-form-item label="客户合同"><span>{{ detailRow.contractName || '-' }}</span></el-form-item>
|
><span>{{ detailRow.projectName || '-' }}</span></el-form-item
|
||||||
|
>
|
||||||
|
<el-form-item label="客户合同"
|
||||||
|
><span>{{ detailRow.contractName || '-' }}</span></el-form-item
|
||||||
|
>
|
||||||
<el-form-item label="运输方式">
|
<el-form-item label="运输方式">
|
||||||
<span>{{ getTransportTypeLabel(detailRow.transportType) || detailRow.transportType || '-' }}</span>
|
<span>{{
|
||||||
|
getTransportTypeLabel(detailRow.transportType) || detailRow.transportType || '-'
|
||||||
|
}}</span>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="备注" class="shipping-template-page__detail-form-wide">
|
<el-form-item label="备注" class="shipping-template-page__detail-form-wide">
|
||||||
<span>{{ detailRow.basicRemark || '-' }}</span>
|
<span>{{ detailRow.basicRemark || '-' }}</span>
|
||||||
@@ -588,7 +597,11 @@
|
|||||||
|
|
||||||
<section class="shipping-template-page__detail-section">
|
<section class="shipping-template-page__detail-section">
|
||||||
<div class="dialog-section-title">收发货信息</div>
|
<div class="dialog-section-title">收发货信息</div>
|
||||||
<el-form label-position="right" label-width="auto" class="shipping-template-page__detail-form">
|
<el-form
|
||||||
|
label-position="right"
|
||||||
|
label-width="auto"
|
||||||
|
class="shipping-template-page__detail-form"
|
||||||
|
>
|
||||||
<el-form-item label="发货地址">
|
<el-form-item label="发货地址">
|
||||||
<div class="shipping-template-page__detail-route-values">
|
<div class="shipping-template-page__detail-route-values">
|
||||||
<span>{{ detailRow.departureName || '-' }}</span>
|
<span>{{ detailRow.departureName || '-' }}</span>
|
||||||
@@ -617,11 +630,21 @@
|
|||||||
<el-table-column prop="cargoName" label="货物名称" min-width="220" align="center" />
|
<el-table-column prop="cargoName" label="货物名称" min-width="220" align="center" />
|
||||||
<el-table-column prop="packageType" label="包装" min-width="120" align="center" />
|
<el-table-column prop="packageType" label="包装" min-width="120" align="center" />
|
||||||
<el-table-column prop="quantity" label="数量" min-width="120" align="center" />
|
<el-table-column prop="quantity" label="数量" min-width="120" align="center" />
|
||||||
<el-table-column prop="quantityUnit" label="数量单位" min-width="120" align="center" />
|
<el-table-column
|
||||||
|
prop="quantityUnit"
|
||||||
|
label="数量单位"
|
||||||
|
min-width="120"
|
||||||
|
align="center"
|
||||||
|
/>
|
||||||
<el-table-column prop="brand" label="品牌" min-width="120" align="center" />
|
<el-table-column prop="brand" label="品牌" min-width="120" align="center" />
|
||||||
<el-table-column prop="specification" label="规格" min-width="120" align="center" />
|
<el-table-column prop="specification" label="规格" min-width="120" align="center" />
|
||||||
<el-table-column prop="model" label="型号" min-width="120" align="center" />
|
<el-table-column prop="model" label="型号" min-width="120" align="center" />
|
||||||
<el-table-column prop="materialCode" label="物料编码" min-width="140" align="center" />
|
<el-table-column
|
||||||
|
prop="materialCode"
|
||||||
|
label="物料编码"
|
||||||
|
min-width="140"
|
||||||
|
align="center"
|
||||||
|
/>
|
||||||
<el-table-column prop="deviceCode" label="设备编码" min-width="140" align="center" />
|
<el-table-column prop="deviceCode" label="设备编码" min-width="140" align="center" />
|
||||||
<el-table-column prop="remark" label="备注" min-width="160" align="center" />
|
<el-table-column prop="remark" label="备注" min-width="160" align="center" />
|
||||||
</el-table>
|
</el-table>
|
||||||
@@ -638,17 +661,22 @@
|
|||||||
label-width="auto"
|
label-width="auto"
|
||||||
:class="[
|
:class="[
|
||||||
'shipping-template-page__freight-form',
|
'shipping-template-page__freight-form',
|
||||||
`shipping-template-page__freight-form--${detailTransportMode === 'road' ? 'road' : 'non-road'}`,
|
`shipping-template-page__freight-form--${
|
||||||
|
detailTransportMode === 'road' ? 'road' : 'non-road'
|
||||||
|
}`,
|
||||||
]"
|
]"
|
||||||
>
|
>
|
||||||
<template v-if="detailTransportMode === 'road'">
|
<template v-if="detailTransportMode === 'road'">
|
||||||
<template v-for="(item, index) in detailFreightItems" :key="item.quantityUnit || index">
|
<template
|
||||||
<el-form-item :label="`单价${index + 1}`">
|
v-for="(item, index) in detailFreightItems"
|
||||||
|
:key="item.quantityUnit || index"
|
||||||
|
>
|
||||||
|
<el-form-item label="单价">
|
||||||
<el-input :model-value="item.unitPrice || '-'" disabled>
|
<el-input :model-value="item.unitPrice || '-'" disabled>
|
||||||
<template #append>{{ item.priceUnit || '-' }}</template>
|
<template #append>{{ item.priceUnit || '-' }}</template>
|
||||||
</el-input>
|
</el-input>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item :label="`运费${index + 1}`">
|
<el-form-item label="运费">
|
||||||
<el-input :model-value="item.freightAmount || '-'" disabled>
|
<el-input :model-value="item.freightAmount || '-'" disabled>
|
||||||
<template #append>{{ detailFreight.currency || '-' }}</template>
|
<template #append>{{ detailFreight.currency || '-' }}</template>
|
||||||
</el-input>
|
</el-input>
|
||||||
@@ -690,7 +718,9 @@
|
|||||||
<el-table-column type="index" label="序号" width="70" />
|
<el-table-column type="index" label="序号" width="70" />
|
||||||
<el-table-column label="文件名" min-width="220">
|
<el-table-column label="文件名" min-width="220">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-link type="primary" @click="previewAttachment(row)">{{ attachmentName(row) }}</el-link>
|
<el-link type="primary" @click="previewAttachment(row)">{{
|
||||||
|
attachmentName(row)
|
||||||
|
}}</el-link>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="附件描述" min-width="220">
|
<el-table-column label="附件描述" min-width="220">
|
||||||
@@ -901,7 +931,8 @@
|
|||||||
><el-button @click="transportMapBox = false">取消</el-button
|
><el-button @click="transportMapBox = false">取消</el-button
|
||||||
><el-button
|
><el-button
|
||||||
type="primary"
|
type="primary"
|
||||||
:disabled="!transportMapSelected.longitude"
|
:loading="transportMapLoading"
|
||||||
|
:disabled="!transportMapSelected.detailAddress || !transportMapSelected.regionName"
|
||||||
@click="confirmTransportMapPick"
|
@click="confirmTransportMapPick"
|
||||||
>确定</el-button
|
>确定</el-button
|
||||||
></template
|
></template
|
||||||
@@ -1410,10 +1441,7 @@ export default {
|
|||||||
return this.parseJsonArray(this.detailRow.goodsJson);
|
return this.parseJsonArray(this.detailRow.goodsJson);
|
||||||
},
|
},
|
||||||
detailCargoQuantityTotal() {
|
detailCargoQuantityTotal() {
|
||||||
const total = this.detailGoodsRows.reduce(
|
const total = this.detailGoodsRows.reduce((sum, row) => sum + (Number(row.quantity) || 0), 0);
|
||||||
(sum, row) => sum + (Number(row.quantity) || 0),
|
|
||||||
0
|
|
||||||
);
|
|
||||||
return total ? String(Number(total.toFixed(3))) : '系统自动计算';
|
return total ? String(Number(total.toFixed(3))) : '系统自动计算';
|
||||||
},
|
},
|
||||||
detailAttachmentRows() {
|
detailAttachmentRows() {
|
||||||
@@ -1433,9 +1461,7 @@ export default {
|
|||||||
return this.resolveTransportMode(this.detailRow.transportType);
|
return this.resolveTransportMode(this.detailRow.transportType);
|
||||||
},
|
},
|
||||||
detailFreightItems() {
|
detailFreightItems() {
|
||||||
return Array.isArray(this.detailFreight.freightItems)
|
return Array.isArray(this.detailFreight.freightItems) ? this.detailFreight.freightItems : [];
|
||||||
? this.detailFreight.freightItems
|
|
||||||
: [];
|
|
||||||
},
|
},
|
||||||
detailFreightTotal() {
|
detailFreightTotal() {
|
||||||
const storedTotal = Number(this.detailFreight.totalFreightAmount);
|
const storedTotal = Number(this.detailFreight.totalFreightAmount);
|
||||||
@@ -1970,6 +1996,7 @@ export default {
|
|||||||
this.contractLoading = true;
|
this.contractLoading = true;
|
||||||
getContractList(1, 9999, {
|
getContractList(1, 9999, {
|
||||||
projectId: this.form.projectId,
|
projectId: this.form.projectId,
|
||||||
|
projectName: this.form.projectName,
|
||||||
...(this.config.contractQueryParams || {}),
|
...(this.config.contractQueryParams || {}),
|
||||||
})
|
})
|
||||||
.then(res => {
|
.then(res => {
|
||||||
@@ -2239,6 +2266,9 @@ export default {
|
|||||||
this.form[`${prefix}Latitude`] = address.latitude || '';
|
this.form[`${prefix}Latitude`] = address.latitude || '';
|
||||||
this.form[`${prefix}Contact`] = address.contactName || this.form[`${prefix}Contact`] || '';
|
this.form[`${prefix}Contact`] = address.contactName || this.form[`${prefix}Contact`] || '';
|
||||||
this.form[`${prefix}Phone`] = address.contactPhone || this.form[`${prefix}Phone`] || '';
|
this.form[`${prefix}Phone`] = address.contactPhone || this.form[`${prefix}Phone`] || '';
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.$refs.crud?.clearValidate?.([`${prefix}Address`]);
|
||||||
|
});
|
||||||
},
|
},
|
||||||
openTransportStationDialog(target) {
|
openTransportStationDialog(target) {
|
||||||
if (!this.form.transportType) return this.$message.warning('请先选择运输方式');
|
if (!this.form.transportType) return this.$message.warning('请先选择运输方式');
|
||||||
@@ -2299,8 +2329,19 @@ export default {
|
|||||||
},
|
},
|
||||||
openTransportMapDialog(target) {
|
openTransportMapDialog(target) {
|
||||||
if (this.dialogReadonly || !this.form.transportType) return;
|
if (this.dialogReadonly || !this.form.transportType) return;
|
||||||
this.transportMapTarget = target;
|
const prefix = target === 'departure' ? 'departure' : 'arrival';
|
||||||
this.transportMapKeyword = this.form[`${target}Address`] || '';
|
this.transportMapTarget = prefix;
|
||||||
|
this.transportMapKeyword = this.form[`${prefix}Address`] || this.form[`${prefix}Name`] || '';
|
||||||
|
this.transportMapSelected = this.buildTransportMapSelection({
|
||||||
|
longitude: this.form[`${prefix}Longitude`],
|
||||||
|
latitude: this.form[`${prefix}Latitude`],
|
||||||
|
detailAddress: this.form[`${prefix}Address`],
|
||||||
|
regionName: this.form[`${prefix}Name`],
|
||||||
|
addressCode: this.form[`${prefix}AddressCode`],
|
||||||
|
});
|
||||||
|
this.transportMapStatus = this.transportMapSelected.longitude
|
||||||
|
? '已加载当前选点,可重新选点'
|
||||||
|
: '可搜索地址或点击地图选点';
|
||||||
this.transportMapBox = true;
|
this.transportMapBox = true;
|
||||||
},
|
},
|
||||||
loadAmap() {
|
loadAmap() {
|
||||||
@@ -2310,8 +2351,9 @@ export default {
|
|||||||
window._AMapSecurityConfig = { securityJsCode: AMAP_SECURITY_CODE };
|
window._AMapSecurityConfig = { securityJsCode: AMAP_SECURITY_CODE };
|
||||||
const script = document.createElement('script');
|
const script = document.createElement('script');
|
||||||
script.src = `https://webapi.amap.com/maps?v=2.0&key=${AMAP_KEY}`;
|
script.src = `https://webapi.amap.com/maps?v=2.0&key=${AMAP_KEY}`;
|
||||||
|
script.async = true;
|
||||||
script.onload = resolve;
|
script.onload = resolve;
|
||||||
script.onerror = reject;
|
script.onerror = () => reject(new Error('高德地图组件加载失败'));
|
||||||
document.body.appendChild(script);
|
document.body.appendChild(script);
|
||||||
});
|
});
|
||||||
return amapLoader;
|
return amapLoader;
|
||||||
@@ -2321,56 +2363,185 @@ export default {
|
|||||||
.then(() => {
|
.then(() => {
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
if (!this.transportMapInstance) {
|
if (!this.transportMapInstance) {
|
||||||
|
const center = this.transportMapSelected.longitude
|
||||||
|
? this.toLngLat(
|
||||||
|
this.transportMapSelected.longitude,
|
||||||
|
this.transportMapSelected.latitude
|
||||||
|
)
|
||||||
|
: [116.40769, 39.89945];
|
||||||
this.transportMapInstance = new window.AMap.Map(this.$refs.transportMap, {
|
this.transportMapInstance = new window.AMap.Map(this.$refs.transportMap, {
|
||||||
center: [116.40769, 39.89945],
|
center,
|
||||||
zoom: 11,
|
zoom: this.transportMapSelected.longitude ? 14 : 11,
|
||||||
});
|
});
|
||||||
this.transportMapInstance.on('click', event => this.pickMapPoint(event.lnglat));
|
this.transportMapInstance.on('click', event => this.pickMapPoint(event.lnglat));
|
||||||
} else this.transportMapInstance.resize();
|
} else {
|
||||||
|
this.transportMapInstance.resize();
|
||||||
|
}
|
||||||
|
if (this.transportMapSelected.longitude) {
|
||||||
|
this.renderTransportMapMarker(
|
||||||
|
this.toLngLat(
|
||||||
|
this.transportMapSelected.longitude,
|
||||||
|
this.transportMapSelected.latitude
|
||||||
|
)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
this.transportMapMarker?.setMap(null);
|
||||||
|
this.transportMapMarker = null;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
this.$message.error('高德地图组件加载失败');
|
this.$message.error('高德地图组件加载失败,请稍后重试或重新打开弹窗');
|
||||||
this.transportMapBox = false;
|
this.transportMapBox = false;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
searchTransportMapKeyword() {
|
searchTransportMapKeyword() {
|
||||||
if (!this.transportMapKeyword) return this.$message.warning('请输入地址关键词');
|
const keyword = String(this.transportMapKeyword || '').trim();
|
||||||
this.loadAmap().then(() => {
|
if (!keyword) return this.$message.warning('请输入地址关键词');
|
||||||
this.transportMapLoading = true;
|
this.transportMapLoading = true;
|
||||||
window.AMap.plugin('AMap.Geocoder', () => {
|
this.loadAmap()
|
||||||
this.transportMapGeocoder = this.transportMapGeocoder || new window.AMap.Geocoder();
|
.then(() => this.ensureTransportMapGeocoder())
|
||||||
this.transportMapGeocoder.getLocation(this.transportMapKeyword, (status, result) => {
|
.then(() => this.runTransportMapGeocode('location', keyword))
|
||||||
|
.then(result => {
|
||||||
|
const point = this.resolveTransportMapPoint(result);
|
||||||
|
if (!point) throw new Error('未找到匹配地址');
|
||||||
|
return this.pickMapPoint(point, keyword);
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
this.transportMapStatus = '地图搜索失败';
|
||||||
|
this.$message.warning('地图搜索无匹配地址');
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
this.transportMapLoading = false;
|
this.transportMapLoading = false;
|
||||||
const location = status === 'complete' && result.geocodes?.[0]?.location;
|
|
||||||
if (location) this.pickMapPoint(location, this.transportMapKeyword);
|
|
||||||
else this.$message.warning('地图搜索无匹配地址');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
pickMapPoint(point, keyword = '') {
|
pickMapPoint(point, keyword = '') {
|
||||||
const longitude = point.getLng ? point.getLng() : point.lng;
|
const longitude = this.getTransportMapPointLng(point);
|
||||||
const latitude = point.getLat ? point.getLat() : point.lat;
|
const latitude = this.getTransportMapPointLat(point);
|
||||||
if (longitude === undefined || latitude === undefined) return;
|
if (!Number.isFinite(Number(longitude)) || !Number.isFinite(Number(latitude))) {
|
||||||
this.transportMapSelected = {
|
this.$message.warning('选点坐标无效');
|
||||||
longitude: Number(longitude).toFixed(6),
|
return Promise.resolve();
|
||||||
latitude: Number(latitude).toFixed(6),
|
|
||||||
detailAddress: keyword || this.transportMapKeyword,
|
|
||||||
};
|
|
||||||
this.transportMapStatus = '已选点,可确认回填';
|
|
||||||
if (this.transportMapInstance) {
|
|
||||||
this.transportMapMarker?.setMap(null);
|
|
||||||
this.transportMapMarker = new window.AMap.Marker({ position: [longitude, latitude] });
|
|
||||||
this.transportMapMarker.setMap(this.transportMapInstance);
|
|
||||||
this.transportMapInstance.setCenter([longitude, latitude]);
|
|
||||||
}
|
}
|
||||||
|
const lnglat = this.toLngLat(longitude, latitude);
|
||||||
|
this.renderTransportMapMarker(lnglat);
|
||||||
|
this.transportMapSelected = this.buildTransportMapSelection({ longitude, latitude });
|
||||||
|
this.transportMapStatus = '正在反查地址...';
|
||||||
|
this.transportMapLoading = true;
|
||||||
|
return this.ensureTransportMapGeocoder()
|
||||||
|
.then(() => this.runTransportMapGeocode('address', lnglat))
|
||||||
|
.then(result => {
|
||||||
|
const address = this.resolveTransportMapAddress(result);
|
||||||
|
this.transportMapSelected = {
|
||||||
|
...this.transportMapSelected,
|
||||||
|
detailAddress: address.detailAddress || keyword,
|
||||||
|
regionName: address.regionName,
|
||||||
|
addressCode: address.addressCode,
|
||||||
|
};
|
||||||
|
this.transportMapKeyword =
|
||||||
|
this.transportMapSelected.detailAddress || this.transportMapKeyword;
|
||||||
|
this.transportMapStatus = this.transportMapSelected.detailAddress || '已选点,可确认回填';
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
this.transportMapSelected = {};
|
||||||
|
this.transportMapStatus = '反查地址失败';
|
||||||
|
this.$message.error('反查地址失败,请重新选点');
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
this.transportMapLoading = false;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
renderTransportMapMarker(point) {
|
||||||
|
if (!this.transportMapInstance || !window.AMap) return;
|
||||||
|
this.transportMapMarker?.setMap(null);
|
||||||
|
this.transportMapMarker = new window.AMap.Marker({ position: point });
|
||||||
|
this.transportMapMarker.setMap(this.transportMapInstance);
|
||||||
|
this.transportMapInstance.setCenter(point);
|
||||||
},
|
},
|
||||||
confirmTransportMapPick() {
|
confirmTransportMapPick() {
|
||||||
|
if (!this.transportMapSelected.detailAddress || !this.transportMapSelected.regionName) {
|
||||||
|
this.$message.warning('请等待地址解析完成后再确认');
|
||||||
|
return;
|
||||||
|
}
|
||||||
const prefix = this.transportMapTarget;
|
const prefix = this.transportMapTarget;
|
||||||
this.applyAddress(prefix, this.transportMapSelected);
|
this.applyAddress(prefix, this.transportMapSelected);
|
||||||
this.transportMapBox = false;
|
this.transportMapBox = false;
|
||||||
},
|
},
|
||||||
|
buildTransportMapSelection({ longitude, latitude, detailAddress, regionName, addressCode }) {
|
||||||
|
return {
|
||||||
|
longitude: this.formatTransportMapCoordinate(longitude),
|
||||||
|
latitude: this.formatTransportMapCoordinate(latitude),
|
||||||
|
detailAddress: detailAddress || '',
|
||||||
|
regionName: regionName || '',
|
||||||
|
addressCode: addressCode || '',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
formatTransportMapCoordinate(value) {
|
||||||
|
if (value === undefined || value === null || value === '') return '';
|
||||||
|
const numberValue = Number(value);
|
||||||
|
return Number.isFinite(numberValue) ? numberValue.toFixed(6) : '';
|
||||||
|
},
|
||||||
|
toLngLat(longitude, latitude) {
|
||||||
|
return new window.AMap.LngLat(Number(longitude), Number(latitude));
|
||||||
|
},
|
||||||
|
getTransportMapPointLng(point) {
|
||||||
|
if (!point) return undefined;
|
||||||
|
return typeof point.getLng === 'function' ? point.getLng() : point.lng ?? point.lon;
|
||||||
|
},
|
||||||
|
getTransportMapPointLat(point) {
|
||||||
|
if (!point) return undefined;
|
||||||
|
return typeof point.getLat === 'function' ? point.getLat() : point.lat;
|
||||||
|
},
|
||||||
|
ensureTransportMapGeocoder() {
|
||||||
|
if (this.transportMapGeocoder) return Promise.resolve(this.transportMapGeocoder);
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
if (!window.AMap?.plugin) {
|
||||||
|
reject(new Error('高德地图组件未就绪'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
window.AMap.plugin(['AMap.Geocoder'], () => {
|
||||||
|
try {
|
||||||
|
this.transportMapGeocoder = new window.AMap.Geocoder();
|
||||||
|
resolve(this.transportMapGeocoder);
|
||||||
|
} catch (error) {
|
||||||
|
reject(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
runTransportMapGeocode(action, input) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const timer = window.setTimeout(() => reject(new Error('高德地图请求超时')), 10000);
|
||||||
|
const done = (status, result) => {
|
||||||
|
window.clearTimeout(timer);
|
||||||
|
if (status === 'complete' && result) {
|
||||||
|
resolve(result);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
reject(new Error('高德地图请求失败'));
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
if (action === 'location') this.transportMapGeocoder.getLocation(input, done);
|
||||||
|
else this.transportMapGeocoder.getAddress(input, done);
|
||||||
|
} catch (error) {
|
||||||
|
window.clearTimeout(timer);
|
||||||
|
reject(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
resolveTransportMapPoint(result) {
|
||||||
|
const geocode = Array.isArray(result?.geocodes) ? result.geocodes[0] : null;
|
||||||
|
return geocode?.location || result?.location || result?.lnglat || null;
|
||||||
|
},
|
||||||
|
resolveTransportMapAddress(result = {}) {
|
||||||
|
const regeocode = result.regeocode || {};
|
||||||
|
const component = regeocode.addressComponent || {};
|
||||||
|
const city = Array.isArray(component.city) ? '' : component.city;
|
||||||
|
return {
|
||||||
|
detailAddress: regeocode.formattedAddress || '',
|
||||||
|
regionName: [component.province, city, component.district].filter(Boolean).join(''),
|
||||||
|
addressCode: component.adcode || '',
|
||||||
|
};
|
||||||
|
},
|
||||||
ensureCargoTypeOptions() {
|
ensureCargoTypeOptions() {
|
||||||
if (this.cargoTypeRequest) return this.cargoTypeRequest;
|
if (this.cargoTypeRequest) return this.cargoTypeRequest;
|
||||||
if (this.cargoTypeOptions.length) return Promise.resolve(this.cargoTypeOptions);
|
if (this.cargoTypeOptions.length) return Promise.resolve(this.cargoTypeOptions);
|
||||||
@@ -2538,9 +2709,7 @@ export default {
|
|||||||
quantityUnits() {
|
quantityUnits() {
|
||||||
return [
|
return [
|
||||||
...new Set(
|
...new Set(
|
||||||
this.transportCargoRows
|
this.transportCargoRows.map(row => String(row.quantityUnit || '').trim()).filter(Boolean)
|
||||||
.map(row => String(row.quantityUnit || '').trim())
|
|
||||||
.filter(Boolean)
|
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
},
|
},
|
||||||
@@ -2990,10 +3159,7 @@ export default {
|
|||||||
}
|
}
|
||||||
.shipping-template-page__detail-route-values {
|
.shipping-template-page__detail-route-values {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: minmax(160px, 1fr) minmax(220px, 2fr) minmax(140px, 1fr) minmax(
|
grid-template-columns: minmax(160px, 1fr) minmax(220px, 2fr) minmax(140px, 1fr) minmax(160px, 1fr);
|
||||||
160px,
|
|
||||||
1fr
|
|
||||||
);
|
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1544,15 +1544,10 @@
|
|||||||
</el-form-item>
|
</el-form-item>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div class="transport-plan-page__dispatch-mode-switch">
|
||||||
class="dialog-section-title dialog-section-title--action"
|
|
||||||
style="display: flex; width: 100%; justify-content: space-between"
|
|
||||||
>
|
|
||||||
<span class="transport-plan-page__dispatch-task-title">任务信息</span>
|
|
||||||
<el-segmented
|
<el-segmented
|
||||||
v-model="dispatchItemForm.taskEntryMode"
|
v-model="dispatchItemForm.taskEntryMode"
|
||||||
:options="dispatchTaskEntryModeOptions"
|
:options="dispatchTaskEntryModeOptions"
|
||||||
class="transport-plan-page__dispatch-entry-mode"
|
|
||||||
@change="handleDispatchTaskEntryModeChange"
|
@change="handleDispatchTaskEntryModeChange"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -1708,18 +1703,20 @@
|
|||||||
<div
|
<div
|
||||||
class="transport-plan-page__dispatch-item-grid transport-plan-page__dispatch-item-grid--full"
|
class="transport-plan-page__dispatch-item-grid transport-plan-page__dispatch-item-grid--full"
|
||||||
>
|
>
|
||||||
<template v-for="(cargo, index) in dispatchItemCargoRows" :key="cargo._key || index">
|
<template v-for="group in dispatchItemFreightGroups" :key="group.key">
|
||||||
<el-form-item :label="`单价${index + 1}`">
|
<el-form-item label="单价">
|
||||||
<el-input
|
<el-input
|
||||||
v-model="cargo.unitPrice"
|
:model-value="dispatchFreightGroupUnitPrice(group)"
|
||||||
placeholder="请输入"
|
placeholder="请输入"
|
||||||
@input="value => handleDispatchCargoNumberInput(cargo, 'unitPrice', value)"
|
@input="value => handleDispatchFreightGroupNumberInput(group, value)"
|
||||||
|
@clear="() => handleDispatchFreightGroupNumberInput(group, '')"
|
||||||
>
|
>
|
||||||
<template #append
|
<template #append
|
||||||
><el-select
|
><el-select
|
||||||
v-model="cargo.priceUnit"
|
:model-value="dispatchFreightGroupPriceUnit(group)"
|
||||||
class="transport-plan-page__dispatch-unit-select"
|
class="transport-plan-page__dispatch-unit-select"
|
||||||
placeholder="元/吨"
|
placeholder="元/吨"
|
||||||
|
@change="value => handleDispatchFreightGroupPriceUnitChange(group, value)"
|
||||||
@visible-change="visible => visible && loadTaskFeeUnitOptions()"
|
@visible-change="visible => visible && loadTaskFeeUnitOptions()"
|
||||||
><el-option
|
><el-option
|
||||||
v-for="item in taskFeeUnitOptions"
|
v-for="item in taskFeeUnitOptions"
|
||||||
@@ -1729,13 +1726,17 @@
|
|||||||
></template>
|
></template>
|
||||||
</el-input>
|
</el-input>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item :label="`数量合计${index + 1}`"
|
<el-form-item label="数量合计"
|
||||||
><el-input :model-value="formatDispatchQuantity(cargo.quantity)" disabled
|
><el-input :model-value="dispatchFreightGroupQuantity(group)" disabled
|
||||||
><template #append>{{ cargo.quantityUnit || '吨' }}</template></el-input
|
><template #append>{{ fixedQuantityUnit }}</template></el-input
|
||||||
></el-form-item
|
></el-form-item
|
||||||
>
|
>
|
||||||
<el-form-item :label="`运费${index + 1}`"
|
<el-form-item label="运费"
|
||||||
><el-input :model-value="dispatchCargoFreightAmount(cargo)" disabled
|
><el-input
|
||||||
|
:model-value="dispatchFreightGroupAmount(group)"
|
||||||
|
placeholder="请输入运费"
|
||||||
|
@input="value => handleDispatchFreightGroupAmountInput(group, value)"
|
||||||
|
@clear="() => handleDispatchFreightGroupAmountInput(group, '')"
|
||||||
><template #suffix>{{ dispatchItemFreightCurrencyLabel }}</template></el-input
|
><template #suffix>{{ dispatchItemFreightCurrencyLabel }}</template></el-input
|
||||||
></el-form-item
|
></el-form-item
|
||||||
>
|
>
|
||||||
@@ -1756,6 +1757,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
|
<div class="dialog-section-title transport-plan-page__dispatch-task-content-title">
|
||||||
|
任务信息
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
class="transport-plan-page__dispatch-item-grid transport-plan-page__dispatch-item-grid--compact"
|
class="transport-plan-page__dispatch-item-grid transport-plan-page__dispatch-item-grid--compact"
|
||||||
>
|
>
|
||||||
@@ -1786,8 +1790,22 @@
|
|||||||
:key="
|
:key="
|
||||||
item.id || item.customerName || item.carrierName || item.fullName || item.name
|
item.id || item.customerName || item.carrierName || item.fullName || item.name
|
||||||
"
|
"
|
||||||
:label="item.label || item.customerName || item.carrierName || item.fullName || item.name"
|
:label="
|
||||||
:value="dispatchIsCarrierMode ? item.carrierContractId : item.value || item.customerName || item.carrierName || item.fullName || item.name"
|
item.label ||
|
||||||
|
item.customerName ||
|
||||||
|
item.carrierName ||
|
||||||
|
item.fullName ||
|
||||||
|
item.name
|
||||||
|
"
|
||||||
|
:value="
|
||||||
|
dispatchIsCarrierMode
|
||||||
|
? item.carrierContractId
|
||||||
|
: item.value ||
|
||||||
|
item.customerName ||
|
||||||
|
item.carrierName ||
|
||||||
|
item.fullName ||
|
||||||
|
item.name
|
||||||
|
"
|
||||||
/>
|
/>
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
@@ -1872,6 +1890,7 @@
|
|||||||
<el-select
|
<el-select
|
||||||
v-model="dispatchItemForm.quantityUnit"
|
v-model="dispatchItemForm.quantityUnit"
|
||||||
class="transport-plan-page__dispatch-unit-select"
|
class="transport-plan-page__dispatch-unit-select"
|
||||||
|
clearable
|
||||||
>
|
>
|
||||||
<el-option
|
<el-option
|
||||||
v-for="item in quantityUnitOptions"
|
v-for="item in quantityUnitOptions"
|
||||||
@@ -1940,6 +1959,15 @@
|
|||||||
</template>
|
</template>
|
||||||
</el-input>
|
</el-input>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
<el-form-item label="运费">
|
||||||
|
<el-input
|
||||||
|
v-model="dispatchItemForm.freightAmount"
|
||||||
|
placeholder="请输入运费"
|
||||||
|
@input="value => handleDispatchNumberInput('freightAmount', value)"
|
||||||
|
>
|
||||||
|
<template #suffix>{{ dispatchItemFreightCurrencyLabel }}</template>
|
||||||
|
</el-input>
|
||||||
|
</el-form-item>
|
||||||
<el-form-item label="其他费用合计">
|
<el-form-item label="其他费用合计">
|
||||||
<el-input v-model="dispatchItemForm.otherFeeTotal" placeholder="请输入">
|
<el-input v-model="dispatchItemForm.otherFeeTotal" placeholder="请输入">
|
||||||
<template #suffix>{{ dispatchItemFreightCurrencyLabel }}</template>
|
<template #suffix>{{ dispatchItemFreightCurrencyLabel }}</template>
|
||||||
@@ -1966,10 +1994,9 @@
|
|||||||
|
|
||||||
<div
|
<div
|
||||||
v-if="dispatchItemForm.taskEntryMode === 'full'"
|
v-if="dispatchItemForm.taskEntryMode === 'full'"
|
||||||
class="dialog-section-title transport-plan-page__dispatch-carrier-title"
|
class="dialog-section-title transport-plan-page__dispatch-task-content-title"
|
||||||
style="display: flex; width: 100%; margin-top: 16px"
|
|
||||||
>
|
>
|
||||||
承运信息
|
任务信息
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="dispatchItemForm.taskEntryMode === 'full'"
|
v-if="dispatchItemForm.taskEntryMode === 'full'"
|
||||||
@@ -1998,8 +2025,18 @@
|
|||||||
:key="
|
:key="
|
||||||
item.id || item.customerName || item.carrierName || item.fullName || item.name
|
item.id || item.customerName || item.carrierName || item.fullName || item.name
|
||||||
"
|
"
|
||||||
:label="item.label || item.customerName || item.carrierName || item.fullName || item.name"
|
:label="
|
||||||
:value="dispatchIsCarrierMode ? item.carrierContractId : item.value || item.customerName || item.carrierName || item.fullName || item.name"
|
item.label || item.customerName || item.carrierName || item.fullName || item.name
|
||||||
|
"
|
||||||
|
:value="
|
||||||
|
dispatchIsCarrierMode
|
||||||
|
? item.carrierContractId
|
||||||
|
: item.value ||
|
||||||
|
item.customerName ||
|
||||||
|
item.carrierName ||
|
||||||
|
item.fullName ||
|
||||||
|
item.name
|
||||||
|
"
|
||||||
/>
|
/>
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
@@ -2834,6 +2871,8 @@ const transportPlanImportColumns = [
|
|||||||
['备注', 'remark'],
|
['备注', 'remark'],
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const TRANSPORT_PLAN_QUANTITY_UNIT = '吨';
|
||||||
|
|
||||||
const defaultTransportCargo = () => ({
|
const defaultTransportCargo = () => ({
|
||||||
cargoName: '',
|
cargoName: '',
|
||||||
cargoType: '',
|
cargoType: '',
|
||||||
@@ -2885,6 +2924,7 @@ const defaultDispatchRow = () => ({
|
|||||||
materialCode: '',
|
materialCode: '',
|
||||||
unitPrice: '',
|
unitPrice: '',
|
||||||
priceUnit: '元/吨',
|
priceUnit: '元/吨',
|
||||||
|
freightAmount: '',
|
||||||
otherFeeTotal: '',
|
otherFeeTotal: '',
|
||||||
freightCurrency: 'RMB',
|
freightCurrency: 'RMB',
|
||||||
freightJson: '',
|
freightJson: '',
|
||||||
@@ -3304,7 +3344,7 @@ export default {
|
|||||||
dispatchSummaryItems() {
|
dispatchSummaryItems() {
|
||||||
const summaryMap = new Map();
|
const summaryMap = new Map();
|
||||||
const addQuantity = (unit, field, quantity) => {
|
const addQuantity = (unit, field, quantity) => {
|
||||||
const normalizedUnit = unit || '吨';
|
const normalizedUnit = unit || TRANSPORT_PLAN_QUANTITY_UNIT;
|
||||||
if (!summaryMap.has(normalizedUnit)) {
|
if (!summaryMap.has(normalizedUnit)) {
|
||||||
summaryMap.set(normalizedUnit, { unit: normalizedUnit, total: 0, assigned: 0 });
|
summaryMap.set(normalizedUnit, { unit: normalizedUnit, total: 0, assigned: 0 });
|
||||||
}
|
}
|
||||||
@@ -3390,6 +3430,9 @@ export default {
|
|||||||
dispatchTransportMode() {
|
dispatchTransportMode() {
|
||||||
return this.resolveTransportMode(this.dispatchItemForm.transportType);
|
return this.resolveTransportMode(this.dispatchItemForm.transportType);
|
||||||
},
|
},
|
||||||
|
fixedQuantityUnit() {
|
||||||
|
return TRANSPORT_PLAN_QUANTITY_UNIT;
|
||||||
|
},
|
||||||
dispatchIsRoadTransport() {
|
dispatchIsRoadTransport() {
|
||||||
return this.dispatchTransportMode === 'road';
|
return this.dispatchTransportMode === 'road';
|
||||||
},
|
},
|
||||||
@@ -3438,42 +3481,59 @@ export default {
|
|||||||
},
|
},
|
||||||
dispatchItemFreightTotal() {
|
dispatchItemFreightTotal() {
|
||||||
if (this.dispatchItemForm.taskEntryMode !== 'full') {
|
if (this.dispatchItemForm.taskEntryMode !== 'full') {
|
||||||
const quantity = Number(this.dispatchItemForm.quantity || 0);
|
|
||||||
const unitPrice = Number(this.dispatchItemForm.unitPrice || 0);
|
|
||||||
const otherFeeTotal = Number(this.dispatchItemForm.otherFeeTotal || 0);
|
const otherFeeTotal = Number(this.dispatchItemForm.otherFeeTotal || 0);
|
||||||
const hasFreight = this.dispatchItemForm.unitPrice !== '' && quantity > 0;
|
const freight = this.dispatchItemFreightSubtotal;
|
||||||
|
const hasFreight = freight !== '';
|
||||||
const hasOtherFee = this.dispatchItemForm.otherFeeTotal !== '';
|
const hasOtherFee = this.dispatchItemForm.otherFeeTotal !== '';
|
||||||
if (!hasFreight && !hasOtherFee) return '';
|
if (!hasFreight && !hasOtherFee) return '';
|
||||||
const total = (hasFreight ? quantity * unitPrice : 0) + (hasOtherFee ? otherFeeTotal : 0);
|
const total = (hasFreight ? Number(freight) : 0) + (hasOtherFee ? otherFeeTotal : 0);
|
||||||
return Number.isInteger(total) ? String(total) : String(Number(total.toFixed(2)));
|
return Number.isInteger(total) ? String(total) : String(Number(total.toFixed(2)));
|
||||||
}
|
}
|
||||||
const otherFeeTotal = Number(this.dispatchItemForm.otherFeeTotal || 0);
|
const otherFeeTotal = Number(this.dispatchItemForm.otherFeeTotal || 0);
|
||||||
const freightTotal = this.dispatchItemCargoRows.reduce(
|
const freightSubtotal = this.dispatchItemFreightSubtotal;
|
||||||
(total, cargo) => total + Number(this.dispatchCargoFreightAmount(cargo) || 0),
|
|
||||||
0
|
|
||||||
);
|
|
||||||
const hasOtherFee = this.dispatchItemForm.otherFeeTotal !== '';
|
const hasOtherFee = this.dispatchItemForm.otherFeeTotal !== '';
|
||||||
if (!freightTotal && !hasOtherFee) return '';
|
if (freightSubtotal === '' && !hasOtherFee) return '';
|
||||||
const total = freightTotal + (hasOtherFee ? otherFeeTotal : 0);
|
const total = Number(freightSubtotal || 0) + (hasOtherFee ? otherFeeTotal : 0);
|
||||||
return Number.isInteger(total) ? String(total) : String(Number(total.toFixed(2)));
|
return Number.isInteger(total) ? String(total) : String(Number(total.toFixed(2)));
|
||||||
},
|
},
|
||||||
dispatchItemFreightSubtotal() {
|
dispatchItemFreightSubtotal() {
|
||||||
if (this.dispatchItemForm.taskEntryMode !== 'full') {
|
if (this.dispatchItemForm.taskEntryMode !== 'full') {
|
||||||
|
if (
|
||||||
|
this.dispatchItemForm.freightAmount !== undefined &&
|
||||||
|
this.dispatchItemForm.freightAmount !== null &&
|
||||||
|
String(this.dispatchItemForm.freightAmount).trim() !== ''
|
||||||
|
) {
|
||||||
|
return this.formatDispatchAmount(this.dispatchItemForm.freightAmount);
|
||||||
|
}
|
||||||
const quantity = Number(this.dispatchItemForm.quantity || 0);
|
const quantity = Number(this.dispatchItemForm.quantity || 0);
|
||||||
const unitPrice = Number(this.dispatchItemForm.unitPrice || 0);
|
const unitPrice = Number(this.dispatchItemForm.unitPrice || 0);
|
||||||
if (!quantity || !unitPrice) return '';
|
if (!quantity || !unitPrice) return '';
|
||||||
const total = quantity * unitPrice;
|
const total = quantity * unitPrice;
|
||||||
return Number.isInteger(total) ? String(total) : String(Number(total.toFixed(2)));
|
return Number.isInteger(total) ? String(total) : String(Number(total.toFixed(2)));
|
||||||
}
|
}
|
||||||
const total = this.dispatchItemCargoRows.reduce(
|
const total = this.dispatchItemFreightGroups.reduce(
|
||||||
(sum, cargo) => sum + Number(this.dispatchCargoFreightAmount(cargo) || 0),
|
(sum, group) => sum + Number(this.dispatchFreightGroupAmount(group) || 0),
|
||||||
0
|
0
|
||||||
);
|
);
|
||||||
return total
|
const hasEnteredAmount = this.dispatchItemFreightGroups.some(group =>
|
||||||
? Number.isInteger(total)
|
(group.rows || []).some(
|
||||||
? String(total)
|
cargo =>
|
||||||
: String(Number(total.toFixed(2)))
|
cargo.freightAmount !== undefined &&
|
||||||
: '';
|
cargo.freightAmount !== null &&
|
||||||
|
String(cargo.freightAmount).trim() !== ''
|
||||||
|
)
|
||||||
|
);
|
||||||
|
if (!total && !hasEnteredAmount) return '';
|
||||||
|
return Number.isInteger(total) ? String(total) : String(Number(total.toFixed(2)));
|
||||||
|
},
|
||||||
|
dispatchItemFreightGroups() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
key: '__all__',
|
||||||
|
quantityUnit: TRANSPORT_PLAN_QUANTITY_UNIT,
|
||||||
|
rows: this.dispatchItemCargoRows || [],
|
||||||
|
},
|
||||||
|
];
|
||||||
},
|
},
|
||||||
dispatchItemFreightCurrencyLabel() {
|
dispatchItemFreightCurrencyLabel() {
|
||||||
const value = this.dispatchItemForm.freightCurrency || 'RMB';
|
const value = this.dispatchItemForm.freightCurrency || 'RMB';
|
||||||
@@ -4408,7 +4468,7 @@ export default {
|
|||||||
return Number.isFinite(number) ? number : 0;
|
return Number.isFinite(number) ? number : 0;
|
||||||
},
|
},
|
||||||
getDispatchQuantityUnit(row = {}) {
|
getDispatchQuantityUnit(row = {}) {
|
||||||
return row.quantityUnit || row.goodsQuantityUnit || row.unit || '吨';
|
return row.quantityUnit || row.goodsQuantityUnit || row.unit || TRANSPORT_PLAN_QUANTITY_UNIT;
|
||||||
},
|
},
|
||||||
getDispatchVehicleIdentifier(row = {}) {
|
getDispatchVehicleIdentifier(row = {}) {
|
||||||
return (
|
return (
|
||||||
@@ -4868,7 +4928,7 @@ export default {
|
|||||||
specification: row.specification || row.spec || '',
|
specification: row.specification || row.spec || '',
|
||||||
freightAmount: row.freightAmount ?? row.amount ?? row.totalAmount ?? '',
|
freightAmount: row.freightAmount ?? row.amount ?? row.totalAmount ?? '',
|
||||||
};
|
};
|
||||||
['quantity', 'mileage', 'unitPrice'].forEach(prop => {
|
['quantity', 'mileage', 'unitPrice', 'freightAmount'].forEach(prop => {
|
||||||
if (Number(cargo[prop]) === -1) cargo[prop] = '';
|
if (Number(cargo[prop]) === -1) cargo[prop] = '';
|
||||||
});
|
});
|
||||||
return cargo;
|
return cargo;
|
||||||
@@ -6869,7 +6929,7 @@ export default {
|
|||||||
item.quantityUnit ||
|
item.quantityUnit ||
|
||||||
item.goodsQuantityUnit ||
|
item.goodsQuantityUnit ||
|
||||||
item.unit ||
|
item.unit ||
|
||||||
'吨',
|
TRANSPORT_PLAN_QUANTITY_UNIT,
|
||||||
specification:
|
specification:
|
||||||
item.specification || item.spec || itemGoods.specification || itemGoods.spec || '',
|
item.specification || item.spec || itemGoods.specification || itemGoods.spec || '',
|
||||||
model: item.model || itemGoods.model || '',
|
model: item.model || itemGoods.model || '',
|
||||||
@@ -6929,17 +6989,16 @@ export default {
|
|||||||
0
|
0
|
||||||
);
|
);
|
||||||
const calculatedFreight = unitPrice !== '' && quantity ? Number(unitPrice) * quantity : '';
|
const calculatedFreight = unitPrice !== '' && quantity ? Number(unitPrice) * quantity : '';
|
||||||
const freight =
|
const explicitFreight = [
|
||||||
calculatedFreight !== ''
|
item.freight,
|
||||||
? calculatedFreight
|
item.freightAmount,
|
||||||
: item.freight ??
|
item.transportFee,
|
||||||
item.freightAmount ??
|
freightInfo.freightAmount,
|
||||||
item.transportFee ??
|
freightInfo.transportFee,
|
||||||
plan.freight ??
|
plan.freight,
|
||||||
plan.freightAmount ??
|
plan.freightAmount,
|
||||||
freightInfo.freightAmount ??
|
].find(value => value !== undefined && value !== null && String(value).trim() !== '');
|
||||||
freightInfo.transportFee ??
|
const freight = explicitFreight ?? calculatedFreight;
|
||||||
'';
|
|
||||||
const freightTotal =
|
const freightTotal =
|
||||||
freight !== '' || otherFeeTotal !== ''
|
freight !== '' || otherFeeTotal !== ''
|
||||||
? Number(freight || 0) + Number(otherFeeTotal || 0)
|
? Number(freight || 0) + Number(otherFeeTotal || 0)
|
||||||
@@ -6950,7 +7009,7 @@ export default {
|
|||||||
freightInfo.totalFreightAmount ??
|
freightInfo.totalFreightAmount ??
|
||||||
freightInfo.freightTotal ??
|
freightInfo.freightTotal ??
|
||||||
'';
|
'';
|
||||||
return { unitPrice, freight, otherFeeTotal, freightTotal };
|
return { unitPrice, freight, freightAmount: freight, otherFeeTotal, freightTotal };
|
||||||
},
|
},
|
||||||
getDispatchWaybillFallback(plan = {}, item = {}, index = 0) {
|
getDispatchWaybillFallback(plan = {}, item = {}, index = 0) {
|
||||||
const sources = [
|
const sources = [
|
||||||
@@ -7225,6 +7284,8 @@ export default {
|
|||||||
...cargo,
|
...cargo,
|
||||||
unitPrice: cargo.unitPrice || freightInfo.freightItems[itemIndex]?.unitPrice || '',
|
unitPrice: cargo.unitPrice || freightInfo.freightItems[itemIndex]?.unitPrice || '',
|
||||||
priceUnit: cargo.priceUnit || freightInfo.freightItems[itemIndex]?.priceUnit || '元/吨',
|
priceUnit: cargo.priceUnit || freightInfo.freightItems[itemIndex]?.priceUnit || '元/吨',
|
||||||
|
freightAmount:
|
||||||
|
cargo.freightAmount || freightInfo.freightItems[itemIndex]?.freightAmount || '',
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
this.dispatchItemAttachmentRows = this.parseJsonArray(
|
this.dispatchItemAttachmentRows = this.parseJsonArray(
|
||||||
@@ -7321,7 +7382,8 @@ export default {
|
|||||||
if (carrierTypeAtRequest === '承运商') {
|
if (carrierTypeAtRequest === '承运商') {
|
||||||
this.dispatchCarrierOptions = carrierContracts || [];
|
this.dispatchCarrierOptions = carrierContracts || [];
|
||||||
const current = this.dispatchCarrierOptions.find(
|
const current = this.dispatchCarrierOptions.find(
|
||||||
item => String(item.carrierContractId) === String(this.dispatchItemForm.carrierContractId)
|
item =>
|
||||||
|
String(item.carrierContractId) === String(this.dispatchItemForm.carrierContractId)
|
||||||
);
|
);
|
||||||
if (current) {
|
if (current) {
|
||||||
this.dispatchItemForm.carrierName = current.carrierName;
|
this.dispatchItemForm.carrierName = current.carrierName;
|
||||||
@@ -7440,19 +7502,91 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
dispatchCargoFreightAmount(cargo = {}) {
|
dispatchCargoFreightAmount(cargo = {}) {
|
||||||
|
if (cargo.freightAmount !== undefined && cargo.freightAmount !== null) {
|
||||||
|
const enteredAmount = String(cargo.freightAmount).trim();
|
||||||
|
if (enteredAmount !== '' && Number.isFinite(Number(enteredAmount))) {
|
||||||
|
const amount = Number(enteredAmount);
|
||||||
|
return Number.isInteger(amount) ? String(amount) : String(Number(amount.toFixed(2)));
|
||||||
|
}
|
||||||
|
}
|
||||||
const quantity = Number(cargo.quantity || 0);
|
const quantity = Number(cargo.quantity || 0);
|
||||||
const unitPrice = Number(cargo.unitPrice || 0);
|
const unitPrice = Number(cargo.unitPrice || 0);
|
||||||
if (!quantity || !unitPrice) return '';
|
if (!quantity || !unitPrice) return '';
|
||||||
const amount = quantity * unitPrice;
|
const amount = quantity * unitPrice;
|
||||||
return Number.isInteger(amount) ? String(amount) : String(Number(amount.toFixed(2)));
|
return Number.isInteger(amount) ? String(amount) : String(Number(amount.toFixed(2)));
|
||||||
},
|
},
|
||||||
|
formatDispatchAmount(value) {
|
||||||
|
const amount = Number(value || 0);
|
||||||
|
if (!Number.isFinite(amount)) return '';
|
||||||
|
return Number(amount.toFixed(2)).toString();
|
||||||
|
},
|
||||||
|
dispatchFreightGroupQuantity(group = {}) {
|
||||||
|
const quantity = (group.rows || []).reduce(
|
||||||
|
(sum, cargo) => sum + Number(cargo.quantity || 0),
|
||||||
|
0
|
||||||
|
);
|
||||||
|
return this.formatDispatchQuantity(quantity);
|
||||||
|
},
|
||||||
|
dispatchFreightGroupUnitPrice(group = {}) {
|
||||||
|
const prices = (group.rows || [])
|
||||||
|
.map(cargo => String(cargo.unitPrice ?? '').trim())
|
||||||
|
.filter(Boolean);
|
||||||
|
if (!prices.length || prices.some(price => price !== prices[0])) return '';
|
||||||
|
return prices[0];
|
||||||
|
},
|
||||||
|
dispatchFreightGroupPriceUnit(group = {}) {
|
||||||
|
return group.rows?.[0]?.priceUnit || (group.quantityUnit ? `元/${group.quantityUnit}` : '');
|
||||||
|
},
|
||||||
|
dispatchFreightGroupAmount(group = {}) {
|
||||||
|
const amount = (group.rows || []).reduce(
|
||||||
|
(sum, cargo) => sum + Number(this.dispatchCargoFreightAmount(cargo) || 0),
|
||||||
|
0
|
||||||
|
);
|
||||||
|
return Number.isInteger(amount) ? String(amount) : String(Number(amount.toFixed(2)));
|
||||||
|
},
|
||||||
|
handleDispatchFreightGroupNumberInput(group, value) {
|
||||||
|
this.handleFreightNumberInput(group.rows?.[0] || {}, 'unitPrice', value);
|
||||||
|
const unitPrice = group.rows?.[0]?.unitPrice || '';
|
||||||
|
(group.rows || []).forEach(cargo => {
|
||||||
|
cargo.unitPrice = unitPrice;
|
||||||
|
cargo.freightAmount = '';
|
||||||
|
});
|
||||||
|
},
|
||||||
|
handleDispatchFreightGroupPriceUnitChange(group, value) {
|
||||||
|
(group.rows || []).forEach(cargo => {
|
||||||
|
cargo.priceUnit = value || '';
|
||||||
|
});
|
||||||
|
},
|
||||||
|
handleDispatchFreightGroupAmountInput(group, value) {
|
||||||
|
const text = String(value || '').replace(/[^\d.]/g, '');
|
||||||
|
const parts = text.split('.');
|
||||||
|
const normalized =
|
||||||
|
parts.length > 1 ? `${parts[0]}.${parts.slice(1).join('').slice(0, 2)}` : parts[0];
|
||||||
|
const rows = group.rows || [];
|
||||||
|
const total = Number(normalized || 0);
|
||||||
|
const quantities = rows.map(cargo => Math.max(0, Number(cargo.quantity || 0)));
|
||||||
|
const quantityTotal = quantities.reduce((sum, quantity) => sum + quantity, 0);
|
||||||
|
let allocated = 0;
|
||||||
|
rows.forEach((cargo, index) => {
|
||||||
|
if (index === rows.length - 1) {
|
||||||
|
cargo.freightAmount = this.formatDispatchAmount(total - allocated);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let amount = 0;
|
||||||
|
if (quantityTotal) {
|
||||||
|
amount = Number((total * (quantities[index] / quantityTotal)).toFixed(2));
|
||||||
|
} else if (index === 0) {
|
||||||
|
amount = total;
|
||||||
|
}
|
||||||
|
allocated += amount;
|
||||||
|
cargo.freightAmount = this.formatDispatchAmount(amount);
|
||||||
|
});
|
||||||
|
},
|
||||||
dispatchItemRemainingQuantity(row = {}) {
|
dispatchItemRemainingQuantity(row = {}) {
|
||||||
const unit = this.getDispatchQuantityUnit(row);
|
const unit = this.getDispatchQuantityUnit(row);
|
||||||
const hasCargoIdentity = Boolean(
|
const hasCargoIdentity = Boolean(
|
||||||
String(row.cargoName || row.goodsName || row.name || '').trim() ||
|
String(row.cargoName || row.goodsName || row.name || '').trim() ||
|
||||||
String(
|
String(row.cargoType || row.secondCargoTypeName || row.goodsType || row.type || '').trim()
|
||||||
row.cargoType || row.secondCargoTypeName || row.goodsType || row.type || ''
|
|
||||||
).trim()
|
|
||||||
);
|
);
|
||||||
const planGoodsRows = this.dispatchPlanGoodsRows;
|
const planGoodsRows = this.dispatchPlanGoodsRows;
|
||||||
const cargoKey = this.getDispatchCargoIdentity(row);
|
const cargoKey = this.getDispatchCargoIdentity(row);
|
||||||
@@ -7472,10 +7606,7 @@ export default {
|
|||||||
0
|
0
|
||||||
);
|
);
|
||||||
const assigned = this.dispatchRows.reduce((sum, dispatchRow, index) => {
|
const assigned = this.dispatchRows.reduce((sum, dispatchRow, index) => {
|
||||||
if (
|
if (index === this.dispatchItemIndex || !this.hasDispatchVehicleIdentifier(dispatchRow)) {
|
||||||
index === this.dispatchItemIndex ||
|
|
||||||
!this.hasDispatchVehicleIdentifier(dispatchRow)
|
|
||||||
) {
|
|
||||||
return sum;
|
return sum;
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
@@ -7647,6 +7778,11 @@ export default {
|
|||||||
detailPrefix ? `的${goodsPrefix}单价不能小于0` : `${goodsPrefix}单价不能小于0`
|
detailPrefix ? `的${goodsPrefix}单价不能小于0` : `${goodsPrefix}单价不能小于0`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
if (!this.isBillingFieldEmpty(goods.freightAmount) && Number(goods.freightAmount) < 0) {
|
||||||
|
return warn(
|
||||||
|
detailPrefix ? `的${goodsPrefix}运费不能小于0` : `${goodsPrefix}运费不能小于0`
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const invalidPhoneField = [
|
const invalidPhoneField = [
|
||||||
@@ -7783,7 +7919,7 @@ export default {
|
|||||||
attachmentsJson: JSON.stringify(this.dispatchItemAttachmentRows),
|
attachmentsJson: JSON.stringify(this.dispatchItemAttachmentRows),
|
||||||
};
|
};
|
||||||
nextRow.cargoInfo = this.formatDispatchCargoInfo({}, nextRow);
|
nextRow.cargoInfo = this.formatDispatchCargoInfo({}, nextRow);
|
||||||
nextRow.freight = this.getDispatchFeeFields({}, nextRow).freight;
|
nextRow.freight = this.dispatchItemFreightSubtotal;
|
||||||
nextRow.otherFeeTotal = this.dispatchItemForm.otherFeeTotal || '';
|
nextRow.otherFeeTotal = this.dispatchItemForm.otherFeeTotal || '';
|
||||||
const quantityUnit = this.getDispatchQuantityUnit(nextRow);
|
const quantityUnit = this.getDispatchQuantityUnit(nextRow);
|
||||||
const otherDispatchedQuantity = this.dispatchRows.reduce((total, row, index) => {
|
const otherDispatchedQuantity = this.dispatchRows.reduce((total, row, index) => {
|
||||||
@@ -8022,6 +8158,7 @@ export default {
|
|||||||
:deep(.el-form-item__content > .el-input),
|
:deep(.el-form-item__content > .el-input),
|
||||||
:deep(.el-form-item__content > .el-select),
|
:deep(.el-form-item__content > .el-select),
|
||||||
:deep(.el-form-item__content > .el-cascader),
|
:deep(.el-form-item__content > .el-cascader),
|
||||||
|
:deep(.el-form-item__content > .el-autocomplete),
|
||||||
:deep(.el-form-item__content > .el-input-number),
|
:deep(.el-form-item__content > .el-input-number),
|
||||||
:deep(.el-form-item__content > .el-date-editor) {
|
:deep(.el-form-item__content > .el-date-editor) {
|
||||||
width: 250px;
|
width: 250px;
|
||||||
@@ -8063,7 +8200,9 @@ export default {
|
|||||||
|
|
||||||
&__shipping-title,
|
&__shipping-title,
|
||||||
&__goods-title {
|
&__goods-title {
|
||||||
|
box-sizing: border-box;
|
||||||
min-width: 100%;
|
min-width: 100%;
|
||||||
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__shipping-title,
|
&__shipping-title,
|
||||||
@@ -8144,21 +8283,19 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
&__dispatch-shipping-form &__route-address-row {
|
&__dispatch-shipping-form &__route-address-row {
|
||||||
grid-template-columns: 260px minmax(280px, 1fr) 58px minmax(160px, 220px) 72px minmax(
|
grid-template-columns: 260px minmax(280px, 360px) 58px minmax(160px, 220px) 72px minmax(
|
||||||
160px,
|
160px,
|
||||||
220px
|
220px
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
&__dispatch-carrier-title {
|
&__dispatch-mode-switch {
|
||||||
flex-basis: 100%;
|
display: flex;
|
||||||
|
justify-content: flex-start;
|
||||||
|
width: 100%;
|
||||||
margin-top: 16px;
|
margin-top: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__dispatch-task-title {
|
|
||||||
margin-right: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__route-label {
|
&__route-label {
|
||||||
color: #606266;
|
color: #606266;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
@@ -8179,8 +8316,10 @@ export default {
|
|||||||
|
|
||||||
&__cargo-wrap {
|
&__cargo-wrap {
|
||||||
display: block;
|
display: block;
|
||||||
|
box-sizing: border-box;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
|
max-width: 100%;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -8638,13 +8777,21 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
&__dispatch-item-dialog {
|
&__dispatch-item-dialog {
|
||||||
|
width: calc(100% - 32px) !important;
|
||||||
|
max-width: 1400px;
|
||||||
|
|
||||||
:deep(.el-dialog__body) {
|
:deep(.el-dialog__body) {
|
||||||
|
min-width: 0;
|
||||||
padding-top: 20px;
|
padding-top: 20px;
|
||||||
|
overflow-x: hidden;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&__dispatch-item-form {
|
&__dispatch-item-form {
|
||||||
|
box-sizing: border-box;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
min-width: 0;
|
||||||
|
max-width: 100%;
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
border: 1px solid #ebeef5;
|
border: 1px solid #ebeef5;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
@@ -8741,7 +8888,10 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
&__attachment {
|
&__attachment {
|
||||||
|
box-sizing: border-box;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
min-width: 0;
|
||||||
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__attachment-description {
|
&__attachment-description {
|
||||||
@@ -8763,8 +8913,12 @@ export default {
|
|||||||
|
|
||||||
&__attachment-head {
|
&__attachment-head {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
box-sizing: border-box;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
|
width: 100%;
|
||||||
|
min-width: 0;
|
||||||
|
max-width: 100%;
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -9135,6 +9289,13 @@ export default {
|
|||||||
grid-template-columns: 240px minmax(240px, 1fr) 40px 58px minmax(140px, 1fr);
|
grid-template-columns: 240px minmax(240px, 1fr) 40px 58px minmax(140px, 1fr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&__dispatch-shipping-form &__route-address-row {
|
||||||
|
grid-template-columns: 240px minmax(240px, 300px) 58px minmax(140px, 180px) 72px minmax(
|
||||||
|
140px,
|
||||||
|
180px
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
&__route-address-row &__route-label:nth-of-type(2),
|
&__route-address-row &__route-label:nth-of-type(2),
|
||||||
&__route-address-row .el-input:last-child {
|
&__route-address-row .el-input:last-child {
|
||||||
grid-column: auto;
|
grid-column: auto;
|
||||||
@@ -9146,6 +9307,10 @@ export default {
|
|||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&__dispatch-shipping-form &__route-address-row {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
&__route-label {
|
&__route-label {
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -153,6 +153,15 @@
|
|||||||
|
|
||||||
<template v-if="config.enableProjectSelect" #projectName-label>项目</template>
|
<template v-if="config.enableProjectSelect" #projectName-label>项目</template>
|
||||||
|
|
||||||
|
<template #relationNo-label>
|
||||||
|
<span class="waybill-manage-page__label-with-info">
|
||||||
|
<span>关联单号</span>
|
||||||
|
<el-tooltip content="仅做关联标记,用于业务中子母单情况" placement="top">
|
||||||
|
<el-icon class="waybill-manage-page__info-icon"><InfoFilled /></el-icon>
|
||||||
|
</el-tooltip>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
|
||||||
<template #projectName-form>
|
<template #projectName-form>
|
||||||
<el-select
|
<el-select
|
||||||
v-model="selectedProjectId"
|
v-model="selectedProjectId"
|
||||||
@@ -353,10 +362,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #taskInfoTitle-form>
|
<template #taskInfoTitle-form>
|
||||||
<div
|
<div class="waybill-manage-page__task-mode-switch">
|
||||||
class="dialog-section-title dialog-section-title--action waybill-manage-page__task-title"
|
|
||||||
>
|
|
||||||
<span>任务信息</span>
|
|
||||||
<el-segmented
|
<el-segmented
|
||||||
v-model="form.taskEntryMode"
|
v-model="form.taskEntryMode"
|
||||||
:options="taskEntryModeOptions"
|
:options="taskEntryModeOptions"
|
||||||
@@ -561,6 +567,7 @@
|
|||||||
</el-table>
|
</el-table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="dialog-section-title waybill-manage-page__task-content-title">任务信息</div>
|
||||||
<el-form
|
<el-form
|
||||||
:model="form"
|
:model="form"
|
||||||
label-position="right"
|
label-position="right"
|
||||||
@@ -1033,7 +1040,12 @@
|
|||||||
</el-form>
|
</el-form>
|
||||||
<div class="waybill-manage-page__subsection">
|
<div class="waybill-manage-page__subsection">
|
||||||
<div class="waybill-manage-page__subsection-head">
|
<div class="waybill-manage-page__subsection-head">
|
||||||
|
<span class="waybill-manage-page__label-with-info">
|
||||||
<span>运费信息</span>
|
<span>运费信息</span>
|
||||||
|
<el-tooltip content="可选填写,仅做记录,不影响应付结算" placement="top">
|
||||||
|
<el-icon class="waybill-manage-page__info-icon"><InfoFilled /></el-icon>
|
||||||
|
</el-tooltip>
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<el-form
|
<el-form
|
||||||
:model="form"
|
:model="form"
|
||||||
@@ -1041,21 +1053,25 @@
|
|||||||
label-width="auto"
|
label-width="auto"
|
||||||
class="waybill-manage-page__freight-form waybill-manage-page__freight-form--road"
|
class="waybill-manage-page__freight-form waybill-manage-page__freight-form--road"
|
||||||
>
|
>
|
||||||
<template v-for="(cargo, index) in transportCargoRows" :key="index">
|
<template v-for="group in taskFreightGroups" :key="group.key">
|
||||||
<el-form-item :label="`单价${index + 1}`">
|
<el-form-item label="单价">
|
||||||
<el-input
|
<el-input
|
||||||
v-model="cargo.unitPrice"
|
:model-value="taskFullFreightGroupUnitPrice(group)"
|
||||||
placeholder="请输入"
|
placeholder="请输入"
|
||||||
clearable
|
clearable
|
||||||
:disabled="dialogReadonly"
|
:disabled="dialogReadonly"
|
||||||
@input="value => handleTaskFullFreightNumberInput(cargo, 'unitPrice', value)"
|
@input="
|
||||||
|
value => handleTaskFullFreightGroupNumberInput(group, 'unitPrice', value)
|
||||||
|
"
|
||||||
|
@clear="() => handleTaskFullFreightGroupNumberInput(group, 'unitPrice', '')"
|
||||||
>
|
>
|
||||||
<template #append>
|
<template #append>
|
||||||
<el-select
|
<el-select
|
||||||
v-model="cargo.priceUnit"
|
:model-value="taskFullFreightGroupPriceUnit(group)"
|
||||||
class="waybill-manage-page__append-select waybill-manage-page__append-select--wide"
|
class="waybill-manage-page__append-select waybill-manage-page__append-select--wide"
|
||||||
placeholder="单位"
|
placeholder="单位"
|
||||||
:disabled="dialogReadonly"
|
:disabled="dialogReadonly"
|
||||||
|
@change="value => handleTaskFullFreightGroupPriceUnitChange(group, value)"
|
||||||
@visible-change="visible => visible && loadTaskFeeUnitOptions()"
|
@visible-change="visible => visible && loadTaskFeeUnitOptions()"
|
||||||
>
|
>
|
||||||
<el-option
|
<el-option
|
||||||
@@ -1068,29 +1084,27 @@
|
|||||||
</template>
|
</template>
|
||||||
</el-input>
|
</el-input>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item :label="`数量合计${index + 1}`">
|
<el-form-item label="数量合计">
|
||||||
<el-input :model-value="taskFullFreightQuantity(index)" disabled>
|
<el-input :model-value="taskFullFreightGroupQuantity(group)" disabled>
|
||||||
<template #append>
|
<template #append>
|
||||||
<el-select
|
<el-select
|
||||||
:model-value="cargo.quantityUnit"
|
:model-value="group.quantityUnit"
|
||||||
class="waybill-manage-page__append-select waybill-manage-page__append-select--wide"
|
class="waybill-manage-page__append-select waybill-manage-page__append-select--wide"
|
||||||
placeholder="单位"
|
placeholder="单位"
|
||||||
disabled
|
disabled
|
||||||
>
|
>
|
||||||
<el-option
|
<el-option :label="fixedQuantityUnit" :value="fixedQuantityUnit" />
|
||||||
:label="cargo.quantityUnit || '单位'"
|
|
||||||
:value="cargo.quantityUnit || ''"
|
|
||||||
/>
|
|
||||||
</el-select>
|
</el-select>
|
||||||
</template>
|
</template>
|
||||||
</el-input>
|
</el-input>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item :label="`运费${index + 1}`">
|
<el-form-item label="运费">
|
||||||
<el-input
|
<el-input
|
||||||
:model-value="taskFullFreightAmount(cargo)"
|
:model-value="taskFullFreightGroupAmount(group)"
|
||||||
placeholder="请输入运费"
|
placeholder="请输入运费"
|
||||||
:disabled="dialogReadonly"
|
:disabled="dialogReadonly"
|
||||||
@input="value => handleTaskFullFreightAmountInput(cargo, value)"
|
@input="value => handleTaskFullFreightGroupAmountInput(group, value)"
|
||||||
|
@clear="() => handleTaskFullFreightGroupAmountInput(group, '')"
|
||||||
>
|
>
|
||||||
<template #suffix>{{ taskFreightCurrencyLabel }}</template>
|
<template #suffix>{{ taskFreightCurrencyLabel }}</template>
|
||||||
</el-input>
|
</el-input>
|
||||||
@@ -1115,8 +1129,14 @@
|
|||||||
</el-form>
|
</el-form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="!isTaskFullMode"
|
||||||
|
class="dialog-section-title waybill-manage-page__task-content-title"
|
||||||
|
>
|
||||||
|
任务信息
|
||||||
|
</div>
|
||||||
<el-form
|
<el-form
|
||||||
v-else
|
v-if="!isTaskFullMode"
|
||||||
:model="form"
|
:model="form"
|
||||||
label-position="right"
|
label-position="right"
|
||||||
label-width="auto"
|
label-width="auto"
|
||||||
@@ -1520,11 +1540,7 @@
|
|||||||
<span>
|
<span>
|
||||||
运费合计:<strong>¥{{ formatFooterFreightTotal }}</strong>
|
运费合计:<strong>¥{{ formatFooterFreightTotal }}</strong>
|
||||||
</span>
|
</span>
|
||||||
<el-link
|
<el-link v-if="hasProjectProcessConfig" type="primary" @click="openWaybillProcessConfig">
|
||||||
v-if="hasProjectProcessConfig"
|
|
||||||
type="primary"
|
|
||||||
@click="openWaybillProcessConfig"
|
|
||||||
>
|
|
||||||
查看过程配置
|
查看过程配置
|
||||||
</el-link>
|
</el-link>
|
||||||
</div>
|
</div>
|
||||||
@@ -1699,32 +1715,53 @@
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="waybill-manage-page__waybill-route">
|
<div class="waybill-manage-page__waybill-route">
|
||||||
<div class="waybill-manage-page__waybill-route-item">
|
<div class="waybill-manage-page__waybill-route-item is-start">
|
||||||
<span class="waybill-manage-page__waybill-route-dot"></span>
|
<div class="waybill-manage-page__waybill-route-marker">起</div>
|
||||||
<div class="waybill-manage-page__waybill-route-detail">
|
<div class="waybill-manage-page__waybill-route-detail">
|
||||||
<span class="waybill-manage-page__waybill-route-label">发货地</span>
|
<strong class="waybill-manage-page__waybill-route-name">
|
||||||
<strong class="waybill-manage-page__waybill-route-name">{{
|
{{
|
||||||
waybillDetailRouteName(detailRow, 'departure')
|
formatWaybillRouteCityDistrict(
|
||||||
}}</strong>
|
detailRow.departureAddress || detailRow.departureName
|
||||||
<el-tooltip :content="detailRow.departureAddress || '-'" placement="top">
|
)
|
||||||
<span class="waybill-manage-page__waybill-route-addr">{{
|
}}
|
||||||
formatWaybillProvinceCityDistrict(detailRow.departureAddress)
|
</strong>
|
||||||
}}</span>
|
<el-tooltip
|
||||||
|
:content="detailRow.departureAddress || detailRow.departureName || '-'"
|
||||||
|
placement="top"
|
||||||
|
>
|
||||||
|
<span class="waybill-manage-page__waybill-route-addr">
|
||||||
|
{{ detailRow.departureAddress || detailRow.departureName || '-' }}
|
||||||
|
</span>
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
|
<div class="waybill-manage-page__waybill-route-contact">
|
||||||
|
<span>联系人:{{ detailRow.departureContact || '-' }}</span>
|
||||||
|
<span>联系方式:{{ detailRow.departurePhone || '-' }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="waybill-manage-page__waybill-route-item">
|
</div>
|
||||||
<span class="waybill-manage-page__waybill-route-dot is-end"></span>
|
<span class="waybill-manage-page__waybill-route-line" aria-hidden="true" />
|
||||||
|
<div class="waybill-manage-page__waybill-route-item is-end">
|
||||||
|
<div class="waybill-manage-page__waybill-route-marker">终</div>
|
||||||
<div class="waybill-manage-page__waybill-route-detail">
|
<div class="waybill-manage-page__waybill-route-detail">
|
||||||
<span class="waybill-manage-page__waybill-route-label">收货地</span>
|
<strong class="waybill-manage-page__waybill-route-name">
|
||||||
<strong class="waybill-manage-page__waybill-route-name">{{
|
{{
|
||||||
waybillDetailRouteName(detailRow, 'arrival')
|
formatWaybillRouteCityDistrict(
|
||||||
}}</strong>
|
detailRow.arrivalAddress || detailRow.arrivalName
|
||||||
<el-tooltip :content="detailRow.arrivalAddress || '-'" placement="top">
|
)
|
||||||
<span class="waybill-manage-page__waybill-route-addr">{{
|
}}
|
||||||
formatWaybillProvinceCityDistrict(detailRow.arrivalAddress)
|
</strong>
|
||||||
}}</span>
|
<el-tooltip
|
||||||
|
:content="detailRow.arrivalAddress || detailRow.arrivalName || '-'"
|
||||||
|
placement="top"
|
||||||
|
>
|
||||||
|
<span class="waybill-manage-page__waybill-route-addr">
|
||||||
|
{{ detailRow.arrivalAddress || detailRow.arrivalName || '-' }}
|
||||||
|
</span>
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
|
<div class="waybill-manage-page__waybill-route-contact">
|
||||||
|
<span>联系人:{{ detailRow.arrivalContact || '-' }}</span>
|
||||||
|
<span>联系方式:{{ detailRow.arrivalPhone || '-' }}</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1774,75 +1811,65 @@
|
|||||||
<div class="waybill-manage-page__waybill-detail-field">
|
<div class="waybill-manage-page__waybill-detail-field">
|
||||||
<span>承运类型</span><strong>{{ detailRow.carrierType || '-' }}</strong>
|
<span>承运类型</span><strong>{{ detailRow.carrierType || '-' }}</strong>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div class="waybill-manage-page__waybill-detail-field">
|
||||||
v-if="waybillIsCarrier(detailRow)"
|
|
||||||
class="waybill-manage-page__waybill-detail-field"
|
|
||||||
>
|
|
||||||
<span>承运商</span><strong>{{ detailRow.carrierName || '-' }}</strong>
|
<span>承运商</span><strong>{{ detailRow.carrierName || '-' }}</strong>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<template v-if="waybillIsRoadTransport(detailRow)">
|
||||||
v-if="waybillIsRoadTransport(detailRow)"
|
<div class="waybill-manage-page__waybill-detail-field">
|
||||||
class="waybill-manage-page__waybill-detail-field"
|
|
||||||
>
|
|
||||||
<span>司机</span
|
<span>司机</span
|
||||||
><strong
|
><strong
|
||||||
>{{ detailRow.driverName || '-' }} {{ detailRow.driverPhone || '' }}</strong
|
>{{
|
||||||
|
[detailRow.driverName, detailRow.driverPhone].filter(Boolean).join(' ') || '-'
|
||||||
|
}}</strong
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="waybill-manage-page__waybill-detail-field">
|
||||||
|
<span>车牌号</span><strong>{{ detailRow.vehicleNo || '-' }}</strong>
|
||||||
|
</div>
|
||||||
|
<div class="waybill-manage-page__waybill-detail-field">
|
||||||
|
<span>挂车车牌</span><strong>{{ detailRow.trailerVehicleNo || '-' }}</strong>
|
||||||
|
</div>
|
||||||
|
<div class="waybill-manage-page__waybill-detail-field">
|
||||||
|
<span>押运人</span
|
||||||
|
><strong
|
||||||
|
>{{
|
||||||
|
[detailRow.escortName, detailRow.escortPhone].filter(Boolean).join(' ') || '-'
|
||||||
|
}}</strong
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="waybillIsNonRoadTransport(detailRow)">
|
||||||
|
<div class="waybill-manage-page__waybill-detail-field">
|
||||||
|
<span>船长</span
|
||||||
|
><strong
|
||||||
|
>{{
|
||||||
|
[detailRow.captainName, detailRow.driverPhone].filter(Boolean).join(' ') || '-'
|
||||||
|
}}</strong
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<div class="waybill-manage-page__waybill-detail-field">
|
||||||
|
<span>航班号/船次号/班列号</span>
|
||||||
|
<strong>{{ detailRow.vehicleNo || '-' }}</strong>
|
||||||
|
</div>
|
||||||
|
<div class="waybill-manage-page__waybill-detail-field">
|
||||||
|
<span>箱号</span><strong>{{ detailRow.containerNo || '-' }}</strong>
|
||||||
|
</div>
|
||||||
|
<div class="waybill-manage-page__waybill-detail-field">
|
||||||
|
<span>仓位</span><strong>{{ detailRow.cabinNo || '-' }}</strong>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
<div class="waybill-manage-page__waybill-detail-field">
|
<div class="waybill-manage-page__waybill-detail-field">
|
||||||
<span>{{ waybillVehicleNoLabel(detailRow) }}</span>
|
<span>{{ waybillVehicleNoLabel(detailRow) }}</span>
|
||||||
<strong>{{ detailRow.vehicleNo || '-' }}</strong>
|
<strong>{{ detailRow.vehicleNo || '-' }}</strong>
|
||||||
</div>
|
</div>
|
||||||
|
</template>
|
||||||
<div
|
<div
|
||||||
v-if="waybillIsNonRoadTransport(detailRow)"
|
|
||||||
class="waybill-manage-page__waybill-detail-field"
|
class="waybill-manage-page__waybill-detail-field"
|
||||||
>
|
:class="{
|
||||||
<span>船长</span><strong>{{ detailRow.captainName || '-' }}</strong>
|
'waybill-manage-page__waybill-detail-field--remark': waybillIsRoadTransport(detailRow),
|
||||||
</div>
|
'waybill-manage-page__waybill-detail-field--remark-full': waybillIsNonRoadTransport(detailRow),
|
||||||
<div
|
}"
|
||||||
v-if="waybillIsNonRoadTransport(detailRow) && waybillIsCarrier(detailRow)"
|
|
||||||
class="waybill-manage-page__waybill-detail-field"
|
|
||||||
>
|
|
||||||
<span>联系电话</span><strong>{{ detailRow.driverPhone || '-' }}</strong>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
v-if="waybillIsNonRoadTransport(detailRow)"
|
|
||||||
class="waybill-manage-page__waybill-detail-field"
|
|
||||||
>
|
|
||||||
<span>箱号</span><strong>{{ detailRow.containerNo || '-' }}</strong>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
v-if="waybillIsNonRoadTransport(detailRow)"
|
|
||||||
class="waybill-manage-page__waybill-detail-field"
|
|
||||||
>
|
|
||||||
<span>舱位</span><strong>{{ detailRow.cabinNo || '-' }}</strong>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
v-if="waybillIsRoadTransport(detailRow) && !waybillIsCarrier(detailRow)"
|
|
||||||
class="waybill-manage-page__waybill-detail-field"
|
|
||||||
>
|
|
||||||
<span>挂车车牌号</span><strong>{{ detailRow.trailerVehicleNo || '-' }}</strong>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
v-if="waybillIsRoadTransport(detailRow) && !waybillIsCarrier(detailRow)"
|
|
||||||
class="waybill-manage-page__waybill-detail-field"
|
|
||||||
>
|
|
||||||
<span>押运人</span><strong>{{ detailRow.escortName || '-' }}</strong>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
v-if="waybillIsRoadTransport(detailRow) && !waybillIsCarrier(detailRow)"
|
|
||||||
class="waybill-manage-page__waybill-detail-field"
|
|
||||||
>
|
|
||||||
<span>押运人手机号</span><strong>{{ detailRow.escortPhone || '-' }}</strong>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
v-if="waybillIsRoadTransport(detailRow) && !waybillIsCarrier(detailRow)"
|
|
||||||
class="waybill-manage-page__waybill-detail-field"
|
|
||||||
>
|
|
||||||
<span>里程(km)</span><strong>{{ detailRow.mileage || '-' }}</strong>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="waybill-manage-page__waybill-detail-field waybill-manage-page__waybill-detail-field--remark"
|
|
||||||
>
|
>
|
||||||
<span>备注</span
|
<span>备注</span
|
||||||
><strong>{{ detailRow.taskRemark || detailRow.remark || '-' }}</strong>
|
><strong>{{ detailRow.taskRemark || detailRow.remark || '-' }}</strong>
|
||||||
@@ -2185,7 +2212,11 @@
|
|||||||
</template>
|
</template>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
|
|
||||||
<waybill-import-dialog v-if="config.enableWaybillImport" v-model="waybillImportBox" />
|
<waybill-import-dialog
|
||||||
|
v-if="config.enableWaybillImport"
|
||||||
|
v-model="waybillImportBox"
|
||||||
|
@closed="refreshChange"
|
||||||
|
/>
|
||||||
|
|
||||||
<flow-design
|
<flow-design
|
||||||
v-if="website.design.designMode"
|
v-if="website.design.designMode"
|
||||||
@@ -2780,7 +2811,7 @@ 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 { isMobile } from '@/utils/validate';
|
||||||
import { Location, OfficeBuilding, Rank, Search } from '@element-plus/icons-vue';
|
import { InfoFilled, Location, OfficeBuilding, Rank, Search } from '@element-plus/icons-vue';
|
||||||
import { ElImageViewer } from 'element-plus';
|
import { ElImageViewer } from 'element-plus';
|
||||||
import { OpenFileViewer } from '@open-file-viewer/vue';
|
import { OpenFileViewer } from '@open-file-viewer/vue';
|
||||||
import {
|
import {
|
||||||
@@ -2872,6 +2903,8 @@ const extractRecords = res => {
|
|||||||
return [];
|
return [];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const WAYBILL_QUANTITY_UNIT = '吨';
|
||||||
|
|
||||||
const defaultTransportCargo = () => ({
|
const defaultTransportCargo = () => ({
|
||||||
cargoName: '',
|
cargoName: '',
|
||||||
cargoType: '',
|
cargoType: '',
|
||||||
@@ -2942,6 +2975,7 @@ export default {
|
|||||||
components: {
|
components: {
|
||||||
WaybillImportDialog,
|
WaybillImportDialog,
|
||||||
PageAvueForm,
|
PageAvueForm,
|
||||||
|
InfoFilled,
|
||||||
Rank,
|
Rank,
|
||||||
ElImageViewer,
|
ElImageViewer,
|
||||||
OpenFileViewer,
|
OpenFileViewer,
|
||||||
@@ -3355,6 +3389,9 @@ export default {
|
|||||||
if (!total) return '';
|
if (!total) return '';
|
||||||
return Number.isInteger(total) ? String(total) : String(Number(total.toFixed(3)));
|
return Number.isInteger(total) ? String(total) : String(Number(total.toFixed(3)));
|
||||||
},
|
},
|
||||||
|
fixedQuantityUnit() {
|
||||||
|
return WAYBILL_QUANTITY_UNIT;
|
||||||
|
},
|
||||||
taskFreightAmount() {
|
taskFreightAmount() {
|
||||||
const quantity = Number(this.taskCargoQuantityTotal || this.form.quantity || 0);
|
const quantity = Number(this.taskCargoQuantityTotal || this.form.quantity || 0);
|
||||||
const unitPrice = Number(this.form.unitPrice || 0);
|
const unitPrice = Number(this.form.unitPrice || 0);
|
||||||
@@ -3362,9 +3399,19 @@ export default {
|
|||||||
const amount = quantity * unitPrice;
|
const amount = quantity * unitPrice;
|
||||||
return Number.isInteger(amount) ? String(amount) : String(Number(amount.toFixed(2)));
|
return Number.isInteger(amount) ? String(amount) : String(Number(amount.toFixed(2)));
|
||||||
},
|
},
|
||||||
|
taskFreightGroups() {
|
||||||
|
// 运单统一按吨计价,运费录入只保留一行汇总所有货物。
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
key: '__all__',
|
||||||
|
quantityUnit: WAYBILL_QUANTITY_UNIT,
|
||||||
|
rows: this.transportCargoRows || [],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
},
|
||||||
taskFullFreightTotal() {
|
taskFullFreightTotal() {
|
||||||
const total = this.transportCargoRows.reduce(
|
const total = this.taskFreightGroups.reduce(
|
||||||
(sum, cargo) => sum + Number(this.taskFullFreightAmount(cargo) || 0),
|
(sum, group) => sum + Number(this.taskFullFreightGroupAmount(group) || 0),
|
||||||
0
|
0
|
||||||
);
|
);
|
||||||
return this.formatTaskFullFreightNumber(total);
|
return this.formatTaskFullFreightNumber(total);
|
||||||
@@ -3650,6 +3697,21 @@ export default {
|
|||||||
const cityMatch = text.match(/市/);
|
const cityMatch = text.match(/市/);
|
||||||
return cityMatch ? text.slice(0, cityMatch.index + 1) : text;
|
return cityMatch ? text.slice(0, cityMatch.index + 1) : text;
|
||||||
},
|
},
|
||||||
|
formatWaybillRouteCityDistrict(value) {
|
||||||
|
const text = String(value || '').trim();
|
||||||
|
if (!text) return '-';
|
||||||
|
const provinceMatch = text.match(/^.+?(?:省|自治区|特别行政区)/);
|
||||||
|
const cityRegion = provinceMatch ? text.slice(provinceMatch[0].length) : text;
|
||||||
|
const cityMatch = cityRegion.match(/^(.+?市)/);
|
||||||
|
if (!cityMatch) return this.formatWaybillProvinceCityDistrict(text);
|
||||||
|
const city = cityMatch[1];
|
||||||
|
const remainder = cityRegion.slice(city.length);
|
||||||
|
const districtMatch = remainder.match(
|
||||||
|
/^(.+?(?:自治县|自治旗|林区|矿区|新区|开发区|区|县|旗){1,2})/
|
||||||
|
);
|
||||||
|
const district = districtMatch ? districtMatch[1] : '';
|
||||||
|
return [city, district].filter(Boolean).join(' ');
|
||||||
|
},
|
||||||
waybillDetailRouteName(row = {}, field) {
|
waybillDetailRouteName(row = {}, field) {
|
||||||
const address = String(row[`${field}Address`] || '').trim();
|
const address = String(row[`${field}Address`] || '').trim();
|
||||||
const name = String(row[`${field}Name`] || '').trim();
|
const name = String(row[`${field}Name`] || '').trim();
|
||||||
@@ -3852,7 +3914,8 @@ export default {
|
|||||||
fillCustomerNameFromProject(projectId = this.form.projectId) {
|
fillCustomerNameFromProject(projectId = this.form.projectId) {
|
||||||
if (this.form.customerName || !projectId) return Promise.resolve(this.form.customerName);
|
if (this.form.customerName || !projectId) return Promise.resolve(this.form.customerName);
|
||||||
const project = this.projectOptions.find(item => String(item.id) === String(projectId));
|
const project = this.projectOptions.find(item => String(item.id) === String(projectId));
|
||||||
const customerName = project?.customerNames || project?.customerName || project?.customer || '';
|
const customerName =
|
||||||
|
project?.customerNames || project?.customerName || project?.customer || '';
|
||||||
if (customerName) {
|
if (customerName) {
|
||||||
this.form.customerName = customerName;
|
this.form.customerName = customerName;
|
||||||
return Promise.resolve(customerName);
|
return Promise.resolve(customerName);
|
||||||
@@ -5253,6 +5316,69 @@ export default {
|
|||||||
Number(cargo.unitPrice || 0) * Number(cargo.quantity || 0)
|
Number(cargo.unitPrice || 0) * Number(cargo.quantity || 0)
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
taskFullFreightGroupQuantity(group = {}) {
|
||||||
|
const total = (group.rows || []).reduce((sum, cargo) => sum + Number(cargo.quantity || 0), 0);
|
||||||
|
return this.formatTaskFullFreightNumber(total);
|
||||||
|
},
|
||||||
|
taskFullFreightGroupUnitPrice(group = {}) {
|
||||||
|
const prices = (group.rows || [])
|
||||||
|
.map(cargo => String(cargo.unitPrice ?? '').trim())
|
||||||
|
.filter(Boolean);
|
||||||
|
if (!prices.length || prices.some(price => price !== prices[0])) return '';
|
||||||
|
return prices[0];
|
||||||
|
},
|
||||||
|
taskFullFreightGroupPriceUnit(group = {}) {
|
||||||
|
return group.rows?.[0]?.priceUnit || (group.quantityUnit ? `元/${group.quantityUnit}` : '');
|
||||||
|
},
|
||||||
|
taskFullFreightGroupAmount(group = {}) {
|
||||||
|
const total = (group.rows || []).reduce(
|
||||||
|
(sum, cargo) => sum + Number(this.taskFullFreightAmount(cargo) || 0),
|
||||||
|
0
|
||||||
|
);
|
||||||
|
return this.formatTaskFullFreightNumber(total);
|
||||||
|
},
|
||||||
|
handleTaskFullFreightGroupNumberInput(group, prop, value) {
|
||||||
|
const text = String(value || '').replace(/[^\d.]/g, '');
|
||||||
|
const parts = text.split('.');
|
||||||
|
const normalized =
|
||||||
|
parts.length > 1 ? `${parts[0]}.${parts.slice(1).join('').slice(0, 2)}` : parts[0];
|
||||||
|
(group.rows || []).forEach(cargo => {
|
||||||
|
cargo[prop] = normalized;
|
||||||
|
});
|
||||||
|
this.syncTransportCargoJson();
|
||||||
|
},
|
||||||
|
handleTaskFullFreightGroupPriceUnitChange(group, value) {
|
||||||
|
(group.rows || []).forEach(cargo => {
|
||||||
|
cargo.priceUnit = value || '';
|
||||||
|
});
|
||||||
|
this.syncTransportCargoJson();
|
||||||
|
},
|
||||||
|
handleTaskFullFreightGroupAmountInput(group, value) {
|
||||||
|
const text = String(value || '').replace(/[^\d.]/g, '');
|
||||||
|
const parts = text.split('.');
|
||||||
|
const normalized =
|
||||||
|
parts.length > 1 ? `${parts[0]}.${parts.slice(1).join('').slice(0, 2)}` : parts[0];
|
||||||
|
const rows = group.rows || [];
|
||||||
|
const total = Number(normalized || 0);
|
||||||
|
const quantities = rows.map(cargo => Math.max(0, Number(cargo.quantity || 0)));
|
||||||
|
const quantityTotal = quantities.reduce((sum, quantity) => sum + quantity, 0);
|
||||||
|
let allocated = 0;
|
||||||
|
rows.forEach((cargo, index) => {
|
||||||
|
if (index === rows.length - 1) {
|
||||||
|
cargo.freightAmount = this.formatTaskFullFreightNumber(total - allocated);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let amount = 0;
|
||||||
|
if (quantityTotal) {
|
||||||
|
amount = Number((total * (quantities[index] / quantityTotal)).toFixed(2));
|
||||||
|
} else if (index === 0) {
|
||||||
|
amount = total;
|
||||||
|
}
|
||||||
|
allocated += amount;
|
||||||
|
cargo.freightAmount = this.formatTaskFullFreightNumber(amount);
|
||||||
|
});
|
||||||
|
this.syncTransportCargoJson();
|
||||||
|
},
|
||||||
formatTaskFullFreightNumber(value) {
|
formatTaskFullFreightNumber(value) {
|
||||||
if (value === undefined || value === null || String(value).trim() === '') return '';
|
if (value === undefined || value === null || String(value).trim() === '') return '';
|
||||||
const number = Number(value || 0);
|
const number = Number(value || 0);
|
||||||
@@ -7727,8 +7853,7 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
&__shipping-title,
|
&__shipping-title,
|
||||||
&__goods-title,
|
&__goods-title {
|
||||||
&__task-title {
|
|
||||||
min-width: 100%;
|
min-width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -7752,6 +7877,20 @@ export default {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&__task-mode-switch {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-start;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__task-content-title {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__task-full &__task-content-title {
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
&__task-form {
|
&__task-form {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||||
@@ -7789,7 +7928,6 @@ export default {
|
|||||||
|
|
||||||
&__task-form--full {
|
&__task-form--full {
|
||||||
display: block;
|
display: block;
|
||||||
margin-top: 16px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&__task-row {
|
&__task-row {
|
||||||
@@ -7828,6 +7966,18 @@ export default {
|
|||||||
grid-column: span 1;
|
grid-column: span 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&__label-with-info {
|
||||||
|
display: inline-flex;
|
||||||
|
gap: 4px;
|
||||||
|
align-items: center;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__info-icon {
|
||||||
|
color: #909399;
|
||||||
|
cursor: help;
|
||||||
|
}
|
||||||
|
|
||||||
&__task-carrier-type {
|
&__task-carrier-type {
|
||||||
:deep(.el-form-item__content) {
|
:deep(.el-form-item__content) {
|
||||||
flex: 0 1 260px;
|
flex: 0 1 260px;
|
||||||
@@ -7868,16 +8018,6 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 运单独立表单的任务信息:标题靠左,短字段保持与里程输入框一致的宽度。
|
|
||||||
:global(.waybill-manage-page--form-page .waybill-manage-page__task-title) {
|
|
||||||
justify-content: flex-start !important;
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
:global(.waybill-manage-page--form-page .waybill-manage-page__task-title > .el-segmented) {
|
|
||||||
margin-left: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
:global(
|
:global(
|
||||||
.waybill-manage-page--form-page
|
.waybill-manage-page--form-page
|
||||||
.waybill-manage-page__task-form
|
.waybill-manage-page__task-form
|
||||||
@@ -8511,7 +8651,7 @@ export default {
|
|||||||
|
|
||||||
&__waybill-summary-grid {
|
&__waybill-summary-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(4, minmax(130px, 1fr)) minmax(420px, 2.5fr);
|
grid-template-columns: repeat(4, minmax(120px, 1fr)) minmax(360px, 1.5fr);
|
||||||
gap: 16px 24px;
|
gap: 16px 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -8544,93 +8684,91 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
&__waybill-route {
|
&__waybill-route {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: minmax(100px, 1fr) minmax(36px, 0.7fr) minmax(100px, 1fr);
|
||||||
grid-row: 1 / span 3;
|
grid-row: 1 / span 3;
|
||||||
grid-column: 5;
|
grid-column: 5;
|
||||||
|
align-items: start;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
padding: 4px 0;
|
padding: 4px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__waybill-route-item {
|
&__waybill-route-item {
|
||||||
position: relative;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: stretch;
|
min-width: 0;
|
||||||
gap: 12px;
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
& + & {
|
text-align: center;
|
||||||
margin-top: 18px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&__waybill-route-dot {
|
&__waybill-route-marker {
|
||||||
position: relative;
|
display: inline-flex;
|
||||||
flex: 0 0 auto;
|
width: 36px;
|
||||||
width: 12px;
|
height: 36px;
|
||||||
margin-top: 2px;
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
&::before {
|
color: #fff;
|
||||||
display: block;
|
border-radius: 8px;
|
||||||
width: 12px;
|
|
||||||
height: 12px;
|
|
||||||
border-radius: 50%;
|
|
||||||
content: '';
|
|
||||||
background: #409eff;
|
background: #409eff;
|
||||||
box-shadow: 0 0 0 3px #fff, 0 0 0 4px #c6e0ff;
|
font-size: 18px;
|
||||||
}
|
font-weight: 600;
|
||||||
|
|
||||||
&.is-end::before {
|
.is-end & {
|
||||||
background: #e6a23c;
|
background: #67c23a;
|
||||||
box-shadow: 0 0 0 3px #fff, 0 0 0 4px #f5d9b0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&__waybill-route-item:not(:last-child) &__waybill-route-dot::after {
|
&__waybill-route-line {
|
||||||
content: '';
|
width: 100%;
|
||||||
position: absolute;
|
height: 6px;
|
||||||
top: 14px;
|
margin-top: 15px;
|
||||||
left: 5px;
|
border-radius: 1px;
|
||||||
width: 2px;
|
background: #e4e7ed;
|
||||||
height: calc(100% + 10px);
|
|
||||||
background: repeating-linear-gradient(
|
|
||||||
to bottom,
|
|
||||||
#c0c4cc 0,
|
|
||||||
#c0c4cc 4px,
|
|
||||||
transparent 4px,
|
|
||||||
transparent 8px
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&__waybill-route-detail {
|
&__waybill-route-detail {
|
||||||
flex: 1;
|
|
||||||
min-width: 0;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
|
min-width: 0;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 4px;
|
align-items: center;
|
||||||
}
|
gap: 5px;
|
||||||
|
margin-top: 8px;
|
||||||
&__waybill-route-label {
|
width: 100%;
|
||||||
color: #909399;
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&__waybill-route-name {
|
&__waybill-route-name {
|
||||||
color: #303133;
|
max-width: 100%;
|
||||||
font-size: 15px;
|
|
||||||
font-weight: 500;
|
|
||||||
line-height: 1.4;
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
display: -webkit-box;
|
color: #303133;
|
||||||
-webkit-line-clamp: 2;
|
font-size: 18px;
|
||||||
-webkit-box-orient: vertical;
|
font-weight: 600;
|
||||||
|
line-height: 1.35;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__waybill-route-addr {
|
&__waybill-route-addr {
|
||||||
|
display: block;
|
||||||
|
max-width: 100%;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
color: #606266;
|
color: #606266;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
|
line-height: 1.5;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&__waybill-route-contact {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 2px 8px;
|
||||||
|
max-width: 100%;
|
||||||
|
color: #909399;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
&__waybill-attachments {
|
&__waybill-attachments {
|
||||||
grid-column: 1 / 5;
|
grid-column: 1 / 5;
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -8649,6 +8787,7 @@ export default {
|
|||||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||||
gap: 18px 32px;
|
gap: 18px 32px;
|
||||||
padding: 4px 20px 8px;
|
padding: 4px 20px 8px;
|
||||||
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__waybill-detail-field {
|
&__waybill-detail-field {
|
||||||
@@ -8656,6 +8795,7 @@ export default {
|
|||||||
align-items: baseline;
|
align-items: baseline;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
|
text-align: left;
|
||||||
|
|
||||||
span {
|
span {
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
@@ -8676,6 +8816,10 @@ export default {
|
|||||||
grid-column: 3 / -1;
|
grid-column: 3 / -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&__waybill-detail-field--remark-full {
|
||||||
|
grid-column: 1 / -1;
|
||||||
|
}
|
||||||
|
|
||||||
&__waybill-fee-grid {
|
&__waybill-fee-grid {
|
||||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||||
|
|
||||||
@@ -8815,11 +8959,6 @@ export default {
|
|||||||
grid-template-columns: repeat(4, minmax(130px, 1fr));
|
grid-template-columns: repeat(4, minmax(130px, 1fr));
|
||||||
}
|
}
|
||||||
|
|
||||||
&__waybill-route {
|
|
||||||
grid-row: auto;
|
|
||||||
grid-column: 1 / -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__waybill-attachments {
|
&__waybill-attachments {
|
||||||
grid-column: 1 / -1;
|
grid-column: 1 / -1;
|
||||||
}
|
}
|
||||||
@@ -8831,6 +8970,21 @@ export default {
|
|||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&__waybill-route {
|
||||||
|
grid-template-columns: minmax(0, 1fr);
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__waybill-route-line {
|
||||||
|
width: 10px;
|
||||||
|
height: 32px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__waybill-attachments {
|
||||||
|
grid-column: auto;
|
||||||
|
}
|
||||||
|
|
||||||
&__waybill-carrier-grid {
|
&__waybill-carrier-grid {
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
}
|
}
|
||||||
@@ -8838,6 +8992,10 @@ export default {
|
|||||||
&__waybill-detail-field--remark {
|
&__waybill-detail-field--remark {
|
||||||
grid-column: auto;
|
grid-column: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&__waybill-detail-field--remark-full {
|
||||||
|
grid-column: auto;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 1280px) {
|
@media (max-width: 1280px) {
|
||||||
|
|||||||
@@ -79,10 +79,23 @@
|
|||||||
<section class="change-section attachment-section">
|
<section class="change-section attachment-section">
|
||||||
<div class="section-head"><div class="dialog-section-title">其它附件</div><el-button type="primary" plain>批量下载</el-button></div>
|
<div class="section-head"><div class="dialog-section-title">其它附件</div><el-button type="primary" plain>批量下载</el-button></div>
|
||||||
<el-table :data="attachments" border class="change-table"><el-table-column type="index" label="序号" width="70" /><el-table-column label="文件名" min-width="240"><template #default="{ row }"><el-link type="primary" @click="previewAttachment(row, attachments)">{{ row.originalName || row.name }}</el-link></template></el-table-column><el-table-column prop="description" label="附件描述" min-width="240" /><el-table-column prop="size" label="文件大小" width="120" /><el-table-column prop="userName" label="上传人" width="140" /><el-table-column prop="uploadTime" label="上传时间" width="180" /><el-table-column label="操作" width="100"><template #default="{ $index }"><el-link type="danger" @click="attachments.splice($index, 1)">删除</el-link></template></el-table-column></el-table>
|
<el-table :data="attachments" border class="change-table"><el-table-column type="index" label="序号" width="70" /><el-table-column label="文件名" min-width="240"><template #default="{ row }"><el-link type="primary" @click="previewAttachment(row, attachments)">{{ row.originalName || row.name }}</el-link></template></el-table-column><el-table-column prop="description" label="附件描述" min-width="240" /><el-table-column prop="size" label="文件大小" width="120" /><el-table-column prop="userName" label="上传人" width="140" /><el-table-column prop="uploadTime" label="上传时间" width="180" /><el-table-column label="操作" width="100"><template #default="{ $index }"><el-link type="danger" @click="attachments.splice($index, 1)">删除</el-link></template></el-table-column></el-table>
|
||||||
<el-upload action="#" :auto-upload="false" multiple :show-file-list="false" @change="handleAttachment"><el-button type="primary" plain icon="el-icon-upload">上传附件</el-button></el-upload>
|
<el-upload action="#" :auto-upload="false" multiple :show-file-list="false" @change="handleAttachment"><el-button plain>上传附件</el-button></el-upload>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="change-section change-reason-section"><div class="dialog-section-title">变更原因(必填)</div><el-form-item prop="changeReason"><el-input v-model="form.changeReason" type="textarea" :rows="2" maxlength="2000" show-word-limit placeholder="请输入变更原因(必填)" /></el-form-item><div class="dialog-section-title">变更材料</div><el-upload action="#" :auto-upload="false" multiple :show-file-list="false" @change="handleChangeMaterial"><el-button type="primary" plain icon="el-icon-upload">上传变更材料</el-button></el-upload></section>
|
<section class="change-section change-reason-section">
|
||||||
|
<div class="dialog-section-title">变更内容</div>
|
||||||
|
<el-form-item prop="changeContent"><el-input v-model="form.changeContent" type="textarea" :rows="3" maxlength="2000" show-word-limit placeholder="请输入变更内容" /></el-form-item>
|
||||||
|
<div class="dialog-section-title">变更原因</div>
|
||||||
|
<el-form-item prop="changeReason"><el-input v-model="form.changeReason" type="textarea" :rows="3" maxlength="500" show-word-limit placeholder="请输入变更原因" /></el-form-item>
|
||||||
|
<div class="dialog-section-title">变更材料</div>
|
||||||
|
<el-table :data="changeMaterials" border class="change-table">
|
||||||
|
<el-table-column type="index" label="序号" width="70" />
|
||||||
|
<el-table-column label="文件名" min-width="240"><template #default="{ row }">{{ row.originalName || row.name }}</template></el-table-column>
|
||||||
|
<el-table-column label="文件大小" width="120"><template #default="{ row }">{{ formatFileSize(row.size) }}</template></el-table-column>
|
||||||
|
<el-table-column label="操作" width="100"><template #default="{ $index }"><el-link type="danger" @click="changeMaterials.splice($index, 1)">删除</el-link></template></el-table-column>
|
||||||
|
</el-table>
|
||||||
|
<div class="attachment-upload"><vehicle-attachment-upload v-model="changeMaterials" :readonly="false" :file-types="attachmentFileTypes" :max-size="500" :show-file-list="false" button-text="上传变更材料" /></div>
|
||||||
|
</section>
|
||||||
<div class="page-footer">
|
<div class="page-footer">
|
||||||
<el-button @click="$router.back()">取消</el-button>
|
<el-button @click="$router.back()">取消</el-button>
|
||||||
<el-button type="primary" @click="submit">提交</el-button>
|
<el-button type="primary" @click="submit">提交</el-button>
|
||||||
@@ -125,7 +138,7 @@ export default {
|
|||||||
mounted() { this.load(); },
|
mounted() { this.load(); },
|
||||||
watch: { settlementConfigTab(tab, oldTab) { if (tab === oldTab) return; if (oldTab === 'pre') this.preSettlementConfig = { ...this.settlementRule }; else this.formalSettlementConfig = { ...this.settlementRule }; this.settlementRule = { ...(tab === 'pre' ? this.preSettlementConfig : this.formalSettlementConfig) }; } },
|
watch: { settlementConfigTab(tab, oldTab) { if (tab === oldTab) return; if (oldTab === 'pre') this.preSettlementConfig = { ...this.settlementRule }; else this.formalSettlementConfig = { ...this.settlementRule }; this.settlementRule = { ...(tab === 'pre' ? this.preSettlementConfig : this.formalSettlementConfig) }; } },
|
||||||
methods: {
|
methods: {
|
||||||
async load() { const id = this.$route.query.id; if (!id) return; const res = await api.getDetail(id); const data = res.data?.data || res.data || {}; this.form = { ...data, copyCount: normalizeOptionalPositiveInteger(data.copyCount), paymentDays: normalizeOptionalPositiveInteger(data.paymentDays), changeType: '合同信息变更' }; this.period = data.startDate && data.endDate ? [data.startDate, data.endDate] : []; this.plans = this.parse(data.billingPlanJson); this.attachments = this.parse(data.attachmentsJson); this.contractFileRows = this.parse(data.contractFileJson); const rules = this.parseObject(data.settlementRuleJson); const pre = this.parseObject(data.preSettlementConfigJson); const formal = this.parseObject(data.formalSettlementConfigJson); const legacy = Object.keys(rules).some(key => !['preSettlementConfig', 'formalSettlementConfig'].includes(key)) ? rules : {}; this.preSettlementConfig = rules.preSettlementConfig || (Object.keys(pre).length ? pre : legacy); this.formalSettlementConfig = rules.formalSettlementConfig || (Object.keys(formal).length ? formal : legacy); this.settlementRule = { ...this.preSettlementConfig }; this.feeGenerationMode = data.feeGenerationMode || (Number(data.billingEnabled) === 0 ? 'manual' : 'system'); this.paymentRatioRows = this.parse(data.paymentRatioJson); },
|
async load() { const id = this.$route.query.id; if (!id) return; const res = await api.getDetail(id); const data = res.data?.data || res.data || {}; this.form = { ...data, changeContent: '', changeReason: '', changeAttachmentsJson: '', copyCount: normalizeOptionalPositiveInteger(data.copyCount), paymentDays: normalizeOptionalPositiveInteger(data.paymentDays), changeType: '合同信息变更' }; this.changeMaterials = []; this.period = data.startDate && data.endDate ? [data.startDate, data.endDate] : []; this.plans = this.parse(data.billingPlanJson); this.attachments = this.parse(data.attachmentsJson); this.contractFileRows = this.parse(data.contractFileJson); const rules = this.parseObject(data.settlementRuleJson); const pre = this.parseObject(data.preSettlementConfigJson); const formal = this.parseObject(data.formalSettlementConfigJson); const legacy = Object.keys(rules).some(key => !['preSettlementConfig', 'formalSettlementConfig'].includes(key)) ? rules : {}; this.preSettlementConfig = rules.preSettlementConfig || (Object.keys(pre).length ? pre : legacy); this.formalSettlementConfig = rules.formalSettlementConfig || (Object.keys(formal).length ? formal : legacy); this.settlementRule = { ...this.preSettlementConfig }; this.feeGenerationMode = data.feeGenerationMode || (Number(data.billingEnabled) === 0 ? 'manual' : 'system'); this.paymentRatioRows = this.parse(data.paymentRatioJson); },
|
||||||
parse(value) { try { const result = JSON.parse(value || '[]'); return Array.isArray(result) ? result : []; } catch { return []; } },
|
parse(value) { try { const result = JSON.parse(value || '[]'); return Array.isArray(result) ? result : []; } catch { return []; } },
|
||||||
parseObject(value) { try { return { autoGenerate: 1, settlementType: '月结', billCycleType: '固定截单日', billCutoffDay: 25, cycleDays: 15, ...(JSON.parse(value || '{}') || {}) }; } catch { return { autoGenerate: 1, settlementType: '月结', billCycleType: '固定截单日', billCutoffDay: 25, cycleDays: 15 }; } },
|
parseObject(value) { try { return { autoGenerate: 1, settlementType: '月结', billCycleType: '固定截单日', billCutoffDay: 25, cycleDays: 15, ...(JSON.parse(value || '{}') || {}) }; } catch { return { autoGenerate: 1, settlementType: '月结', billCycleType: '固定截单日', billCutoffDay: 25, cycleDays: 15 }; } },
|
||||||
positiveIntegerInput(prop, value) { this.form[prop] = String(value ?? '').replace(/\D/g, '').replace(/^0+/, ''); },
|
positiveIntegerInput(prop, value) { this.form[prop] = String(value ?? '').replace(/\D/g, '').replace(/^0+/, ''); },
|
||||||
@@ -147,9 +160,8 @@ export default {
|
|||||||
removeContractFile(index) { this.contractFileRows.splice(index, 1); },
|
removeContractFile(index) { this.contractFileRows.splice(index, 1); },
|
||||||
handleContractFileBatchDownload() { (this.selectedContractFiles.length ? this.selectedContractFiles : this.contractFileRows).forEach(row => { if (row.url) window.open(row.url, '_blank'); }); },
|
handleContractFileBatchDownload() { (this.selectedContractFiles.length ? this.selectedContractFiles : this.contractFileRows).forEach(row => { if (row.url) window.open(row.url, '_blank'); }); },
|
||||||
formatFileSize(value) { const size = Number(value || 0); return size > 1024 * 1024 ? `${(size / 1024 / 1024).toFixed(2)}MB` : `${Math.max(1, Math.ceil(size / 1024))}KB`; },
|
formatFileSize(value) { const size = Number(value || 0); return size > 1024 * 1024 ? `${(size / 1024 / 1024).toFixed(2)}MB` : `${Math.max(1, Math.ceil(size / 1024))}KB`; },
|
||||||
handleChangeMaterial(event) { if (event.raw) this.changeMaterials.push(event.raw); },
|
|
||||||
addPaymentRatioRow() { this.paymentRatioRows.push({ paymentTerm: `第${this.paymentRatioRows.length + 1}笔`, ratioLimit: '', remark: '' }); },
|
addPaymentRatioRow() { this.paymentRatioRows.push({ paymentTerm: `第${this.paymentRatioRows.length + 1}笔`, ratioLimit: '', remark: '' }); },
|
||||||
async submit() { await this.$refs.formRef.validate(); const total = this.paymentRatioRows.reduce((sum, row) => sum + Number(row.ratioLimit || 0), 0); if (this.paymentRatioRows.length && Math.abs(total - 100) > 0.0001) { this.$message.warning('付款比例上限合计必须等于100%'); return; } const settlementRule = { preSettlementConfig: this.preSettlementConfig, formalSettlementConfig: this.formalSettlementConfig }; await api.submitChange({ ...this.form, copyCount: normalizeOptionalPositiveInteger(this.form.copyCount), paymentDays: normalizeOptionalPositiveInteger(this.form.paymentDays), startDate: this.period[0], endDate: this.period[1], feeGenerationMode: this.feeGenerationMode, billingEnabled: this.feeGenerationMode === 'system' ? 1 : 0, billingPlanJson: JSON.stringify(this.plans), settlementRuleJson: JSON.stringify(settlementRule), preSettlementConfigJson: JSON.stringify(this.preSettlementConfig), formalSettlementConfigJson: JSON.stringify(this.formalSettlementConfig), paymentRatioJson: JSON.stringify(this.paymentRatioRows), contractFileJson: JSON.stringify(this.contractFileRows), attachmentsJson: JSON.stringify(this.attachments), changeContent: this.form.changeReason, changeReason: this.form.changeReason }); this.$message.success('变更已提交'); this.$router.back(); },
|
async submit() { await this.$refs.formRef.validate(); const total = this.paymentRatioRows.reduce((sum, row) => sum + Number(row.ratioLimit || 0), 0); if (this.paymentRatioRows.length && Math.abs(total - 100) > 0.0001) { this.$message.warning('付款比例上限合计必须等于100%'); return; } const settlementRule = { preSettlementConfig: this.preSettlementConfig, formalSettlementConfig: this.formalSettlementConfig }; await api.submitChange({ ...this.form, copyCount: normalizeOptionalPositiveInteger(this.form.copyCount), paymentDays: normalizeOptionalPositiveInteger(this.form.paymentDays), startDate: this.period[0], endDate: this.period[1], feeGenerationMode: this.feeGenerationMode, billingEnabled: this.feeGenerationMode === 'system' ? 1 : 0, billingPlanJson: JSON.stringify(this.plans), settlementRuleJson: JSON.stringify(settlementRule), preSettlementConfigJson: JSON.stringify(this.preSettlementConfig), formalSettlementConfigJson: JSON.stringify(this.formalSettlementConfig), paymentRatioJson: JSON.stringify(this.paymentRatioRows), contractFileJson: JSON.stringify(this.contractFileRows), attachmentsJson: JSON.stringify(this.attachments), changeContent: this.form.changeContent, changeReason: this.form.changeReason, changeAttachmentsJson: JSON.stringify(this.changeMaterials) }); this.$message.success('变更已提交'); this.$router.back(); },
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -769,7 +769,7 @@
|
|||||||
<el-table-column prop="statusName" label="状态" min-width="140" align="center" />
|
<el-table-column prop="statusName" label="状态" min-width="140" align="center" />
|
||||||
<el-table-column label="操作" width="120" align="center" fixed="right">
|
<el-table-column label="操作" width="120" align="center" fixed="right">
|
||||||
<template #default="{ row }"
|
<template #default="{ row }"
|
||||||
><el-link type="primary" @click="openFlow(row)">流程</el-link></template
|
><el-link type="primary" @click="openDetailChangeRecord(row)">查看详情</el-link></template
|
||||||
>
|
>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
@@ -780,6 +780,46 @@
|
|||||||
>
|
>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
|
|
||||||
|
<el-dialog
|
||||||
|
v-model="detailChangeRecordVisible"
|
||||||
|
title="变更记录详情"
|
||||||
|
append-to-body
|
||||||
|
destroy-on-close
|
||||||
|
width="1100px"
|
||||||
|
top="10px"
|
||||||
|
class="contract-change-record-detail-dialog"
|
||||||
|
>
|
||||||
|
<div v-if="detailChangeRecord" class="contract-change-record-detail-meta">
|
||||||
|
<span>变更日期:{{ detailChangeRecord.changeDate || '-' }}</span>
|
||||||
|
<span>经办人:{{ detailChangeRecord.handlerUserName || '-' }}</span>
|
||||||
|
<span>变更类型:{{ detailChangeRecord.changeType || '-' }}</span>
|
||||||
|
<span>状态:{{ detailChangeRecord.statusName || detailChangeRecord.status || '-' }}</span>
|
||||||
|
</div>
|
||||||
|
<el-table :data="detailChangeRecordDetailRows" border :show-overflow-tooltip="false">
|
||||||
|
<el-table-column prop="field" label="变更字段" min-width="180" />
|
||||||
|
<el-table-column
|
||||||
|
prop="before"
|
||||||
|
label="变更前"
|
||||||
|
min-width="360"
|
||||||
|
class-name="contract-change-record-detail-value"
|
||||||
|
/>
|
||||||
|
<el-table-column
|
||||||
|
prop="after"
|
||||||
|
label="变更后"
|
||||||
|
min-width="500"
|
||||||
|
class-name="contract-change-record-detail-value"
|
||||||
|
/>
|
||||||
|
</el-table>
|
||||||
|
<el-empty
|
||||||
|
v-if="!detailChangeRecordDetailRows.length"
|
||||||
|
description="暂无变更内容"
|
||||||
|
:image-size="60"
|
||||||
|
/>
|
||||||
|
<template #footer>
|
||||||
|
<el-button type="primary" @click="detailChangeRecordVisible = false">关闭</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
|
||||||
<billing-plan-editor
|
<billing-plan-editor
|
||||||
v-model="detailBillingPlanBox"
|
v-model="detailBillingPlanBox"
|
||||||
:value="detailBillingPlanForm"
|
:value="detailBillingPlanForm"
|
||||||
@@ -1220,6 +1260,9 @@ export default {
|
|||||||
detailFormalSettlementRuleForm: defaultSettlementRule(),
|
detailFormalSettlementRuleForm: defaultSettlementRule(),
|
||||||
detailPaymentRatioRows: [],
|
detailPaymentRatioRows: [],
|
||||||
detailChangeRecordRows: [],
|
detailChangeRecordRows: [],
|
||||||
|
detailChangeRecordVisible: false,
|
||||||
|
detailChangeRecord: null,
|
||||||
|
detailChangeRecordDetailRows: [],
|
||||||
flowBox: false,
|
flowBox: false,
|
||||||
flowUrl: '',
|
flowUrl: '',
|
||||||
processInstanceId: '',
|
processInstanceId: '',
|
||||||
@@ -1655,7 +1698,7 @@ export default {
|
|||||||
this.submitAction = action;
|
this.submitAction = action;
|
||||||
const submit = {
|
const submit = {
|
||||||
...this.normalizeSubmit(),
|
...this.normalizeSubmit(),
|
||||||
contractStage: action === 'draft' ? 'draft' : 'temporary',
|
contractStage: action === 'formal' ? 'temporary' : 'draft',
|
||||||
approvalStatus: 'draft',
|
approvalStatus: 'draft',
|
||||||
};
|
};
|
||||||
this.api
|
this.api
|
||||||
@@ -1663,9 +1706,11 @@ export default {
|
|||||||
.then(res => {
|
.then(res => {
|
||||||
const saved = res.data?.data || {};
|
const saved = res.data?.data || {};
|
||||||
const id = saved.id || this.form.id;
|
const id = saved.id || this.form.id;
|
||||||
if (action === 'formal') {
|
if (action === 'temporary' || action === 'formal') {
|
||||||
if (!id) throw new Error('合同保存成功但未返回主键,无法提交正式合同');
|
if (!id) throw new Error('合同保存成功但未返回主键,无法提交合同');
|
||||||
return this.api.submitFormal(id);
|
return action === 'temporary'
|
||||||
|
? this.api.toTemporary(id)
|
||||||
|
: this.api.submitFormal(id);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
})
|
})
|
||||||
@@ -2104,6 +2149,116 @@ export default {
|
|||||||
this.detailBillingPlanIndex = index;
|
this.detailBillingPlanIndex = index;
|
||||||
this.detailBillingPlanBox = true;
|
this.detailBillingPlanBox = true;
|
||||||
},
|
},
|
||||||
|
parseChangeRecordData(value) {
|
||||||
|
if (!value) return {};
|
||||||
|
if (typeof value === 'object') return value;
|
||||||
|
try {
|
||||||
|
const data = JSON.parse(value);
|
||||||
|
return data && typeof data === 'object' && !Array.isArray(data) ? data : {};
|
||||||
|
} catch (error) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
normalizeChangeRecordValue(value) {
|
||||||
|
if (typeof value !== 'string') return value;
|
||||||
|
const text = value.trim();
|
||||||
|
if (!text || (!text.startsWith('[') && !text.startsWith('{') && text !== 'null')) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return JSON.parse(text);
|
||||||
|
} catch (error) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
isEmptyChangeRecordValue(value) {
|
||||||
|
const normalized = this.normalizeChangeRecordValue(value);
|
||||||
|
if (normalized === undefined || normalized === null || normalized === '') return true;
|
||||||
|
if (Array.isArray(normalized)) return normalized.length === 0;
|
||||||
|
if (typeof normalized === 'object') return Object.keys(normalized).length === 0;
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
getContractChangeFieldLabel(field) {
|
||||||
|
const labels = {
|
||||||
|
contractName: '合同名称',
|
||||||
|
partyB: '乙方',
|
||||||
|
startDate: '开始日期',
|
||||||
|
endDate: '结束日期',
|
||||||
|
contractFormat: '合同格式',
|
||||||
|
settlementMode: '结算方式',
|
||||||
|
legalSealFlag: '是否需要加盖法人章',
|
||||||
|
copyCount: '一式(份)',
|
||||||
|
settlementCurrency: '结算币种',
|
||||||
|
invoiceCycle: '开票周期',
|
||||||
|
paymentDays: '回款账期',
|
||||||
|
remark: '备注',
|
||||||
|
feeGenerationMode: '费用生成模式',
|
||||||
|
billingPlanJson: '计费方案',
|
||||||
|
settlementRuleJson: '结算单规则',
|
||||||
|
preSettlementConfigJson: '预结算配置',
|
||||||
|
formalSettlementConfigJson: '正式结算配置',
|
||||||
|
paymentRatioJson: '付款比例设置',
|
||||||
|
contractFileJson: '合同文件',
|
||||||
|
attachmentsJson: '其它附件',
|
||||||
|
changeAttachmentsJson: '变更材料',
|
||||||
|
};
|
||||||
|
return labels[field] || field;
|
||||||
|
},
|
||||||
|
formatChangeRecordAttachments(value) {
|
||||||
|
const attachments = Array.isArray(value)
|
||||||
|
? value
|
||||||
|
: parseArray(typeof value === 'string' ? value : JSON.stringify(value || []));
|
||||||
|
const names = attachments
|
||||||
|
.map(item =>
|
||||||
|
typeof item === 'string'
|
||||||
|
? item
|
||||||
|
: item?.originalName || item?.name || item?.fileName || item?.url || item?.link || ''
|
||||||
|
)
|
||||||
|
.filter(Boolean);
|
||||||
|
return names.length ? names.join('、') : '空';
|
||||||
|
},
|
||||||
|
formatContractChangeValue(field, value) {
|
||||||
|
const normalized = this.normalizeChangeRecordValue(value);
|
||||||
|
if (this.isEmptyChangeRecordValue(normalized)) return '空';
|
||||||
|
if (['contractFileJson', 'attachmentsJson', 'changeAttachmentsJson'].includes(field)) {
|
||||||
|
return this.formatChangeRecordAttachments(normalized);
|
||||||
|
}
|
||||||
|
if (field === 'legalSealFlag') return Number(normalized) === 1 ? '是' : '否';
|
||||||
|
if (field === 'feeGenerationMode') return normalized === 'manual' ? '手动生成' : '系统生成';
|
||||||
|
if (field === 'invoiceCycle' || field === 'paymentDays') return `${normalized}天`;
|
||||||
|
if (Array.isArray(normalized) || typeof normalized === 'object') {
|
||||||
|
return JSON.stringify(normalized);
|
||||||
|
}
|
||||||
|
return String(normalized);
|
||||||
|
},
|
||||||
|
buildDetailChangeRecordRows(row = {}) {
|
||||||
|
const beforeData = this.parseChangeRecordData(row.beforeData);
|
||||||
|
const afterData = this.parseChangeRecordData(row.afterData);
|
||||||
|
const fields = [...new Set([...Object.keys(beforeData), ...Object.keys(afterData)])];
|
||||||
|
const rows = fields
|
||||||
|
.filter(
|
||||||
|
field =>
|
||||||
|
JSON.stringify(this.normalizeChangeRecordValue(beforeData[field])) !==
|
||||||
|
JSON.stringify(this.normalizeChangeRecordValue(afterData[field]))
|
||||||
|
)
|
||||||
|
.map(field => ({
|
||||||
|
field: this.getContractChangeFieldLabel(field),
|
||||||
|
before: this.formatContractChangeValue(field, beforeData[field]),
|
||||||
|
after: this.formatContractChangeValue(field, afterData[field]),
|
||||||
|
}));
|
||||||
|
if (row.changeContent) {
|
||||||
|
rows.unshift({ field: '变更内容', before: '空', after: row.changeContent });
|
||||||
|
}
|
||||||
|
if (row.changeReason) {
|
||||||
|
rows.push({ field: '变更原因', before: '空', after: row.changeReason });
|
||||||
|
}
|
||||||
|
return rows;
|
||||||
|
},
|
||||||
|
openDetailChangeRecord(row) {
|
||||||
|
this.detailChangeRecord = row;
|
||||||
|
this.detailChangeRecordDetailRows = this.buildDetailChangeRecordRows(row);
|
||||||
|
this.detailChangeRecordVisible = true;
|
||||||
|
},
|
||||||
displayValue(value) {
|
displayValue(value) {
|
||||||
return value === undefined || value === null || value === '' ? '-' : value;
|
return value === undefined || value === null || value === '' ? '-' : value;
|
||||||
},
|
},
|
||||||
@@ -2310,6 +2465,23 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.contract-change-record-detail-meta {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 8px 32px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
color: #606266;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.contract-change-record-detail-dialog .el-dialog__body) {
|
||||||
|
padding-top: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.contract-change-record-detail-dialog .contract-change-record-detail-value .cell) {
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
|
||||||
:global(.avue--collapse .contract-manage-page__footer) {
|
:global(.avue--collapse .contract-manage-page__footer) {
|
||||||
left: 60px;
|
left: 60px;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -638,17 +638,18 @@
|
|||||||
label="运单号"
|
label="运单号"
|
||||||
min-width="150"
|
min-width="150"
|
||||||
show-overflow-tooltip
|
show-overflow-tooltip
|
||||||
/>
|
>
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-link v-if="row.waybillNo" type="primary" @click="openWaybillDetail(row)">
|
||||||
|
{{ row.waybillNo }}
|
||||||
|
</el-link>
|
||||||
|
<span v-else>-</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
<el-table-column
|
<el-table-column
|
||||||
prop="projectName"
|
prop="projectName"
|
||||||
label="项目名称"
|
label="项目名称"
|
||||||
min-width="160"
|
min-width="150"
|
||||||
show-overflow-tooltip
|
|
||||||
/>
|
|
||||||
<el-table-column
|
|
||||||
prop="customerName"
|
|
||||||
label="客户名称"
|
|
||||||
min-width="160"
|
|
||||||
show-overflow-tooltip
|
show-overflow-tooltip
|
||||||
/>
|
/>
|
||||||
<el-table-column
|
<el-table-column
|
||||||
@@ -662,48 +663,118 @@
|
|||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column
|
<el-table-column
|
||||||
prop="cargoName"
|
prop="goodsInfo"
|
||||||
label="货物名称"
|
label="货物信息"
|
||||||
min-width="140"
|
min-width="260"
|
||||||
show-overflow-tooltip
|
show-overflow-tooltip
|
||||||
/>
|
>
|
||||||
<el-table-column
|
<template #default="{ row }">
|
||||||
prop="cargoType"
|
{{ formatCandidateWaybillValue(row, 'goodsInfo') }}
|
||||||
label="货物类型"
|
</template>
|
||||||
min-width="140"
|
</el-table-column>
|
||||||
show-overflow-tooltip
|
|
||||||
/>
|
|
||||||
<el-table-column
|
<el-table-column
|
||||||
prop="departureAddress"
|
prop="departureAddress"
|
||||||
label="发货地址"
|
label="发货地址"
|
||||||
min-width="220"
|
min-width="220"
|
||||||
show-overflow-tooltip
|
show-overflow-tooltip
|
||||||
/>
|
>
|
||||||
|
<template #default="{ row }">
|
||||||
|
{{ formatCandidateAddress(row, 'departure') }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
<el-table-column
|
<el-table-column
|
||||||
prop="arrivalAddress"
|
prop="arrivalAddress"
|
||||||
label="收货地址"
|
label="收货地址"
|
||||||
min-width="220"
|
min-width="220"
|
||||||
show-overflow-tooltip
|
show-overflow-tooltip
|
||||||
/>
|
>
|
||||||
|
<template #default="{ row }">
|
||||||
|
{{ formatCandidateAddress(row, 'arrival') }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
<el-table-column
|
<el-table-column
|
||||||
prop="vehicleNo"
|
prop="contractName"
|
||||||
label="车/船/航班/班列"
|
label="客户合同"
|
||||||
min-width="160"
|
min-width="160"
|
||||||
show-overflow-tooltip
|
show-overflow-tooltip
|
||||||
/>
|
/>
|
||||||
<el-table-column
|
<el-table-column
|
||||||
prop="carrierName"
|
prop="planName"
|
||||||
label="承运商名称"
|
label="计划名称"
|
||||||
min-width="160"
|
min-width="150"
|
||||||
show-overflow-tooltip
|
show-overflow-tooltip
|
||||||
/>
|
/>
|
||||||
<el-table-column prop="driverName" label="司机" min-width="120" show-overflow-tooltip />
|
|
||||||
<el-table-column
|
<el-table-column
|
||||||
prop="originalNo"
|
prop="originalNo"
|
||||||
label="原始单号"
|
label="原始单号"
|
||||||
min-width="130"
|
min-width="140"
|
||||||
show-overflow-tooltip
|
show-overflow-tooltip
|
||||||
/>
|
/>
|
||||||
|
<el-table-column
|
||||||
|
prop="relationNo"
|
||||||
|
label="关联单号"
|
||||||
|
min-width="140"
|
||||||
|
show-overflow-tooltip
|
||||||
|
/>
|
||||||
|
<el-table-column prop="remark" label="备注" min-width="180" show-overflow-tooltip />
|
||||||
|
<el-table-column
|
||||||
|
prop="startDate"
|
||||||
|
label="实际发货时间"
|
||||||
|
min-width="170"
|
||||||
|
show-overflow-tooltip
|
||||||
|
>
|
||||||
|
<template #default="{ row }">
|
||||||
|
{{ formatCandidateDate(row.startDate) }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column
|
||||||
|
prop="endDate"
|
||||||
|
label="实际完成时间"
|
||||||
|
min-width="170"
|
||||||
|
show-overflow-tooltip
|
||||||
|
>
|
||||||
|
<template #default="{ row }">
|
||||||
|
{{ formatCandidateDate(row.endDate) }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column
|
||||||
|
prop="createTime"
|
||||||
|
label="创建时间"
|
||||||
|
min-width="170"
|
||||||
|
show-overflow-tooltip
|
||||||
|
>
|
||||||
|
<template #default="{ row }">
|
||||||
|
{{ formatCandidateDate(row.createTime) }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column
|
||||||
|
prop="updateTime"
|
||||||
|
label="更新时间"
|
||||||
|
min-width="170"
|
||||||
|
show-overflow-tooltip
|
||||||
|
>
|
||||||
|
<template #default="{ row }">
|
||||||
|
{{ formatCandidateDate(row.updateTime) }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column
|
||||||
|
prop="businessStatus"
|
||||||
|
label="状态"
|
||||||
|
width="110"
|
||||||
|
fixed="right"
|
||||||
|
align="center"
|
||||||
|
>
|
||||||
|
<template #default="{ row }">
|
||||||
|
<span
|
||||||
|
:class="[
|
||||||
|
'loading-manage-page__status',
|
||||||
|
`is-${row.businessStatus || 'unknown'}`,
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
{{ formatCandidateWaybillValue(row, 'businessStatus') }}
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
<div class="loading-manage-dialog__table-toolbar">
|
<div class="loading-manage-dialog__table-toolbar">
|
||||||
<el-button
|
<el-button
|
||||||
@@ -734,7 +805,10 @@
|
|||||||
<el-table-column type="index" label="序号" width="70" align="center" />
|
<el-table-column type="index" label="序号" width="70" align="center" />
|
||||||
<el-table-column prop="waybillNo" label="运单号" min-width="150">
|
<el-table-column prop="waybillNo" label="运单号" min-width="150">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-link type="primary">{{ row.waybillNo }}</el-link>
|
<el-link v-if="row.waybillNo" type="primary" @click.stop="openWaybillDetail(row)">
|
||||||
|
{{ row.waybillNo }}
|
||||||
|
</el-link>
|
||||||
|
<span v-else>-</span>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="projectName" label="项目名称" min-width="160" />
|
<el-table-column prop="projectName" label="项目名称" min-width="160" />
|
||||||
@@ -1009,6 +1083,7 @@ import {
|
|||||||
loadingStatusMap,
|
loadingStatusMap,
|
||||||
loadingStatusOptions,
|
loadingStatusOptions,
|
||||||
} from '@/option/business/loading-manage';
|
} from '@/option/business/loading-manage';
|
||||||
|
import { option as waybillOption } from '@/option/business/waybill-manage';
|
||||||
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';
|
||||||
@@ -1377,6 +1452,27 @@ export default {
|
|||||||
statusText(row) {
|
statusText(row) {
|
||||||
return row.businessStatusName || loadingStatusMap[row.businessStatus] || '未知';
|
return row.businessStatusName || loadingStatusMap[row.businessStatus] || '未知';
|
||||||
},
|
},
|
||||||
|
formatCandidateWaybillValue(row, prop) {
|
||||||
|
if (!row) return '-';
|
||||||
|
if (prop === 'businessStatus') return this.statusText(row);
|
||||||
|
if (prop === 'transportType') return this.transportTypeLabel(row.transportType);
|
||||||
|
const column = waybillOption.column.find(item => item.prop === prop);
|
||||||
|
if (column?.formatter) {
|
||||||
|
const value = column.formatter(row, {}, row[prop]);
|
||||||
|
return value === undefined || value === null || value === '' ? '-' : value;
|
||||||
|
}
|
||||||
|
const value = row[prop];
|
||||||
|
return value === undefined || value === null || value === '' ? '-' : value;
|
||||||
|
},
|
||||||
|
formatCandidateDate(value) {
|
||||||
|
if (!value) return '-';
|
||||||
|
const date = dayjs(value);
|
||||||
|
return date.isValid() ? date.format('YYYY-MM-DD HH:mm:ss') : value;
|
||||||
|
},
|
||||||
|
formatCandidateAddress(row, type) {
|
||||||
|
const value = row?.[`${type}Address`] || row?.[`${type}Name`] || '';
|
||||||
|
return this.formatProvinceCityDistrict(value) || '-';
|
||||||
|
},
|
||||||
splitText(value) {
|
splitText(value) {
|
||||||
if (!value) return [];
|
if (!value) return [];
|
||||||
return String(value)
|
return String(value)
|
||||||
@@ -1924,15 +2020,17 @@ export default {
|
|||||||
async loadCandidateWaybills() {
|
async loadCandidateWaybills() {
|
||||||
this.candidateLoading = true;
|
this.candidateLoading = true;
|
||||||
try {
|
try {
|
||||||
const res = await getWaybillList(
|
const query = {
|
||||||
this.candidatePage.currentPage,
|
|
||||||
this.candidatePage.pageSize,
|
|
||||||
{
|
|
||||||
...this.buildQueryParams(this.candidateQuery),
|
...this.buildQueryParams(this.candidateQuery),
|
||||||
businessStatus: 'pending',
|
businessStatus: 'pending',
|
||||||
onlyUnassignedLoading: 1,
|
onlyUnassignedLoading: 1,
|
||||||
...(this.dialogMode === 'add' ? { transportType: 'road' } : {}),
|
...(this.dialogMode === 'add' ? { transportType: 'road' } : {}),
|
||||||
}
|
};
|
||||||
|
const transportType = this.getCandidateTransportTypeQuery(query.transportType);
|
||||||
|
const res = await getWaybillList(
|
||||||
|
this.candidatePage.currentPage,
|
||||||
|
this.candidatePage.pageSize,
|
||||||
|
{ ...query, transportType }
|
||||||
);
|
);
|
||||||
const page = unwrapPage(res);
|
const page = unwrapPage(res);
|
||||||
this.candidateRows = page.records;
|
this.candidateRows = page.records;
|
||||||
@@ -1941,6 +2039,11 @@ export default {
|
|||||||
this.candidateLoading = false;
|
this.candidateLoading = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
getCandidateTransportTypeQuery(value) {
|
||||||
|
const normalized = String(value || '').trim().toLowerCase();
|
||||||
|
if (['road', '公路运输'].includes(normalized)) return 'road,公路运输';
|
||||||
|
return value || '';
|
||||||
|
},
|
||||||
openCandidateSearch() {
|
openCandidateSearch() {
|
||||||
this.candidateSearchVisible = true;
|
this.candidateSearchVisible = true;
|
||||||
this.loadCandidateWaybills();
|
this.loadCandidateWaybills();
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<basic-container class="master-order-page">
|
<basic-container class="master-order-page">
|
||||||
<template v-if="mode === 'list'">
|
<template v-if="mode === 'list'">
|
||||||
<el-form :model="query" class="master-search" label-width="88px" @submit.prevent>
|
<el-form :model="query" class="master-search" label-width="160px" @submit.prevent>
|
||||||
<el-row :gutter="16">
|
<el-row :gutter="16">
|
||||||
<el-col v-for="field in primaryFields" :key="field.prop" :span="6"
|
<el-col v-for="field in primaryFields" :key="field.prop" :span="6"
|
||||||
><el-form-item :label="field.label"
|
><el-form-item :label="field.label"
|
||||||
@@ -330,23 +330,100 @@ export default {
|
|||||||
const key = keys.find(item => route[item] !== undefined && route[item] !== null);
|
const key = keys.find(item => route[item] !== undefined && route[item] !== null);
|
||||||
return key ? route[key] : 0;
|
return key ? route[key] : 0;
|
||||||
},
|
},
|
||||||
|
isRoadTransportType(type) {
|
||||||
|
const value = String(type || '').trim().toLowerCase();
|
||||||
|
return value === 'road' || value.includes('公路');
|
||||||
|
},
|
||||||
|
formatRoadAddress(value) {
|
||||||
|
const text = String(value || '').replace(/\s+/g, '');
|
||||||
|
if (!text) return '-';
|
||||||
|
// 公路地址按“市 区县”展示,兼容省市区拼接及历史上只保存市/区县的值。
|
||||||
|
const withoutProvince = text.replace(/^.*?(?:省|自治区|特别行政区)/, '');
|
||||||
|
// “梅州市”同时包含“州”和“市”,避免误把城市截断为“梅州”。
|
||||||
|
const cityMatch = withoutProvince.match(/.+?(?:市|州(?!市)|盟(?!市))/);
|
||||||
|
if (!cityMatch) return text;
|
||||||
|
const city = cityMatch[0];
|
||||||
|
const districtSource = withoutProvince.slice(city.length);
|
||||||
|
const districtMatch = districtSource.match(/^[^市州盟]*?(?:区|县|旗)/);
|
||||||
|
if (!districtMatch) return city;
|
||||||
|
// 兼容“梅县区”这类名称,正则首次命中“县”后还需保留后面的“区”。
|
||||||
|
const district = `${districtMatch[0]}${
|
||||||
|
districtSource.slice(districtMatch[0].length).startsWith('区') ? '区' : ''
|
||||||
|
}`;
|
||||||
|
return `${city} ${district}`;
|
||||||
|
},
|
||||||
|
routeNodeName(value, address, transportType, region = {}) {
|
||||||
|
const text = String(value || '').trim();
|
||||||
|
if (!text) return '-';
|
||||||
|
const siteCode = String(region.siteCode || '').trim();
|
||||||
|
// 运输方式切换不应改变地址本身的展示模式:有站点编码时显示站点名称,
|
||||||
|
// 没有站点编码的区域地址统一按“市 区县”格式展示。
|
||||||
|
if (siteCode && siteCode !== '/') return text;
|
||||||
|
// 优先从完整的地址名称/详细地址解析,避免后端只返回“梅州”等不完整的市名称时
|
||||||
|
// 提前返回城市,导致区县被遗漏。
|
||||||
|
const parsedText = this.formatRoadAddress(text);
|
||||||
|
if (parsedText.includes(' ')) return parsedText;
|
||||||
|
const parsedAddress = this.formatRoadAddress(address);
|
||||||
|
if (parsedAddress.includes(' ')) return parsedAddress;
|
||||||
|
const city = String(region.cityName || '').trim();
|
||||||
|
const district = String(region.districtName || '').trim();
|
||||||
|
if (city && district) return `${city} ${district}`;
|
||||||
|
if (city) return city;
|
||||||
|
if (district && address) {
|
||||||
|
const formattedAddress = this.formatRoadAddress(address);
|
||||||
|
const addressCity = formattedAddress.split(' ')[0];
|
||||||
|
if (addressCity && addressCity !== formattedAddress) return `${addressCity} ${district}`;
|
||||||
|
}
|
||||||
|
const isRegional =
|
||||||
|
/省|自治区|特别行政区/.test(text) ||
|
||||||
|
/(?:市|州|盟).*?(?:区|县|旗)/.test(text) ||
|
||||||
|
/(?:区|县|旗)$/.test(text);
|
||||||
|
if (!isRegional) return text;
|
||||||
|
// 区县简称(如“西乡塘区”)从详细地址中补齐所属城市。
|
||||||
|
const source = /省|自治区|特别行政区|市|州|盟/.test(text) ? text : address || text;
|
||||||
|
const formatted = this.formatRoadAddress(source);
|
||||||
|
if (/(?:区|县|旗)$/.test(text) && !formatted.includes(text)) {
|
||||||
|
const city = formatted.split(' ')[0];
|
||||||
|
return city && city !== formatted ? `${city} ${text}` : formatted;
|
||||||
|
}
|
||||||
|
return formatted;
|
||||||
|
},
|
||||||
routeNodes(row = {}) {
|
routeNodes(row = {}) {
|
||||||
const routes = row.routeProgress || [];
|
const routes = row.routeProgress || [];
|
||||||
const total = this.routeNumber(row.totalQuantity);
|
const total = this.routeNumber(row.totalQuantity);
|
||||||
|
const firstTransportType = routes[0]?.transportType || row.routes?.[0]?.transportType || row.transportType || '';
|
||||||
if (!routes.length) {
|
if (!routes.length) {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
key: 'start',
|
key: 'start',
|
||||||
type: 'start',
|
type: 'start',
|
||||||
text: '起',
|
text: '起',
|
||||||
name: row.departureName || row.departureAddress || '-',
|
name: this.routeNodeName(
|
||||||
|
row.departureName || row.departureAddress,
|
||||||
|
row.departureAddress,
|
||||||
|
firstTransportType,
|
||||||
|
{
|
||||||
|
cityName: row.departureCityName,
|
||||||
|
districtName: row.departureDistrictName,
|
||||||
|
siteCode: row.departureSiteCode,
|
||||||
|
}
|
||||||
|
),
|
||||||
lines: [`已调度0/${total}吨`],
|
lines: [`已调度0/${total}吨`],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'end',
|
key: 'end',
|
||||||
type: 'end',
|
type: 'end',
|
||||||
text: '终',
|
text: '终',
|
||||||
name: row.arrivalName || row.arrivalAddress || '-',
|
name: this.routeNodeName(
|
||||||
|
row.arrivalName || row.arrivalAddress,
|
||||||
|
row.arrivalAddress,
|
||||||
|
row.finalTransportType || firstTransportType,
|
||||||
|
{
|
||||||
|
cityName: row.arrivalCityName,
|
||||||
|
districtName: row.arrivalDistrictName,
|
||||||
|
siteCode: row.arrivalSiteCode,
|
||||||
|
}
|
||||||
|
),
|
||||||
lines: [`到达0/${total}吨`],
|
lines: [`到达0/${total}吨`],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
@@ -356,7 +433,16 @@ export default {
|
|||||||
key: 'start',
|
key: 'start',
|
||||||
type: 'start',
|
type: 'start',
|
||||||
text: '起',
|
text: '起',
|
||||||
name: row.departureName || row.departureAddress || '-',
|
name: this.routeNodeName(
|
||||||
|
row.departureName || row.departureAddress,
|
||||||
|
row.departureAddress,
|
||||||
|
firstTransportType,
|
||||||
|
{
|
||||||
|
cityName: row.departureCityName,
|
||||||
|
districtName: row.departureDistrictName,
|
||||||
|
siteCode: row.departureSiteCode,
|
||||||
|
}
|
||||||
|
),
|
||||||
lines: [`已调度${this.routeNumber(routes[0]?.dispatchedQuantity)}/${total}吨`],
|
lines: [`已调度${this.routeNumber(routes[0]?.dispatchedQuantity)}/${total}吨`],
|
||||||
},
|
},
|
||||||
...routes.map((route, index) => {
|
...routes.map((route, index) => {
|
||||||
@@ -367,7 +453,16 @@ export default {
|
|||||||
key: route.segmentNo || `route-${index}`,
|
key: route.segmentNo || `route-${index}`,
|
||||||
type: isEnd ? 'end' : 'middle',
|
type: isEnd ? 'end' : 'middle',
|
||||||
text: isEnd ? '终' : '经',
|
text: isEnd ? '终' : '经',
|
||||||
name: route.arrivalName || route.departureName || row.arrivalName || '-',
|
name: this.routeNodeName(
|
||||||
|
route.arrivalName || route.departureName || row.arrivalName,
|
||||||
|
route.arrivalAddress || route.departureAddress,
|
||||||
|
route.transportType,
|
||||||
|
{
|
||||||
|
cityName: route.arrivalCityName || route.departureCityName,
|
||||||
|
districtName: route.arrivalDistrictName || route.departureDistrictName,
|
||||||
|
siteCode: route.arrivalSiteCode || route.departureSiteCode,
|
||||||
|
}
|
||||||
|
),
|
||||||
lines: isEnd
|
lines: isEnd
|
||||||
? [`到达${arrived}/${total}吨`]
|
? [`到达${arrived}/${total}吨`]
|
||||||
: [`到达 ${arrived}/${total}吨`, `已调度 ${dispatched}/到达${arrived}/${total}吨`],
|
: [`到达 ${arrived}/${total}吨`, `已调度 ${dispatched}/到达${arrived}/${total}吨`],
|
||||||
@@ -412,6 +507,9 @@ export default {
|
|||||||
.el-date-editor {
|
.el-date-editor {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
:deep(.el-form-item__label) {
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
.search-actions {
|
.search-actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
|
|||||||
@@ -157,7 +157,7 @@
|
|||||||
:disabled="isBasicInfoReadonly"
|
:disabled="isBasicInfoReadonly"
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="项目简称" prop="projectShortName">
|
<el-form-item label="项目简称" prop="projectShortName" required>
|
||||||
<el-input
|
<el-input
|
||||||
v-model="form.projectShortName"
|
v-model="form.projectShortName"
|
||||||
placeholder="请填写项目简称"
|
placeholder="请填写项目简称"
|
||||||
@@ -512,6 +512,12 @@
|
|||||||
<div class="project-apply-form__material-card">
|
<div class="project-apply-form__material-card">
|
||||||
<div class="project-apply-form__material-head">
|
<div class="project-apply-form__material-head">
|
||||||
<div class="dialog-section-title project-apply-form__material-title">项目材料</div>
|
<div class="dialog-section-title project-apply-form__material-title">项目材料</div>
|
||||||
|
<span
|
||||||
|
v-if="!dialogReadonly && missingRequiredAttachmentTypes.length"
|
||||||
|
class="project-apply-form__material-warn"
|
||||||
|
>
|
||||||
|
未上传:{{ missingRequiredAttachmentTypes.join('、') }}
|
||||||
|
</span>
|
||||||
<el-button
|
<el-button
|
||||||
type="primary"
|
type="primary"
|
||||||
:disabled="!attachmentRows.length"
|
:disabled="!attachmentRows.length"
|
||||||
@@ -569,8 +575,7 @@
|
|||||||
<el-table-column prop="uploadUserName" label="上传人" min-width="140" align="center" />
|
<el-table-column prop="uploadUserName" label="上传人" min-width="140" align="center" />
|
||||||
<el-table-column prop="uploadTime" label="上传时间" min-width="170" align="center" sortable/>
|
<el-table-column prop="uploadTime" label="上传时间" min-width="170" align="center" sortable/>
|
||||||
<el-table-column label="操作" width="110" align="center">
|
<el-table-column label="操作" width="110" align="center">
|
||||||
<template #default="{ row, $index }">
|
<template #default="{ $index }">
|
||||||
<el-link type="primary" @click="downloadAttachment(row)">下载</el-link>
|
|
||||||
<el-link v-if="!dialogReadonly" type="danger" @click="removeAttachment($index)"
|
<el-link v-if="!dialogReadonly" type="danger" @click="removeAttachment($index)"
|
||||||
>删除</el-link
|
>删除</el-link
|
||||||
>
|
>
|
||||||
@@ -590,11 +595,14 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="!isChangeDialog" class="dialog-section-title project-apply-form__table-title">
|
<div
|
||||||
|
v-if="!isChangeDialog && !isNewProjectDialog"
|
||||||
|
class="dialog-section-title project-apply-form__table-title"
|
||||||
|
>
|
||||||
变更记录
|
变更记录
|
||||||
</div>
|
</div>
|
||||||
<el-table
|
<el-table
|
||||||
v-if="!isChangeDialog"
|
v-if="!isChangeDialog && !isNewProjectDialog"
|
||||||
:data="changeRows"
|
:data="changeRows"
|
||||||
border
|
border
|
||||||
class="project-apply-form__table"
|
class="project-apply-form__table"
|
||||||
@@ -1174,6 +1182,9 @@ export default {
|
|||||||
isChangeDialog() {
|
isChangeDialog() {
|
||||||
return this.dialogType === 'change';
|
return this.dialogType === 'change';
|
||||||
},
|
},
|
||||||
|
isNewProjectDialog() {
|
||||||
|
return ['add', 'majorSupplement'].includes(this.dialogType);
|
||||||
|
},
|
||||||
isProjectFormPage() {
|
isProjectFormPage() {
|
||||||
return this.$route.path === '/business/project-apply/form';
|
return this.$route.path === '/business/project-apply/form';
|
||||||
},
|
},
|
||||||
@@ -1693,6 +1704,10 @@ export default {
|
|||||||
if (!options.includeChangeType) {
|
if (!options.includeChangeType) {
|
||||||
delete submitRow.changeType;
|
delete submitRow.changeType;
|
||||||
}
|
}
|
||||||
|
if (this.isNewProjectDialog && !options.includeChangeType) {
|
||||||
|
delete submitRow.changeContent;
|
||||||
|
delete submitRow.changeReason;
|
||||||
|
}
|
||||||
['projectIntro', 'profitRemark', 'riskPoint', 'emergencyPlan'].forEach(
|
['projectIntro', 'profitRemark', 'riskPoint', 'emergencyPlan'].forEach(
|
||||||
key => delete submitRow[key]
|
key => delete submitRow[key]
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -438,20 +438,16 @@
|
|||||||
class="settlement-detail-page__dialog-form"
|
class="settlement-detail-page__dialog-form"
|
||||||
>
|
>
|
||||||
<el-form-item label="更新范围" prop="contractId">
|
<el-form-item label="更新范围" prop="contractId">
|
||||||
<el-select
|
<el-input
|
||||||
v-model="updateFeeForm.contractId"
|
:model-value="updateContractName"
|
||||||
clearable
|
class="settlement-detail-page__generate-select"
|
||||||
filterable
|
readonly
|
||||||
placeholder="请选择合同"
|
placeholder="请选择合同"
|
||||||
@change="handleUpdateContractChange"
|
|
||||||
>
|
>
|
||||||
<el-option
|
<template #append>
|
||||||
v-for="item in updateContractOptions"
|
<el-button @click="openUpdateContractDialog">选择</el-button>
|
||||||
:key="item.id"
|
</template>
|
||||||
:label="item.contractName"
|
</el-input>
|
||||||
:value="item.id"
|
|
||||||
/>
|
|
||||||
</el-select>
|
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="合同计费方案" prop="billingPlanId">
|
<el-form-item label="合同计费方案" prop="billingPlanId">
|
||||||
<el-select
|
<el-select
|
||||||
@@ -817,35 +813,112 @@
|
|||||||
ref="generateContractTable"
|
ref="generateContractTable"
|
||||||
v-loading="generateContractDialog.loading"
|
v-loading="generateContractDialog.loading"
|
||||||
:data="generateContractDialog.rows"
|
:data="generateContractDialog.rows"
|
||||||
|
style="width: 100%"
|
||||||
border
|
border
|
||||||
highlight-current-row
|
highlight-current-row
|
||||||
@current-change="generateContractDialog.current = $event"
|
@current-change="generateContractDialog.current = $event"
|
||||||
@row-dblclick="selectGenerateContract"
|
@row-dblclick="selectContract"
|
||||||
>
|
>
|
||||||
<el-table-column type="index" label="序号" width="64" align="center" />
|
<el-table-column type="index" label="序号" width="64" align="center" />
|
||||||
<el-table-column
|
<el-table-column
|
||||||
v-for="column in contractSelectionColumns"
|
prop="contractNo"
|
||||||
:key="column.prop"
|
label="合同编号"
|
||||||
:prop="column.prop"
|
min-width="180"
|
||||||
:label="column.label"
|
|
||||||
:min-width="column.minWidth"
|
|
||||||
align="center"
|
align="center"
|
||||||
show-overflow-tooltip
|
show-overflow-tooltip
|
||||||
>
|
:formatter="formatContractSelectionCell"
|
||||||
<template #default="{ row }">
|
/>
|
||||||
<span v-if="column.prop === 'organizationName'">{{
|
<el-table-column
|
||||||
row.organizationName || row.deptName || '-'
|
prop="contractName"
|
||||||
}}</span>
|
label="合同名称"
|
||||||
<span v-else-if="column.prop === 'contractCategory'">{{
|
min-width="220"
|
||||||
contractCategoryName(row.contractCategory)
|
align="center"
|
||||||
}}</span>
|
show-overflow-tooltip
|
||||||
<span v-else-if="column.prop === 'signType'">{{ signTypeName(row.signType) }}</span>
|
:formatter="formatContractSelectionCell"
|
||||||
<span v-else>{{ displayValue(row[column.prop]) }}</span>
|
/>
|
||||||
</template>
|
<el-table-column
|
||||||
</el-table-column>
|
prop="projectName"
|
||||||
|
label="所属项目"
|
||||||
|
min-width="170"
|
||||||
|
align="center"
|
||||||
|
show-overflow-tooltip
|
||||||
|
:formatter="formatContractSelectionCell"
|
||||||
|
/>
|
||||||
|
<el-table-column
|
||||||
|
prop="organizationName"
|
||||||
|
label="所属组织"
|
||||||
|
min-width="170"
|
||||||
|
align="center"
|
||||||
|
show-overflow-tooltip
|
||||||
|
:formatter="formatContractSelectionCell"
|
||||||
|
/>
|
||||||
|
<el-table-column
|
||||||
|
prop="contractCategory"
|
||||||
|
label="合同类别"
|
||||||
|
min-width="130"
|
||||||
|
align="center"
|
||||||
|
show-overflow-tooltip
|
||||||
|
:formatter="formatContractSelectionCell"
|
||||||
|
/>
|
||||||
|
<el-table-column
|
||||||
|
prop="signType"
|
||||||
|
label="签约类型"
|
||||||
|
min-width="120"
|
||||||
|
align="center"
|
||||||
|
show-overflow-tooltip
|
||||||
|
:formatter="formatContractSelectionCell"
|
||||||
|
/>
|
||||||
|
<el-table-column
|
||||||
|
prop="partyA"
|
||||||
|
label="甲方"
|
||||||
|
min-width="170"
|
||||||
|
align="center"
|
||||||
|
show-overflow-tooltip
|
||||||
|
:formatter="formatContractSelectionCell"
|
||||||
|
/>
|
||||||
|
<el-table-column
|
||||||
|
prop="partyB"
|
||||||
|
label="乙方"
|
||||||
|
min-width="170"
|
||||||
|
align="center"
|
||||||
|
show-overflow-tooltip
|
||||||
|
:formatter="formatContractSelectionCell"
|
||||||
|
/>
|
||||||
|
<el-table-column
|
||||||
|
prop="startDate"
|
||||||
|
label="开始日期"
|
||||||
|
min-width="130"
|
||||||
|
align="center"
|
||||||
|
show-overflow-tooltip
|
||||||
|
:formatter="formatContractSelectionCell"
|
||||||
|
/>
|
||||||
|
<el-table-column
|
||||||
|
prop="endDate"
|
||||||
|
label="结束日期"
|
||||||
|
min-width="130"
|
||||||
|
align="center"
|
||||||
|
show-overflow-tooltip
|
||||||
|
:formatter="formatContractSelectionCell"
|
||||||
|
/>
|
||||||
|
<el-table-column
|
||||||
|
prop="temporaryStartDate"
|
||||||
|
label="临时效力起"
|
||||||
|
min-width="130"
|
||||||
|
align="center"
|
||||||
|
show-overflow-tooltip
|
||||||
|
:formatter="formatContractSelectionCell"
|
||||||
|
/>
|
||||||
|
<el-table-column
|
||||||
|
prop="temporaryEndDate"
|
||||||
|
label="临时效力止"
|
||||||
|
min-width="130"
|
||||||
|
align="center"
|
||||||
|
show-overflow-tooltip
|
||||||
|
:formatter="formatContractSelectionCell"
|
||||||
|
/>
|
||||||
<el-table-column label="操作" width="90" fixed="right" align="center">
|
<el-table-column label="操作" width="90" fixed="right" align="center">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-link type="primary" @click.stop="selectGenerateContract(row)">选择</el-link>
|
<el-link type="primary" @click.stop="selectContract(row)">选择</el-link>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
@@ -862,7 +935,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<el-button @click="generateContractDialog.visible = false">取消</el-button>
|
<el-button @click="generateContractDialog.visible = false">取消</el-button>
|
||||||
<el-button type="primary" @click="selectGenerateContract(generateContractDialog.current)">
|
<el-button type="primary" @click="selectContract(generateContractDialog.current)">
|
||||||
确定
|
确定
|
||||||
</el-button>
|
</el-button>
|
||||||
</template>
|
</template>
|
||||||
@@ -900,21 +973,6 @@ import {
|
|||||||
signTypeOptions,
|
signTypeOptions,
|
||||||
} from '@/option/business/common';
|
} from '@/option/business/common';
|
||||||
|
|
||||||
const contractSelectionColumns = [
|
|
||||||
{ prop: 'contractNo', label: '合同编号', minWidth: 180 },
|
|
||||||
{ prop: 'contractName', label: '合同名称', minWidth: 220 },
|
|
||||||
{ prop: 'projectName', label: '所属项目', minWidth: 170 },
|
|
||||||
{ prop: 'organizationName', label: '所属组织', minWidth: 170 },
|
|
||||||
{ prop: 'contractCategory', label: '合同类别', minWidth: 130 },
|
|
||||||
{ prop: 'signType', label: '签约类型', minWidth: 120 },
|
|
||||||
{ prop: 'partyA', label: '甲方', minWidth: 170 },
|
|
||||||
{ prop: 'partyB', label: '乙方', minWidth: 170 },
|
|
||||||
{ prop: 'startDate', label: '开始日期', minWidth: 130 },
|
|
||||||
{ prop: 'endDate', label: '结束日期', minWidth: 130 },
|
|
||||||
{ prop: 'temporaryStartDate', label: '临时效力起', minWidth: 130 },
|
|
||||||
{ prop: 'temporaryEndDate', label: '临时效力止', minWidth: 130 },
|
|
||||||
];
|
|
||||||
|
|
||||||
const ADJUST_BILLING_ELEMENTS = [
|
const ADJUST_BILLING_ELEMENTS = [
|
||||||
'按重量',
|
'按重量',
|
||||||
'按体积',
|
'按体积',
|
||||||
@@ -1008,7 +1066,6 @@ export default {
|
|||||||
effectiveTypeOptions,
|
effectiveTypeOptions,
|
||||||
contractStageOptions,
|
contractStageOptions,
|
||||||
contractApprovalStatusOptions,
|
contractApprovalStatusOptions,
|
||||||
contractSelectionColumns,
|
|
||||||
transportTypeOptions: [],
|
transportTypeOptions: [],
|
||||||
billingPlanOptions: [],
|
billingPlanOptions: [],
|
||||||
transferDialog: { visible: false, loading: false, submitting: false },
|
transferDialog: { visible: false, loading: false, submitting: false },
|
||||||
@@ -1019,6 +1076,7 @@ export default {
|
|||||||
transferPage: { current: 1, size: 10, total: 0 },
|
transferPage: { current: 1, size: 10, total: 0 },
|
||||||
generateDialog: { visible: false, loading: false },
|
generateDialog: { visible: false, loading: false },
|
||||||
generateQuery: {},
|
generateQuery: {},
|
||||||
|
contractDialogMode: 'generate',
|
||||||
generateContractDialog: {
|
generateContractDialog: {
|
||||||
visible: false,
|
visible: false,
|
||||||
loading: false,
|
loading: false,
|
||||||
@@ -1053,24 +1111,16 @@ export default {
|
|||||||
);
|
);
|
||||||
return contract?.contractName || '';
|
return contract?.contractName || '';
|
||||||
},
|
},
|
||||||
|
updateContractName() {
|
||||||
|
const contractId = this.updateFeeForm.contractId;
|
||||||
|
const contract = [...this.updateContractOptions, ...this.contractOptions].find(
|
||||||
|
item => String(item.id) === String(contractId)
|
||||||
|
);
|
||||||
|
return contract?.contractName || '';
|
||||||
|
},
|
||||||
generateContractCategory() {
|
generateContractCategory() {
|
||||||
return this.settlementType === 'payable' ? '承运商合同' : '客户合同';
|
return this.settlementType === 'payable' ? '承运商合同' : '客户合同';
|
||||||
},
|
},
|
||||||
contractCategoryName(value) {
|
|
||||||
return (
|
|
||||||
this.contractCategoryOptions.find(item => String(item.value) === String(value))?.label ||
|
|
||||||
this.formatCell(value)
|
|
||||||
);
|
|
||||||
},
|
|
||||||
signTypeName(value) {
|
|
||||||
return (
|
|
||||||
this.signTypeOptions.find(item => String(item.value) === String(value))?.label ||
|
|
||||||
this.formatCell(value)
|
|
||||||
);
|
|
||||||
},
|
|
||||||
displayValue(value) {
|
|
||||||
return this.formatCell(value);
|
|
||||||
},
|
|
||||||
settlementTypeLabel() {
|
settlementTypeLabel() {
|
||||||
if (this.settlementType === 'receivable') return '应收';
|
if (this.settlementType === 'receivable') return '应收';
|
||||||
if (this.settlementType === 'payable') return '应付';
|
if (this.settlementType === 'payable') return '应付';
|
||||||
@@ -1158,6 +1208,21 @@ export default {
|
|||||||
this.clearAdjustCalculations();
|
this.clearAdjustCalculations();
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
contractCategoryName(value) {
|
||||||
|
return (
|
||||||
|
this.contractCategoryOptions.find(item => String(item.value) === String(value))?.label ||
|
||||||
|
this.formatCell(value)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
signTypeName(value) {
|
||||||
|
return (
|
||||||
|
this.signTypeOptions.find(item => String(item.value) === String(value))?.label ||
|
||||||
|
this.formatCell(value)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
displayValue(value) {
|
||||||
|
return this.formatCell(value);
|
||||||
|
},
|
||||||
async loadTransportTypeOptions() {
|
async loadTransportTypeOptions() {
|
||||||
const res = await getDictionary({ code: 'transport_type' });
|
const res = await getDictionary({ code: 'transport_type' });
|
||||||
const records = res.data?.data || [];
|
const records = res.data?.data || [];
|
||||||
@@ -1435,6 +1500,26 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
openGenerateContractDialog() {
|
openGenerateContractDialog() {
|
||||||
|
this.contractDialogMode = 'generate';
|
||||||
|
this.generateContractDialog.visible = true;
|
||||||
|
this.generateContractDialog.expanded = false;
|
||||||
|
this.generateContractDialog.current = null;
|
||||||
|
this.generateContractDialog.page.current = 1;
|
||||||
|
this.loadGenerateContracts();
|
||||||
|
},
|
||||||
|
openUpdateContractDialog() {
|
||||||
|
this.contractDialogMode = 'update';
|
||||||
|
this.generateContractDialog.query = {
|
||||||
|
contractNo: '',
|
||||||
|
contractName: '',
|
||||||
|
projectName: '',
|
||||||
|
organizationName: '',
|
||||||
|
contractCategory: '',
|
||||||
|
signType: '',
|
||||||
|
effectiveType: '',
|
||||||
|
contractStage: '',
|
||||||
|
approvalStatus: '',
|
||||||
|
};
|
||||||
this.generateContractDialog.visible = true;
|
this.generateContractDialog.visible = true;
|
||||||
this.generateContractDialog.expanded = false;
|
this.generateContractDialog.expanded = false;
|
||||||
this.generateContractDialog.current = null;
|
this.generateContractDialog.current = null;
|
||||||
@@ -1456,15 +1541,29 @@ export default {
|
|||||||
const data = this.unwrapPage(res);
|
const data = this.unwrapPage(res);
|
||||||
this.generateContractDialog.rows = data.records || [];
|
this.generateContractDialog.rows = data.records || [];
|
||||||
this.generateContractDialog.page.total = Number(data.total || 0);
|
this.generateContractDialog.page.total = Number(data.total || 0);
|
||||||
this.layoutGenerateContractTable();
|
|
||||||
} finally {
|
} finally {
|
||||||
this.generateContractDialog.loading = false;
|
this.generateContractDialog.loading = false;
|
||||||
|
this.layoutGenerateContractTable();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
layoutGenerateContractTable() {
|
layoutGenerateContractTable() {
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
|
window.requestAnimationFrame(() => {
|
||||||
this.$refs.generateContractTable?.doLayout?.();
|
this.$refs.generateContractTable?.doLayout?.();
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
formatContractSelectionCell(row, column, cellValue) {
|
||||||
|
if (column.property === 'organizationName') {
|
||||||
|
return this.displayValue(row.organizationName || row.deptName);
|
||||||
|
}
|
||||||
|
if (column.property === 'contractCategory') {
|
||||||
|
return this.contractCategoryName(row.contractCategory);
|
||||||
|
}
|
||||||
|
if (column.property === 'signType') {
|
||||||
|
return this.signTypeName(row.signType);
|
||||||
|
}
|
||||||
|
return this.displayValue(cellValue);
|
||||||
},
|
},
|
||||||
searchGenerateContracts() {
|
searchGenerateContracts() {
|
||||||
this.generateContractDialog.page.current = 1;
|
this.generateContractDialog.page.current = 1;
|
||||||
@@ -1488,7 +1587,7 @@ export default {
|
|||||||
this.generateContractDialog.page.current = 1;
|
this.generateContractDialog.page.current = 1;
|
||||||
this.loadGenerateContracts();
|
this.loadGenerateContracts();
|
||||||
},
|
},
|
||||||
selectGenerateContract(row) {
|
selectContract(row) {
|
||||||
if (!row) {
|
if (!row) {
|
||||||
this.$message.warning('请选择合同');
|
this.$message.warning('请选择合同');
|
||||||
return;
|
return;
|
||||||
@@ -1500,11 +1599,18 @@ export default {
|
|||||||
partyA: row.partyA || row.payerName,
|
partyA: row.partyA || row.payerName,
|
||||||
partyB: row.partyB || row.payeeName,
|
partyB: row.partyB || row.payeeName,
|
||||||
};
|
};
|
||||||
const index = this.contractOptions.findIndex(item => String(item.id) === String(contract.id));
|
const targetOptions =
|
||||||
if (index >= 0) this.contractOptions.splice(index, 1, contract);
|
this.contractDialogMode === 'update' ? this.updateContractOptions : this.contractOptions;
|
||||||
else this.contractOptions.push(contract);
|
const index = targetOptions.findIndex(item => String(item.id) === String(contract.id));
|
||||||
|
if (index >= 0) targetOptions.splice(index, 1, contract);
|
||||||
|
else targetOptions.push(contract);
|
||||||
|
if (this.contractDialogMode === 'update') {
|
||||||
|
this.updateFeeForm.contractId = contract.id;
|
||||||
|
this.handleUpdateContractChange(contract.id);
|
||||||
|
} else {
|
||||||
this.generateQuery.contractId = contract.id;
|
this.generateQuery.contractId = contract.id;
|
||||||
this.handleGenerateContractChange(contract.id);
|
this.handleGenerateContractChange(contract.id);
|
||||||
|
}
|
||||||
this.generateContractDialog.visible = false;
|
this.generateContractDialog.visible = false;
|
||||||
this.generateContractDialog.current = null;
|
this.generateContractDialog.current = null;
|
||||||
},
|
},
|
||||||
@@ -1918,22 +2024,30 @@ export default {
|
|||||||
this.changePage.current = 1;
|
this.changePage.current = 1;
|
||||||
this.loadChangeRecords();
|
this.loadChangeRecords();
|
||||||
},
|
},
|
||||||
async openUpdateFeeDialog(row) {
|
openUpdateFeeDialog(row) {
|
||||||
await this.loadUpdateFeeContracts();
|
|
||||||
this.updateFeeDialog = { ...this.updateFeeDialog, visible: true, row: row || null };
|
this.updateFeeDialog = { ...this.updateFeeDialog, visible: true, row: row || null };
|
||||||
this.updateFeeForm = {
|
this.updateFeeForm = {
|
||||||
settlementType: this.settlementType || undefined,
|
settlementType: this.settlementType || undefined,
|
||||||
contractId: row?.contractId || '',
|
contractId: row?.contractId || '',
|
||||||
billingPlanId: '',
|
billingPlanId: '',
|
||||||
};
|
};
|
||||||
|
if (row?.contractId) {
|
||||||
|
const contract = {
|
||||||
|
...row,
|
||||||
|
id: row.contractId,
|
||||||
|
deptId: row.deptId || row.organizationId,
|
||||||
|
deptName: row.deptName || row.organizationName,
|
||||||
|
partyA: row.partyA || row.payerName,
|
||||||
|
partyB: row.partyB || row.payeeName,
|
||||||
|
};
|
||||||
|
const index = this.updateContractOptions.findIndex(
|
||||||
|
item => String(item.id) === String(contract.id)
|
||||||
|
);
|
||||||
|
if (index >= 0) this.updateContractOptions.splice(index, 1, contract);
|
||||||
|
else this.updateContractOptions.push(contract);
|
||||||
|
}
|
||||||
this.handleUpdateContractChange(this.updateFeeForm.contractId);
|
this.handleUpdateContractChange(this.updateFeeForm.contractId);
|
||||||
},
|
},
|
||||||
async loadUpdateFeeContracts() {
|
|
||||||
const res = await api.getUpdateFeeContracts({
|
|
||||||
settlementType: this.settlementType || undefined,
|
|
||||||
});
|
|
||||||
this.updateContractOptions = res.data?.data || [];
|
|
||||||
},
|
|
||||||
handleUpdateContractChange(contractId) {
|
handleUpdateContractChange(contractId) {
|
||||||
this.updateFeeForm.billingPlanId = '';
|
this.updateFeeForm.billingPlanId = '';
|
||||||
if (!contractId) {
|
if (!contractId) {
|
||||||
@@ -2392,7 +2506,7 @@ export default {
|
|||||||
const currency = row.currency || 'RMB';
|
const currency = row.currency || 'RMB';
|
||||||
return {
|
return {
|
||||||
...row,
|
...row,
|
||||||
unitPriceText: this.money(row.unitPrice, currency),
|
unitPriceText: this.hasMultipleCargo(row) ? '-' : this.money(row.unitPrice, currency),
|
||||||
freightAmountText: this.money(row.freightAmount, currency),
|
freightAmountText: this.money(row.freightAmount, currency),
|
||||||
otherFeeAmountText: this.money(row.otherFeeAmount, currency),
|
otherFeeAmountText: this.money(row.otherFeeAmount, currency),
|
||||||
totalAmountText: this.money(row.totalAmount, currency),
|
totalAmountText: this.money(row.totalAmount, currency),
|
||||||
@@ -2402,6 +2516,67 @@ export default {
|
|||||||
'-',
|
'-',
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
parseCargoRows(value) {
|
||||||
|
if (value === null || value === undefined || value === '') return [];
|
||||||
|
if (Array.isArray(value)) return value;
|
||||||
|
if (typeof value === 'object') return [value];
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(value);
|
||||||
|
if (Array.isArray(parsed)) return parsed;
|
||||||
|
return parsed && typeof parsed === 'object' ? [parsed] : [];
|
||||||
|
} catch (error) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
hasMultipleCargo(row = {}) {
|
||||||
|
const countFields = [
|
||||||
|
'cargoCount',
|
||||||
|
'goodsCount',
|
||||||
|
'cargoNum',
|
||||||
|
'goodsNum',
|
||||||
|
'cargoNumber',
|
||||||
|
'goodsNumber',
|
||||||
|
'cargoSize',
|
||||||
|
'goodsSize',
|
||||||
|
'cargoItemCount',
|
||||||
|
'goodsItemCount',
|
||||||
|
];
|
||||||
|
if (
|
||||||
|
countFields.some(field => {
|
||||||
|
const count = Number(row[field]);
|
||||||
|
return Number.isFinite(count) && count > 1;
|
||||||
|
})
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const cargoSources = [
|
||||||
|
row.goodsJson,
|
||||||
|
row.goodsList,
|
||||||
|
row.goodsRows,
|
||||||
|
row.cargoList,
|
||||||
|
row.cargoRows,
|
||||||
|
row.goods,
|
||||||
|
row.cargoDetails,
|
||||||
|
row.cargoItems,
|
||||||
|
row.cargoNames,
|
||||||
|
row.goodsNames,
|
||||||
|
];
|
||||||
|
if (cargoSources.some(source => this.parseCargoRows(source).length > 1)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const summaryValues = [row.cargoName, row.cargoInfo, row.goodsInfo];
|
||||||
|
if (summaryValues.some(value => Array.isArray(value) && value.length > 1)) return true;
|
||||||
|
const summaryText = summaryValues
|
||||||
|
.filter(value => value !== null && value !== undefined)
|
||||||
|
.map(value => String(value))
|
||||||
|
.join(';');
|
||||||
|
return (
|
||||||
|
/等\s*\d+\s*[种个]\s*货物/.test(summaryText) ||
|
||||||
|
summaryText.split(/[、,,;;]/).filter(Boolean).length > 1
|
||||||
|
);
|
||||||
|
},
|
||||||
collectFeeItemNames(rows) {
|
collectFeeItemNames(rows) {
|
||||||
const names = [];
|
const names = [];
|
||||||
(rows || []).forEach(row => {
|
(rows || []).forEach(row => {
|
||||||
|
|||||||
@@ -132,7 +132,7 @@ import { getToken } from '@/utils/auth';
|
|||||||
import { normalizeSearchRangeParams } from '@/utils/search-range';
|
import { normalizeSearchRangeParams } from '@/utils/search-range';
|
||||||
import VehicleAttachmentTable from '@/components/vehicle-attachment-table/main.vue';
|
import VehicleAttachmentTable from '@/components/vehicle-attachment-table/main.vue';
|
||||||
import { mapGetters } from 'vuex';
|
import { mapGetters } from 'vuex';
|
||||||
import { excelOption, option } from '@/option/vehicle/accident-record';
|
import { excelOption, exportColumns, option } from '@/option/vehicle/accident-record';
|
||||||
import NProgress from 'nprogress';
|
import NProgress from 'nprogress';
|
||||||
import 'nprogress/nprogress.css';
|
import 'nprogress/nprogress.css';
|
||||||
|
|
||||||
@@ -475,6 +475,7 @@ export default {
|
|||||||
return {
|
return {
|
||||||
...this.buildQuery(),
|
...this.buildQuery(),
|
||||||
ids: this.ids,
|
ids: this.ids,
|
||||||
|
exportColumns: JSON.stringify(exportColumns),
|
||||||
[this.website.tokenHeader]: getToken(),
|
[this.website.tokenHeader]: getToken(),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@@ -483,7 +484,8 @@ export default {
|
|||||||
},
|
},
|
||||||
handleTemplate() {
|
handleTemplate() {
|
||||||
exportBlob(
|
exportBlob(
|
||||||
`/blade-transport/accident-record/export-template?${this.website.tokenHeader}=${getToken()}`,
|
'/blade-transport/accident-record/export-template',
|
||||||
|
{ exportColumns: JSON.stringify(exportColumns), [this.website.tokenHeader]: getToken() },
|
||||||
{ feedback: true }
|
{ feedback: true }
|
||||||
).then(res => {
|
).then(res => {
|
||||||
downloadXls(res.data, '事故记录模板.xlsx');
|
downloadXls(res.data, '事故记录模板.xlsx');
|
||||||
|
|||||||
@@ -118,7 +118,7 @@ import { openImportDialog } from '@/utils/import-excel';
|
|||||||
import { getToken } from '@/utils/auth';
|
import { getToken } from '@/utils/auth';
|
||||||
import VehicleAttachmentTable from '@/components/vehicle-attachment-table/main.vue';
|
import VehicleAttachmentTable from '@/components/vehicle-attachment-table/main.vue';
|
||||||
import { mapGetters } from 'vuex';
|
import { mapGetters } from 'vuex';
|
||||||
import { excelOption, option } from '@/option/vehicle/annual-inspection-record';
|
import { excelOption, exportColumns, option } from '@/option/vehicle/annual-inspection-record';
|
||||||
import NProgress from 'nprogress';
|
import NProgress from 'nprogress';
|
||||||
import 'nprogress/nprogress.css';
|
import 'nprogress/nprogress.css';
|
||||||
|
|
||||||
@@ -445,14 +445,14 @@ export default {
|
|||||||
return {
|
return {
|
||||||
...this.query,
|
...this.query,
|
||||||
ids: this.ids,
|
ids: this.ids,
|
||||||
|
exportColumns: JSON.stringify(exportColumns),
|
||||||
[this.website.tokenHeader]: getToken(),
|
[this.website.tokenHeader]: getToken(),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
handleTemplate() {
|
handleTemplate() {
|
||||||
exportBlob(
|
exportBlob(
|
||||||
`/blade-transport/annual-inspection-record/export-template?${
|
'/blade-transport/annual-inspection-record/export-template',
|
||||||
this.website.tokenHeader
|
{ exportColumns: JSON.stringify(exportColumns), [this.website.tokenHeader]: getToken() },
|
||||||
}=${getToken()}`,
|
|
||||||
{ feedback: true }
|
{ feedback: true }
|
||||||
).then(res => {
|
).then(res => {
|
||||||
downloadXls(res.data, '年检记录模板.xlsx');
|
downloadXls(res.data, '年检记录模板.xlsx');
|
||||||
|
|||||||
@@ -127,7 +127,7 @@ import { openImportDialog } from '@/utils/import-excel';
|
|||||||
import { downloadXls } from '@/utils/util';
|
import { downloadXls } from '@/utils/util';
|
||||||
import VehicleAttachmentTable from '@/components/vehicle-attachment-table/main.vue';
|
import VehicleAttachmentTable from '@/components/vehicle-attachment-table/main.vue';
|
||||||
import { mapGetters } from 'vuex';
|
import { mapGetters } from 'vuex';
|
||||||
import { excelOption, option } from '@/option/vehicle/equipment-ledger';
|
import { excelOption, exportColumns, option } from '@/option/vehicle/equipment-ledger';
|
||||||
import NProgress from 'nprogress';
|
import NProgress from 'nprogress';
|
||||||
import 'nprogress/nprogress.css';
|
import 'nprogress/nprogress.css';
|
||||||
|
|
||||||
@@ -356,7 +356,12 @@ export default {
|
|||||||
NProgress.start();
|
NProgress.start();
|
||||||
exportBlob(
|
exportBlob(
|
||||||
'/blade-transport/equipment-ledger/export-equipment-ledger',
|
'/blade-transport/equipment-ledger/export-equipment-ledger',
|
||||||
{ ...this.query, ids: this.ids, [this.website.tokenHeader]: getToken() },
|
{
|
||||||
|
...this.query,
|
||||||
|
ids: this.ids,
|
||||||
|
exportColumns: JSON.stringify(exportColumns),
|
||||||
|
[this.website.tokenHeader]: getToken(),
|
||||||
|
},
|
||||||
{ feedback: true }
|
{ feedback: true }
|
||||||
)
|
)
|
||||||
.then(res =>
|
.then(res =>
|
||||||
@@ -367,9 +372,8 @@ export default {
|
|||||||
},
|
},
|
||||||
handleTemplate() {
|
handleTemplate() {
|
||||||
exportBlob(
|
exportBlob(
|
||||||
`/blade-transport/equipment-ledger/export-template?${
|
'/blade-transport/equipment-ledger/export-template',
|
||||||
this.website.tokenHeader
|
{ exportColumns: JSON.stringify(exportColumns), [this.website.tokenHeader]: getToken() },
|
||||||
}=${getToken()}`,
|
|
||||||
{ feedback: true }
|
{ feedback: true }
|
||||||
).then(res => downloadXls(res.data, '设备台账模板.xlsx'));
|
).then(res => downloadXls(res.data, '设备台账模板.xlsx'));
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -113,7 +113,7 @@ import { openImportDialog } from '@/utils/import-excel';
|
|||||||
import { getToken } from '@/utils/auth';
|
import { getToken } from '@/utils/auth';
|
||||||
import VehicleAttachmentTable from '@/components/vehicle-attachment-table/main.vue';
|
import VehicleAttachmentTable from '@/components/vehicle-attachment-table/main.vue';
|
||||||
import { mapGetters } from 'vuex';
|
import { mapGetters } from 'vuex';
|
||||||
import { excelOption, option } from '@/option/vehicle/etc-record';
|
import { excelOption, exportColumns, option } from '@/option/vehicle/etc-record';
|
||||||
import NProgress from 'nprogress';
|
import NProgress from 'nprogress';
|
||||||
import 'nprogress/nprogress.css';
|
import 'nprogress/nprogress.css';
|
||||||
|
|
||||||
@@ -416,12 +416,14 @@ export default {
|
|||||||
return {
|
return {
|
||||||
...this.query,
|
...this.query,
|
||||||
ids: this.ids,
|
ids: this.ids,
|
||||||
|
exportColumns: JSON.stringify(exportColumns),
|
||||||
[this.website.tokenHeader]: getToken(),
|
[this.website.tokenHeader]: getToken(),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
handleTemplate() {
|
handleTemplate() {
|
||||||
exportBlob(
|
exportBlob(
|
||||||
`/blade-transport/etc-record/export-template?${this.website.tokenHeader}=${getToken()}`,
|
'/blade-transport/etc-record/export-template',
|
||||||
|
{ exportColumns: JSON.stringify(exportColumns), [this.website.tokenHeader]: getToken() },
|
||||||
{ feedback: true }
|
{ feedback: true }
|
||||||
).then(res => {
|
).then(res => {
|
||||||
downloadXls(res.data, 'ETC记录模板.xlsx');
|
downloadXls(res.data, 'ETC记录模板.xlsx');
|
||||||
|
|||||||
@@ -193,7 +193,7 @@ import { getUploadHeaders } from '@/utils/upload';
|
|||||||
import { normalizeSearchRangeParams } from '@/utils/search-range';
|
import { normalizeSearchRangeParams } from '@/utils/search-range';
|
||||||
import { mapGetters } from 'vuex';
|
import { mapGetters } from 'vuex';
|
||||||
import { ElLoading } from 'element-plus';
|
import { ElLoading } from 'element-plus';
|
||||||
import { excelOption, option } from '@/option/vehicle/insurance-record';
|
import { excelOption, exportColumns, option } from '@/option/vehicle/insurance-record';
|
||||||
import NProgress from 'nprogress';
|
import NProgress from 'nprogress';
|
||||||
import 'nprogress/nprogress.css';
|
import 'nprogress/nprogress.css';
|
||||||
|
|
||||||
@@ -506,14 +506,14 @@ export default {
|
|||||||
return {
|
return {
|
||||||
...this.buildQuery(),
|
...this.buildQuery(),
|
||||||
ids: this.ids,
|
ids: this.ids,
|
||||||
|
exportColumns: JSON.stringify(exportColumns),
|
||||||
[this.website.tokenHeader]: getToken(),
|
[this.website.tokenHeader]: getToken(),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
handleTemplate() {
|
handleTemplate() {
|
||||||
exportBlob(
|
exportBlob(
|
||||||
`/blade-transport/insurance-record/export-template?${
|
'/blade-transport/insurance-record/export-template',
|
||||||
this.website.tokenHeader
|
{ exportColumns: JSON.stringify(exportColumns), [this.website.tokenHeader]: getToken() },
|
||||||
}=${getToken()}`,
|
|
||||||
{ feedback: true }
|
{ feedback: true }
|
||||||
).then(res => {
|
).then(res => {
|
||||||
downloadXls(res.data, '保险记录模板.xlsx');
|
downloadXls(res.data, '保险记录模板.xlsx');
|
||||||
|
|||||||
@@ -177,6 +177,22 @@ const createTimeRangeMap = {
|
|||||||
createTimeRange: ['createTimeStart', 'createTimeEnd'],
|
createTimeRange: ['createTimeStart', 'createTimeEnd'],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const exportColumns = [
|
||||||
|
{ prop: 'vehicleType', label: '车船类型' },
|
||||||
|
{ prop: 'vehicleNo', label: '车牌号/船号' },
|
||||||
|
{ prop: 'maintainer', label: '保养人' },
|
||||||
|
{ prop: 'maintenanceTime', label: '保养时间' },
|
||||||
|
{ prop: 'mileage', label: '里程/航程数' },
|
||||||
|
{ prop: 'maintenanceItem', label: '保养项目' },
|
||||||
|
{ prop: 'cost', label: '费用' },
|
||||||
|
{ prop: 'storeName', label: '店名' },
|
||||||
|
{ prop: 'contactPhone', label: '联系电话' },
|
||||||
|
{ prop: 'address', label: '地址' },
|
||||||
|
{ prop: 'nextMaintenanceTime', label: '下次保养时间' },
|
||||||
|
{ prop: 'nextMaintenanceMileage', label: '下次保养里程/航程' },
|
||||||
|
{ prop: 'remark', label: '备注' },
|
||||||
|
];
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
AddressMapPicker,
|
AddressMapPicker,
|
||||||
@@ -715,6 +731,12 @@ export default {
|
|||||||
detail.attachments = this.parseAttachments(detail.attachments);
|
detail.attachments = this.parseAttachments(detail.attachments);
|
||||||
detail.vehicleType = detail.vehicleType || '车辆';
|
detail.vehicleType = detail.vehicleType || '车辆';
|
||||||
detail.mileageUnit = detail.vehicleType === '船舶' ? '海里' : '公里';
|
detail.mileageUnit = detail.vehicleType === '船舶' ? '海里' : '公里';
|
||||||
|
if (type === 'edit') {
|
||||||
|
if (Number(detail.mileage) === -1) detail.mileage = '';
|
||||||
|
if (Number(detail.nextMaintenanceMileage) === -1) {
|
||||||
|
detail.nextMaintenanceMileage = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
this.form = detail;
|
this.form = detail;
|
||||||
this.updateVehicleType(detail.vehicleType);
|
this.updateVehicleType(detail.vehicleType);
|
||||||
});
|
});
|
||||||
@@ -787,6 +809,7 @@ export default {
|
|||||||
return {
|
return {
|
||||||
...this.buildQuery(),
|
...this.buildQuery(),
|
||||||
ids: this.ids,
|
ids: this.ids,
|
||||||
|
exportColumns: JSON.stringify(exportColumns),
|
||||||
[this.website.tokenHeader]: getToken(),
|
[this.website.tokenHeader]: getToken(),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@@ -795,9 +818,8 @@ export default {
|
|||||||
},
|
},
|
||||||
handleTemplate() {
|
handleTemplate() {
|
||||||
exportBlob(
|
exportBlob(
|
||||||
`/blade-transport/maintenance-plan/export-template?${
|
'/blade-transport/maintenance-plan/export-template',
|
||||||
this.website.tokenHeader
|
{ exportColumns: JSON.stringify(exportColumns), [this.website.tokenHeader]: getToken() },
|
||||||
}=${getToken()}`,
|
|
||||||
{ feedback: true }
|
{ feedback: true }
|
||||||
).then(res => {
|
).then(res => {
|
||||||
downloadXls(res.data, '保养记录模板.xlsx');
|
downloadXls(res.data, '保养记录模板.xlsx');
|
||||||
|
|||||||
@@ -150,6 +150,22 @@ const createTimeRangeMap = {
|
|||||||
createTimeRange: ['createTimeStart', 'createTimeEnd'],
|
createTimeRange: ['createTimeStart', 'createTimeEnd'],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const exportColumns = [
|
||||||
|
{ prop: 'vehicleType', label: '车船类型' },
|
||||||
|
{ prop: 'vehicleNo', label: '车牌号/船号' },
|
||||||
|
{ prop: 'maintainer', label: '维修人' },
|
||||||
|
{ prop: 'maintenanceTime', label: '维修时间' },
|
||||||
|
{ prop: 'location', label: '维修位置' },
|
||||||
|
{ prop: 'replacedPart', label: '更换零件' },
|
||||||
|
{ prop: 'cost', label: '费用' },
|
||||||
|
{ prop: 'company', label: '维修单位' },
|
||||||
|
{ prop: 'contact', label: '联系方式' },
|
||||||
|
{ prop: 'address', label: '地址' },
|
||||||
|
{ prop: 'factoryTime', label: '出厂时间' },
|
||||||
|
{ prop: 'mileage', label: '里程/航程数' },
|
||||||
|
{ prop: 'remark', label: '备注' },
|
||||||
|
];
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
AddressMapPicker,
|
AddressMapPicker,
|
||||||
@@ -321,6 +337,7 @@ export default {
|
|||||||
{
|
{
|
||||||
label: '里程/航程数(公里)',
|
label: '里程/航程数(公里)',
|
||||||
prop: 'mileage',
|
prop: 'mileage',
|
||||||
|
renderHeader: () => '里程/航程数',
|
||||||
type: 'input',
|
type: 'input',
|
||||||
minWidth: 130,
|
minWidth: 130,
|
||||||
slot: true,
|
slot: true,
|
||||||
@@ -749,6 +766,7 @@ export default {
|
|||||||
return {
|
return {
|
||||||
...this.buildQuery(),
|
...this.buildQuery(),
|
||||||
ids: this.ids,
|
ids: this.ids,
|
||||||
|
exportColumns: JSON.stringify(exportColumns),
|
||||||
[this.website.tokenHeader]: getToken(),
|
[this.website.tokenHeader]: getToken(),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@@ -757,9 +775,8 @@ export default {
|
|||||||
},
|
},
|
||||||
handleTemplate() {
|
handleTemplate() {
|
||||||
exportBlob(
|
exportBlob(
|
||||||
`/blade-transport/maintenance-record/export-template?${
|
'/blade-transport/maintenance-record/export-template',
|
||||||
this.website.tokenHeader
|
{ exportColumns: JSON.stringify(exportColumns), [this.website.tokenHeader]: getToken() },
|
||||||
}=${getToken()}`,
|
|
||||||
{ feedback: true }
|
{ feedback: true }
|
||||||
).then(res => {
|
).then(res => {
|
||||||
downloadXls(res.data, '维修记录模板.xlsx');
|
downloadXls(res.data, '维修记录模板.xlsx');
|
||||||
|
|||||||
@@ -184,7 +184,7 @@ import { getToken } from '@/utils/auth';
|
|||||||
import { normalizeSearchRangeParams } from '@/utils/search-range';
|
import { normalizeSearchRangeParams } from '@/utils/search-range';
|
||||||
import VehicleAttachmentTable from '@/components/vehicle-attachment-table/main.vue';
|
import VehicleAttachmentTable from '@/components/vehicle-attachment-table/main.vue';
|
||||||
import { mapGetters } from 'vuex';
|
import { mapGetters } from 'vuex';
|
||||||
import { excelOption, option } from '@/option/vehicle/mileage-record';
|
import { excelOption, exportColumns, option } from '@/option/vehicle/mileage-record';
|
||||||
import NProgress from 'nprogress';
|
import NProgress from 'nprogress';
|
||||||
import 'nprogress/nprogress.css';
|
import 'nprogress/nprogress.css';
|
||||||
|
|
||||||
@@ -531,6 +531,7 @@ export default {
|
|||||||
return {
|
return {
|
||||||
...this.buildQuery(),
|
...this.buildQuery(),
|
||||||
ids: this.ids,
|
ids: this.ids,
|
||||||
|
exportColumns: JSON.stringify(exportColumns),
|
||||||
[this.website.tokenHeader]: getToken(),
|
[this.website.tokenHeader]: getToken(),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@@ -542,7 +543,8 @@ export default {
|
|||||||
},
|
},
|
||||||
handleTemplate() {
|
handleTemplate() {
|
||||||
exportBlob(
|
exportBlob(
|
||||||
`/blade-transport/mileage-record/export-template?${this.website.tokenHeader}=${getToken()}`,
|
'/blade-transport/mileage-record/export-template',
|
||||||
|
{ exportColumns: JSON.stringify(exportColumns), [this.website.tokenHeader]: getToken() },
|
||||||
{ feedback: true }
|
{ feedback: true }
|
||||||
).then(res => {
|
).then(res => {
|
||||||
downloadXls(res.data, '里程记录模板.xlsx');
|
downloadXls(res.data, '里程记录模板.xlsx');
|
||||||
|
|||||||
@@ -145,7 +145,7 @@ import { openImportDialog } from '@/utils/import-excel';
|
|||||||
import { getToken } from '@/utils/auth';
|
import { getToken } from '@/utils/auth';
|
||||||
import VehicleAttachmentTable from '@/components/vehicle-attachment-table/main.vue';
|
import VehicleAttachmentTable from '@/components/vehicle-attachment-table/main.vue';
|
||||||
import { mapGetters } from 'vuex';
|
import { mapGetters } from 'vuex';
|
||||||
import { excelOption, option } from '@/option/vehicle/oil-electric-record';
|
import { excelOption, exportColumns, option } from '@/option/vehicle/oil-electric-record';
|
||||||
import NProgress from 'nprogress';
|
import NProgress from 'nprogress';
|
||||||
import 'nprogress/nprogress.css';
|
import 'nprogress/nprogress.css';
|
||||||
|
|
||||||
@@ -500,14 +500,14 @@ export default {
|
|||||||
return {
|
return {
|
||||||
...this.query,
|
...this.query,
|
||||||
ids: this.ids,
|
ids: this.ids,
|
||||||
|
exportColumns: JSON.stringify(exportColumns),
|
||||||
[this.website.tokenHeader]: getToken(),
|
[this.website.tokenHeader]: getToken(),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
handleTemplate() {
|
handleTemplate() {
|
||||||
exportBlob(
|
exportBlob(
|
||||||
`/blade-transport/oil-electric-record/export-template?${
|
'/blade-transport/oil-electric-record/export-template',
|
||||||
this.website.tokenHeader
|
{ exportColumns: JSON.stringify(exportColumns), [this.website.tokenHeader]: getToken() },
|
||||||
}=${getToken()}`,
|
|
||||||
{ feedback: true }
|
{ feedback: true }
|
||||||
).then(res => {
|
).then(res => {
|
||||||
downloadXls(res.data, '油电记录模板.xlsx');
|
downloadXls(res.data, '油电记录模板.xlsx');
|
||||||
|
|||||||
@@ -114,7 +114,7 @@ import { openImportDialog } from '@/utils/import-excel';
|
|||||||
import { getToken } from '@/utils/auth';
|
import { getToken } from '@/utils/auth';
|
||||||
import VehicleAttachmentTable from '@/components/vehicle-attachment-table/main.vue';
|
import VehicleAttachmentTable from '@/components/vehicle-attachment-table/main.vue';
|
||||||
import { mapGetters } from 'vuex';
|
import { mapGetters } from 'vuex';
|
||||||
import { excelOption, option } from '@/option/vehicle/other-expense-record';
|
import { excelOption, exportColumns, option } from '@/option/vehicle/other-expense-record';
|
||||||
import NProgress from 'nprogress';
|
import NProgress from 'nprogress';
|
||||||
import 'nprogress/nprogress.css';
|
import 'nprogress/nprogress.css';
|
||||||
|
|
||||||
@@ -465,14 +465,14 @@ export default {
|
|||||||
return {
|
return {
|
||||||
...this.buildQuery(),
|
...this.buildQuery(),
|
||||||
ids: this.ids,
|
ids: this.ids,
|
||||||
|
exportColumns: JSON.stringify(exportColumns),
|
||||||
[this.website.tokenHeader]: getToken(),
|
[this.website.tokenHeader]: getToken(),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
handleTemplate() {
|
handleTemplate() {
|
||||||
exportBlob(
|
exportBlob(
|
||||||
`/blade-transport/other-expense-record/export-template?${
|
'/blade-transport/other-expense-record/export-template',
|
||||||
this.website.tokenHeader
|
{ exportColumns: JSON.stringify(exportColumns), [this.website.tokenHeader]: getToken() },
|
||||||
}=${getToken()}`,
|
|
||||||
{ feedback: true }
|
{ feedback: true }
|
||||||
).then(res => {
|
).then(res => {
|
||||||
downloadXls(res.data, '其他费用记录模板.xlsx');
|
downloadXls(res.data, '其他费用记录模板.xlsx');
|
||||||
|
|||||||
@@ -129,7 +129,7 @@ import { getToken } from '@/utils/auth';
|
|||||||
import { normalizeSearchRangeParams } from '@/utils/search-range';
|
import { normalizeSearchRangeParams } from '@/utils/search-range';
|
||||||
import VehicleAttachmentTable from '@/components/vehicle-attachment-table/main.vue';
|
import VehicleAttachmentTable from '@/components/vehicle-attachment-table/main.vue';
|
||||||
import { mapGetters } from 'vuex';
|
import { mapGetters } from 'vuex';
|
||||||
import { excelOption, option } from '@/option/vehicle/tire-replacement-record';
|
import { excelOption, exportColumns, option } from '@/option/vehicle/tire-replacement-record';
|
||||||
import NProgress from 'nprogress';
|
import NProgress from 'nprogress';
|
||||||
import 'nprogress/nprogress.css';
|
import 'nprogress/nprogress.css';
|
||||||
|
|
||||||
@@ -416,6 +416,7 @@ export default {
|
|||||||
return {
|
return {
|
||||||
...this.buildQuery(),
|
...this.buildQuery(),
|
||||||
ids: this.ids,
|
ids: this.ids,
|
||||||
|
exportColumns: JSON.stringify(exportColumns),
|
||||||
[this.website.tokenHeader]: getToken(),
|
[this.website.tokenHeader]: getToken(),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@@ -424,9 +425,8 @@ export default {
|
|||||||
},
|
},
|
||||||
handleTemplate() {
|
handleTemplate() {
|
||||||
exportBlob(
|
exportBlob(
|
||||||
`/blade-transport/tire-replacement-record/export-template?${
|
'/blade-transport/tire-replacement-record/export-template',
|
||||||
this.website.tokenHeader
|
{ exportColumns: JSON.stringify(exportColumns), [this.website.tokenHeader]: getToken() },
|
||||||
}=${getToken()}`,
|
|
||||||
{ feedback: true }
|
{ feedback: true }
|
||||||
).then(res => {
|
).then(res => {
|
||||||
downloadXls(res.data, '换胎记录模板.xlsx');
|
downloadXls(res.data, '换胎记录模板.xlsx');
|
||||||
|
|||||||
@@ -104,7 +104,7 @@ import { getToken } from '@/utils/auth';
|
|||||||
import { normalizeSearchRangeParams } from '@/utils/search-range';
|
import { normalizeSearchRangeParams } from '@/utils/search-range';
|
||||||
import VehicleAttachmentTable from '@/components/vehicle-attachment-table/main.vue';
|
import VehicleAttachmentTable from '@/components/vehicle-attachment-table/main.vue';
|
||||||
import { mapGetters } from 'vuex';
|
import { mapGetters } from 'vuex';
|
||||||
import { excelOption, option } from '@/option/vehicle/transport-change-record';
|
import { excelOption, exportColumns, option } from '@/option/vehicle/transport-change-record';
|
||||||
import NProgress from 'nprogress';
|
import NProgress from 'nprogress';
|
||||||
import 'nprogress/nprogress.css';
|
import 'nprogress/nprogress.css';
|
||||||
|
|
||||||
@@ -416,6 +416,7 @@ export default {
|
|||||||
return {
|
return {
|
||||||
...this.buildQuery(),
|
...this.buildQuery(),
|
||||||
ids: this.ids,
|
ids: this.ids,
|
||||||
|
exportColumns: JSON.stringify(exportColumns),
|
||||||
[this.website.tokenHeader]: getToken(),
|
[this.website.tokenHeader]: getToken(),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@@ -424,9 +425,8 @@ export default {
|
|||||||
},
|
},
|
||||||
handleTemplate() {
|
handleTemplate() {
|
||||||
exportBlob(
|
exportBlob(
|
||||||
`/blade-transport/transport-change-record/export-template?${
|
'/blade-transport/transport-change-record/export-template',
|
||||||
this.website.tokenHeader
|
{ exportColumns: JSON.stringify(exportColumns), [this.website.tokenHeader]: getToken() },
|
||||||
}=${getToken()}`,
|
|
||||||
{ feedback: true }
|
{ feedback: true }
|
||||||
).then(res => {
|
).then(res => {
|
||||||
downloadXls(res.data, '变更记录模板.xlsx');
|
downloadXls(res.data, '变更记录模板.xlsx');
|
||||||
|
|||||||
@@ -172,7 +172,7 @@ import { normalizeSearchRangeParams } from '@/utils/search-range';
|
|||||||
import AddressMapPicker from '@/components/address-map-picker/main.vue';
|
import AddressMapPicker from '@/components/address-map-picker/main.vue';
|
||||||
import VehicleAttachmentTable from '@/components/vehicle-attachment-table/main.vue';
|
import VehicleAttachmentTable from '@/components/vehicle-attachment-table/main.vue';
|
||||||
import { mapGetters } from 'vuex';
|
import { mapGetters } from 'vuex';
|
||||||
import { excelOption, option } from '@/option/vehicle/violation-record';
|
import { excelOption, exportColumns, option } from '@/option/vehicle/violation-record';
|
||||||
import NProgress from 'nprogress';
|
import NProgress from 'nprogress';
|
||||||
import 'nprogress/nprogress.css';
|
import 'nprogress/nprogress.css';
|
||||||
|
|
||||||
@@ -508,11 +508,11 @@ export default {
|
|||||||
if (type === 'add') {
|
if (type === 'add') {
|
||||||
this.form = {
|
this.form = {
|
||||||
vehicleType: '车辆',
|
vehicleType: '车辆',
|
||||||
processStatus: '未处理',
|
processStatus: '已处理',
|
||||||
attachments: [],
|
attachments: [],
|
||||||
};
|
};
|
||||||
this.updateVehicleTypeDisplays('车辆');
|
this.updateVehicleTypeDisplays('车辆');
|
||||||
this.updateProcessResultDisplay('未处理');
|
this.updateProcessResultDisplay('已处理');
|
||||||
}
|
}
|
||||||
if (['edit', 'view'].includes(type)) {
|
if (['edit', 'view'].includes(type)) {
|
||||||
this.isDetailLoading = true;
|
this.isDetailLoading = true;
|
||||||
@@ -598,6 +598,7 @@ export default {
|
|||||||
return {
|
return {
|
||||||
...this.buildQuery(),
|
...this.buildQuery(),
|
||||||
ids: this.ids,
|
ids: this.ids,
|
||||||
|
exportColumns: JSON.stringify(exportColumns),
|
||||||
[this.website.tokenHeader]: getToken(),
|
[this.website.tokenHeader]: getToken(),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@@ -606,9 +607,8 @@ export default {
|
|||||||
},
|
},
|
||||||
handleTemplate() {
|
handleTemplate() {
|
||||||
exportBlob(
|
exportBlob(
|
||||||
`/blade-transport/violation-record/export-template?${
|
'/blade-transport/violation-record/export-template',
|
||||||
this.website.tokenHeader
|
{ exportColumns: JSON.stringify(exportColumns), [this.website.tokenHeader]: getToken() },
|
||||||
}=${getToken()}`,
|
|
||||||
{ feedback: true }
|
{ feedback: true }
|
||||||
).then(res => {
|
).then(res => {
|
||||||
downloadXls(res.data, '违章记录模板.xlsx');
|
downloadXls(res.data, '违章记录模板.xlsx');
|
||||||
|
|||||||
@@ -87,6 +87,10 @@ export default ({ mode, command }) => {
|
|||||||
plugins: createVitePlugins(env, command === 'build'),
|
plugins: createVitePlugins(env, command === 'build'),
|
||||||
build: buildConfig,
|
build: buildConfig,
|
||||||
optimizeDeps: {
|
optimizeDeps: {
|
||||||
|
// vue-pdf-embed is loaded on demand by the PDF preview component. Keeping
|
||||||
|
// it out of the shared dependency bundle avoids stale Vue chunk cycles
|
||||||
|
// when the development dependency cache is refreshed.
|
||||||
|
exclude: ['vue-pdf-embed'],
|
||||||
esbuildOptions: {
|
esbuildOptions: {
|
||||||
target: 'esnext',
|
target: 'esnext',
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user