修复业务模块bug

This commit is contained in:
2026-08-13 18:53:49 +08:00
parent f62e36e846
commit b25b2fae20
7 changed files with 361 additions and 61 deletions
@@ -797,6 +797,7 @@
placeholder="请输入"
clearable
maxlength="10"
inputmode="numeric"
:disabled="dialogReadonly"
@input="handleTaskMileageInput"
/>
@@ -927,6 +928,7 @@
placeholder="请输入"
clearable
maxlength="10"
inputmode="numeric"
:disabled="dialogReadonly"
@input="handleTaskMileageInput"
/>
@@ -1347,6 +1349,7 @@
placeholder="请输入里程"
clearable
maxlength="10"
inputmode="numeric"
:disabled="dialogReadonly"
@input="handleTaskMileageInput"
/>
@@ -2334,6 +2337,7 @@
v-model="detailBox"
:title="`${config.title}详情`"
append-to-body
top="10px"
width="96%"
class="business-crud-page__detail-dialog"
>
@@ -2386,6 +2390,7 @@
v-for="file in attachmentRows"
:key="file.url || file.name || file.originalName"
type="primary"
@click="previewAttachment(file)"
>
{{ file.originalName || file.name || '-' }}
</el-link>
@@ -2502,15 +2507,44 @@
</section-card>
<div class="business-crud-page__waybill-detail-bottom">
<section-card title="执行详情">
<el-timeline v-if="waybillDetailProcessNodes.length">
<el-timeline-item
v-for="(node, index) in waybillDetailProcessNodes"
:key="node.key || index"
:timestamp="node.time || ''"
>{{ node.name || node.nodeName || node.label }}</el-timeline-item
<el-tabs v-model="waybillProcessDetailTab" type="border-card">
<el-tab-pane label="打卡详情" name="punch">
<el-timeline v-if="waybillDetailProcessNodes.length">
<el-timeline-item
v-for="(node, index) in waybillDetailProcessNodes"
: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>
<el-empty v-else description="暂无执行详情" :image-size="50" />
<div v-loading="waybillVoucherImagesLoading" class="business-crud-page__voucher-image-grid">
<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 title="物流轨迹">
<template #extra>
@@ -2565,7 +2599,11 @@
<el-table :data="attachmentRows" empty-text="暂无附件">
<el-table-column type="index" label="序号" width="70" align="center" />
<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 prop="description" label="附件描述" min-width="220" />
<el-table-column label="文件大小" width="110">
@@ -2587,6 +2625,38 @@
</template>
</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
v-model="waybillRouteChangeBox"
title="变更运输路线"
@@ -4674,6 +4744,7 @@ import {
import {
getDetail as getProcessConfigDetail,
getList as getProcessConfigList,
getVoucherImages as getProcessConfigVoucherImages,
} from '@/api/business/process-config';
import { getList as getCarrierCustomerList } from '@/api/vehicle/customer-archive';
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 { isMobile } from '@/utils/validate';
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 NProgress from 'nprogress';
import 'nprogress/nprogress.css';
@@ -4698,6 +4774,14 @@ const AMAP_KEY = '653b7cf105ad7fb8ec9b2f5198ade315';
const AMAP_SECURITY_CODE = '5aab65c632e48ebae0d0e28f5b28e21a';
let amapLoader;
const attachmentViewerPlugins = [
imagePlugin(),
pdfPlugin({ workerSrc: pdfWorkerSrc, useFetchData: true }),
officePlugin({ pdf: { workerSrc: pdfWorkerSrc, useFetchData: true } }),
textPlugin(),
fallbackPlugin(),
];
const estimateMenuButtonCount = config => {
const rowActions = (config.actions || []).filter(action => action !== 'batchComplete');
return 3 + rowActions.length + (config.operations || []).length;
@@ -4859,7 +4943,7 @@ const defaultDispatchRow = () => ({
const taskCarrierTypes = ['承运商', '自运', '网货平台'];
export default {
components: { WaybillImportDialog, Rank },
components: { WaybillImportDialog, Rank, ElImageViewer, OpenFileViewer },
name: 'BusinessCrudPage',
props: {
api: {
@@ -4904,6 +4988,11 @@ export default {
detailLoading: false,
detailRow: {},
waybillDetailProcessNodes: [],
waybillProcessDetailTab: 'punch',
waybillHasRelatedVoucher: false,
waybillVoucherImages: [],
waybillVoucherImagesLoading: false,
waybillVoucherImagesLoaded: false,
waybillRouteChangeBox: false,
waybillRouteChangeTab: 'change',
waybillRouteChangeRows: [],
@@ -4989,6 +5078,19 @@ export default {
selectedAttachmentRows: [],
contractFileRows: [],
selectedContractFileRows: [],
attachmentImagePreviewVisible: false,
attachmentImagePreviewUrls: [],
attachmentImagePreviewIndex: 0,
attachmentDocumentPreviewVisible: false,
attachmentPreviewFile: {},
attachmentViewerPlugins,
attachmentViewerToolbar: {
download: true,
fullscreen: true,
print: true,
rotate: true,
zoom: true,
},
transportCargoRows: [],
taskFreightCurrency: 'CNY',
shippingTemplateFreight: defaultShippingTemplateFreight(),
@@ -5643,6 +5745,9 @@ export default {
}
},
},
waybillProcessDetailTab(tab) {
if (tab === 'batchSupplement') this.loadWaybillVoucherImages();
},
'form.transportType'(value, oldValue) {
if (!this.shippingInfoFormEnabled || this.suppressTransportTypeClear) return;
if (String(value ?? '') === String(oldValue ?? '')) return;
@@ -5990,6 +6095,10 @@ export default {
this.detailBox = true;
this.detailLoading = true;
this.detailRow = { ...row };
this.waybillProcessDetailTab = 'punch';
this.waybillHasRelatedVoucher = false;
this.waybillVoucherImages = [];
this.waybillVoucherImagesLoaded = false;
const request =
typeof this.api.getDetail === 'function'
? this.api.getDetail(row.id)
@@ -6039,8 +6148,9 @@ export default {
return projectIds.includes(String(projectId));
});
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;
this.waybillHasRelatedVoucher = Boolean(detail.hasRelatedVoucher);
return this.getProcessConfigEnabledNodes(detail);
});
})
@@ -6052,6 +6162,25 @@ export default {
})
.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 = []) {
const nodes = [];
rows.forEach((row, rowIndex) => {
@@ -7021,6 +7150,7 @@ export default {
['quantity', 'mileage', 'unitPrice'].forEach(prop => {
if (Number(this.form[prop]) === -1) this.form[prop] = '';
});
this.form.mileage = this.normalizeMileageValue(this.form.mileage);
this.$nextTick(() => {
this.suppressTransportTypeClear = false;
});
@@ -7291,6 +7421,7 @@ export default {
});
},
applyShippingPlan(plan = {}) {
this.suppressTransportTypeClear = true;
this.form.planId = plan.id || '';
this.form.planName = plan.planName || '';
[
@@ -7301,6 +7432,7 @@ export default {
'transportMode',
'transportTypeName',
'transportModeName',
'departureAddressId',
'departureName',
'departureAddress',
'departureContact',
@@ -7308,6 +7440,7 @@ export default {
'departureProvince',
'departureCity',
'departureDistrict',
'arrivalAddressId',
'arrivalName',
'arrivalAddress',
'arrivalContact',
@@ -7337,11 +7470,24 @@ export default {
].forEach(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) {
this.initTaskFullCargoRows(goodsRows);
}
if (this.contractSelectEnabled) this.syncCurrentContractOption();
this.$refs.crud?.validateField?.('planName');
this.$nextTick(() => {
this.suppressTransportTypeClear = false;
});
},
clearShippingPlan() {
this.form.planId = '';
@@ -7417,7 +7563,7 @@ export default {
this.form.quantityUnit =
this.form.quantityUnit || taskInfo.quantityUnit || goodsInfo.quantityUnit || '吨';
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 || '';
['quantity', 'mileage', 'unitPrice'].forEach(prop => {
if (Number(this.form[prop]) === -1) this.form[prop] = '';
@@ -7702,6 +7848,11 @@ export default {
.replace(/\D/g, '')
.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) {
this.dispatchItemForm.mileage = String(value || '')
.replace(/\D/g, '')
@@ -9015,13 +9166,54 @@ export default {
if (number < 1024 * 1024) return `${(number / 1024).toFixed(1)}KB`;
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) {
const url = row.url || row.link;
const url = this.attachmentUrl(row);
if (!url) {
this.$message.warning('附件地址为空');
return;
}
downloadFileByUrl(url, row.originalName || row.name || '附件');
downloadFileByUrl(url, this.attachmentName(row));
},
handleBatchDownload() {
const rows = this.selectedAttachmentRows.length
@@ -12413,6 +12605,32 @@ export default {
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 {
:deep(.el-dialog__body) {
padding-top: 12px;