修复业务模块bug
This commit is contained in:
@@ -1,9 +1,24 @@
|
|||||||
|
import request from '@/axios';
|
||||||
import { createCrudApi } from './common';
|
import { createCrudApi } from './common';
|
||||||
|
|
||||||
const api = createCrudApi('/blade-transport/process-config');
|
const api = createCrudApi('/blade-transport/process-config');
|
||||||
|
|
||||||
export const getList = api.getList;
|
export const getList = api.getList;
|
||||||
export const getDetail = api.getDetail;
|
export const getDetail = (id, waybillId) =>
|
||||||
|
request({
|
||||||
|
url: '/blade-transport/process-config/detail',
|
||||||
|
method: 'get',
|
||||||
|
params: {
|
||||||
|
id,
|
||||||
|
...(waybillId === undefined ? {} : { waybillId }),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
export const getVoucherImages = waybillId =>
|
||||||
|
request({
|
||||||
|
url: '/blade-transport/process-config/voucher-images',
|
||||||
|
method: 'get',
|
||||||
|
params: { waybillId },
|
||||||
|
});
|
||||||
export const submit = api.submit;
|
export const submit = api.submit;
|
||||||
export const remove = api.remove;
|
export const remove = api.remove;
|
||||||
export const copy = api.copy;
|
export const copy = api.copy;
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ export const getPartUploadUrl = (id, partNumber) =>
|
|||||||
export const updateFileTask = data =>
|
export const updateFileTask = data =>
|
||||||
request({ url: `${fileTaskBaseUrl}/updateFileTask`, method: 'post', data });
|
request({ url: `${fileTaskBaseUrl}/updateFileTask`, method: 'post', data });
|
||||||
export const pauseFileTask = ids =>
|
export const pauseFileTask = ids =>
|
||||||
request({ url: `${fileTaskBaseUrl}/updatePaused`, method: 'post', params: { ids: ids.join(',') } });
|
request({ url: `${fileTaskBaseUrl}/updatePaused`, method: 'post', data: { ids } });
|
||||||
export const resumeFileTask = id =>
|
export const resumeFileTask = id =>
|
||||||
request({ url: `${fileTaskBaseUrl}/resume`, method: 'post', params: { id } });
|
request({ url: `${fileTaskBaseUrl}/resume`, method: 'post', params: { id } });
|
||||||
export const getFileTaskList = (current, size, params) =>
|
export const getFileTaskList = (current, size, params) =>
|
||||||
|
|||||||
@@ -797,6 +797,7 @@
|
|||||||
placeholder="请输入"
|
placeholder="请输入"
|
||||||
clearable
|
clearable
|
||||||
maxlength="10"
|
maxlength="10"
|
||||||
|
inputmode="numeric"
|
||||||
:disabled="dialogReadonly"
|
:disabled="dialogReadonly"
|
||||||
@input="handleTaskMileageInput"
|
@input="handleTaskMileageInput"
|
||||||
/>
|
/>
|
||||||
@@ -927,6 +928,7 @@
|
|||||||
placeholder="请输入"
|
placeholder="请输入"
|
||||||
clearable
|
clearable
|
||||||
maxlength="10"
|
maxlength="10"
|
||||||
|
inputmode="numeric"
|
||||||
:disabled="dialogReadonly"
|
:disabled="dialogReadonly"
|
||||||
@input="handleTaskMileageInput"
|
@input="handleTaskMileageInput"
|
||||||
/>
|
/>
|
||||||
@@ -1347,6 +1349,7 @@
|
|||||||
placeholder="请输入里程"
|
placeholder="请输入里程"
|
||||||
clearable
|
clearable
|
||||||
maxlength="10"
|
maxlength="10"
|
||||||
|
inputmode="numeric"
|
||||||
:disabled="dialogReadonly"
|
:disabled="dialogReadonly"
|
||||||
@input="handleTaskMileageInput"
|
@input="handleTaskMileageInput"
|
||||||
/>
|
/>
|
||||||
@@ -2334,6 +2337,7 @@
|
|||||||
v-model="detailBox"
|
v-model="detailBox"
|
||||||
:title="`${config.title}详情`"
|
:title="`${config.title}详情`"
|
||||||
append-to-body
|
append-to-body
|
||||||
|
top="10px"
|
||||||
width="96%"
|
width="96%"
|
||||||
class="business-crud-page__detail-dialog"
|
class="business-crud-page__detail-dialog"
|
||||||
>
|
>
|
||||||
@@ -2386,6 +2390,7 @@
|
|||||||
v-for="file in attachmentRows"
|
v-for="file in attachmentRows"
|
||||||
:key="file.url || file.name || file.originalName"
|
:key="file.url || file.name || file.originalName"
|
||||||
type="primary"
|
type="primary"
|
||||||
|
@click="previewAttachment(file)"
|
||||||
>
|
>
|
||||||
{{ file.originalName || file.name || '-' }}
|
{{ file.originalName || file.name || '-' }}
|
||||||
</el-link>
|
</el-link>
|
||||||
@@ -2502,15 +2507,44 @@
|
|||||||
</section-card>
|
</section-card>
|
||||||
<div class="business-crud-page__waybill-detail-bottom">
|
<div class="business-crud-page__waybill-detail-bottom">
|
||||||
<section-card title="执行详情">
|
<section-card title="执行详情">
|
||||||
<el-timeline v-if="waybillDetailProcessNodes.length">
|
<el-tabs v-model="waybillProcessDetailTab" type="border-card">
|
||||||
<el-timeline-item
|
<el-tab-pane label="打卡详情" name="punch">
|
||||||
v-for="(node, index) in waybillDetailProcessNodes"
|
<el-timeline v-if="waybillDetailProcessNodes.length">
|
||||||
:key="node.key || index"
|
<el-timeline-item
|
||||||
:timestamp="node.time || ''"
|
v-for="(node, index) in waybillDetailProcessNodes"
|
||||||
>{{ node.name || node.nodeName || node.label }}</el-timeline-item
|
:key="node.key || index"
|
||||||
|
:timestamp="node.time || ''"
|
||||||
|
>{{ node.name || node.nodeName || node.label }}</el-timeline-item
|
||||||
|
>
|
||||||
|
</el-timeline>
|
||||||
|
<el-empty v-else description="暂无打卡详情" :image-size="50" />
|
||||||
|
</el-tab-pane>
|
||||||
|
<el-tab-pane
|
||||||
|
v-if="waybillHasRelatedVoucher"
|
||||||
|
label="批量补录"
|
||||||
|
name="batchSupplement"
|
||||||
>
|
>
|
||||||
</el-timeline>
|
<div v-loading="waybillVoucherImagesLoading" class="business-crud-page__voucher-image-grid">
|
||||||
<el-empty v-else description="暂无执行详情" :image-size="50" />
|
<div
|
||||||
|
v-for="(image, index) in waybillVoucherImages"
|
||||||
|
:key="image.id || image.objectKey || index"
|
||||||
|
class="business-crud-page__voucher-image-item"
|
||||||
|
:title="image.imageName"
|
||||||
|
@click="previewWaybillVoucherImage(index)"
|
||||||
|
>
|
||||||
|
<el-image :src="image.url" fit="cover" lazy />
|
||||||
|
</div>
|
||||||
|
<el-empty
|
||||||
|
v-if="!waybillVoucherImagesLoading && !waybillVoucherImages.length"
|
||||||
|
description="暂无凭证图片"
|
||||||
|
:image-size="50"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</el-tab-pane>
|
||||||
|
<!-- <el-tab-pane label="司机上传" name="driverUpload">
|
||||||
|
<el-empty description="暂无司机上传数据" :image-size="50" />
|
||||||
|
</el-tab-pane> -->
|
||||||
|
</el-tabs>
|
||||||
</section-card>
|
</section-card>
|
||||||
<section-card title="物流轨迹">
|
<section-card title="物流轨迹">
|
||||||
<template #extra>
|
<template #extra>
|
||||||
@@ -2565,7 +2599,11 @@
|
|||||||
<el-table :data="attachmentRows" empty-text="暂无附件">
|
<el-table :data="attachmentRows" empty-text="暂无附件">
|
||||||
<el-table-column type="index" label="序号" width="70" align="center" />
|
<el-table-column type="index" label="序号" width="70" align="center" />
|
||||||
<el-table-column label="文件名" min-width="260">
|
<el-table-column label="文件名" min-width="260">
|
||||||
<template #default="{ row }">{{ row.originalName || row.name || '-' }}</template>
|
<template #default="{ row }">
|
||||||
|
<el-link type="primary" @click="previewAttachment(row)">
|
||||||
|
{{ attachmentName(row) }}
|
||||||
|
</el-link>
|
||||||
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="description" label="附件描述" min-width="220" />
|
<el-table-column prop="description" label="附件描述" min-width="220" />
|
||||||
<el-table-column label="文件大小" width="110">
|
<el-table-column label="文件大小" width="110">
|
||||||
@@ -2587,6 +2625,38 @@
|
|||||||
</template>
|
</template>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
|
|
||||||
|
<el-dialog
|
||||||
|
v-model="attachmentDocumentPreviewVisible"
|
||||||
|
:title="attachmentPreviewFile.name || '附件预览'"
|
||||||
|
append-to-body
|
||||||
|
destroy-on-close
|
||||||
|
width="90%"
|
||||||
|
top="4vh"
|
||||||
|
>
|
||||||
|
<open-file-viewer
|
||||||
|
v-if="attachmentDocumentPreviewVisible && attachmentPreviewFile.url"
|
||||||
|
:file="attachmentPreviewFile.url"
|
||||||
|
:file-name="attachmentPreviewFile.name"
|
||||||
|
:mime-type="attachmentPreviewFile.mimeType"
|
||||||
|
width="100%"
|
||||||
|
height="72vh"
|
||||||
|
fit="contain"
|
||||||
|
theme="auto"
|
||||||
|
locale="zh-CN"
|
||||||
|
:toolbar="attachmentViewerToolbar"
|
||||||
|
:plugins="attachmentViewerPlugins"
|
||||||
|
@unsupported="handleAttachmentPreviewUnsupported"
|
||||||
|
@error="handleAttachmentPreviewError"
|
||||||
|
/>
|
||||||
|
</el-dialog>
|
||||||
|
|
||||||
|
<el-image-viewer
|
||||||
|
v-if="attachmentImagePreviewVisible"
|
||||||
|
:url-list="attachmentImagePreviewUrls"
|
||||||
|
:initial-index="attachmentImagePreviewIndex"
|
||||||
|
@close="attachmentImagePreviewVisible = false"
|
||||||
|
/>
|
||||||
|
|
||||||
<el-dialog
|
<el-dialog
|
||||||
v-model="waybillRouteChangeBox"
|
v-model="waybillRouteChangeBox"
|
||||||
title="变更运输路线"
|
title="变更运输路线"
|
||||||
@@ -4674,6 +4744,7 @@ import {
|
|||||||
import {
|
import {
|
||||||
getDetail as getProcessConfigDetail,
|
getDetail as getProcessConfigDetail,
|
||||||
getList as getProcessConfigList,
|
getList as getProcessConfigList,
|
||||||
|
getVoucherImages as getProcessConfigVoucherImages,
|
||||||
} from '@/api/business/process-config';
|
} from '@/api/business/process-config';
|
||||||
import { getList as getCarrierCustomerList } from '@/api/vehicle/customer-archive';
|
import { getList as getCarrierCustomerList } from '@/api/vehicle/customer-archive';
|
||||||
import { getList as getDriverList } from '@/api/transportCapacity/driver';
|
import { getList as getDriverList } from '@/api/transportCapacity/driver';
|
||||||
@@ -4689,6 +4760,11 @@ 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 { List, Location, Rank, Search } from '@element-plus/icons-vue';
|
import { List, Location, Rank, Search } from '@element-plus/icons-vue';
|
||||||
|
import { ElImageViewer } from 'element-plus';
|
||||||
|
import { OpenFileViewer } from '@open-file-viewer/vue';
|
||||||
|
import { fallbackPlugin, imagePlugin, officePlugin, pdfPlugin, textPlugin } from '@open-file-viewer/core';
|
||||||
|
import '@open-file-viewer/core/style.css';
|
||||||
|
import pdfWorkerSrc from 'pdfjs-dist/build/pdf.worker.mjs?url';
|
||||||
import { mapGetters } from 'vuex';
|
import { mapGetters } from 'vuex';
|
||||||
import NProgress from 'nprogress';
|
import NProgress from 'nprogress';
|
||||||
import 'nprogress/nprogress.css';
|
import 'nprogress/nprogress.css';
|
||||||
@@ -4698,6 +4774,14 @@ const AMAP_KEY = '653b7cf105ad7fb8ec9b2f5198ade315';
|
|||||||
const AMAP_SECURITY_CODE = '5aab65c632e48ebae0d0e28f5b28e21a';
|
const AMAP_SECURITY_CODE = '5aab65c632e48ebae0d0e28f5b28e21a';
|
||||||
let amapLoader;
|
let amapLoader;
|
||||||
|
|
||||||
|
const attachmentViewerPlugins = [
|
||||||
|
imagePlugin(),
|
||||||
|
pdfPlugin({ workerSrc: pdfWorkerSrc, useFetchData: true }),
|
||||||
|
officePlugin({ pdf: { workerSrc: pdfWorkerSrc, useFetchData: true } }),
|
||||||
|
textPlugin(),
|
||||||
|
fallbackPlugin(),
|
||||||
|
];
|
||||||
|
|
||||||
const estimateMenuButtonCount = config => {
|
const estimateMenuButtonCount = config => {
|
||||||
const rowActions = (config.actions || []).filter(action => action !== 'batchComplete');
|
const rowActions = (config.actions || []).filter(action => action !== 'batchComplete');
|
||||||
return 3 + rowActions.length + (config.operations || []).length;
|
return 3 + rowActions.length + (config.operations || []).length;
|
||||||
@@ -4859,7 +4943,7 @@ const defaultDispatchRow = () => ({
|
|||||||
const taskCarrierTypes = ['承运商', '自运', '网货平台'];
|
const taskCarrierTypes = ['承运商', '自运', '网货平台'];
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: { WaybillImportDialog, Rank },
|
components: { WaybillImportDialog, Rank, ElImageViewer, OpenFileViewer },
|
||||||
name: 'BusinessCrudPage',
|
name: 'BusinessCrudPage',
|
||||||
props: {
|
props: {
|
||||||
api: {
|
api: {
|
||||||
@@ -4904,6 +4988,11 @@ export default {
|
|||||||
detailLoading: false,
|
detailLoading: false,
|
||||||
detailRow: {},
|
detailRow: {},
|
||||||
waybillDetailProcessNodes: [],
|
waybillDetailProcessNodes: [],
|
||||||
|
waybillProcessDetailTab: 'punch',
|
||||||
|
waybillHasRelatedVoucher: false,
|
||||||
|
waybillVoucherImages: [],
|
||||||
|
waybillVoucherImagesLoading: false,
|
||||||
|
waybillVoucherImagesLoaded: false,
|
||||||
waybillRouteChangeBox: false,
|
waybillRouteChangeBox: false,
|
||||||
waybillRouteChangeTab: 'change',
|
waybillRouteChangeTab: 'change',
|
||||||
waybillRouteChangeRows: [],
|
waybillRouteChangeRows: [],
|
||||||
@@ -4989,6 +5078,19 @@ export default {
|
|||||||
selectedAttachmentRows: [],
|
selectedAttachmentRows: [],
|
||||||
contractFileRows: [],
|
contractFileRows: [],
|
||||||
selectedContractFileRows: [],
|
selectedContractFileRows: [],
|
||||||
|
attachmentImagePreviewVisible: false,
|
||||||
|
attachmentImagePreviewUrls: [],
|
||||||
|
attachmentImagePreviewIndex: 0,
|
||||||
|
attachmentDocumentPreviewVisible: false,
|
||||||
|
attachmentPreviewFile: {},
|
||||||
|
attachmentViewerPlugins,
|
||||||
|
attachmentViewerToolbar: {
|
||||||
|
download: true,
|
||||||
|
fullscreen: true,
|
||||||
|
print: true,
|
||||||
|
rotate: true,
|
||||||
|
zoom: true,
|
||||||
|
},
|
||||||
transportCargoRows: [],
|
transportCargoRows: [],
|
||||||
taskFreightCurrency: 'CNY',
|
taskFreightCurrency: 'CNY',
|
||||||
shippingTemplateFreight: defaultShippingTemplateFreight(),
|
shippingTemplateFreight: defaultShippingTemplateFreight(),
|
||||||
@@ -5643,6 +5745,9 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
waybillProcessDetailTab(tab) {
|
||||||
|
if (tab === 'batchSupplement') this.loadWaybillVoucherImages();
|
||||||
|
},
|
||||||
'form.transportType'(value, oldValue) {
|
'form.transportType'(value, oldValue) {
|
||||||
if (!this.shippingInfoFormEnabled || this.suppressTransportTypeClear) return;
|
if (!this.shippingInfoFormEnabled || this.suppressTransportTypeClear) return;
|
||||||
if (String(value ?? '') === String(oldValue ?? '')) return;
|
if (String(value ?? '') === String(oldValue ?? '')) return;
|
||||||
@@ -5990,6 +6095,10 @@ export default {
|
|||||||
this.detailBox = true;
|
this.detailBox = true;
|
||||||
this.detailLoading = true;
|
this.detailLoading = true;
|
||||||
this.detailRow = { ...row };
|
this.detailRow = { ...row };
|
||||||
|
this.waybillProcessDetailTab = 'punch';
|
||||||
|
this.waybillHasRelatedVoucher = false;
|
||||||
|
this.waybillVoucherImages = [];
|
||||||
|
this.waybillVoucherImagesLoaded = false;
|
||||||
const request =
|
const request =
|
||||||
typeof this.api.getDetail === 'function'
|
typeof this.api.getDetail === 'function'
|
||||||
? this.api.getDetail(row.id)
|
? this.api.getDetail(row.id)
|
||||||
@@ -6039,8 +6148,9 @@ export default {
|
|||||||
return projectIds.includes(String(projectId));
|
return projectIds.includes(String(projectId));
|
||||||
});
|
});
|
||||||
if (!config?.id) return [];
|
if (!config?.id) return [];
|
||||||
return getProcessConfigDetail(config.id).then(detailRes => {
|
return getProcessConfigDetail(config.id, this.detailRow.id).then(detailRes => {
|
||||||
const detail = detailRes?.data?.data || detailRes?.data || config;
|
const detail = detailRes?.data?.data || detailRes?.data || config;
|
||||||
|
this.waybillHasRelatedVoucher = Boolean(detail.hasRelatedVoucher);
|
||||||
return this.getProcessConfigEnabledNodes(detail);
|
return this.getProcessConfigEnabledNodes(detail);
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
@@ -6052,6 +6162,25 @@ export default {
|
|||||||
})
|
})
|
||||||
.catch(() => []);
|
.catch(() => []);
|
||||||
},
|
},
|
||||||
|
loadWaybillVoucherImages() {
|
||||||
|
if (!this.detailRow.id || this.waybillVoucherImagesLoading || this.waybillVoucherImagesLoaded) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.waybillVoucherImagesLoading = true;
|
||||||
|
getProcessConfigVoucherImages(this.detailRow.id)
|
||||||
|
.then(res => {
|
||||||
|
this.waybillVoucherImages = extractRecords(res);
|
||||||
|
this.waybillVoucherImagesLoaded = true;
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
this.waybillVoucherImagesLoading = false;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
previewWaybillVoucherImage(index) {
|
||||||
|
this.attachmentImagePreviewUrls = this.waybillVoucherImages.map(image => image.url).filter(Boolean);
|
||||||
|
this.attachmentImagePreviewIndex = index;
|
||||||
|
this.attachmentImagePreviewVisible = this.attachmentImagePreviewUrls.length > 0;
|
||||||
|
},
|
||||||
buildWaybillRouteNodes(rows = []) {
|
buildWaybillRouteNodes(rows = []) {
|
||||||
const nodes = [];
|
const nodes = [];
|
||||||
rows.forEach((row, rowIndex) => {
|
rows.forEach((row, rowIndex) => {
|
||||||
@@ -7021,6 +7150,7 @@ export default {
|
|||||||
['quantity', 'mileage', 'unitPrice'].forEach(prop => {
|
['quantity', 'mileage', 'unitPrice'].forEach(prop => {
|
||||||
if (Number(this.form[prop]) === -1) this.form[prop] = '';
|
if (Number(this.form[prop]) === -1) this.form[prop] = '';
|
||||||
});
|
});
|
||||||
|
this.form.mileage = this.normalizeMileageValue(this.form.mileage);
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
this.suppressTransportTypeClear = false;
|
this.suppressTransportTypeClear = false;
|
||||||
});
|
});
|
||||||
@@ -7291,6 +7421,7 @@ export default {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
applyShippingPlan(plan = {}) {
|
applyShippingPlan(plan = {}) {
|
||||||
|
this.suppressTransportTypeClear = true;
|
||||||
this.form.planId = plan.id || '';
|
this.form.planId = plan.id || '';
|
||||||
this.form.planName = plan.planName || '';
|
this.form.planName = plan.planName || '';
|
||||||
[
|
[
|
||||||
@@ -7301,6 +7432,7 @@ export default {
|
|||||||
'transportMode',
|
'transportMode',
|
||||||
'transportTypeName',
|
'transportTypeName',
|
||||||
'transportModeName',
|
'transportModeName',
|
||||||
|
'departureAddressId',
|
||||||
'departureName',
|
'departureName',
|
||||||
'departureAddress',
|
'departureAddress',
|
||||||
'departureContact',
|
'departureContact',
|
||||||
@@ -7308,6 +7440,7 @@ export default {
|
|||||||
'departureProvince',
|
'departureProvince',
|
||||||
'departureCity',
|
'departureCity',
|
||||||
'departureDistrict',
|
'departureDistrict',
|
||||||
|
'arrivalAddressId',
|
||||||
'arrivalName',
|
'arrivalName',
|
||||||
'arrivalAddress',
|
'arrivalAddress',
|
||||||
'arrivalContact',
|
'arrivalContact',
|
||||||
@@ -7337,11 +7470,24 @@ export default {
|
|||||||
].forEach(prop => {
|
].forEach(prop => {
|
||||||
if (firstGoods[prop] !== undefined) this.form[prop] = firstGoods[prop];
|
if (firstGoods[prop] !== undefined) this.form[prop] = firstGoods[prop];
|
||||||
});
|
});
|
||||||
|
this.form.cargoTypePath = this.normalizeBillingCargoTypePath(firstGoods.cargoTypePath);
|
||||||
|
this.initTaskInfoForm();
|
||||||
|
this.ensureBillingCargoTypeOptions().then(() => {
|
||||||
|
if (this.form.cargoTypePath.length) return;
|
||||||
|
const cargoType = this.findBillingCargoTypeByCodeOrName({
|
||||||
|
cargoTypeCode: this.form.cargoTypeCode,
|
||||||
|
cargoType: this.form.cargoType,
|
||||||
|
});
|
||||||
|
if (cargoType?.path) this.form.cargoTypePath = cargoType.path;
|
||||||
|
});
|
||||||
if (this.isTaskFullMode) {
|
if (this.isTaskFullMode) {
|
||||||
this.initTaskFullCargoRows(goodsRows);
|
this.initTaskFullCargoRows(goodsRows);
|
||||||
}
|
}
|
||||||
if (this.contractSelectEnabled) this.syncCurrentContractOption();
|
if (this.contractSelectEnabled) this.syncCurrentContractOption();
|
||||||
this.$refs.crud?.validateField?.('planName');
|
this.$refs.crud?.validateField?.('planName');
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.suppressTransportTypeClear = false;
|
||||||
|
});
|
||||||
},
|
},
|
||||||
clearShippingPlan() {
|
clearShippingPlan() {
|
||||||
this.form.planId = '';
|
this.form.planId = '';
|
||||||
@@ -7417,7 +7563,7 @@ export default {
|
|||||||
this.form.quantityUnit =
|
this.form.quantityUnit =
|
||||||
this.form.quantityUnit || taskInfo.quantityUnit || goodsInfo.quantityUnit || '吨';
|
this.form.quantityUnit || taskInfo.quantityUnit || goodsInfo.quantityUnit || '吨';
|
||||||
const mileage = this.form.mileage || taskInfo.mileage || goodsInfo.mileage || '';
|
const mileage = this.form.mileage || taskInfo.mileage || goodsInfo.mileage || '';
|
||||||
this.form.mileage = Number(mileage) === -1 ? '' : mileage;
|
this.form.mileage = this.normalizeMileageValue(mileage);
|
||||||
this.form.unitPrice = this.form.unitPrice || taskInfo.unitPrice || goodsInfo.unitPrice || '';
|
this.form.unitPrice = this.form.unitPrice || taskInfo.unitPrice || goodsInfo.unitPrice || '';
|
||||||
['quantity', 'mileage', 'unitPrice'].forEach(prop => {
|
['quantity', 'mileage', 'unitPrice'].forEach(prop => {
|
||||||
if (Number(this.form[prop]) === -1) this.form[prop] = '';
|
if (Number(this.form[prop]) === -1) this.form[prop] = '';
|
||||||
@@ -7702,6 +7848,11 @@ export default {
|
|||||||
.replace(/\D/g, '')
|
.replace(/\D/g, '')
|
||||||
.slice(0, 10);
|
.slice(0, 10);
|
||||||
},
|
},
|
||||||
|
normalizeMileageValue(value) {
|
||||||
|
if (value === undefined || value === null || value === '' || Number(value) === -1) return '';
|
||||||
|
const mileage = Number(value);
|
||||||
|
return Number.isFinite(mileage) && mileage >= 0 ? String(Math.trunc(mileage)) : '';
|
||||||
|
},
|
||||||
handleDispatchMileageInput(value) {
|
handleDispatchMileageInput(value) {
|
||||||
this.dispatchItemForm.mileage = String(value || '')
|
this.dispatchItemForm.mileage = String(value || '')
|
||||||
.replace(/\D/g, '')
|
.replace(/\D/g, '')
|
||||||
@@ -9015,13 +9166,54 @@ export default {
|
|||||||
if (number < 1024 * 1024) return `${(number / 1024).toFixed(1)}KB`;
|
if (number < 1024 * 1024) return `${(number / 1024).toFixed(1)}KB`;
|
||||||
return `${(number / 1024 / 1024).toFixed(1)}MB`;
|
return `${(number / 1024 / 1024).toFixed(1)}MB`;
|
||||||
},
|
},
|
||||||
|
attachmentUrl(row = {}) {
|
||||||
|
return row.url || row.link || row.fileUrl || row.downloadUrl || row.domain || '';
|
||||||
|
},
|
||||||
|
attachmentName(row = {}) {
|
||||||
|
return row.originalName || row.name || row.fileName || '附件';
|
||||||
|
},
|
||||||
|
attachmentExtension(row = {}) {
|
||||||
|
const source = String(this.attachmentName(row) || this.attachmentUrl(row)).split('?')[0];
|
||||||
|
const index = source.lastIndexOf('.');
|
||||||
|
return index > -1 ? source.slice(index + 1).toLowerCase() : '';
|
||||||
|
},
|
||||||
|
isAttachmentImage(row) {
|
||||||
|
return ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp'].includes(this.attachmentExtension(row));
|
||||||
|
},
|
||||||
|
previewAttachment(row) {
|
||||||
|
const url = this.attachmentUrl(row);
|
||||||
|
if (!url) {
|
||||||
|
this.$message.warning('附件地址为空,无法预览');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this.isAttachmentImage(row)) {
|
||||||
|
this.attachmentImagePreviewUrls = this.attachmentRows
|
||||||
|
.filter(item => this.isAttachmentImage(item) && this.attachmentUrl(item))
|
||||||
|
.map(item => this.attachmentUrl(item));
|
||||||
|
this.attachmentImagePreviewIndex = Math.max(this.attachmentImagePreviewUrls.indexOf(url), 0);
|
||||||
|
this.attachmentImagePreviewVisible = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.attachmentPreviewFile = {
|
||||||
|
name: this.attachmentName(row),
|
||||||
|
url,
|
||||||
|
mimeType: row.mimeType || row.contentType || '',
|
||||||
|
};
|
||||||
|
this.attachmentDocumentPreviewVisible = true;
|
||||||
|
},
|
||||||
|
handleAttachmentPreviewUnsupported() {
|
||||||
|
this.$message.warning('当前文件暂不支持在线预览');
|
||||||
|
},
|
||||||
|
handleAttachmentPreviewError() {
|
||||||
|
this.$message.error('附件预览失败');
|
||||||
|
},
|
||||||
downloadAttachment(row) {
|
downloadAttachment(row) {
|
||||||
const url = row.url || row.link;
|
const url = this.attachmentUrl(row);
|
||||||
if (!url) {
|
if (!url) {
|
||||||
this.$message.warning('附件地址为空');
|
this.$message.warning('附件地址为空');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
downloadFileByUrl(url, row.originalName || row.name || '附件');
|
downloadFileByUrl(url, this.attachmentName(row));
|
||||||
},
|
},
|
||||||
handleBatchDownload() {
|
handleBatchDownload() {
|
||||||
const rows = this.selectedAttachmentRows.length
|
const rows = this.selectedAttachmentRows.length
|
||||||
@@ -12413,6 +12605,32 @@ export default {
|
|||||||
gap: 8px;
|
gap: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&__waybill-process-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__voucher-image-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(5, minmax(0, 1fr));
|
||||||
|
gap: 8px;
|
||||||
|
min-height: 120px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__voucher-image-item {
|
||||||
|
aspect-ratio: 1;
|
||||||
|
overflow: hidden;
|
||||||
|
cursor: pointer;
|
||||||
|
border: 1px solid #eff1f7;
|
||||||
|
border-radius: 4px;
|
||||||
|
|
||||||
|
.el-image {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&__route-change-dialog {
|
&__route-change-dialog {
|
||||||
:deep(.el-dialog__body) {
|
:deep(.el-dialog__body) {
|
||||||
padding-top: 12px;
|
padding-top: 12px;
|
||||||
|
|||||||
@@ -2,14 +2,19 @@
|
|||||||
<div v-loading="loading" class="master-detail">
|
<div v-loading="loading" class="master-detail">
|
||||||
<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)">{{ statusName(master.businessStatus) }}</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)">{{ statusName(master.businessStatus) }}</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><dt>货物名称</dt><dd>{{ goodsNames }}</dd></div><div><dt>货物类型</dt><dd>{{ goodsTypes }}</dd></div><div><dt>运输周期</dt><dd>{{ dateRange }}</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>{{ 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>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<el-dialog v-model="documentPreviewVisible" :title="previewFile.name || '附件预览'" append-to-body destroy-on-close width="90%" top="4vh">
|
||||||
|
<open-file-viewer v-if="documentPreviewVisible && previewFile.url" :file="previewFile.url" :file-name="previewFile.name" :mime-type="previewFile.mimeType" width="100%" height="72vh" fit="contain" theme="auto" locale="zh-CN" :toolbar="viewerToolbar" :plugins="viewerPlugins" />
|
||||||
|
</el-dialog>
|
||||||
|
<el-image-viewer v-if="imagePreviewVisible" :url-list="imagePreviewUrls" :initial-index="imagePreviewIndex" @close="imagePreviewVisible = false" />
|
||||||
|
|
||||||
<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></h3><div class="segment-detail__header-right"><span>已调度 <em>{{ quantity(route.dispatchedQuantity) }}</em> / {{ quantity(master.totalQuantity) }},已到达 <em>{{ quantity(route.arrivedQuantity) }}</em> / {{ quantity(master.totalQuantity) }}</span><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 class="segment-detail__transport-type">{{ segmentTransportType(route) || '-' }}</span><span class="segment-detail__progress-bar"><span :style="{ width: `${segmentProgress(route)}%` }"></span></span><span>{{ route.departureName || '-' }} → {{ route.arrivalName || '-' }}</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>
|
||||||
<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">{{ waybillStatusName(row.businessStatus) }}</el-tag></template></el-table-column><el-table-column prop="createTime" label="创建时间" min-width="180" /></el-table>
|
<el-table :data="route.waybills || []" border class="waybill-table"><el-table-column label="运单号" min-width="180"><template #default="{ row }"><el-link type="primary" @click="openWaybill(row)">{{ row.waybillNo }}</el-link></template></el-table-column><el-table-column prop="carrierName" label="承运商" min-width="190" /><el-table-column label="司机/车牌" min-width="180"><template #default="{ row }">{{ driverVehicle(row) }}</template></el-table-column><el-table-column prop="cargoType" label="货物类型" min-width="120" /><el-table-column label="数量(吨)" width="130"><template #default="{ row }">{{ quantity(row.quantity) }}</template></el-table-column><el-table-column label="状态" width="130"><template #default="{ row }"><el-tag :type="waybillStatusType(row.businessStatus)" size="small">{{ waybillStatusName(row.businessStatus) }}</el-tag></template></el-table-column><el-table-column prop="createTime" label="创建时间" min-width="180" /></el-table>
|
||||||
@@ -29,11 +34,19 @@
|
|||||||
<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 { ArrowDown, ArrowUp } from '@element-plus/icons-vue';
|
||||||
|
import { ElImageViewer } from 'element-plus';
|
||||||
|
import { OpenFileViewer } from '@open-file-viewer/vue';
|
||||||
|
import { fallbackPlugin, imagePlugin, officePlugin, pdfPlugin, textPlugin } from '@open-file-viewer/core';
|
||||||
|
import '@open-file-viewer/core/style.css';
|
||||||
|
import pdfWorkerSrc from 'pdfjs-dist/build/pdf.worker.mjs?url';
|
||||||
|
|
||||||
|
const viewerPlugins = [imagePlugin(), pdfPlugin({ workerSrc: pdfWorkerSrc, useFetchData: true }), officePlugin({ pdf: { workerSrc: pdfWorkerSrc, useFetchData: true } }), textPlugin(), fallbackPlugin()];
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
components: { ElImageViewer, OpenFileViewer },
|
||||||
props: { id: [String, Number] },
|
props: { id: [String, Number] },
|
||||||
emits: ['back'],
|
emits: ['back'],
|
||||||
data() { return { loading: false, master: null, expandedSegments: {}, ArrowDown, ArrowUp }; },
|
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 } }; },
|
||||||
computed: {
|
computed: {
|
||||||
segments() {
|
segments() {
|
||||||
const routes = this.master?.routeProgress || this.master?.routes || [];
|
const routes = this.master?.routeProgress || this.master?.routes || [];
|
||||||
@@ -42,6 +55,7 @@ export default {
|
|||||||
return { ...route, departureName: previous?.departureName, departureAddress: previous?.departureAddress, arrivalName: route.departureName || this.master?.arrivalName, arrivalAddress: route.departureAddress || this.master?.arrivalAddress };
|
return { ...route, departureName: previous?.departureName, departureAddress: previous?.departureAddress, arrivalName: route.departureName || this.master?.arrivalName, arrivalAddress: route.departureAddress || this.master?.arrivalAddress };
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
attachments() { const value = this.master?.attachmentsJson; if (Array.isArray(value)) return value; if (!value) return []; try { const parsed = JSON.parse(value); return Array.isArray(parsed) ? parsed : []; } catch (error) { return []; } },
|
||||||
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('、') || '-'; },
|
||||||
dateRange() { return this.master?.planStartTime && this.master?.planEndTime ? `${String(this.master.planStartTime).slice(0, 10)} ~ ${String(this.master.planEndTime).slice(0, 10)}` : '-'; },
|
dateRange() { return this.master?.planStartTime && this.master?.planEndTime ? `${String(this.master.planStartTime).slice(0, 10)} ~ ${String(this.master.planEndTime).slice(0, 10)}` : '-'; },
|
||||||
@@ -63,6 +77,26 @@ export default {
|
|||||||
isSegmentExpanded(segmentNo) { return Boolean(this.expandedSegments[segmentNo]); },
|
isSegmentExpanded(segmentNo) { return Boolean(this.expandedSegments[segmentNo]); },
|
||||||
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 || ''; },
|
||||||
|
plannedQuantity(route = {}) { return route.planQuantity || route.totalQuantity || this.master?.totalQuantity || 0; },
|
||||||
|
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; },
|
||||||
|
attachmentUrl(file = {}) { return file.url || file.link || file.fileUrl || file.downloadUrl || file.domain || ''; },
|
||||||
|
attachmentName(file = {}) { return file.originalName || file.name || file.fileName || '附件'; },
|
||||||
|
attachmentExtension(file = {}) { const source = this.attachmentName(file).split('?')[0]; const index = source.lastIndexOf('.'); return index > -1 ? source.slice(index + 1).toLowerCase() : ''; },
|
||||||
|
isImageAttachment(file) { return ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp'].includes(this.attachmentExtension(file)); },
|
||||||
|
previewAttachment(file) {
|
||||||
|
const url = this.attachmentUrl(file);
|
||||||
|
if (!url) return this.$message.warning('附件地址为空,无法预览');
|
||||||
|
if (this.isImageAttachment(file)) {
|
||||||
|
this.imagePreviewUrls = this.attachments.filter(item => this.isImageAttachment(item)).map(item => this.attachmentUrl(item)).filter(Boolean);
|
||||||
|
this.imagePreviewIndex = Math.max(this.imagePreviewUrls.indexOf(url), 0);
|
||||||
|
this.imagePreviewVisible = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.previewFile = { name: this.attachmentName(file), url, mimeType: file.mimeType || file.contentType || '' };
|
||||||
|
this.documentPreviewVisible = true;
|
||||||
|
},
|
||||||
driverVehicle(row) { return [row.driverName, row.vehicleNo].filter(Boolean).join(' / ') || '-'; },
|
driverVehicle(row) { return [row.driverName, row.vehicleNo].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 } }); },
|
||||||
@@ -78,14 +112,18 @@ export default {
|
|||||||
.master-detail { padding-bottom: 20px; color: #303133; }
|
.master-detail { padding-bottom: 20px; color: #303133; }
|
||||||
.detail-overview, .execution-detail-card { margin-bottom: 8px; border: 1px solid #eff1f7; background: #fff; }
|
.detail-overview, .execution-detail-card { margin-bottom: 8px; border: 1px solid #eff1f7; background: #fff; }
|
||||||
.detail-heading { display: flex; align-items: center; justify-content: space-between; padding: 18px 24px; h2 { display: inline-block; margin: 0 16px 0 0; font-size: 20px; } h2 span { margin: 0 8px; color: #909399; font-weight: 400; } }
|
.detail-heading { display: flex; align-items: center; justify-content: space-between; padding: 18px 24px; h2 { display: inline-block; margin: 0 16px 0 0; font-size: 20px; } h2 span { margin: 0 8px; color: #909399; font-weight: 400; } }
|
||||||
.detail-meta { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 18px 24px; margin: 0; padding: 0 24px 20px; dt { margin-bottom: 6px; color: #909399; font-size: 13px; } dd { 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: #409eff !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: 2px solid #606266; }
|
.route-map__line { flex: 1 0 64px; min-width: 64px; height: 32px; border-bottom: 2px solid #606266; }
|
||||||
.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-bottom: 8px; cursor: pointer; h3 { display: flex; align-items: center; 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: #409eff; font-style: normal; } } }
|
||||||
|
.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__progress-bar { display: inline-flex; width: 72px; 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__header-right { display: flex; align-items: center; gap: 12px; }
|
.segment-detail__header-right { display: flex; align-items: center; gap: 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; }
|
||||||
|
|||||||
@@ -46,7 +46,7 @@
|
|||||||
<div v-show="route.selected" class="segment-content">
|
<div v-show="route.selected" class="segment-content">
|
||||||
<el-form :model="route" label-position="right" label-width="auto" class="dispatch-form">
|
<el-form :model="route" label-position="right" label-width="auto" class="dispatch-form">
|
||||||
<el-row :gutter="16">
|
<el-row :gutter="16">
|
||||||
<el-col :span="24">
|
<el-col :span="8">
|
||||||
<el-form-item label="运输方式" required class="transport-type-field"><el-input :model-value="route.transportType" readonly /></el-form-item>
|
<el-form-item label="运输方式" required class="transport-type-field"><el-input :model-value="route.transportType" readonly /></el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col v-if="isRoad(route)" :span="8">
|
<el-col v-if="isRoad(route)" :span="8">
|
||||||
@@ -56,13 +56,13 @@
|
|||||||
</el-radio-group>
|
</el-radio-group>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="8">
|
<el-col :span="12">
|
||||||
<el-form-item label="发货地址" required><el-input :model-value="addressText(route.departureName, route.departureAddress)" readonly /></el-form-item>
|
<el-form-item label="发货地址" required><el-input :model-value="addressText(route.departureName, route.departureAddress)" readonly /></el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="4"><el-form-item label="联系人"><el-input v-model="route.departureContact" placeholder="请输入" /></el-form-item></el-col>
|
<el-col :span="4"><el-form-item label="联系人"><el-input v-model="route.departureContact" placeholder="请输入" /></el-form-item></el-col>
|
||||||
<el-col :span="4"><el-form-item label="联系方式"><el-input v-model="route.departurePhone" placeholder="请输入" /></el-form-item></el-col>
|
<el-col :span="4"><el-form-item label="联系方式"><el-input v-model="route.departurePhone" placeholder="请输入" /></el-form-item></el-col>
|
||||||
<el-col :span="4"><el-form-item :label="dateStartLabel(route)" required><el-date-picker v-model="route.estimatedStartTime" type="date" value-format="YYYY-MM-DD" placeholder="请选择" /></el-form-item></el-col>
|
<el-col :span="4"><el-form-item :label="dateStartLabel(route)" required><el-date-picker v-model="route.estimatedStartTime" type="date" value-format="YYYY-MM-DD" placeholder="请选择" /></el-form-item></el-col>
|
||||||
<el-col :span="8">
|
<el-col :span="12">
|
||||||
<el-form-item label="收货地址" required><el-input :model-value="addressText(route.arrivalName, route.arrivalAddress)" readonly /></el-form-item>
|
<el-form-item label="收货地址" required><el-input :model-value="addressText(route.arrivalName, route.arrivalAddress)" readonly /></el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="4"><el-form-item label="联系人"><el-input v-model="route.arrivalContact" placeholder="请输入" /></el-form-item></el-col>
|
<el-col :span="4"><el-form-item label="联系人"><el-input v-model="route.arrivalContact" placeholder="请输入" /></el-form-item></el-col>
|
||||||
@@ -128,7 +128,7 @@
|
|||||||
<el-col v-if="route.carrierType !== '承运商'" :span="6"><el-form-item label="挂车车牌号" required><el-input v-model="route.trailerVehicleNo" placeholder="请输入" /></el-form-item></el-col>
|
<el-col v-if="route.carrierType !== '承运商'" :span="6"><el-form-item label="挂车车牌号" required><el-input v-model="route.trailerVehicleNo" placeholder="请输入" /></el-form-item></el-col>
|
||||||
<el-col v-if="route.carrierType !== '承运商'" :span="6"><el-form-item label="押运人" required><el-input v-model="route.escortName" placeholder="请输入" /></el-form-item></el-col>
|
<el-col v-if="route.carrierType !== '承运商'" :span="6"><el-form-item label="押运人" required><el-input v-model="route.escortName" placeholder="请输入" /></el-form-item></el-col>
|
||||||
<el-col v-if="route.carrierType !== '承运商'" :span="6"><el-form-item label="押运人手机号" required><el-input v-model="route.escortPhone" placeholder="请输入" /></el-form-item></el-col>
|
<el-col v-if="route.carrierType !== '承运商'" :span="6"><el-form-item label="押运人手机号" required><el-input v-model="route.escortPhone" placeholder="请输入" /></el-form-item></el-col>
|
||||||
<el-col v-if="route.carrierType !== '承运商'" :span="6"><el-form-item label="里程(km)" required><el-input :model-value="route.mileage" inputmode="decimal" placeholder="请输入" @input="value => handleMileageInput(route, value)" /></el-form-item></el-col>
|
<el-col :span="6"><el-form-item label="里程(km)" required><el-input :model-value="route.mileage" inputmode="numeric" maxlength="10" placeholder="请输入" @input="value => handleMileageInput(route, value)" /></el-form-item></el-col>
|
||||||
<el-col :span="6"><el-form-item label="备注"><el-input v-model="route.remark" maxlength="200" show-word-limit placeholder="请输入" /></el-form-item></el-col>
|
<el-col :span="6"><el-form-item label="备注"><el-input v-model="route.remark" maxlength="200" show-word-limit placeholder="请输入" /></el-form-item></el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
</el-form>
|
</el-form>
|
||||||
@@ -155,6 +155,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
|
<div v-if="pendingExpanded" class="pending-list-spacer" aria-hidden="true"></div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -208,12 +209,25 @@ export default {
|
|||||||
const res = await api.getDetail(this.id);
|
const res = await api.getDetail(this.id);
|
||||||
this.master = res.data?.data || res.data || res;
|
this.master = res.data?.data || res.data || res;
|
||||||
this.cargoTypeOptions = this.buildMasterCargoTypeOptions(this.master.goods || []);
|
this.cargoTypeOptions = this.buildMasterCargoTypeOptions(this.master.goods || []);
|
||||||
this.routes = (this.master.routes || []).map((node, index) => this.createRoute(node, index));
|
this.routes = this.dispatchRouteNodes().map((node, index) => this.createRoute(node, index));
|
||||||
} finally { this.loading = false; }
|
} finally { this.loading = false; }
|
||||||
},
|
},
|
||||||
|
dispatchRouteNodes() {
|
||||||
|
const routes = Array.isArray(this.master?.routes) ? this.master.routes : [];
|
||||||
|
if (routes.length) return routes;
|
||||||
|
return [{
|
||||||
|
segmentNo: '段1',
|
||||||
|
transportType: this.master?.transportType || '',
|
||||||
|
departureName: this.master?.arrivalName || '',
|
||||||
|
departureAddress: this.master?.arrivalAddress || '',
|
||||||
|
departureContact: this.master?.arrivalContact || '',
|
||||||
|
departurePhone: this.master?.arrivalPhone || '',
|
||||||
|
}];
|
||||||
|
},
|
||||||
createRoute(node, index) {
|
createRoute(node, index) {
|
||||||
const previous = index ? this.master.routes[index - 1] : this.master;
|
const routeNodes = this.dispatchRouteNodes();
|
||||||
return {
|
const previous = index ? routeNodes[index - 1] : this.master;
|
||||||
|
const route = {
|
||||||
...node,
|
...node,
|
||||||
selected: false,
|
selected: false,
|
||||||
documentType: '运单',
|
documentType: '运单',
|
||||||
@@ -223,8 +237,10 @@ export default {
|
|||||||
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 || '',
|
||||||
arrivalName: node.departureName || this.master.arrivalName || '', arrivalAddress: node.departureAddress || this.master.arrivalAddress || '', arrivalContact: node.departureContact || '', arrivalPhone: node.departurePhone || '',
|
arrivalName: node.departureName || this.master.arrivalName || '', arrivalAddress: node.departureAddress || this.master.arrivalAddress || '', arrivalContact: node.departureContact || '', arrivalPhone: node.departurePhone || '',
|
||||||
goods: [],
|
goods: (this.master.goods || []).map((goods, sourceIndex) => this.createGoodsRow(goods, sourceIndex)),
|
||||||
};
|
};
|
||||||
|
this.syncFreightItems(route);
|
||||||
|
return route;
|
||||||
},
|
},
|
||||||
buildMasterCargoTypeOptions(goods = []) {
|
buildMasterCargoTypeOptions(goods = []) {
|
||||||
return [...new Set(goods.map(item => item.cargoType).filter(Boolean))].map(cargoType => ({
|
return [...new Set(goods.map(item => item.cargoType).filter(Boolean))].map(cargoType => ({
|
||||||
@@ -248,8 +264,10 @@ export default {
|
|||||||
for (const option of options || []) {
|
for (const option of options || []) {
|
||||||
const path = [...parentPath, option.id];
|
const path = [...parentPath, option.id];
|
||||||
if (option.cargoName === cargoType) return path;
|
if (option.cargoName === cargoType) return path;
|
||||||
const result = this.findCargoTypePath(cargoType, option.children, path);
|
if (option.children?.length) {
|
||||||
if (result.length) return result;
|
const result = this.findCargoTypePath(cargoType, option.children, path);
|
||||||
|
if (result.length) return result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return [];
|
return [];
|
||||||
},
|
},
|
||||||
@@ -417,8 +435,12 @@ export default {
|
|||||||
if (goods) goods.quantityUnit = value;
|
if (goods) goods.quantityUnit = value;
|
||||||
},
|
},
|
||||||
handleMileageInput(route, value) {
|
handleMileageInput(route, value) {
|
||||||
const [integer = '', decimal = ''] = String(value || '').replace(/[^\d.]/g, '').split('.');
|
route.mileage = String(value || '').replace(/\D/g, '').slice(0, 10);
|
||||||
route.mileage = decimal || String(value || '').includes('.') ? `${integer}.${decimal.slice(0, 2)}` : integer;
|
},
|
||||||
|
normalizeMileage(value) {
|
||||||
|
if (value === undefined || value === null || value === '') return '';
|
||||||
|
const mileage = Number(value);
|
||||||
|
return Number.isFinite(mileage) && mileage >= 0 ? String(Math.trunc(mileage)) : '';
|
||||||
},
|
},
|
||||||
addressText(name, address) { return [name, address].filter(Boolean).join(' / ') || '-'; },
|
addressText(name, address) { return [name, address].filter(Boolean).join(' / ') || '-'; },
|
||||||
contactText(name, phone) { return [name, phone].filter(Boolean).join(' - ') || '-'; },
|
contactText(name, phone) { return [name, phone].filter(Boolean).join(' - ') || '-'; },
|
||||||
@@ -428,10 +450,7 @@ export default {
|
|||||||
return {
|
return {
|
||||||
...goods,
|
...goods,
|
||||||
sourceIndex,
|
sourceIndex,
|
||||||
cargoTypePath:
|
cargoTypePath: this.findCargoTypePath(goods.cargoType),
|
||||||
Array.isArray(goods.cargoTypePath) && goods.cargoTypePath.length
|
|
||||||
? goods.cargoTypePath
|
|
||||||
: this.findCargoTypePath(goods.cargoType),
|
|
||||||
dispatchQuantity: undefined,
|
dispatchQuantity: undefined,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@@ -529,7 +548,7 @@ export default {
|
|||||||
if (!route.estimatedEndTime) return this.$message.warning(`请选择${this.dateEndLabel(route)}`);
|
if (!route.estimatedEndTime) return this.$message.warning(`请选择${this.dateEndLabel(route)}`);
|
||||||
if (!this.validateRoutePhones(route)) return;
|
if (!this.validateRoutePhones(route)) return;
|
||||||
if (route.documentType === '运单') {
|
if (route.documentType === '运单') {
|
||||||
if (route.carrierType === '承运商' && (!route.carrierName || !route.vehicleNo)) return this.$message.warning('请填写承运商和车牌号');
|
if (route.carrierType === '承运商' && (!route.carrierName || !route.vehicleNo || !route.mileage)) return this.$message.warning('请填写承运商、车牌号和里程');
|
||||||
if (route.carrierType !== '承运商' && (!route.driverName || !route.driverPhone || !route.vehicleNo || !route.trailerVehicleNo || !route.escortName || !route.escortPhone || route.mileage === undefined || route.mileage === null || route.mileage === '')) return this.$message.warning('请补全自运或网货平台的车辆与人员信息');
|
if (route.carrierType !== '承运商' && (!route.driverName || !route.driverPhone || !route.vehicleNo || !route.trailerVehicleNo || !route.escortName || !route.escortPhone || route.mileage === undefined || route.mileage === null || route.mileage === '')) return this.$message.warning('请补全自运或网货平台的车辆与人员信息');
|
||||||
}
|
}
|
||||||
const batchNo = `${route.segmentNo}-${Date.now()}`;
|
const batchNo = `${route.segmentNo}-${Date.now()}`;
|
||||||
@@ -537,7 +556,7 @@ export default {
|
|||||||
id: `${route.segmentNo}-${item.cargoName}-${Date.now()}-${Math.random()}`,
|
id: `${route.segmentNo}-${item.cargoName}-${Date.now()}-${Math.random()}`,
|
||||||
batchNo,
|
batchNo,
|
||||||
segmentNo: route.segmentNo, relationNo: route.segmentNo, documentType: route.documentType, transportType: route.transportType, carrierType: route.carrierType,
|
segmentNo: route.segmentNo, relationNo: route.segmentNo, documentType: route.documentType, transportType: route.transportType, carrierType: route.carrierType,
|
||||||
carrierName: route.carrierName, driverName: route.driverName, driverPhone: route.driverPhone, vehicleNo: route.vehicleNo, trailerVehicleNo: route.trailerVehicleNo, escortName: route.escortName, escortPhone: route.escortPhone, mileage: route.mileage, unitPrice: this.freightItemsForGoods(route, item).unitPrice || '', priceUnit: this.freightItemsForGoods(route, item).priceUnit || '', currency: route.currency || 'CNY', freightAmount: this.freightAmount(this.freightItemsForGoods(route, item)), freightTotal: this.freightTotal(route), otherFeeTotal: route.otherFeeTotal || '', freightJson: this.buildFreightJson(route), remark: route.remark,
|
carrierName: route.carrierName, driverName: route.driverName, driverPhone: route.driverPhone, vehicleNo: route.vehicleNo, 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 || 'CNY', freightAmount: this.freightAmount(this.freightItemsForGoods(route, item)), freightTotal: this.freightTotal(route), otherFeeTotal: route.otherFeeTotal || '', freightJson: this.buildFreightJson(route), remark: route.remark,
|
||||||
departureName: route.departureName, departureAddress: route.departureAddress, departureContact: route.departureContact, departurePhone: route.departurePhone,
|
departureName: route.departureName, departureAddress: route.departureAddress, departureContact: route.departureContact, departurePhone: route.departurePhone,
|
||||||
arrivalName: route.arrivalName, arrivalAddress: route.arrivalAddress, arrivalContact: route.arrivalContact, arrivalPhone: route.arrivalPhone,
|
arrivalName: route.arrivalName, arrivalAddress: route.arrivalAddress, arrivalContact: route.arrivalContact, arrivalPhone: route.arrivalPhone,
|
||||||
estimatedStartTime: route.estimatedStartTime, estimatedEndTime: route.estimatedEndTime,
|
estimatedStartTime: route.estimatedStartTime, estimatedEndTime: route.estimatedEndTime,
|
||||||
@@ -589,7 +608,7 @@ export default {
|
|||||||
if (!this.pending.length) return this.$message.warning('请加入待提交调度清单');
|
if (!this.pending.length) return this.$message.warning('请加入待提交调度清单');
|
||||||
if (this.pending.some(item => !this.validateRoutePhones(item))) return;
|
if (this.pending.some(item => !this.validateRoutePhones(item))) return;
|
||||||
this.submitting = true;
|
this.submitting = true;
|
||||||
try { await api.dispatch({ id: this.master.id, dispatches: this.pending.map(({ id, ...item }) => item) }); this.$message.success('调度成功'); this.$emit('back'); } finally { this.submitting = false; }
|
try { await api.dispatch({ id: this.master.id, dispatches: this.pending.map(({ id, ...item }) => ({ ...item, mileage: this.normalizeMileage(item.mileage) })) }); this.$message.success('调度成功'); this.$emit('back'); } finally { this.submitting = false; }
|
||||||
},
|
},
|
||||||
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'; },
|
||||||
@@ -599,6 +618,7 @@ export default {
|
|||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
.master-dispatch { min-height: 100%; padding-bottom: 124px; background: #fff; color: #303133; }
|
.master-dispatch { min-height: 100%; padding-bottom: 124px; background: #fff; color: #303133; }
|
||||||
|
.pending-list-spacer { height: min(436px, calc(100vh - 130px)); }
|
||||||
.master-overview, .dispatch-section { margin-bottom: 8px; border: 1px solid #eff1f7; background: #fff; }
|
.master-overview, .dispatch-section { margin-bottom: 8px; border: 1px solid #eff1f7; background: #fff; }
|
||||||
.overview-heading, .segment-header, .pending-bar { display: flex; align-items: center; justify-content: space-between; }
|
.overview-heading, .segment-header, .pending-bar { display: flex; align-items: center; justify-content: space-between; }
|
||||||
.overview-heading { padding: 18px 24px 12px; h2 { display: inline-block; margin: 0 16px 0 0; font-size: 20px; } h2 span { margin: 0 8px; color: #909399; font-weight: 400; } }
|
.overview-heading { padding: 18px 24px 12px; h2 { display: inline-block; margin: 0 16px 0 0; font-size: 20px; } h2 span { margin: 0 8px; color: #909399; font-weight: 400; } }
|
||||||
|
|||||||
@@ -304,7 +304,11 @@
|
|||||||
prop="transportType"
|
prop="transportType"
|
||||||
min-width="150"
|
min-width="150"
|
||||||
show-overflow-tooltip
|
show-overflow-tooltip
|
||||||
/>
|
>
|
||||||
|
<template #default="{ row }">
|
||||||
|
{{ transportTypeLabel(row.transportType) }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
<el-table-column label="数据来源" prop="dataSource" min-width="130" show-overflow-tooltip />
|
<el-table-column label="数据来源" prop="dataSource" min-width="130" show-overflow-tooltip />
|
||||||
<el-table-column label="创建时间" prop="createTime" min-width="170" show-overflow-tooltip sortable/>
|
<el-table-column label="创建时间" prop="createTime" min-width="170" show-overflow-tooltip sortable/>
|
||||||
<el-table-column label="更新时间" prop="updateTime" min-width="170" show-overflow-tooltip sortable/>
|
<el-table-column label="更新时间" prop="updateTime" min-width="170" show-overflow-tooltip sortable/>
|
||||||
@@ -624,7 +628,11 @@
|
|||||||
label="运输方式"
|
label="运输方式"
|
||||||
min-width="130"
|
min-width="130"
|
||||||
show-overflow-tooltip
|
show-overflow-tooltip
|
||||||
/>
|
>
|
||||||
|
<template #default="{ row }">
|
||||||
|
{{ transportTypeLabel(row.transportType) }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
<el-table-column
|
<el-table-column
|
||||||
prop="cargoName"
|
prop="cargoName"
|
||||||
label="货物名称"
|
label="货物名称"
|
||||||
|
|||||||
@@ -521,6 +521,8 @@ const openUpload = async row => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
const clearFile = () => Object.assign(editing, { fileName: '', fileUrl: '', fileTaskId: '' });
|
const clearFile = () => Object.assign(editing, { fileName: '', fileUrl: '', fileTaskId: '' });
|
||||||
|
const isCurrentUploadTask = taskId =>
|
||||||
|
taskId && activeUploadTaskId.value && String(taskId) === String(activeUploadTaskId.value);
|
||||||
const stopCurrentUpload = async taskId => {
|
const stopCurrentUpload = async taskId => {
|
||||||
// 先使整个上传会话失效,阻止 MD5 计算、创建任务等异步流程返回后继续上传。
|
// 先使整个上传会话失效,阻止 MD5 计算、创建任务等异步流程返回后继续上传。
|
||||||
uploadCancelled.value = true;
|
uploadCancelled.value = true;
|
||||||
@@ -529,7 +531,13 @@ const stopCurrentUpload = async taskId => {
|
|||||||
const currentTaskId = taskId || activeUploadTaskId.value || editing.fileTaskId;
|
const currentTaskId = taskId || activeUploadTaskId.value || editing.fileTaskId;
|
||||||
if (!currentTaskId) return;
|
if (!currentTaskId) return;
|
||||||
cancelledTaskIds.value.add(String(currentTaskId));
|
cancelledTaskIds.value.add(String(currentTaskId));
|
||||||
|
if (isCurrentUploadTask(currentTaskId)) {
|
||||||
|
uploading.value = false;
|
||||||
|
activeUploadTaskId.value = undefined;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
|
const taskRes = await api.getFileTask(currentTaskId);
|
||||||
|
if (taskRes.data?.data?.status === 'completed') return;
|
||||||
await api.pauseFileTask([currentTaskId]);
|
await api.pauseFileTask([currentTaskId]);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
ElMessage.error(error.message || '暂停上传失败');
|
ElMessage.error(error.message || '暂停上传失败');
|
||||||
@@ -740,7 +748,7 @@ const loadBatches = async () => {
|
|||||||
createTimeEnd: batchCreateRange.value?.[1] ? `${batchCreateRange.value[1]} 23:59:59` : '',
|
createTimeEnd: batchCreateRange.value?.[1] ? `${batchCreateRange.value[1]} 23:59:59` : '',
|
||||||
});
|
});
|
||||||
const data = res.data?.data || {};
|
const data = res.data?.data || {};
|
||||||
batchRows.value = (data.records || []).map(item => ({ ...item, id: item.waybillIds }));
|
batchRows.value = data.records || [];
|
||||||
batchPage.total = data.total || 0;
|
batchPage.total = data.total || 0;
|
||||||
};
|
};
|
||||||
const resetBatchQuery = () => {
|
const resetBatchQuery = () => {
|
||||||
@@ -755,27 +763,18 @@ const confirmBatches = () => {
|
|||||||
if (!batches.has(item.batchNo)) batches.set(item.batchNo, item);
|
if (!batches.has(item.batchNo)) batches.set(item.batchNo, item);
|
||||||
});
|
});
|
||||||
selectedBatches.value = [...batches.values()];
|
selectedBatches.value = [...batches.values()];
|
||||||
editing.waybillImportBatchIds = selectedBatches.value.flatMap(item =>
|
editing.waybillImportBatchIds = selectedBatches.value.map(item => item.id);
|
||||||
String(item.waybillIds || item.id)
|
|
||||||
.split(',')
|
|
||||||
.filter(Boolean)
|
|
||||||
);
|
|
||||||
batchVisible.value = false;
|
batchVisible.value = false;
|
||||||
};
|
};
|
||||||
const removeBatch = row => {
|
const removeBatch = row => {
|
||||||
selectedBatches.value = selectedBatches.value.filter(item => item.id !== row.id);
|
selectedBatches.value = selectedBatches.value.filter(item => item.id !== row.id);
|
||||||
editing.waybillImportBatchIds = selectedBatches.value.flatMap(item =>
|
editing.waybillImportBatchIds = selectedBatches.value.map(item => item.id);
|
||||||
String(item.waybillIds || item.id)
|
|
||||||
.split(',')
|
|
||||||
.filter(Boolean)
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
const submitUpload = async () => {
|
const submitUpload = async () => {
|
||||||
saving.value = true;
|
saving.value = true;
|
||||||
try {
|
try {
|
||||||
if (uploading.value && editing.fileTaskId) {
|
if (uploading.value && editing.fileTaskId) {
|
||||||
await api.pauseFileTask([editing.fileTaskId]);
|
await stopCurrentUpload(editing.fileTaskId);
|
||||||
uploadAbortController.value?.abort();
|
|
||||||
}
|
}
|
||||||
const valid = await uploadFormRef.value.validate().catch(() => false);
|
const valid = await uploadFormRef.value.validate().catch(() => false);
|
||||||
if (!valid) return;
|
if (!valid) return;
|
||||||
@@ -785,11 +784,7 @@ const submitUpload = async () => {
|
|||||||
}
|
}
|
||||||
await api.submit({
|
await api.submit({
|
||||||
...editing,
|
...editing,
|
||||||
waybillImportBatchIds: selectedBatches.value.flatMap(item =>
|
waybillImportBatchIds: selectedBatches.value.map(item => item.id),
|
||||||
String(item.waybillIds || item.id)
|
|
||||||
.split(',')
|
|
||||||
.filter(Boolean)
|
|
||||||
),
|
|
||||||
});
|
});
|
||||||
ElMessage.success('提交成功');
|
ElMessage.success('提交成功');
|
||||||
await closeUploadDialog();
|
await closeUploadDialog();
|
||||||
@@ -826,7 +821,13 @@ const taskStatusName = status =>
|
|||||||
const formatSize = size =>
|
const formatSize = size =>
|
||||||
size >= 1024 ** 3 ? `${(size / 1024 ** 3).toFixed(1)}G` : `${(size / 1024 ** 2).toFixed(1)}M`;
|
size >= 1024 ** 3 ? `${(size / 1024 ** 3).toFixed(1)}G` : `${(size / 1024 ** 2).toFixed(1)}M`;
|
||||||
const pauseTask = async row => {
|
const pauseTask = async row => {
|
||||||
await api.pauseFileTask([row.id]);
|
if (row.status === 'completed') return;
|
||||||
|
if (isCurrentUploadTask(row.id)) {
|
||||||
|
await stopCurrentUpload(row.id);
|
||||||
|
} else {
|
||||||
|
await api.pauseFileTask([row.id]);
|
||||||
|
cancelledTaskIds.value.add(String(row.id));
|
||||||
|
}
|
||||||
ElMessage.success('已取消上传');
|
ElMessage.success('已取消上传');
|
||||||
loadProgress();
|
loadProgress();
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user