diff --git a/src/api/business/process-config.js b/src/api/business/process-config.js index 21a3bc4..a43262c 100644 --- a/src/api/business/process-config.js +++ b/src/api/business/process-config.js @@ -1,9 +1,24 @@ +import request from '@/axios'; import { createCrudApi } from './common'; const api = createCrudApi('/blade-transport/process-config'); 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 remove = api.remove; export const copy = api.copy; diff --git a/src/api/business/voucher-manage.js b/src/api/business/voucher-manage.js index b3c34c3..3f3ccd5 100644 --- a/src/api/business/voucher-manage.js +++ b/src/api/business/voucher-manage.js @@ -30,7 +30,7 @@ export const getPartUploadUrl = (id, partNumber) => export const updateFileTask = data => request({ url: `${fileTaskBaseUrl}/updateFileTask`, method: 'post', data }); 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 => request({ url: `${fileTaskBaseUrl}/resume`, method: 'post', params: { id } }); export const getFileTaskList = (current, size, params) => diff --git a/src/views/business/components/business-crud-page.vue b/src/views/business/components/business-crud-page.vue index 7a5928c..3f514d6 100644 --- a/src/views/business/components/business-crud-page.vue +++ b/src/views/business/components/business-crud-page.vue @@ -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 || '-' }} @@ -2502,15 +2507,44 @@
- - {{ node.name || node.nodeName || node.label }} + + + {{ node.name || node.nodeName || node.label }} + + + + - - +
+
+ +
+ +
+ + +
+ + + + + + { 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; diff --git a/src/views/business/components/master-order-detail.vue b/src/views/business/components/master-order-detail.vue index 8dea2d6..b526abb 100644 --- a/src/views/business/components/master-order-detail.vue +++ b/src/views/business/components/master-order-detail.vue @@ -2,14 +2,19 @@

多联总单详情 | {{ master.masterNo }}

{{ statusName(master.businessStatus) }}
返回
-
客户
{{ master.customerName || '-' }}
合同编号
{{ master.contractNo || '-' }}
项目
{{ master.projectName || '-' }}
货物名称
{{ goodsNames }}
货物类型
{{ goodsTypes }}
运输周期
{{ dateRange }}
+
客户
{{ master.customerName || '-' }}
合同编号
{{ master.contractNo || '-' }}
项目
{{ master.projectName || '-' }}
附件
-
+ + + + +

分段执行明细

-

{{ segmentNumber(route, index) }}{{ route.departureName || '-' }} → {{ route.arrivalName || '-' }}

已调度 {{ quantity(route.dispatchedQuantity) }} / {{ quantity(master.totalQuantity) }},已到达 {{ quantity(route.arrivedQuantity) }} / {{ quantity(master.totalQuantity) }}
+

{{ segmentNumber(route, index) }}{{ segmentTransportType(route) || '-' }}{{ route.departureName || '-' }} → {{ route.arrivalName || '-' }}已调度 {{ quantity(route.dispatchedQuantity) }} / {{ quantity(plannedQuantity(route)) }} {{ plannedUnit(route) }}

总量{{ quantity(master.totalQuantity) }}
已调度{{ quantity(route.dispatchedQuantity) }}
剩余{{ quantity(remainingQuantity(route)) }}
@@ -29,11 +34,19 @@