From 16c5ed63016cb8a3806bd3e480a88b5f3c5b30fd Mon Sep 17 00:00:00 2001
From: ouyangxq <775153912@qq.com>
Date: Wed, 12 Aug 2026 16:58:52 +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/iam/pages/RolesView.test.ts | 11 +++-
src/modules/iam/pages/RolesView.vue | 31 +++++++--
.../loading-lists/api/containerFees.test.ts | 10 ++-
.../loading-lists/api/containerFees.ts | 15 +++++
.../components/ContainerFeesPanel.test.ts | 56 ++++++++++++++--
.../components/ContainerFeesPanel.vue | 66 ++++++++++++++-----
.../InboundFeeDetailManager.test.ts | 15 ++++-
.../locations/pages/LocationsView.test.ts | 10 +++
.../locations/pages/LocationsView.vue | 19 ++++--
.../pallets/pages/PalletsView.test.ts | 20 ++++--
.../pallets/pages/PalletsView.vue | 24 +++----
.../warehouses/pages/WarehousesView.test.ts | 38 +++++++----
.../warehouses/pages/WarehousesView.vue | 12 +++-
13 files changed, 254 insertions(+), 73 deletions(-)
diff --git a/src/modules/iam/pages/RolesView.test.ts b/src/modules/iam/pages/RolesView.test.ts
index f7142337..10a1a56a 100644
--- a/src/modules/iam/pages/RolesView.test.ts
+++ b/src/modules/iam/pages/RolesView.test.ts
@@ -43,7 +43,7 @@ function mountView(pinia = createPinia()) {
AdminPage: { template: '' },
ListPageLayout: { template: '' },
FormSection: { template: '' },
- AppDrawer: { template: '' },
+ AppDialog: { template: '' },
LoadErrorState: { props: ['message'], template: '
{{ message }}
' },
DataTable: { props: ['data'], template: '' },
'el-alert': { props: ['title'], template: '{{ title }}
' },
@@ -95,6 +95,15 @@ describe('RolesView', () => {
expect(rolesSource).toContain('aria-label="平台角色列表"')
})
+ it('uses the prototype-sized centered role editor with responsive split panes', () => {
+ expect(rolesSource).toContain(' {
const pinia = createPinia()
setActivePinia(pinia)
diff --git a/src/modules/iam/pages/RolesView.vue b/src/modules/iam/pages/RolesView.vue
index 6f7cb4b1..5cd395f0 100644
--- a/src/modules/iam/pages/RolesView.vue
+++ b/src/modules/iam/pages/RolesView.vue
@@ -19,7 +19,7 @@ import StatusBadge from '@/shared/components/StatusBadge.vue'
import DataTable, { type DataTableColumn } from '@/shared/components/DataTable.vue'
import QueryToolbar, { type QueryField, type QueryModel } from '@/shared/components/QueryToolbar.vue'
import AdminPage from '@/shared/components/AdminPage.vue'
-import AppDrawer from '@/shared/components/AppDrawer.vue'
+import AppDialog from '@/shared/components/AppDialog.vue'
import FormSection from '@/shared/components/FormSection.vue'
import ListPageLayout from '@/shared/components/ListPageLayout.vue'
import LoadErrorState from '@/shared/components/LoadErrorState.vue'
@@ -259,11 +259,30 @@ onMounted(loadRoles)
-
-
-
- {{ data.name }}{{ data.code }}
+
+
+
+
+
+
+
+
+
{{ data.name }}{{ data.code }}
+
-
+
+
+
diff --git a/src/modules/loading-lists/api/containerFees.test.ts b/src/modules/loading-lists/api/containerFees.test.ts
index 20646267..2f0e0d3b 100644
--- a/src/modules/loading-lists/api/containerFees.test.ts
+++ b/src/modules/loading-lists/api/containerFees.test.ts
@@ -1,13 +1,13 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { http } from '@/shared/api/http'
-import { adjustContainerFee, approveContainerFee, createContainerFee, listContainerCostDetails, listContainerFees, rejectContainerFee, voidContainerFee } from './containerFees'
+import { adjustContainerFee, approveContainerFee, createContainerFee, listContainerCostDetails, listContainerFeePartnerOptions, listContainerFees, rejectContainerFee, voidContainerFee } from './containerFees'
describe('container fee API', () => {
beforeEach(() => vi.restoreAllMocks())
it('passes accountId and exact controller DTOs', async () => {
const get = vi.spyOn(http, 'get').mockResolvedValue({ code: '200', data: [] })
const post = vi.spyOn(http, 'post').mockResolvedValue({ code: '200', data: { id: 3, sourceVersion: 1 } })
- const values = { feeItemId: '7', direction: 'PAYABLE' as const, businessDate: '2026-08-12', amount: 100, currency: 'CNY' as const, settlementIncluded: true, remark: null }
+ const values = { feeItemId: '7', direction: 'PAYABLE' as const, partnerId: '41', partnerName: '承运商A', businessDate: '2026-08-12', amount: 100, currency: 'CNY' as const, settlementIncluded: true, remark: null }
await listContainerFees('9', '11'); await createContainerFee('9', '11', values)
await approveContainerFee('9', '11', '3'); await rejectContainerFee('9', '11', '3', '资料不全')
await adjustContainerFee('9', '11', '3', 2, values); await voidContainerFee('9', '11', '3', 3)
@@ -17,6 +17,12 @@ describe('container fee API', () => {
expect(post).toHaveBeenCalledWith('/platform/v1/loading-plans/9/fee-details/3/void', { expectedSourceVersion: 3 }, { params: { accountId: '11' } })
})
+ it('loads only ACTIVE counterparties for payable selection', async () => {
+ const get = vi.spyOn(http, 'get').mockResolvedValue({ code: '200', data: { records: [{ id: 41, partnerName: '承运商A' }] } })
+ await expect(listContainerFeePartnerOptions()).resolves.toEqual([{ id: '41', partnerName: '承运商A' }])
+ expect(get).toHaveBeenCalledWith('/platform/v1/partners', { params: { status: 'ACTIVE', page: 1, size: 200 } })
+ })
+
it('reads allocation rows from Finance and never calculates them', async () => {
const get = vi.spyOn(http, 'get')
.mockResolvedValueOnce({ code: '200', data: [{ id: 81, containerId: 99 }] })
diff --git a/src/modules/loading-lists/api/containerFees.ts b/src/modules/loading-lists/api/containerFees.ts
index cb09d9ab..3761e58a 100644
--- a/src/modules/loading-lists/api/containerFees.ts
+++ b/src/modules/loading-lists/api/containerFees.ts
@@ -11,6 +11,8 @@ export interface ContainerFeeResource {
feeItemId: string
feeItemName: string
direction: ContainerFeeDirection
+ partnerId: string | null
+ partnerName: string | null
businessDate: string
amount: number
currency: 'CNY'
@@ -27,6 +29,8 @@ export interface ContainerFeeResource {
export interface ContainerFeeSaveRequest {
feeItemId: string
direction: ContainerFeeDirection
+ partnerId: string | null
+ partnerName: string | null
businessDate: string
amount: number
currency: 'CNY'
@@ -43,6 +47,11 @@ export interface ApplicableFeeItem {
pickupPayable: boolean
}
+export interface ContainerFeePartnerOption {
+ id: string
+ partnerName: string
+}
+
export interface ContainerCostAllocation {
id: string
orderId: string
@@ -73,6 +82,7 @@ function normalizeFee(raw: Record): ContainerFeeResource {
id: id(raw.id), loadingPlanId: id(raw.loadingPlanId), containerBookingId: raw.containerBookingId == null ? null : id(raw.containerBookingId),
containerNo: raw.containerNo == null ? null : String(raw.containerNo), feeItemId: id(raw.feeItemId),
feeItemName: String(raw.feeItemNameSnapshot || ''), direction: raw.direction as ContainerFeeDirection,
+ partnerId: raw.partnerId == null ? null : id(raw.partnerId), partnerName: raw.partnerName == null ? null : String(raw.partnerName),
businessDate: String(raw.businessDate || ''), amount: numberValue(raw.amount), currency: 'CNY',
settlementIncluded: Boolean(raw.settlementIncluded), reviewStatus: raw.reviewStatus as ContainerFeeReviewStatus,
sourceStatus: raw.sourceStatus as ContainerFeeResource['sourceStatus'], sourceVersion: numberValue(raw.sourceVersion),
@@ -113,6 +123,11 @@ export async function listApplicableFeeItems() {
return (unwrapApiResponse(response).records || []).map((raw) => ({ id: id(raw.id), name: String(raw.feeTypeName || ''), warehouseReceivable: Boolean(raw.warehouseReceivable), warehousePayable: Boolean(raw.warehousePayable), pickupReceivable: Boolean(raw.pickupReceivable), pickupPayable: Boolean(raw.pickupPayable) }))
}
+export async function listContainerFeePartnerOptions(): Promise {
+ const response = await http.get[] }>, ApiEnvelope<{ records: Record[] }>>('/platform/v1/partners', { params: { status: 'ACTIVE', page: 1, size: 200 } })
+ return (unwrapApiResponse(response).records || []).map((raw) => ({ id: id(raw.id), partnerName: String(raw.partnerName || '') }))
+}
+
export async function listContainerCostDetails(customerId: string, containerId: string): Promise {
const response = await http.get[]>, ApiEnvelope[]>>('/platform/finance/v1/cost-entries', { params: { customerId } })
const costs = unwrapApiResponse(response).filter((raw) => id(raw.containerId) === containerId)
diff --git a/src/modules/loading-lists/components/ContainerFeesPanel.test.ts b/src/modules/loading-lists/components/ContainerFeesPanel.test.ts
index cc729172..5cb0b454 100644
--- a/src/modules/loading-lists/components/ContainerFeesPanel.test.ts
+++ b/src/modules/loading-lists/components/ContainerFeesPanel.test.ts
@@ -6,12 +6,12 @@ import { readFileSync } from 'node:fs'
import { resolve } from 'node:path'
import { useAccessPreviewStore } from '@/core/access/accessPreview'
import { useAuthStore } from '@/core/auth/auth'
-import { createContainerFee, listApplicableFeeItems, listContainerCostDetails, listContainerFees } from '../api/containerFees'
+import { createContainerFee, listApplicableFeeItems, listContainerCostDetails, listContainerFeePartnerOptions, listContainerFees } from '../api/containerFees'
import ContainerFeesPanel from './ContainerFeesPanel.vue'
vi.mock('../api/containerFees', async (importOriginal) => {
const actual = await importOriginal()
- return { ...actual, listContainerFees: vi.fn(), listApplicableFeeItems: vi.fn(), listContainerCostDetails: vi.fn(), createContainerFee: vi.fn(), adjustContainerFee: vi.fn(), approveContainerFee: vi.fn(), rejectContainerFee: vi.fn(), voidContainerFee: vi.fn() }
+ return { ...actual, listContainerFees: vi.fn(), listApplicableFeeItems: vi.fn(), listContainerFeePartnerOptions: vi.fn(), listContainerCostDetails: vi.fn(), createContainerFee: vi.fn(), adjustContainerFee: vi.fn(), approveContainerFee: vi.fn(), rejectContainerFee: vi.fn(), voidContainerFee: vi.fn() }
})
const panel = readFileSync(resolve(process.cwd(), 'src/modules/loading-lists/components/ContainerFeesPanel.vue'), 'utf8')
@@ -30,6 +30,7 @@ async function mountPanel(permissions: string[], customers = [{ accountId: '11',
beforeEach(() => {
vi.stubGlobal('ResizeObserver', ResizeObserverStub); vi.stubGlobal('localStorage', memoryStorage()); vi.stubGlobal('sessionStorage', memoryStorage())
vi.mocked(listContainerFees).mockReset().mockResolvedValue([]); vi.mocked(listApplicableFeeItems).mockReset().mockResolvedValue([])
+ vi.mocked(listContainerFeePartnerOptions).mockReset().mockResolvedValue([])
vi.mocked(listContainerCostDetails).mockReset().mockResolvedValue([]); vi.mocked(createContainerFee).mockReset()
document.body.innerHTML = ''
})
@@ -51,6 +52,7 @@ describe('ContainerFeesPanel contract', () => {
expect(panel).toContain('adjustContainerFee')
expect(panel).toContain('voidContainerFee')
expect(panel).toContain('调整后版本递增并重新进入待审核状态')
+ expect(panel).toContain("row.partnerName || '服务端未返回'")
})
it('shows only server allocations and supplier snapshots', () => {
@@ -71,7 +73,7 @@ describe('ContainerFeesPanel contract', () => {
state.accountId = '11'; await flushPromises(); state.editorOpen = true
state.accountId = '22'; await flushPromises()
expect(state.fees).toEqual([]); expect(state.costs).toEqual([]); expect(state.editorOpen).toBe(false)
- resolveFirst([{ id: 'old', loadingPlanId: '9', containerBookingId: '99', containerNo: null, feeItemId: '1', feeItemName: '旧客户费用', direction: 'RECEIVABLE', businessDate: '2026-08-12', amount: 10, currency: 'CNY', settlementIncluded: true, reviewStatus: 'PENDING', sourceStatus: 'ACTIVE', sourceVersion: 1, remark: null, reviewerName: null, reviewedAt: null, reviewReason: null }])
+ resolveFirst([{ id: 'old', loadingPlanId: '9', containerBookingId: '99', containerNo: null, feeItemId: '1', feeItemName: '旧客户费用', direction: 'RECEIVABLE', partnerId: null, partnerName: null, businessDate: '2026-08-12', amount: 10, currency: 'CNY', settlementIncluded: true, reviewStatus: 'PENDING', sourceStatus: 'ACTIVE', sourceVersion: 1, remark: null, reviewerName: null, reviewedAt: null, reviewReason: null }])
await flushPromises()
expect(state.fees).toEqual([])
expect(listContainerCostDetails).not.toHaveBeenCalledWith('11', expect.anything())
@@ -87,11 +89,55 @@ describe('ContainerFeesPanel contract', () => {
it('hides all mutation buttons when permissions are missing', async () => {
vi.mocked(listContainerFees).mockResolvedValue([
- { id: '1', loadingPlanId: '9', containerBookingId: '99', containerNo: null, feeItemId: '1', feeItemName: '待审费用', direction: 'RECEIVABLE', businessDate: '2026-08-12', amount: 10, currency: 'CNY', settlementIncluded: true, reviewStatus: 'PENDING', sourceStatus: 'ACTIVE', sourceVersion: 1, remark: null, reviewerName: null, reviewedAt: null, reviewReason: null },
- { id: '2', loadingPlanId: '9', containerBookingId: '99', containerNo: null, feeItemId: '2', feeItemName: '已审费用', direction: 'PAYABLE', businessDate: '2026-08-12', amount: 20, currency: 'CNY', settlementIncluded: true, reviewStatus: 'APPROVED', sourceStatus: 'ACTIVE', sourceVersion: 1, remark: null, reviewerName: null, reviewedAt: null, reviewReason: null },
+ { id: '1', loadingPlanId: '9', containerBookingId: '99', containerNo: null, feeItemId: '1', feeItemName: '待审费用', direction: 'RECEIVABLE', partnerId: null, partnerName: null, businessDate: '2026-08-12', amount: 10, currency: 'CNY', settlementIncluded: true, reviewStatus: 'PENDING', sourceStatus: 'ACTIVE', sourceVersion: 1, remark: null, reviewerName: null, reviewedAt: null, reviewReason: null },
+ { id: '2', loadingPlanId: '9', containerBookingId: '99', containerNo: null, feeItemId: '2', feeItemName: '已审费用', direction: 'PAYABLE', partnerId: '41', partnerName: '承运商A', businessDate: '2026-08-12', amount: 20, currency: 'CNY', settlementIncluded: true, reviewStatus: 'APPROVED', sourceStatus: 'ACTIVE', sourceVersion: 1, remark: null, reviewerName: null, reviewedAt: null, reviewReason: null },
])
const wrapper = await mountPanel(['firstleg:inbound:fee-detail:view'])
expect(wrapper.text()).not.toContain('新增费用')
for (const label of ['通过', '驳回', '调整', '作废']) expect(wrapper.findAll('button').some((button) => button.text() === label)).toBe(false)
})
+
+ it('keeps receivable usable when counterparty permission is missing', async () => {
+ vi.mocked(listApplicableFeeItems).mockResolvedValue([{ id: '7', name: '操作费', warehouseReceivable: true, warehousePayable: true, pickupReceivable: false, pickupPayable: false }])
+ const wrapper = await mountPanel(['firstleg:inbound:fee-detail:view', 'firstleg:inbound:fee-detail:edit', 'firstleg:fee-item:view'])
+ const state = (wrapper.vm.$ as unknown as { setupState: { openCreate(): void; form: { direction: string; partnerId: string | null; partnerName: string | null } } }).setupState
+ state.openCreate(); await flushPromises()
+ expect(wrapper.text()).toContain('当前账号缺少往来单位查看权限,仅可维护应收费用')
+ expect(listApplicableFeeItems).toHaveBeenCalledTimes(1)
+ expect(listContainerFeePartnerOptions).not.toHaveBeenCalled()
+ expect(state.form.direction).toBe('RECEIVABLE')
+ })
+
+ it('isolates a counterparty load failure and clears partner fields for receivable', async () => {
+ vi.mocked(listApplicableFeeItems).mockResolvedValue([{ id: '7', name: '操作费', warehouseReceivable: true, warehousePayable: true, pickupReceivable: false, pickupPayable: false }])
+ vi.mocked(listContainerFeePartnerOptions).mockRejectedValue(new Error('partners unavailable'))
+ const wrapper = await mountPanel(['firstleg:inbound:fee-detail:view', 'firstleg:inbound:fee-detail:edit', 'firstleg:fee-item:view', 'platform:counterparty:list'])
+ const state = (wrapper.vm.$ as unknown as { setupState: { openCreate(): void; form: { direction: string; partnerId: string | null; partnerName: string | null }; feeItems: unknown[] } }).setupState
+ state.openCreate(); await flushPromises()
+ expect(state.feeItems).toHaveLength(1)
+ expect(wrapper.text()).toContain('往来单位加载失败')
+ state.form.direction = 'PAYABLE'; await flushPromises()
+ state.form.partnerId = '41'; state.form.partnerName = '旧快照'; state.form.direction = 'RECEIVABLE'; await flushPromises()
+ expect(state.form.partnerId).toBeNull(); expect(state.form.partnerName).toBeNull()
+ })
+
+ it('submits the selected ACTIVE counterparty snapshot for payable fees', async () => {
+ vi.mocked(listApplicableFeeItems).mockResolvedValue([{ id: '7', name: '操作费', warehouseReceivable: true, warehousePayable: true, pickupReceivable: false, pickupPayable: false }])
+ vi.mocked(listContainerFeePartnerOptions).mockResolvedValue([{ id: '41', partnerName: '承运商A' }])
+ const wrapper = await mountPanel(['firstleg:inbound:fee-detail:view', 'firstleg:inbound:fee-detail:edit', 'firstleg:fee-item:view', 'platform:counterparty:list'])
+ const state = (wrapper.vm.$ as unknown as { setupState: { openCreate(): void; save(): Promise; form: { direction: string; feeItemId: string; partnerId: string | null; amount: number } } }).setupState
+ state.openCreate(); await flushPromises()
+ state.form.direction = 'PAYABLE'; state.form.feeItemId = '7'; state.form.partnerId = '41'; state.form.amount = 100; await flushPromises()
+ await state.save()
+ expect(createContainerFee).toHaveBeenCalledWith('9', '11', expect.objectContaining({ direction: 'PAYABLE', partnerId: '41', partnerName: '承运商A' }))
+ })
+
+ it('requires fee-item permission before opening a write form', async () => {
+ const wrapper = await mountPanel(['firstleg:inbound:fee-detail:view', 'firstleg:inbound:fee-detail:edit'])
+ const state = (wrapper.vm.$ as unknown as { setupState: { openCreate(): void; editorOpen: boolean } }).setupState
+ state.openCreate(); await flushPromises()
+ expect(state.editorOpen).toBe(false)
+ expect(wrapper.text()).toContain('当前账号缺少费用项目查看权限')
+ expect(listApplicableFeeItems).not.toHaveBeenCalled()
+ })
})
diff --git a/src/modules/loading-lists/components/ContainerFeesPanel.vue b/src/modules/loading-lists/components/ContainerFeesPanel.vue
index 13f32c7a..1c099309 100644
--- a/src/modules/loading-lists/components/ContainerFeesPanel.vue
+++ b/src/modules/loading-lists/components/ContainerFeesPanel.vue
@@ -9,28 +9,35 @@ import DataTable, { type DataTableColumn } from '@/shared/components/DataTable.v
import LoadErrorState from '@/shared/components/LoadErrorState.vue'
import StatusBadge from '@/shared/components/StatusBadge.vue'
import { getApiErrorDetails, type ApiErrorDetails } from '@/shared/api/http'
-import { adjustContainerFee, approveContainerFee, createContainerFee, listApplicableFeeItems, listContainerCostDetails, listContainerFees, rejectContainerFee, voidContainerFee, type ApplicableFeeItem, type ContainerCostDetail, type ContainerFeeDirection, type ContainerFeeResource, type ContainerFeeSaveRequest } from '../api/containerFees'
+import { adjustContainerFee, approveContainerFee, createContainerFee, listApplicableFeeItems, listContainerCostDetails, listContainerFeePartnerOptions, listContainerFees, rejectContainerFee, voidContainerFee, type ApplicableFeeItem, type ContainerCostDetail, type ContainerFeePartnerOption, type ContainerFeeResource, type ContainerFeeSaveRequest } from '../api/containerFees'
const props = defineProps<{ planId: string; customers: Array<{ accountId: string; customerName: string | null }> }>()
const access = useAccessPreviewStore()
const accountId = ref('')
const fees = ref([])
const feeItems = ref([])
+const partners = ref([])
const costs = ref([])
const loading = ref(false)
const error = ref(null)
+const feeItemsError = ref(null)
+const partnersError = ref(null)
+const feeItemsLoading = ref(false)
+const partnersLoading = ref(false)
const editorOpen = ref(false)
const rejectOpen = ref(false)
const saving = ref(false)
const target = ref(null)
const formRef = ref()
-const form = reactive({ feeItemId: '', direction: 'RECEIVABLE', businessDate: new Date().toISOString().slice(0, 10), amount: 0, currency: 'CNY', settlementIncluded: true, remark: null })
+const form = reactive({ feeItemId: '', direction: 'RECEIVABLE', partnerId: null, partnerName: null, businessDate: new Date().toISOString().slice(0, 10), amount: 0, currency: 'CNY', settlementIncluded: true, remark: null })
const rejection = ref('')
let loadSequence = 0
const canView = computed(() => access.can('firstleg:inbound:fee-detail:view'))
const canEdit = computed(() => access.can('firstleg:inbound:fee-detail:edit'))
const canReview = computed(() => access.can('firstleg:inbound:fee-detail:review'))
+const canViewFeeItems = computed(() => access.can('firstleg:fee-item:view'))
+const canViewPartners = computed(() => access.can('platform:counterparty:list'))
const customerOptions = computed(() => {
const unique = new Map()
for (const item of props.customers) if (item.accountId) unique.set(item.accountId, item.customerName || '客户信息缺失')
@@ -44,6 +51,7 @@ const columns: DataTableColumn[] = [
{ key: 'actions', label: '操作', fixed: 'left', width: 300, slotName: 'actions', hideable: false, movable: false },
{ key: 'feeItemName', prop: 'feeItemName', label: '费用项目', minWidth: 150 },
{ key: 'direction', label: '方向', width: 90, slotName: 'direction' },
+ { key: 'partner', label: '往来单位/客户', minWidth: 170, slotName: 'partner' },
{ key: 'amount', label: '金额(CNY)', width: 120, align: 'right', slotName: 'amount' },
{ key: 'businessDate', prop: 'businessDate', label: '业务日期', width: 120 },
{ key: 'reviewStatus', label: '审核状态', width: 110, slotName: 'reviewStatus' },
@@ -62,8 +70,26 @@ const rules: FormRules = { feeItemId: [{ required: true, message: '请选择费
function resetForm(item?: ContainerFeeResource) {
target.value = item || null
- Object.assign(form, item ? { feeItemId: item.feeItemId, direction: item.direction, businessDate: item.businessDate, amount: item.amount, currency: 'CNY', settlementIncluded: item.settlementIncluded, remark: item.remark }
- : { feeItemId: '', direction: 'RECEIVABLE', businessDate: new Date().toISOString().slice(0, 10), amount: 0, currency: 'CNY', settlementIncluded: true, remark: null })
+ Object.assign(form, item ? { feeItemId: item.feeItemId, direction: item.direction, partnerId: item.partnerId, partnerName: item.partnerName, businessDate: item.businessDate, amount: item.amount, currency: 'CNY', settlementIncluded: item.settlementIncluded, remark: item.remark }
+ : { feeItemId: '', direction: 'RECEIVABLE', partnerId: null, partnerName: null, businessDate: new Date().toISOString().slice(0, 10), amount: 0, currency: 'CNY', settlementIncluded: true, remark: null })
+}
+
+async function loadFeeItems() {
+ feeItems.value = []; feeItemsError.value = null
+ if (!canViewFeeItems.value) return
+ feeItemsLoading.value = true
+ try { feeItems.value = await listApplicableFeeItems() }
+ catch (cause) { feeItemsError.value = getApiErrorDetails(cause, '费用项目加载失败') }
+ finally { feeItemsLoading.value = false }
+}
+
+async function loadPartners() {
+ partners.value = []; partnersError.value = null
+ if (!canViewPartners.value) return
+ partnersLoading.value = true
+ try { partners.value = await listContainerFeePartnerOptions() }
+ catch (cause) { partnersError.value = getApiErrorDetails(cause, '往来单位加载失败') }
+ finally { partnersLoading.value = false }
}
async function load() {
@@ -72,9 +98,9 @@ async function load() {
const requestedAccountId = accountId.value
loading.value = true; error.value = null
try {
- const [feeRows, items] = await Promise.all([listContainerFees(props.planId, requestedAccountId), listApplicableFeeItems()])
+ const feeRows = await listContainerFees(props.planId, requestedAccountId)
if (sequence !== loadSequence || requestedAccountId !== accountId.value) return
- fees.value = feeRows; feeItems.value = items
+ fees.value = feeRows
costs.value = []
const currentContainerId = feeRows.find((item) => item.containerBookingId)?.containerBookingId
if (currentContainerId) {
@@ -86,14 +112,19 @@ async function load() {
finally { if (sequence === loadSequence) loading.value = false }
}
-function openCreate() { if (!canEdit.value || !accountId.value) return; resetForm(); editorOpen.value = true }
-function openAdjust(item: ContainerFeeResource) { if (!canEdit.value || !accountId.value) return; resetForm(item); editorOpen.value = true }
+function openCreate() { if (!canEdit.value || !canViewFeeItems.value || !accountId.value) return; resetForm(); editorOpen.value = true; void Promise.all([loadFeeItems(), loadPartners()]) }
+function openAdjust(item: ContainerFeeResource) { if (!canEdit.value || !canViewFeeItems.value || !accountId.value) return; resetForm(item); editorOpen.value = true; void Promise.all([loadFeeItems(), loadPartners()]) }
async function save() {
- if (!canEdit.value || !accountId.value || !await formRef.value?.validate().catch(() => false)) return
+ if (!canEdit.value || !canViewFeeItems.value || !accountId.value) return
+ if (form.direction === 'PAYABLE' && (!canViewPartners.value || !form.partnerId)) { ElMessage.warning(canViewPartners.value ? '应付费用必须选择往来单位' : '当前账号缺少往来单位查看权限,无法保存应付费用'); return }
+ if (!await formRef.value?.validate().catch(() => false)) return
+ const partner = partners.value.find((item) => item.id === String(form.partnerId))
+ const payload: ContainerFeeSaveRequest = { ...form, partnerId: form.direction === 'PAYABLE' ? form.partnerId : null, partnerName: form.direction === 'PAYABLE' ? partner?.partnerName || null : null }
+ if (form.direction === 'PAYABLE' && !payload.partnerName) { ElMessage.warning('请选择有效的往来单位'); return }
saving.value = true
try {
- if (target.value) await adjustContainerFee(props.planId, accountId.value, target.value.id, target.value.sourceVersion, { ...form })
- else await createContainerFee(props.planId, accountId.value, { ...form })
+ if (target.value) await adjustContainerFee(props.planId, accountId.value, target.value.id, target.value.sourceVersion, payload)
+ else await createContainerFee(props.planId, accountId.value, payload)
editorOpen.value = false; ElMessage.success(target.value ? '费用调整已提交审核' : '费用已新增'); await load()
} catch (cause) { ElMessage.error(getApiErrorDetails(cause, '费用保存失败').message) }
finally { saving.value = false }
@@ -116,35 +147,40 @@ function money(value: number) { return value.toLocaleString('zh-CN', { minimumFr
function initializeCustomer() { accountId.value = customerOptions.value.length === 1 ? customerOptions.value[0].value : '' }
function clearCustomerContext() {
loadSequence += 1
- fees.value = []; costs.value = []; feeItems.value = []; error.value = null; loading.value = false
+ fees.value = []; costs.value = []; feeItems.value = []; partners.value = []; error.value = null; feeItemsError.value = null; partnersError.value = null; loading.value = false; feeItemsLoading.value = false; partnersLoading.value = false
editorOpen.value = false; rejectOpen.value = false; target.value = null; rejection.value = ''
resetForm()
}
watch(customerOptions, initializeCustomer, { immediate: true })
watch(accountId, () => { clearCustomerContext(); void load() })
-watch(() => form.direction, () => { if (!applicableItems.value.some((item) => item.id === form.feeItemId)) form.feeItemId = '' })
+watch(() => form.direction, (direction) => {
+ if (!applicableItems.value.some((item) => item.id === form.feeItemId)) form.feeItemId = ''
+ if (direction === 'RECEIVABLE') { form.partnerId = null; form.partnerName = null; partnersError.value = null }
+})
onMounted(() => void load())
-
+
客户
+
{{ row.direction === 'RECEIVABLE' ? '应收' : '应付' }}
+ {{ row.direction === 'PAYABLE' ? row.partnerName || '服务端未返回' : selectedCustomer?.label || '客户信息缺失' }}
{{ money(row.amount) }}{{ row.settlementIncluded ? '是' : '否' }}
{{ cost.feeItemName || '柜次成本' }}往来单位:{{ cost.supplierName || '服务端未返回' }}金额:¥{{ money(cost.amount) }}
{{ row.volumeCbm.toFixed(3) }}{{ (row.allocationRate * 100).toFixed(2) }}%{{ money(row.allocatedAmount) }}
-
+
diff --git a/src/modules/unified-orders/components/InboundFeeDetailManager.test.ts b/src/modules/unified-orders/components/InboundFeeDetailManager.test.ts
index bef6757b..40b834ce 100644
--- a/src/modules/unified-orders/components/InboundFeeDetailManager.test.ts
+++ b/src/modules/unified-orders/components/InboundFeeDetailManager.test.ts
@@ -1,4 +1,6 @@
import ElementPlus from 'element-plus'
+import { readFileSync } from 'node:fs'
+import { join } from 'node:path'
import { createPinia, setActivePinia } from 'pinia'
import { enableAutoUnmount, flushPromises, mount } from '@vue/test-utils'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
@@ -27,6 +29,7 @@ const pending: InboundFeeDetail = {
reviewReason: null, reviewerName: null, reviewedAt: null, publicationStatus: 'DRAFT', settlementIncluded: true,
}
const approved: InboundFeeDetail = { ...pending, id: '12', sourceFeeVersion: 3, reviewStatus: 'APPROVED', publicationStatus: 'PUBLISHED' }
+const source = readFileSync(join(process.cwd(), 'src/modules/unified-orders/components/InboundFeeDetailManager.vue'), 'utf8')
enableAutoUnmount(afterEach)
@@ -136,9 +139,19 @@ describe('InboundFeeDetailManager', () => {
await flushPromises()
expect(listInboundFeeItemOptions).toHaveBeenCalledTimes(1)
expect(listInboundFeePartnerOptions).toHaveBeenCalledTimes(1)
- expect(document.body.textContent).toContain('往来单位不可用')
+ expect(document.body.textContent).toContain('当前账号无权访问该数据')
+ expect(document.body.textContent).toContain('A0403')
expect(document.body.textContent).toContain('request-partner')
expect(document.body.textContent).toContain('trace-partner')
expect(document.querySelector('.fee-form .el-select')?.classList.contains('is-disabled')).toBe(false)
})
+
+ it('refreshes fee rows and the order detail after every successful write', () => {
+ expect(source).toContain("async function finishAction(message: string) {\n await load(); emit('changed')")
+ expect(source).toMatch(/createInboundFeeDetail[\s\S]*updateInboundFeeDetail[\s\S]*adjustInboundFeeDetail[\s\S]*await finishAction\(/)
+ expect(source).toMatch(/deleteInboundFeeDetail[\s\S]*await finishAction\('费用明细已删除'\)/)
+ expect(source).toMatch(/reviewInboundFeeDetail[\s\S]*await finishAction\('费用审核通过'\)/)
+ expect(source).toMatch(/reviewInboundFeeDetail[\s\S]*rejectReason[\s\S]*await finishAction\('费用已驳回'\)/)
+ expect(source).toMatch(/voidInboundFeeDetail[\s\S]*await finishAction\('费用明细已作废'\)/)
+ })
})
diff --git a/src/modules/warehouse-resources/locations/pages/LocationsView.test.ts b/src/modules/warehouse-resources/locations/pages/LocationsView.test.ts
index dddddade..6ffe9239 100644
--- a/src/modules/warehouse-resources/locations/pages/LocationsView.test.ts
+++ b/src/modules/warehouse-resources/locations/pages/LocationsView.test.ts
@@ -1,4 +1,6 @@
import ElementPlus from 'element-plus'
+import { readFileSync } from 'node:fs'
+import { resolve } from 'node:path'
import { createPinia, setActivePinia } from 'pinia'
import { enableAutoUnmount, flushPromises, mount, type VueWrapper } from '@vue/test-utils'
import { afterAll, afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
@@ -15,6 +17,8 @@ import QueryToolbar, { type QueryField, type QueryModel } from '@/shared/compone
import DataTable from '@/shared/components/DataTable.vue'
import LocationsView from './LocationsView.vue'
+const locationsSource = readFileSync(resolve(process.cwd(), 'src/modules/warehouse-resources/locations/pages/LocationsView.vue'), 'utf8')
+
vi.mock('../../../warehouses/api/platformWarehouses', () => ({ listAllPlatformWarehouses: vi.fn() }))
vi.mock('../api/platformLocations', () => ({
listPlatformLocations: vi.fn(),
@@ -138,6 +142,12 @@ enableAutoUnmount(afterEach)
afterAll(() => vi.unstubAllGlobals())
describe('LocationsView', () => {
+ it('uses the prototype-sized centered editor while retaining the detail drawer', () => {
+ expect(locationsSource).toContain("drawerMode.value === 'view' ? AppDrawer : AppDialog")
+ expect(locationsSource).toContain("width: 'min(560px, calc(100vw - 28px))'")
+ expect(locationsSource).toContain(':global(.location-editor-dialog .el-dialog__body) { min-height: 0; overflow-y: auto; }')
+ expect(locationsSource).toContain('.drawer-form :deep(.form-grid.two-cols) { grid-template-columns: minmax(0, 1fr); }')
+ })
beforeEach(() => {
vi.stubGlobal('ResizeObserver', ResizeObserverStub)
document.body.innerHTML = ''
diff --git a/src/modules/warehouse-resources/locations/pages/LocationsView.vue b/src/modules/warehouse-resources/locations/pages/LocationsView.vue
index 9e706717..27298f00 100644
--- a/src/modules/warehouse-resources/locations/pages/LocationsView.vue
+++ b/src/modules/warehouse-resources/locations/pages/LocationsView.vue
@@ -7,6 +7,7 @@ import { useUnsavedWorkRegistration } from '@/core/unsaved-work/useUnsavedWorkRe
import { getApiErrorDetails, type ApiErrorDetails } from '@/shared/api/http'
import AdminPage from '@/shared/components/AdminPage.vue'
import ApiErrorMeta from '@/shared/components/ApiErrorMeta.vue'
+import AppDialog from '@/shared/components/AppDialog.vue'
import AppDrawer from '@/shared/components/AppDrawer.vue'
import DataTable, { type DataTableColumn } from '@/shared/components/DataTable.vue'
import FormSection from '@/shared/components/FormSection.vue'
@@ -149,6 +150,10 @@ const domesticWarehouses = computed(() => warehouses.value.filter((warehouse) =>
const editorOpen = computed(() => drawerOpen.value && drawerMode.value !== 'view')
const editorTitle = computed(() => drawerMode.value === 'create' ? '新增库位' : '编辑库位')
const drawerTitle = computed(() => drawerMode.value === 'view' ? '库位详情' : editorTitle.value)
+const drawerContainer = computed(() => drawerMode.value === 'view' ? AppDrawer : AppDialog)
+const drawerContainerProps = computed(() => drawerMode.value === 'view'
+ ? { size: 'min(620px, 94vw)', showConfirm: false }
+ : { width: 'min(560px, calc(100vw - 28px))', showConfirm: false, destroyOnClose: true })
const editorPermission = computed(() => drawerMode.value === 'create' ? 'platform:location:create' : 'platform:location:edit')
const selectionDisabled = computed(() => drawerOpen.value || saving.value || actionLoadingIds.value.size > 0)
const filtered = computed(() => Object.values(appliedFilters.value).some((value) => String(value || '').trim()))
@@ -646,14 +651,14 @@ onMounted(() => {
- {
保存库位
-
+
@@ -801,6 +806,9 @@ onMounted(() => {
.location-cell small, .stacked-value small { color: var(--cc-muted); font-size: 11px; }
.remark-cell { display: -webkit-box; overflow: hidden; line-height: 1.5; -webkit-box-orient: vertical; -webkit-line-clamp: 2; }
.drawer-content { min-height: 260px; }
+:global(.location-editor-dialog) { max-height: min(760px, calc(100vh - 32px)); display: flex; flex-direction: column; }
+:global(.location-editor-dialog .el-dialog__body) { min-height: 0; overflow-y: auto; }
+:global(.location-editor-dialog .el-dialog__footer) { flex: 0 0 auto; }
.drawer-content > .el-alert + .el-alert, .detail-descriptions + .el-alert { margin-top: 14px; }
.detail-identity { display: grid; grid-template-columns: 48px minmax(0, 1fr) auto; align-items: center; gap: 12px; margin-bottom: 18px; }
.detail-identity__icon { display: grid; width: 48px; height: 48px; place-items: center; border-radius: 10px; background: #eef3fa; color: var(--cc-primary); }
@@ -820,6 +828,7 @@ onMounted(() => {
:global(.location-action-menu .location-action-menu__danger:not(.is-disabled)) { color: var(--el-color-danger); }
@media (max-width: 720px) {
+ .drawer-form :deep(.form-grid.two-cols) { grid-template-columns: minmax(0, 1fr); }
.detail-identity { grid-template-columns: 44px minmax(0, 1fr); }
.detail-identity > .el-tag { grid-column: 2; justify-self: start; }
.detail-descriptions :deep(.el-descriptions__body), .detail-descriptions :deep(table), .detail-descriptions :deep(tbody) { display: block; width: 100%; }
diff --git a/src/modules/warehouse-resources/pallets/pages/PalletsView.test.ts b/src/modules/warehouse-resources/pallets/pages/PalletsView.test.ts
index c8de8533..4b79eb93 100644
--- a/src/modules/warehouse-resources/pallets/pages/PalletsView.test.ts
+++ b/src/modules/warehouse-resources/pallets/pages/PalletsView.test.ts
@@ -1,4 +1,6 @@
import ElementPlus from 'element-plus'
+import { readFileSync } from 'node:fs'
+import { resolve } from 'node:path'
import { createPinia, setActivePinia } from 'pinia'
import { enableAutoUnmount, flushPromises, mount, type VueWrapper } from '@vue/test-utils'
import { afterAll, afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
@@ -15,6 +17,8 @@ import {
} from '../api/platformPallets'
import PalletsView from './PalletsView.vue'
+const palletsSource = readFileSync(resolve(process.cwd(), 'src/modules/warehouse-resources/pallets/pages/PalletsView.vue'), 'utf8')
+
vi.mock('@/modules/warehouses', () => ({ listAllPlatformWarehouses: vi.fn() }))
vi.mock('../api/platformPallets', () => ({
listPlatformPallets: vi.fn(),
@@ -108,6 +112,13 @@ enableAutoUnmount(afterEach)
afterAll(() => vi.unstubAllGlobals())
describe('PalletsView', () => {
+ it('uses the prototype-sized centered editor while retaining the detail drawer', () => {
+ expect(palletsSource).toContain(' {
vi.stubGlobal('ResizeObserver', ResizeObserverStub)
document.body.innerHTML = ''
@@ -272,12 +283,9 @@ describe('PalletsView', () => {
await createButton?.trigger('click')
await flushPromises()
- expect(document.body.querySelector('.pallet-editor-header')?.textContent).toContain('新增托盘')
- expect(document.body.querySelector('.pallet-editor-header')?.textContent).toContain('录入归属仓库与基础规格')
- const editorDialog = document.body.querySelector('.pallet-editor-drawer[role="dialog"]')
- const labelledBy = editorDialog?.getAttribute('aria-labelledby')
- expect(labelledBy).toBeTruthy()
- expect(document.getElementById(labelledBy || '')?.textContent).toBe('新增托盘')
+ const editorDialog = document.body.querySelector('.pallet-editor-dialog')
+ expect(editorDialog?.textContent).toContain('新增托盘')
+ expect(editorDialog?.querySelector('.el-dialog__title')?.textContent).toBe('新增托盘')
expect(document.body.querySelectorAll('.editor-section')).toHaveLength(2)
expect(document.body.querySelector('#pallet-ownership-heading')?.textContent).toBe('归属仓库')
expect(document.body.querySelector('#pallet-spec-heading')?.textContent).toBe('托盘规格')
diff --git a/src/modules/warehouse-resources/pallets/pages/PalletsView.vue b/src/modules/warehouse-resources/pallets/pages/PalletsView.vue
index e19e61d0..391a9b95 100644
--- a/src/modules/warehouse-resources/pallets/pages/PalletsView.vue
+++ b/src/modules/warehouse-resources/pallets/pages/PalletsView.vue
@@ -7,6 +7,7 @@ import { useUnsavedWorkRegistration } from '@/core/unsaved-work/useUnsavedWorkRe
import { listAllPlatformWarehouses, type PlatformWarehouseResource } from '@/modules/warehouses'
import { getApiErrorDetails, type ApiErrorDetails } from '@/shared/api/http'
import AdminPage from '@/shared/components/AdminPage.vue'
+import AppDialog from '@/shared/components/AppDialog.vue'
import AppDrawer from '@/shared/components/AppDrawer.vue'
import DataTable, { type DataTableColumn } from '@/shared/components/DataTable.vue'
import FormSection from '@/shared/components/FormSection.vue'
@@ -474,16 +475,7 @@ onMounted(() => {
关闭
-
-
-
-
+
归属仓库
@@ -543,7 +535,7 @@ onMounted(() => {
取消保存托盘
-
+
@@ -569,10 +561,9 @@ onMounted(() => {
.detail-hero p { margin-top: 4px; color: var(--cc-muted); font-size: 13px; }
.detail-section-title { margin: 20px 0 8px; color: var(--cc-text); font-size: 14px; }
.detail-section-title:first-of-type { margin-top: 0; }
-.pallet-editor-header { display: flex; min-width: 0; align-items: center; gap: 12px; }
-.pallet-editor-header > span { display: grid; width: 38px; height: 38px; flex: 0 0 auto; place-items: center; border-radius: 8px; background: var(--el-color-primary-light-9); color: var(--cc-primary); }
-.pallet-editor-header h2 { margin: 0; color: #172033; font-size: 17px; line-height: 1.4; }
-.pallet-editor-header p { margin: 3px 0 0; color: var(--cc-muted); font-size: 12px; line-height: 1.5; }
+:global(.pallet-editor-dialog) { max-height: min(760px, calc(100vh - 32px)); display: flex; flex-direction: column; }
+:global(.pallet-editor-dialog .el-dialog__body) { min-height: 0; overflow-y: auto; }
+:global(.pallet-editor-dialog .el-dialog__footer) { flex: 0 0 auto; }
.editor-form { padding: 4px 24px 28px; }
.editor-section { padding: 20px 0 4px; border-bottom: 1px solid var(--cc-border); }
.editor-section:last-child { padding-bottom: 0; border-bottom: 0; }
@@ -588,6 +579,9 @@ onMounted(() => {
.editor-form :deep(.el-input-number), .editor-form :deep(.el-select) { width: 100%; }
.editor-form :deep(.el-form-item) { margin-bottom: 18px; }
.editor-form :deep(.el-form-item__label) { padding-bottom: 7px; color: #344054; font-size: 12px; font-weight: 650; line-height: 1.4; }
+@media (max-width: 720px) {
+ .editor-field-grid { grid-template-columns: minmax(0, 1fr); }
+}
.editor-remark-field { margin-bottom: 0 !important; }
.field-help { margin: 6px 0 0; color: var(--cc-muted); font-size: 12px; line-height: 1.5; }
.drawer-footer { display: flex; justify-content: flex-end; gap: 8px; }
diff --git a/src/modules/warehouses/pages/WarehousesView.test.ts b/src/modules/warehouses/pages/WarehousesView.test.ts
index b5e43daa..2a88b43d 100644
--- a/src/modules/warehouses/pages/WarehousesView.test.ts
+++ b/src/modules/warehouses/pages/WarehousesView.test.ts
@@ -1,4 +1,6 @@
import ElementPlus from 'element-plus'
+import { readFileSync } from 'node:fs'
+import { resolve } from 'node:path'
import { createPinia, setActivePinia } from 'pinia'
import { enableAutoUnmount, flushPromises, mount } from '@vue/test-utils'
import { afterAll, afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
@@ -19,6 +21,8 @@ import { useAccessPreviewStore } from '@/core/access/accessPreview'
import { useAuthStore } from '@/core/auth/auth'
import WarehousesView from './WarehousesView.vue'
+const warehousesSource = readFileSync(resolve(process.cwd(), 'src/modules/warehouses/pages/WarehousesView.vue'), 'utf8')
+
vi.mock('@/modules/warehouses/api/platformWarehouses', () => ({
listPlatformWarehouses: vi.fn(),
getPlatformWarehouse: vi.fn(),
@@ -171,6 +175,12 @@ enableAutoUnmount(afterEach)
afterAll(() => vi.unstubAllGlobals())
describe('WarehousesView', () => {
+ it('uses the prototype-sized centered warehouse editor with a fixed footer', () => {
+ expect(warehousesSource).toContain(' {
vi.stubGlobal('ResizeObserver', ResizeObserverStub)
document.body.innerHTML = ''
@@ -263,12 +273,12 @@ describe('WarehousesView', () => {
wrapper.findComponent({ name: 'ElDropdown' }).vm.$emit('command', 'FBA')
await flushPromises()
- const drawerText = document.querySelector('.el-drawer')?.textContent || ''
- expect(drawerText).toContain('新增FBA 地址')
- expect(drawerText).toContain('国家或地区')
- expect(drawerText).not.toContain('时区覆盖')
- expect(drawerText).not.toContain('联系人')
- expect(drawerText).not.toContain('收货时间')
+ const dialogText = document.querySelector('.warehouse-editor-dialog')?.textContent || ''
+ expect(dialogText).toContain('新增FBA 地址')
+ expect(dialogText).toContain('国家或地区')
+ expect(dialogText).not.toContain('时区覆盖')
+ expect(dialogText).not.toContain('联系人')
+ expect(dialogText).not.toContain('收货时间')
const countrySelect = wrapper.findAllComponents({ name: 'ElSelect' })
.find((select) => select.props('placeholder') === '美国 US')
expect(countrySelect).toBeDefined()
@@ -309,10 +319,10 @@ describe('WarehousesView', () => {
wrapper.findComponent({ name: 'ElDropdown' }).vm.$emit('command', 'DOMESTIC')
await flushPromises()
- const drawerText = document.querySelector('.el-drawer')?.textContent || ''
- expect(drawerText).not.toContain('国家或地区')
- expect(drawerText).not.toContain('时区')
- expect(drawerText).not.toContain('仓库类型')
+ const dialogText = document.querySelector('.warehouse-editor-dialog')?.textContent || ''
+ expect(dialogText).not.toContain('国家或地区')
+ expect(dialogText).not.toContain('时区')
+ expect(dialogText).not.toContain('仓库类型')
setField('input[placeholder="请输入仓库名称"]', '义乌新仓')
setField('input[placeholder="例如 浙江省"]', '浙江省')
@@ -384,10 +394,10 @@ describe('WarehousesView', () => {
await editButton.trigger('click')
await flushPromises()
- const drawerText = document.querySelector('.el-drawer')?.textContent || ''
- expect(drawerText).toContain('编辑FBA 地址')
- expect(drawerText).not.toContain('例如 US')
- expect(drawerText).not.toContain('例如 UTC-8')
+ const dialogText = document.querySelector('.warehouse-editor-dialog')?.textContent || ''
+ expect(dialogText).toContain('编辑FBA 地址')
+ expect(dialogText).not.toContain('例如 US')
+ expect(dialogText).not.toContain('例如 UTC-8')
const countrySelect = wrapper.findAllComponents({ name: 'ElSelect' })
.find((select) => select.props('placeholder') === '请选择国家或地区')
diff --git a/src/modules/warehouses/pages/WarehousesView.vue b/src/modules/warehouses/pages/WarehousesView.vue
index d26c2ef3..035b7c15 100644
--- a/src/modules/warehouses/pages/WarehousesView.vue
+++ b/src/modules/warehouses/pages/WarehousesView.vue
@@ -3,7 +3,7 @@ import { computed, onMounted, reactive, ref, watch } from 'vue'
import { ElMessage, ElMessageBox, type UploadFile } from 'element-plus'
import { ChevronDown, Download, FileSpreadsheet, FileUp, MapPin, Pencil, Plus, Power, Save, Warehouse as WarehouseIcon } from '@lucide/vue'
import AdminPage from '@/shared/components/AdminPage.vue'
-import AppDrawer from '@/shared/components/AppDrawer.vue'
+import AppDialog from '@/shared/components/AppDialog.vue'
import DataTable, { type DataTableColumn } from '@/shared/components/DataTable.vue'
import FormSection from '@/shared/components/FormSection.vue'
import ListPageLayout from '@/shared/components/ListPageLayout.vue'
@@ -733,7 +733,7 @@ onMounted(() => void loadWarehouses())
-
+