fix(client): correct return inventory carton paging and label printing

This commit is contained in:
2026-09-12 16:16:40 +08:00
parent bee47f1a6c
commit 61a562b828
8 changed files with 201 additions and 23 deletions
+33
View File
@@ -0,0 +1,33 @@
import assert from 'node:assert/strict'
import { readFile } from 'node:fs/promises'
import test from 'node:test'
import ts from 'typescript'
const source = await readFile(new URL('./types.ts', import.meta.url), 'utf8')
const compiled = ts.transpileModule(source, {
compilerOptions: { module: ts.ModuleKind.ESNext, target: ts.ScriptTarget.ES2022 },
}).outputText
const { isApiFailure, isApiEnvelope } = await import(`data:text/javascript;base64,${Buffer.from(compiled).toString('base64')}`)
test('recognizes a security failure without data and preserves diagnostics', () => {
const failure = { code: 'A0403', message: 'Access denied', requestId: 'request-1', traceId: 'trace-1' }
assert.equal(isApiFailure(failure), true)
assert.equal(isApiEnvelope(failure), false)
assert.equal(failure.requestId, 'request-1')
assert.equal(failure.traceId, 'trace-1')
})
test('does not accept malformed or data-less successful responses', () => {
for (const value of [null, {}, { code: 403, message: 'Denied' }, { code: 'A0403' },
{ code: 'A0403', message: 'Denied', traceId: {} }, { code: '200', message: 'OK' }]) {
assert.equal(isApiFailure(value), false)
}
assert.equal(isApiEnvelope({ code: '200', message: 'OK' }), false)
assert.equal(isApiEnvelope({ code: '200', message: 'OK', data: [] }), true)
})
test('overseas transport handles business-code and HTTP security failures', async () => {
const transport = await readFile(new URL('./overseasHttp.ts', import.meta.url), 'utf8')
assert.match(transport, /if \(isApiFailure\(response.data\)\)/)
assert.match(transport, /isApiEnvelope\(body\) \|\| isApiFailure\(body\)/)
})
+8 -4
View File
@@ -1,6 +1,6 @@
import axios, { type AxiosRequestConfig, type InternalAxiosRequestConfig } from 'axios'
import { parseApiJsonResponse } from '@chongchong/shared'
import { ApiError, isApiEnvelope, type ApiEnvelope } from '@/api/types'
import { ApiError, isApiEnvelope, isApiFailure } from '@/api/types'
import { getOverseasAccessToken } from '@/api/session'
import { refreshAuthenticatedSession } from '@/api/http'
@@ -37,7 +37,11 @@ transport.interceptors.response.use((response) => response, async (error) => {
export async function overseasApiRequest<T>(config: AxiosRequestConfig) {
try {
const response = await transport.request<ApiEnvelope<T>>(config)
const response = await transport.request<unknown>(config)
if (isApiFailure(response.data)) {
throw new ApiError(response.data.message, response.data.code, response.status, undefined,
response.data.traceId, response.data.requestId)
}
if (!isApiEnvelope(response.data) || response.data.code !== '200') {
throw new ApiError(
isApiEnvelope(response.data) ? response.data.message : '海外接口响应格式错误',
@@ -48,12 +52,12 @@ export async function overseasApiRequest<T>(config: AxiosRequestConfig) {
isApiEnvelope(response.data) ? response.data.requestId : null,
)
}
return response.data.data
return response.data.data as T
} catch (error) {
if (error instanceof ApiError) throw error
if (axios.isAxiosError(error)) {
const body = error.response?.data
if (isApiEnvelope(body)) {
if (isApiEnvelope(body) || isApiFailure(body)) {
throw new ApiError(body.message, body.code, error.response?.status, undefined, body.traceId, body.requestId)
}
throw new ApiError(error.code === 'ERR_NETWORK' ? '无法连接海外服务' : error.message, error.code || 'OVERSEAS_HTTP_ERROR', error.response?.status)
+10
View File
@@ -30,3 +30,13 @@ export function isApiEnvelope(value: unknown): value is ApiEnvelope<unknown> {
const record = value as Record<string, unknown>
return typeof record.code === 'string' && typeof record.message === 'string' && 'data' in record
}
// Security failures have no business data, unlike successful API responses.
export function isApiFailure(value: unknown): value is Omit<ApiEnvelope<never>, 'data'> {
if (!value || typeof value !== 'object') return false
const record = value as Record<string, unknown>
return typeof record.code === 'string' && record.code !== '200'
&& typeof record.message === 'string'
&& (record.requestId == null || typeof record.requestId === 'string')
&& (record.traceId == null || typeof record.traceId === 'string')
}