From 010204da57b997a927f69cad294633f43da0d684 Mon Sep 17 00:00:00 2001 From: ouyangxq <775153912@qq.com> Date: Fri, 14 Aug 2026 17:38:06 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../InboundMeasurementPanel.test.ts | 18 ++- .../measurement/InboundMeasurementPanel.vue | 9 +- src/modules/inbounds/measurement/types.ts | 8 +- .../review/MeasurementReviewPanel.test.ts | 9 +- .../review/MeasurementReviewPanel.vue | 20 +-- src/modules/inbounds/review/types.ts | 8 +- .../inbounds/weight/api/weightOcr.test.ts | 13 ++ src/modules/inbounds/weight/api/weightOcr.ts | 9 ++ .../WeightOcrPanel.behavior.test.ts | 60 ++++++++- .../weight/components/WeightOcrPanel.test.ts | 7 +- .../weight/components/WeightOcrPanel.vue | 114 ++++++++++++++++-- src/modules/inbounds/weight/types.ts | 17 ++- 12 files changed, 259 insertions(+), 33 deletions(-) diff --git a/src/modules/inbounds/measurement/InboundMeasurementPanel.test.ts b/src/modules/inbounds/measurement/InboundMeasurementPanel.test.ts index 58f81308..abbbc8d5 100644 --- a/src/modules/inbounds/measurement/InboundMeasurementPanel.test.ts +++ b/src/modules/inbounds/measurement/InboundMeasurementPanel.test.ts @@ -201,7 +201,7 @@ describe('InboundMeasurementPanel', () => { expect(text).toContain('扫描箱测量数据明细') expect(text).toContain('订单总箱数') expect(text).toContain('确认箱数合计') - expect(text).toContain('实测总毛重') + expect(text).toContain('货物总重量') expect(text).toContain('实测总体积') expect(text).toContain('实测总材积重') expect(text).not.toContain('租户') @@ -271,6 +271,22 @@ describe('InboundMeasurementPanel', () => { expect(getInboundMeasurement).toHaveBeenCalledTimes(1) }) + it('shows a normal A0409 business reason instead of relabeling it as a version conflict', async () => { + vi.mocked(updateConfirmedPackageCount).mockRejectedValueOnce( + new ApiResponseError('该托盘已被占用,请重新选择', 'A0409', 409), + ) + const wrapper = await mountPanel() + const countInput = wrapper.find('.measurement-count-editor input') + await countInput.setValue('9') + const save = wrapper.findAll('button').find((button) => button.text().includes('保存测量数据')) + await save?.trigger('click') + await flushPromises() + + expect(wrapper.text()).toContain('该托盘已被占用,请重新选择') + expect(wrapper.text()).toContain('操作未完成') + expect(wrapper.text()).not.toContain('数据版本已变化,当前修改尚未丢失') + }) + it('requires the dirty draft to be saved before submitting for review', async () => { const wrapper = await mountPanel() const countInput = wrapper.find('.measurement-count-editor input') diff --git a/src/modules/inbounds/measurement/InboundMeasurementPanel.vue b/src/modules/inbounds/measurement/InboundMeasurementPanel.vue index 3c4c71ce..8b4eecb6 100644 --- a/src/modules/inbounds/measurement/InboundMeasurementPanel.vue +++ b/src/modules/inbounds/measurement/InboundMeasurementPanel.vue @@ -314,11 +314,8 @@ function validateDraft() { function setOperationError(error: unknown, fallback: string) { const details = getApiErrorDetails(error, fallback) operationError.value = details - conflictDetected.value = details.code === 'A0409' - || details.code === 'CONFLICT' - || details.code === 'HTTP_409' - || details.message.includes('版本') - || details.message.includes('并发') + conflictDetected.value = details.message.includes('版本已变化') + || details.message.includes('并发修改') return details } @@ -532,7 +529,7 @@ defineExpose({ saveDraft, submitForReview, updateActualGrossWeight, mayMaintain, - + diff --git a/src/modules/inbounds/measurement/types.ts b/src/modules/inbounds/measurement/types.ts index cf88c644..7d328dcc 100644 --- a/src/modules/inbounds/measurement/types.ts +++ b/src/modules/inbounds/measurement/types.ts @@ -39,6 +39,11 @@ export interface InboundMeasurementItem { export interface InboundMeasurementWeightResult { id: MeasurementIdentifier fileId: MeasurementIdentifier + palletId: MeasurementIdentifier + palletCode: string + palletType: string + palletCapacityCbm: number + palletTareWeightKg: number ocrProvider: string | null ocrRawText: string | null ocrWeightValue: number | null @@ -46,7 +51,8 @@ export interface InboundMeasurementWeightResult { confidence: number | null adjustedWeightValue: number | null adjustedWeightUnit: string | null - finalWeightKg: number | null + grossWeightKg: number | null + netWeightKg: number | null recognitionStatus: string adjustmentReason: string | null adjustedBy: MeasurementIdentifier | null diff --git a/src/modules/inbounds/review/MeasurementReviewPanel.test.ts b/src/modules/inbounds/review/MeasurementReviewPanel.test.ts index 60eb57de..e339e4eb 100644 --- a/src/modules/inbounds/review/MeasurementReviewPanel.test.ts +++ b/src/modules/inbounds/review/MeasurementReviewPanel.test.ts @@ -23,7 +23,7 @@ const detail = { originalDocumentNo: 'ORD-001', originalGrossWeightKg: 100, originalVolumeCbm: 2, originalPackageCount: 10, actualPackageCount: 10, actualGrossWeightKg: 110, actualVolumeCbm: 2.2, volumeWeightKg: 367.4, actualGoodsName: '测试货物', items: [{ id: '1', boxCode: 'RK90001#10-1', itemNo: '001', boxSpecification: '60×40×30', packageCount: 10, lengthCm: 60, widthCm: 40, heightCm: 30, girthCm: 240, unitWeightKg: 11, unitVolumeCbm: 0.072, totalWeightKg: 110, totalVolumeCbm: 0.72, volumeWeightKg: 120.24, intelligentImageFileId: '30001', machineNo: 'MJ20210327', measuredAt: '2026-08-04T10:30:00+08:00', overLimitWarning: '周长超过渠道限制,请人工核验尺寸后再作出审核决定', outerLengthCm: 61, outerWidthCm: 41, outerHeightCm: 31, remark: '外箱存在轻微变形' }], - weightResults: [{ id: '2', fileId: 'file-1', ocrProvider: 'OCR', ocrRawText: '设备屏幕原始识别内容 110.5 KG,包含一段需要自动换行展示的较长文本', ocrWeightValue: 110.5, ocrWeightUnit: 'KG', confidence: 0.98, adjustedWeightValue: 110, adjustedWeightUnit: 'KG', finalWeightKg: 110, recognitionStatus: 'CONFIRMED', adjustmentReason: '去除托盘重量后采用净值', adjustedBy: '20001', adjustedAt: '2026-08-04T09:58:00', confirmedBy: '20002', confirmedAt: '2026-08-04T10:00:00', version: 1 }], + weightResults: [{ id: '2', fileId: 'file-1', palletId: '50001', palletCode: 'TP-001', palletType: '木托盘', palletCapacityCbm: 1.2, palletTareWeightKg: 10, ocrProvider: 'OCR', ocrRawText: '设备屏幕原始识别内容 120.5 KG,包含一段需要自动换行展示的较长文本', ocrWeightValue: 120.5, ocrWeightUnit: 'KG', confidence: 0.98, adjustedWeightValue: 120, adjustedWeightUnit: 'KG', grossWeightKg: 120, netWeightKg: 110, recognitionStatus: 'CONFIRMED', adjustmentReason: '修正 OCR 毛重读数', adjustedBy: '20001', adjustedAt: '2026-08-04T09:58:00', confirmedBy: '20002', confirmedAt: '2026-08-04T10:00:00', version: 1 }], } enableAutoUnmount(afterEach) @@ -48,11 +48,14 @@ describe('MeasurementReviewPanel', () => { expect(wrapper.text()).toContain('设备实测') expect(wrapper.text()).toContain('设备采集') expect(wrapper.text()).not.toContain('DEVICE') - expect(wrapper.text()).toContain('称重 OCR') + expect(wrapper.text()).toContain('货物总重量') expect(wrapper.text()).toContain('实测长 × 宽 × 高(cm)') expect(wrapper.text()).toContain('周长超过渠道限制') expect(wrapper.text()).toContain('OCR 原文:设备屏幕原始识别内容') - expect(wrapper.text()).toContain('调整原因:去除托盘重量后采用净值') + expect(wrapper.text()).toContain('TP-001') + expect(wrapper.text()).toContain('托盘皮重') + expect(wrapper.text()).toContain('货物重量') + expect(wrapper.text()).toContain('调整原因:修正 OCR 毛重读数') expect(wrapper.findComponent(DataTable).exists()).toBe(true) expect(wrapper.text()).not.toContain('租户') expect(wrapper.text()).toContain('通过并进入待分柜') diff --git a/src/modules/inbounds/review/MeasurementReviewPanel.vue b/src/modules/inbounds/review/MeasurementReviewPanel.vue index 0e179877..0bce1ccc 100644 --- a/src/modules/inbounds/review/MeasurementReviewPanel.vue +++ b/src/modules/inbounds/review/MeasurementReviewPanel.vue @@ -49,7 +49,7 @@ const canRelease = computed(() => access.can('firstleg:inbound:exception-release const customerId = computed(() => props.customerId || props.tenantId || '') const isPending = computed(() => detail.value?.measureReviewStatus === 'PENDING' && detail.value?.processStatus === 'MEASURE_PENDING') const isRejected = computed(() => detail.value?.measureReviewStatus === 'REJECTED' && detail.value?.processStatus === 'MEASURE_REJECTED') -const confirmedWeights = computed(() => detail.value?.weightResults.filter((item) => item.confirmedAt && item.finalWeightKg !== null).length ?? 0) +const confirmedWeights = computed(() => detail.value?.weightResults.filter((item) => item.confirmedAt && item.netWeightKg !== null).length ?? 0) const reviewStatusLabel = computed(() => inboundStatusLabel(detail.value?.measureReviewStatus, inboundReviewStatusLabels, '未提交')) const measurementColumns: DataTableColumn[] = [ @@ -69,10 +69,13 @@ const measurementColumns: DataTableColumn[] = [ const weightColumns: DataTableColumn[] = [ { key: 'file', label: '称重文件 / OCR 原文', minWidth: 260, slotName: 'file' }, + { key: 'pallet', label: '托盘', minWidth: 170, slotName: 'pallet' }, { key: 'status', label: '识别状态', width: 100, slotName: 'status' }, - { key: 'ocrWeight', label: '原始重量', minWidth: 110, slotName: 'ocrWeight' }, - { key: 'adjustedWeight', label: '调整后重量', minWidth: 120, slotName: 'adjustedWeight' }, - { key: 'finalWeight', label: '最终重量', minWidth: 110, slotName: 'finalWeight' }, + { key: 'ocrWeight', label: 'OCR 含托盘毛重', minWidth: 140, slotName: 'ocrWeight' }, + { key: 'adjustedWeight', label: '人工修正毛重', minWidth: 130, slotName: 'adjustedWeight' }, + { key: 'grossWeight', label: '服务端毛重', minWidth: 115, slotName: 'grossWeight' }, + { key: 'tareWeight', label: '托盘皮重', minWidth: 110, slotName: 'tareWeight' }, + { key: 'netWeight', label: '货物重量', minWidth: 110, slotName: 'netWeight' }, { key: 'confidence', label: '置信度', width: 90, slotName: 'confidence' }, { key: 'reason', label: '调整原因', minWidth: 180, slotName: 'reason' }, ] @@ -103,7 +106,7 @@ function measurementSourceLabel(value: string | null | undefined) { function isConflict(error: unknown) { const details = getApiErrorDetails(error) - return details.code === 'A0409' || details.code === '409' || /并发|版本|已被更新|状态已变化/.test(details.message) + return details.message.includes('版本已变化') || details.message.includes('并发修改') } async function loadReview() { @@ -251,7 +254,7 @@ defineExpose({ confirmApprove, openReasonDialog, isPending, isRejected, canRevie
申报箱数
{{ formatNumber(detail.originalPackageCount, ' 箱') }}
实测箱数
{{ formatNumber(detail.actualPackageCount, ' 箱') }}
申报毛重
{{ formatNumber(detail.originalGrossWeightKg, ' kg') }}
-
称重 OCR
{{ formatNumber(detail.actualGrossWeightKg, ' kg') }}
+
货物总重量
{{ formatNumber(detail.actualGrossWeightKg, ' kg') }}
申报体积
{{ formatNumber(detail.originalVolumeCbm, ' CBM') }}
设备实测体积
{{ formatNumber(detail.actualVolumeCbm, ' CBM') }}
材积重
{{ formatNumber(detail.volumeWeightKg, ' kg') }}
@@ -289,10 +292,13 @@ defineExpose({ confirmApprove, openReasonDialog, isPending, isRejected, canRevie

称重图片识别结果

{{ detail.weightResults.length }} 张
+ - + + + diff --git a/src/modules/inbounds/review/types.ts b/src/modules/inbounds/review/types.ts index fc36e82d..09c0982b 100644 --- a/src/modules/inbounds/review/types.ts +++ b/src/modules/inbounds/review/types.ts @@ -28,6 +28,11 @@ export interface MeasurementReviewItem { export interface MeasurementReviewWeightResult { id: string fileId: string | null + palletId: string + palletCode: string + palletType: string + palletCapacityCbm: number + palletTareWeightKg: number ocrProvider: string | null ocrRawText: string | null ocrWeightValue: number | null @@ -35,7 +40,8 @@ export interface MeasurementReviewWeightResult { confidence: number | null adjustedWeightValue: number | null adjustedWeightUnit: string | null - finalWeightKg: number | null + grossWeightKg: number | null + netWeightKg: number | null recognitionStatus: string | null adjustmentReason: string | null adjustedBy: string | null diff --git a/src/modules/inbounds/weight/api/weightOcr.test.ts b/src/modules/inbounds/weight/api/weightOcr.test.ts index 31994205..53829841 100644 --- a/src/modules/inbounds/weight/api/weightOcr.test.ts +++ b/src/modules/inbounds/weight/api/weightOcr.test.ts @@ -8,6 +8,7 @@ import { downloadWeightImage, downloadWeightThumbnail, getWeightOcrRawText, + getWeightPalletOptions, getWeightWorkspace, normalizeWeightConfidence, retryWeightRecognition, @@ -81,12 +82,24 @@ describe('inbound weight OCR API', () => { ) }) + it('loads same-warehouse pallet snapshots through Firstleg', async () => { + const options = [{ palletId: '11', palletCode: 'TP-11', palletType: '木托盘', capacityCbm: 1.2, tareWeightKg: 12 }] + vi.mocked(http.get).mockResolvedValue({ code: '200', message: 'success', data: options }) + + await expect(getWeightPalletOptions('10001', '20001')).resolves.toEqual(options) + expect(http.get).toHaveBeenCalledWith( + '/platform/v1/first-leg/inbounds/20001/weight-pallet-options', + { params: { tenantId: '10001' } }, + ) + }) + it('registers, corrects, and confirms a weight result using the implemented contracts', async () => { vi.mocked(http.post).mockResolvedValue({ code: '200', message: 'success', data: { id: '1' } }) vi.mocked(http.put).mockResolvedValue({ code: '200', message: 'success', data: { id: '1', version: 1 } }) const createPayload = { fileId: '9007199254740995', + palletId: '11', ocrProvider: 'FILE_WEIGHT_OCR', ocrRawText: null, ocrWeightValue: 12.5, diff --git a/src/modules/inbounds/weight/api/weightOcr.ts b/src/modules/inbounds/weight/api/weightOcr.ts index 1d48ef37..8caafc52 100644 --- a/src/modules/inbounds/weight/api/weightOcr.ts +++ b/src/modules/inbounds/weight/api/weightOcr.ts @@ -5,6 +5,7 @@ import type { CorrectWeightResultRequest, CreateWeightResultRequest, WeightImageUploadResult, + WeightPalletOption, WeightResultResource, WeightWorkspaceResource, } from '../types' @@ -59,6 +60,14 @@ export async function getWeightWorkspace(tenantId: string, inboundId: string) { return unwrapApiResponse(response) } +export async function getWeightPalletOptions(tenantId: string, inboundId: string) { + const response = await http.get, ApiEnvelope>( + `${measurementBasePath}/${segment(inboundId)}/weight-pallet-options`, + { params: { tenantId } }, + ) + return unwrapApiResponse(response) +} + export async function uploadWeightImage(tenantId: string, inboundId: string, file: File) { const body = new FormData() body.append('file', file) diff --git a/src/modules/inbounds/weight/components/WeightOcrPanel.behavior.test.ts b/src/modules/inbounds/weight/components/WeightOcrPanel.behavior.test.ts index f0b04e39..286383bb 100644 --- a/src/modules/inbounds/weight/components/WeightOcrPanel.behavior.test.ts +++ b/src/modules/inbounds/weight/components/WeightOcrPanel.behavior.test.ts @@ -14,6 +14,7 @@ import { downloadWeightImage, downloadWeightThumbnail, getWeightWorkspace, + getWeightPalletOptions, uploadWeightImage, } from '../api/weightOcr' import type { WeightResultResource, WeightWorkspaceResource } from '../types' @@ -30,6 +31,7 @@ vi.mock('../api/weightOcr', async () => { downloadWeightImage: vi.fn(), downloadWeightThumbnail: vi.fn(), getWeightWorkspace: vi.fn(), + getWeightPalletOptions: vi.fn(), uploadWeightImage: vi.fn(), } }) @@ -37,6 +39,11 @@ vi.mock('../api/weightOcr', async () => { const pendingWeight: WeightResultResource = { id: '30001', fileId: '40001', + palletId: '50001', + palletCode: 'TP-001', + palletType: '木托盘', + palletCapacityCbm: 1.2, + palletTareWeightKg: 12, ocrProvider: 'FILE_WEIGHT_OCR', ocrRawText: '285.5', ocrWeightValue: 285.5, @@ -44,7 +51,8 @@ const pendingWeight: WeightResultResource = { confidence: 0.98, adjustedWeightValue: null, adjustedWeightUnit: null, - finalWeightKg: null, + grossWeightKg: 285.5, + netWeightKg: null, recognitionStatus: 'PENDING_CONFIRM', adjustmentReason: null, adjustedBy: null, @@ -120,6 +128,9 @@ describe('WeightOcrPanel confirmation behavior', () => { vi.clearAllMocks() document.body.innerHTML = '' vi.mocked(getWeightWorkspace).mockResolvedValue(workspace) + vi.mocked(getWeightPalletOptions).mockResolvedValue([{ + palletId: '50001', palletCode: 'TP-001', palletType: '木托盘', capacityCbm: 1.2, tareWeightKg: 12, + }]) vi.mocked(downloadWeightImage).mockResolvedValue(new Blob(['image'], { type: 'image/jpeg' })) vi.mocked(downloadWeightThumbnail).mockResolvedValue(new Blob(['thumb'], { type: 'image/jpeg' })) vi.stubGlobal('URL', { @@ -129,7 +140,8 @@ describe('WeightOcrPanel confirmation behavior', () => { vi.mocked(confirmWeightResult).mockResolvedValue({ ...pendingWeight, recognitionStatus: 'CONFIRMED', - finalWeightKg: 285.5, + grossWeightKg: 297.5, + netWeightKg: 285.5, version: 1, }) }) @@ -154,6 +166,8 @@ describe('WeightOcrPanel confirmation behavior', () => { }) const wrapper = mountPanel() await flushPromises() + await wrapper.find('.pallet-picker .el-select').trigger('click') + await new DOMWrapper(document.body).find('.el-select-dropdown__item').trigger('click') const input = wrapper.get('input[type="file"]') const file = new File(['image'], 'weight.jpg', { type: 'image/jpeg' }) Object.defineProperty(input.element, 'files', { configurable: true, value: [file] }) @@ -162,6 +176,7 @@ describe('WeightOcrPanel confirmation behavior', () => { await flushPromises() expect(uploadWeightImage).toHaveBeenCalledWith('10001', '20001', file) + expect(createWeightResult).toHaveBeenCalledWith('10001', '20001', expect.objectContaining({ palletId: '50001' })) expect(document.body.textContent).toContain('确认称重图片') expect(document.body.textContent).toContain('识别重量 12.500 KG') expect(document.body.querySelector('input[aria-label="当前图片确认重量"]')).not.toBeNull() @@ -225,7 +240,7 @@ describe('WeightOcrPanel confirmation behavior', () => { .mockResolvedValueOnce({ ...workspace, actualGrossWeightKg: 285.5, - weightResults: [{ ...pendingWeight, recognitionStatus: 'CONFIRMED', finalWeightKg: 285.5, version: 1 }], + weightResults: [{ ...pendingWeight, recognitionStatus: 'CONFIRMED', grossWeightKg: 297.5, netWeightKg: 285.5, version: 1 }], }) const wrapper = mountPanel() await flushPromises() @@ -236,10 +251,47 @@ describe('WeightOcrPanel confirmation behavior', () => { expect(wrapper.emitted('summaryChanged')).toEqual([[285.5]]) expect(wrapper.emitted('changed')).toHaveLength(1) - expect(wrapper.text()).toContain('已确认总毛重') + expect(wrapper.text()).toContain('货物总重量') expect(wrapper.text()).toContain('285.500 KG') }) + it('requires a pallet selection before opening the file picker', async () => { + const wrapper = mountPanel() + await flushPromises() + const click = vi.spyOn(wrapper.get('input[type="file"]').element as HTMLInputElement, 'click') + + await wrapper.findAll('button').find((button) => button.text().includes('上传称重图片'))?.trigger('click') + + expect(click).not.toHaveBeenCalled() + }) + + it('shows an actionable empty state when the warehouse has no pallet options', async () => { + vi.mocked(getWeightPalletOptions).mockResolvedValueOnce([]) + const wrapper = mountPanel() + await flushPromises() + + expect(wrapper.text()).toContain('当前仓库暂无可选托盘') + expect(wrapper.findAll('button').some((button) => button.text().includes('重新加载'))).toBe(true) + }) + + it('shows pallet loading failures without hiding existing weight results', async () => { + vi.mocked(getWeightPalletOptions).mockRejectedValueOnce(new ApiResponseError('托盘服务暂不可用', 'A0500', 503)) + const wrapper = mountPanel() + await flushPromises() + + expect(wrapper.text()).toContain('托盘服务暂不可用') + expect(wrapper.findAll('.weight-card')).toHaveLength(1) + }) + + it('does not load weight or pallet data without measurement view permission', async () => { + const wrapper = mountPanel([]) + await flushPromises() + + expect(wrapper.text()).toContain('缺少入库测量查看权限') + expect(getWeightWorkspace).not.toHaveBeenCalled() + expect(getWeightPalletOptions).not.toHaveBeenCalled() + }) + it('requires explicit image review before confirming a low-confidence OCR value', async () => { vi.mocked(getWeightWorkspace).mockResolvedValue({ ...workspace, diff --git a/src/modules/inbounds/weight/components/WeightOcrPanel.test.ts b/src/modules/inbounds/weight/components/WeightOcrPanel.test.ts index df442d78..583fffa2 100644 --- a/src/modules/inbounds/weight/components/WeightOcrPanel.test.ts +++ b/src/modules/inbounds/weight/components/WeightOcrPanel.test.ts @@ -16,6 +16,8 @@ describe('WeightOcrPanel contract', () => { it('covers upload, immediate preview, inline correction, deletion, confirmation, and server totals', () => { expect(source).toContain('uploadWeightImage') + expect(source).toContain('getWeightPalletOptions') + expect(source).toContain('palletId: selectedPalletId.value') expect(source).toContain('downloadWeightImage') expect(source).toContain('await openPreview(created, URL.createObjectURL(file))') expect(source).toContain('previewShowsRecognition.value = Boolean(localUrl)') @@ -26,7 +28,9 @@ describe('WeightOcrPanel contract', () => { expect(source).toContain('识别重量') expect(source).toContain('payload.adjustedWeightValue = Number(draft.value)') expect(source).toContain('payload.adjustmentReason = buildAdjustmentReason(row)') - expect(source).toContain('已确认总毛重') + expect(source).toContain('货物总重量') + expect(source).toContain('palletTareWeightKg') + expect(source).toContain('netWeightKg') expect(source).toContain("emit('summaryChanged', workspace.value?.actualGrossWeightKg ?? null)") }) @@ -53,6 +57,7 @@ describe('WeightOcrPanel contract', () => { expect(source).toContain('() const workspace = ref(null) +const palletOptions = ref([]) +const selectedPalletId = ref('') +const palletLoading = ref(false) +const palletError = ref(null) const loading = ref(false) const uploading = ref(false) const loadError = ref(null) @@ -58,6 +64,7 @@ const canConfirm = computed(() => access.can('platform:firstleg:inbound:weight:c const canView = computed(() => access.can('platform:firstleg:inbound:measure:view')) const editable = computed(() => Boolean(workspace.value && editableStatuses.has(workspace.value.processStatus))) const rows = computed(() => workspace.value?.weightResults ?? []) +const selectedPallet = computed(() => palletOptions.value.find((option) => option.palletId === selectedPalletId.value) ?? null) const previewResult = computed(() => rows.value.find((row) => row.id === previewResultId.value) || previewSnapshot.value) const previewConfirmDisabled = computed(() => { @@ -124,7 +131,7 @@ function isWeightDraftValid(row: WeightResultResource) { } function cardWeight(row: WeightResultResource) { - if (row.recognitionStatus === 'CONFIRMED') return formatWeight(row.finalWeightKg) + if (row.recognitionStatus === 'CONFIRMED') return formatWeight(row.netWeightKg) const draft = weightDrafts.value[row.id] return formatWeight(draft?.value ?? null, draft?.unit ?? 'KG') } @@ -176,11 +183,36 @@ async function loadWorkspace() { } } +async function loadPalletOptions() { + if (!customerId.value || !props.inboundId || !canView.value) { + palletOptions.value = [] + selectedPalletId.value = '' + return + } + palletLoading.value = true + palletError.value = null + try { + const result = await getWeightPalletOptions(customerId.value, props.inboundId) + palletOptions.value = result + if (!result.some((option) => option.palletId === selectedPalletId.value)) selectedPalletId.value = '' + } catch (error) { + palletOptions.value = [] + selectedPalletId.value = '' + palletError.value = getApiErrorDetails(error, '可选托盘加载失败,请稍后重试') + } finally { + palletLoading.value = false + } +} + function chooseFile() { if (!editable.value) { ElMessage.warning('当前入库状态不允许上传称重图片') return } + if (!selectedPalletId.value) { + ElMessage.warning('请先选择本次称重使用的托盘') + return + } fileInput.value?.click() } @@ -206,6 +238,7 @@ async function handleFileChange(event: Event) { const upload = await uploadWeightImage(customerId.value, props.inboundId, file) const created = await createWeightResult(customerId.value, props.inboundId, { fileId: String(upload.fileId), + palletId: selectedPalletId.value, ocrProvider: 'FILE_WEIGHT_OCR', ocrRawText: getWeightOcrRawText(upload), ocrWeightValue: upload.weightValue, @@ -295,7 +328,7 @@ async function removeResult(row: WeightResultResource) { try { await ElMessageBox.confirm( row.recognitionStatus === 'CONFIRMED' - ? '删除后将重新计算实际总毛重,确认删除这张称重图片吗?' + ? '删除后将重新计算货物总重量,确认删除这张称重图片吗?' : '确认删除这张称重图片吗?', '删除称重图片', { type: 'warning', confirmButtonText: '删除', cancelButtonText: '取消' }, @@ -324,12 +357,16 @@ watch( () => { ++loadSequence workspace.value = null + palletOptions.value = [] + selectedPalletId.value = '' + palletError.value = null weightDrafts.value = {} loadError.value = null actionError.value = null previewOpen.value = false releasePreview() void loadWorkspace() + void loadPalletOptions() }, { immediate: true }, ) @@ -350,13 +387,31 @@ defineExpose({ reload: loadWorkspace })

-

上传后直接核对重量,确认结果自动计入实际总毛重。

+

先选择托盘,再上传图片核对含托盘毛重;皮重与货物净重由服务端计算。

- 已确认总毛重 + 货物总重量 {{ formatWeight(workspace.actualGrossWeightKg) }}
+
+ + + + 皮重 {{ formatWeight(selectedPallet.tareWeightKg) }} · 容量 {{ selectedPallet.capacityCbm }} m³ +
上传称重图片
@@ -392,6 +447,27 @@ defineExpose({ reload: loadWorkspace })