From 24575223a0b1340238a117fbcd7f910df42d9150 Mon Sep 17 00:00:00 2001 From: ouyangxq <775153912@qq.com> Date: Thu, 27 Aug 2026 13:50:41 +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 --- .../inbounds/weight/api/weightOcr.test.ts | 21 +++++++--- src/modules/inbounds/weight/api/weightOcr.ts | 25 ++++++++++- .../WeightOcrPanel.behavior.test.ts | 42 +++++++++++++++---- .../weight/components/WeightOcrPanel.vue | 4 +- src/modules/inbounds/weight/types.ts | 8 ++++ 5 files changed, 83 insertions(+), 17 deletions(-) diff --git a/src/modules/inbounds/weight/api/weightOcr.test.ts b/src/modules/inbounds/weight/api/weightOcr.test.ts index e7ed3816..1e71a319 100644 --- a/src/modules/inbounds/weight/api/weightOcr.test.ts +++ b/src/modules/inbounds/weight/api/weightOcr.test.ts @@ -82,11 +82,20 @@ 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 }) + it('normalizes the formal paged pallet response returned by Firstleg', async () => { + const records = [ + { palletId: '2092803000000000001', palletCode: 'TP-11', palletType: '木托盘', capacityCbm: 1.2, tareWeightKg: 12 }, + { palletId: '2092803000000000002', palletCode: 'TP-12', palletType: '塑料托盘', capacityCbm: 1.1, tareWeightKg: 10 }, + ] + vi.mocked(http.get).mockResolvedValue({ + code: '200', + message: 'success', + data: { records, page: '1', size: '20', total: '2', pages: '1' }, + }) - await expect(getWeightPalletOptions('10001', '20001')).resolves.toEqual(options) + await expect(getWeightPalletOptions('10001', '20001')).resolves.toEqual({ + records, page: 1, size: 20, total: 2, pages: 1, + }) expect(http.get).toHaveBeenCalledWith( '/platform/v1/first-leg/inbounds/20001/weight-pallet-options', { params: { tenantId: '10001' } }, @@ -94,7 +103,9 @@ describe('inbound weight OCR API', () => { }) it('passes an abort signal without changing the frozen pallet endpoint parameters', async () => { - vi.mocked(http.get).mockResolvedValue({ code: '200', message: 'success', data: [] }) + vi.mocked(http.get).mockResolvedValue({ + code: '200', message: 'success', data: { records: [], page: '1', size: '20', total: '0', pages: '0' }, + }) const controller = new AbortController() await getWeightPalletOptions('10001', '20001', controller.signal) diff --git a/src/modules/inbounds/weight/api/weightOcr.ts b/src/modules/inbounds/weight/api/weightOcr.ts index 72324d19..927a3c76 100644 --- a/src/modules/inbounds/weight/api/weightOcr.ts +++ b/src/modules/inbounds/weight/api/weightOcr.ts @@ -6,6 +6,7 @@ import type { CreateWeightResultRequest, WeightImageUploadResult, WeightPalletOption, + WeightPalletOptionPage, WeightResultResource, WeightWorkspaceResource, } from '../types' @@ -15,10 +16,23 @@ const imageBasePath = '/platform/v1/files/measurements' const maxErrorEnvelopeBytes = 1024 * 1024 const confidenceScale = 100_000 +interface WeightPalletOptionPageResponse { + records: WeightPalletOption[] + page: number | string + size: number | string + total: number | string + pages: number | string +} + function segment(value: string) { return encodeURIComponent(value) } +function normalizedPageNumber(value: number | string, fallback: number, minimum: number) { + const parsed = typeof value === 'number' ? value : Number(value) + return Number.isFinite(parsed) ? Math.max(minimum, Math.trunc(parsed)) : fallback +} + export function normalizeWeightConfidence(value: number | null) { if (value == null || !Number.isFinite(value)) return null const clamped = Math.min(1, Math.max(0, value)) @@ -61,11 +75,18 @@ export async function getWeightWorkspace(tenantId: string, inboundId: string) { } export async function getWeightPalletOptions(tenantId: string, inboundId: string, signal?: AbortSignal) { - const response = await http.get, ApiEnvelope>( + const response = await http.get, ApiEnvelope>( `${measurementBasePath}/${segment(inboundId)}/weight-pallet-options`, { params: { tenantId }, ...(signal ? { signal } : {}) }, ) - return unwrapApiResponse(response) + const page = unwrapApiResponse(response) + return { + records: page.records, + page: normalizedPageNumber(page.page, 1, 1), + size: normalizedPageNumber(page.size, page.records.length, 0), + total: normalizedPageNumber(page.total, page.records.length, 0), + pages: normalizedPageNumber(page.pages, 0, 0), + } satisfies WeightPalletOptionPage } export async function uploadWeightImage(tenantId: string, inboundId: string, 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 78013526..12c44f01 100644 --- a/src/modules/inbounds/weight/components/WeightOcrPanel.behavior.test.ts +++ b/src/modules/inbounds/weight/components/WeightOcrPanel.behavior.test.ts @@ -72,6 +72,12 @@ const workspace: WeightWorkspaceResource = { weightResults: [pendingWeight], } +function palletPage(records = [{ + palletId: '50001', palletCode: 'TP-001', palletType: '木托盘', capacityCbm: 1.2, tareWeightKg: 12, +}]) { + return { records, page: 1, size: 20, total: records.length, pages: records.length ? 1 : 0 } +} + enableAutoUnmount(afterEach) const allWeightPermissions = [ @@ -122,9 +128,7 @@ 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(getWeightPalletOptions).mockResolvedValue(palletPage()) vi.mocked(downloadWeightImage).mockResolvedValue(new Blob(['image'], { type: 'image/jpeg' })) vi.mocked(downloadWeightThumbnail).mockResolvedValue(new Blob(['thumb'], { type: 'image/jpeg' })) vi.stubGlobal('URL', { @@ -326,7 +330,7 @@ describe('WeightOcrPanel confirmation behavior', () => { }) it('shows an actionable empty state when the warehouse has no pallet options', async () => { - vi.mocked(getWeightPalletOptions).mockResolvedValueOnce([]) + vi.mocked(getWeightPalletOptions).mockResolvedValueOnce(palletPage([])) const wrapper = mountPanel() await flushPromises() @@ -334,6 +338,28 @@ describe('WeightOcrPanel confirmation behavior', () => { expect(wrapper.findAll('button').some((button) => button.text().includes('重新加载'))).toBe(true) }) + it('renders records from the formal paged response without showing a load failure', async () => { + vi.mocked(getWeightPalletOptions).mockResolvedValueOnce({ + records: [ + { palletId: '2092803000000000001', palletCode: 'TP-11', palletType: '木托盘', capacityCbm: 1.2, tareWeightKg: 12 }, + { palletId: '2092803000000000002', palletCode: 'TP-12', palletType: '塑料托盘', capacityCbm: 1.1, tareWeightKg: 10 }, + ], + page: 1, + size: 20, + total: 2, + pages: 1, + }) + const wrapper = mountPanel() + await flushPromises() + + await wrapper.find('.pallet-picker .el-select').trigger('click') + await flushPromises() + + expect(document.body.textContent).toContain('木托盘 · 皮重 12.000 KG · 容量 1.2 m³') + expect(document.body.textContent).toContain('塑料托盘 · 皮重 10.000 KG · 容量 1.1 m³') + expect(wrapper.text()).not.toContain('可选托盘加载失败') + }) + it('shows pallet loading failures without hiding existing weight results', async () => { vi.mocked(getWeightPalletOptions).mockRejectedValueOnce(new ApiResponseError('托盘服务暂不可用', 'A0500', 503)) const wrapper = mountPanel() @@ -373,13 +399,13 @@ describe('WeightOcrPanel confirmation behavior', () => { }) it('virtualizes one thousand pallet candidates instead of rendering one thousand option nodes', async () => { - vi.mocked(getWeightPalletOptions).mockResolvedValue(Array.from({ length: 1_000 }, (_, index) => ({ + vi.mocked(getWeightPalletOptions).mockResolvedValue(palletPage(Array.from({ length: 1_000 }, (_, index) => ({ palletId: String(60_000 + index), palletCode: `TP-${index}`, palletType: `托盘类型 ${index}`, capacityCbm: 1.2, tareWeightKg: 12, - }))) + })))) const wrapper = mountPanel() await flushPromises() @@ -401,9 +427,9 @@ describe('WeightOcrPanel confirmation behavior', () => { const signal = vi.mocked(getWeightPalletOptions).mock.calls[0]?.[2] wrapper.unmount() - resolveOptions?.([{ + resolveOptions?.(palletPage([{ palletId: 'late', palletCode: 'TP-LATE', palletType: '过期托盘', capacityCbm: 1, tareWeightKg: 10, - }]) + }])) await flushPromises() expect(signal?.aborted).toBe(true) diff --git a/src/modules/inbounds/weight/components/WeightOcrPanel.vue b/src/modules/inbounds/weight/components/WeightOcrPanel.vue index 732198be..a8e1daf7 100644 --- a/src/modules/inbounds/weight/components/WeightOcrPanel.vue +++ b/src/modules/inbounds/weight/components/WeightOcrPanel.vue @@ -242,8 +242,8 @@ async function loadPalletOptions() { try { const result = await getWeightPalletOptions(customerId.value, props.inboundId, controller.signal) if (sequence !== palletLoadSequence || controller.signal.aborted) return - palletOptions.value = result - if (!result.some((option) => option.palletId === selectedPalletId.value)) selectedPalletId.value = '' + palletOptions.value = result.records + if (!result.records.some((option) => option.palletId === selectedPalletId.value)) selectedPalletId.value = '' } catch (error) { if (sequence !== palletLoadSequence || controller.signal.aborted) return palletOptions.value = [] diff --git a/src/modules/inbounds/weight/types.ts b/src/modules/inbounds/weight/types.ts index 7209a57e..fa84ae19 100644 --- a/src/modules/inbounds/weight/types.ts +++ b/src/modules/inbounds/weight/types.ts @@ -43,6 +43,14 @@ export interface WeightPalletOption { tareWeightKg: number } +export interface WeightPalletOptionPage { + records: WeightPalletOption[] + page: number + size: number + total: number + pages: number +} + export interface WeightImageUploadResult { fileId: string fileName: string