From 44ee2be24654979f7f980eefc8994585fda771f6 Mon Sep 17 00:00:00 2001 From: ouyangxq <775153912@qq.com> Date: Fri, 14 Aug 2026 18:50:11 +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 --- src/modules/auth/pages/LoginView.test.ts | 47 ++++++++++ src/modules/auth/pages/LoginView.vue | 6 +- .../api/platformOceanBookings.test.ts | 10 +- .../api/platformOceanBookings.ts | 9 ++ .../pages/OceanBookingDetailView.test.ts | 29 +++++- .../pages/OceanBookingDetailView.vue | 91 ++++++++++++++----- src/modules/ocean-bookings/types.ts | 2 +- src/shared/api/http.test.ts | 19 ++++ src/shared/api/http.ts | 12 ++- 9 files changed, 196 insertions(+), 29 deletions(-) diff --git a/src/modules/auth/pages/LoginView.test.ts b/src/modules/auth/pages/LoginView.test.ts index 9e55dc8d..755736f1 100644 --- a/src/modules/auth/pages/LoginView.test.ts +++ b/src/modules/auth/pages/LoginView.test.ts @@ -6,6 +6,7 @@ import { createMemoryHistory, createRouter } from 'vue-router' import { beforeEach, describe, expect, it, vi } from 'vitest' import { useAccessPreviewStore } from '@/core/access/accessPreview' import { useAuthStore } from '@/core/auth/auth' +import { ApiResponseError } from '@/shared/api/http' import LoginView from './LoginView.vue' const loginSource = readFileSync(resolve(process.cwd(), 'src/modules/auth/pages/LoginView.vue'), 'utf8') @@ -177,4 +178,50 @@ describe('LoginView authentication lifecycle', () => { expect(initialize).not.toHaveBeenCalled() expect(replace).not.toHaveBeenCalled() }) + + it('shows the backend credential error and diagnostic identifiers', async () => { + const pinia = createPinia() + setActivePinia(pinia) + const auth = useAuthStore() + const router = createRouter({ + history: createMemoryHistory(), + routes: [ + { path: '/login', component: LoginView }, + { path: '/', component: { template: '
home
' } }, + ], + }) + await router.push('/login') + await router.isReady() + vi.spyOn(auth, 'login').mockRejectedValue(new ApiResponseError( + '用户名或密码错误', + 'A0401', + undefined, + null, + 'request-login', + 'trace-login', + )) + const wrapper = mount(LoginView, { + global: { + plugins: [pinia, router], + stubs: { + 'el-form': formStub, + 'el-form-item': { template: '' }, + 'el-input': { props: ['modelValue'], emits: ['update:modelValue'], template: '' }, + 'el-button': { emits: ['click'], template: '' }, + }, + }, + }) + + const inputs = wrapper.findAll('input') + await inputs[0]!.setValue('platform_admin') + await inputs[1]!.setValue('incorrect-password') + await wrapper.get('form').trigger('submit') + await flushPromises() + + const alert = wrapper.get('[role="alert"]').text() + expect(alert).toContain('用户名或密码错误') + expect(alert).toContain('request-login') + expect(alert).toContain('trace-login') + expect(alert).not.toContain('登录状态已失效') + }) }) diff --git a/src/modules/auth/pages/LoginView.vue b/src/modules/auth/pages/LoginView.vue index 7f62784e..a520e614 100644 --- a/src/modules/auth/pages/LoginView.vue +++ b/src/modules/auth/pages/LoginView.vue @@ -41,7 +41,11 @@ async function submit() { : requestedPath await router.replace(targetPath) } catch (error) { - loginError.value = getApiErrorDetails(error, '登录失败,请检查账号和密码后重试') + loginError.value = getApiErrorDetails( + error, + '登录失败,请检查账号和密码后重试', + { preserveAuthenticationMessage: true }, + ) ElMessage.error(loginError.value.message) } finally { loading.value = false diff --git a/src/modules/ocean-bookings/api/platformOceanBookings.test.ts b/src/modules/ocean-bookings/api/platformOceanBookings.test.ts index 0191d9b8..6e31c513 100644 --- a/src/modules/ocean-bookings/api/platformOceanBookings.test.ts +++ b/src/modules/ocean-bookings/api/platformOceanBookings.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it, vi } from 'vitest' -import { createOceanBooking, listOceanBookings, performOceanBookingAction, releaseOceanBooking, requestOceanBookingChange } from './platformOceanBookings' +import { createOceanBooking, listOceanBookings, listWarehouseLogisticsChannels, performOceanBookingAction, releaseOceanBooking, requestOceanBookingChange } from './platformOceanBookings' import { http } from '@/shared/api/http' const detailResponse = (version = 0) => ({ @@ -22,7 +22,7 @@ describe('ocean bookings api', () => { it('uses version zero for create and extracts the id from the returned Detail', async () => { const post = vi.spyOn(http, 'post').mockResolvedValue({ code: '200', message: 'ok', data: detailResponse() }) - const created = await createOceanBooking({ expectedVersion: 0, data: { customerId: '456', customerName: '客户', channelCode: 'SEA', channelName: '海运', warehouseId: '8', warehouseName: '义乌仓', parties: [], containerRequirements: [] } }) + const created = await createOceanBooking({ expectedVersion: 0, data: { customerId: '456', customerName: '客户', channelCode: 'SEA', warehouseId: '8', warehouseName: '义乌仓', parties: [], containerRequirements: [] } }) expect(post.mock.calls[0]?.[1]).toMatchObject({ expectedVersion: 0 }) expect(post.mock.calls[0]?.[2]).toMatchObject({ headers: { 'Idempotency-Key': expect.any(String) } }) expect(created.id).toBe('123') @@ -43,4 +43,10 @@ describe('ocean bookings api', () => { expect(post.mock.calls[2]?.[0]).toContain('/123/release') expect(post.mock.calls[2]?.[1]).toMatchObject({ billOfLadingNo: 'BL-1', vesselName: 'VESSEL', voyageNo: 'V001', estimatedSailingAt: '2026-08-12T10:00:00', estimatedArrivalAt: '2026-09-01T10:00:00' }) }) + + it('loads warehouse-scoped logistics channels from the booking contract', async () => { + vi.spyOn(http, 'get').mockResolvedValue({ code: '200', message: 'ok', data: [{ channelCode: 'SEA_FCL', channelName: '海运整箱' }] }) + await expect(listWarehouseLogisticsChannels('8')).resolves.toEqual([{ channelCode: 'SEA_FCL', channelName: '海运整箱' }]) + expect(http.get).toHaveBeenCalledWith('/platform/v1/ocean-bookings/channels', { params: { originWarehouseId: '8' } }) + }) }) diff --git a/src/modules/ocean-bookings/api/platformOceanBookings.ts b/src/modules/ocean-bookings/api/platformOceanBookings.ts index 484a5d5c..193919e0 100644 --- a/src/modules/ocean-bookings/api/platformOceanBookings.ts +++ b/src/modules/ocean-bookings/api/platformOceanBookings.ts @@ -2,6 +2,11 @@ import { http, unwrapApiResponse, type ApiEnvelope } from '@/shared/api/http' import type { OceanBookingActionRequest, OceanBookingChangeRequest, OceanBookingDetail, OceanBookingPage, OceanBookingQuery, OceanBookingResource, OceanBookingSaveRequest, OceanBookingStatus, OceanBookingReleaseRequest } from '../types' const basePath = '/platform/v1/ocean-bookings' + +export interface OceanLogisticsChannel { + channelCode: string + channelName: string +} function pageNumber(value: number | string, fallback: number) { const n = Number(value); return Number.isFinite(n) ? Math.trunc(n) : fallback } function booking(raw: OceanBookingResource): OceanBookingResource { const item = raw as OceanBookingResource & { version?: number | string | null } @@ -49,4 +54,8 @@ export async function performOceanBookingAction(id: string, action: Exclude| 操作 | 箱型 | 箱种 | 容量(m³) | 限重(kg) | 数量 |
|---|---|---|---|---|---|
|
+ |
+