style(admin): unify dialog layouts and dense readonly presentation

This commit is contained in:
2026-10-20 10:11:31 +08:00
parent 7ff7c9914f
commit 3267679758
214 changed files with 10931 additions and 2654 deletions
@@ -8,10 +8,12 @@ import { useAccessPreviewStore } from '@/core/access/accessPreview'
import { useAuthStore } from '@/core/auth/auth'
import { adjustContainerFee, createContainerFee, getContainerSettlementDetails, listApplicableFeeItems, listContainerFeeSupplierOptions, listContainerFees, uploadContainerFeePaymentVoucher } from '../api/containerFees'
import ContainerFeesPanel from './ContainerFeesPanel.vue'
import AppDialog from '@/shared/components/AppDialog.vue'
import { createContainerFeeAllocationGroup } from '../api/containerFees'
vi.mock('../api/containerFees', async (importOriginal) => {
const actual = await importOriginal<typeof import('../api/containerFees')>()
return { ...actual, listContainerFees: vi.fn(), listApplicableFeeItems: vi.fn(), listContainerFeeSupplierOptions: vi.fn(), getContainerSettlementDetails: vi.fn(), createContainerFee: vi.fn(), adjustContainerFee: vi.fn(), uploadContainerFeePaymentVoucher: vi.fn(), approveContainerFee: vi.fn(), rejectContainerFee: vi.fn(), voidContainerFee: vi.fn() }
return { ...actual, listContainerFees: vi.fn(), listApplicableFeeItems: vi.fn(), listContainerFeeSupplierOptions: vi.fn(), getContainerSettlementDetails: vi.fn(), createContainerFee: vi.fn(), createContainerFeeAllocationGroup: vi.fn(), adjustContainerFee: vi.fn(), uploadContainerFeePaymentVoucher: 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')
@@ -24,7 +26,7 @@ async function mountPanel(permissions: string[], customers = [{ accountId: '11',
const pinia = createPinia(); setActivePinia(pinia)
const auth = useAuthStore(); auth.token = 'token'; auth.permissions = permissions
useAccessPreviewStore().initializeAfterLogin()
const wrapper = mount(ContainerFeesPanel, { attachTo: document.body, props: { planId: '9', containerBookingId, customers, businessReferences: [{ accountId: '11', customerName: '客户A', orderId: '501', orderNo: 'FL-501', inboundId: '601', inboundCode: 'NYEXX2608300001' }] }, global: { plugins: [pinia, ElementPlus] } })
const wrapper = mount(ContainerFeesPanel, { attachTo: document.body, props: { planId: '9', cabinetNo: 'CAB-9', containerNo: 'CONT-9', containerBookingId, customers, businessReferences: [{ accountId: '11', customerName: '客户A', orderId: '501', orderNo: 'FL-501', inboundId: '601', inboundCode: 'NYEXX2608300001' }] }, global: { plugins: [pinia, ElementPlus] } })
await flushPromises(); return wrapper
}
@@ -35,10 +37,50 @@ beforeEach(() => {
vi.mocked(getContainerSettlementDetails).mockReset().mockResolvedValue({ customerId: '11', containerBookingId: '99', settledSourceFeeIds: [], costs: [] }); vi.mocked(createContainerFee).mockReset()
vi.mocked(adjustContainerFee).mockReset().mockResolvedValue({} as never)
vi.mocked(uploadContainerFeePaymentVoucher).mockReset().mockResolvedValue({ fileId: '9001', fileName: '付款凭证.pdf' })
vi.mocked(createContainerFeeAllocationGroup).mockReset().mockResolvedValue({} as never)
document.body.innerHTML = ''
})
describe('ContainerFeesPanel contract', () => {
it('连续分摊表保留两个客户归属,合计不符时阻止保存,全屏后按原归属提交', async () => {
vi.mocked(listApplicableFeeItems).mockResolvedValue([{ id: '7', name: '操作费', feeCode: 'OPERATION', defaultPrice: null, supplierCategory: null, warehouseReceivable: true, warehousePayable: false, pickupReceivable: false, pickupPayable: false }])
const wrapper = await mountPanel(['firstleg:inbound:fee-detail:view', 'firstleg:inbound:fee-detail:edit', 'firstleg:fee-item:view'], [{ accountId: '11', customerName: '客户A' }, { accountId: '22', customerName: '客户B' }])
await wrapper.setProps({ businessReferences: [
{ accountId: '11', customerName: '客户A', orderId: '501', orderNo: 'FL-501', inboundId: '601', inboundCode: 'NYEXX2608300001' },
{ accountId: '22', customerName: '客户B', orderId: '502', orderNo: 'FL-502', inboundId: null, inboundCode: null },
] })
const state = (wrapper.vm.$ as unknown as { setupState: {
accountId: string; openCreate(): void; save(): Promise<void>; form: { feeItemId: string; amount: number; remark: string }
} }).setupState
state.accountId = '11'
await flushPromises()
state.openCreate()
await flushPromises()
state.form.feeItemId = '7'; state.form.amount = 100; state.form.remark = '分摊测试备注'
const dialog = wrapper.findAllComponents(AppDialog).find(item => item.props('title').startsWith('新增柜次费用'))!
const rows = dialog.findAll('table[aria-label="业务归属分摊"] tbody tr')
expect(rows).toHaveLength(2)
expect(rows[0]!.text()).toContain('客户A')
expect(rows[1]!.text()).toContain('客户B')
expect(rows[1]!.text()).toContain('无入仓码')
rows[0]!.findComponent({ name: 'ElInputNumber' }).vm.$emit('update:modelValue', 40)
rows[1]!.findComponent({ name: 'ElInputNumber' }).vm.$emit('update:modelValue', 50)
await state.save()
expect(createContainerFeeAllocationGroup).not.toHaveBeenCalled()
rows[1]!.findComponent({ name: 'ElInputNumber' }).vm.$emit('update:modelValue', 60)
await dialog.get('[aria-label="展开全屏"]').trigger('click')
expect(dialog.find('textarea').element.value).toBe('分摊测试备注')
await state.save()
expect(createContainerFeeAllocationGroup).toHaveBeenCalledWith('9', {
fee: expect.objectContaining({ amount: 100, feeItemId: '7', remark: '分摊测试备注', direction: 'RECEIVABLE' }),
allocations: [
{ customerId: '11', orderId: '501', orderNo: 'FL-501', inboundId: '601', inboundCode: 'NYEXX2608300001', amount: 40 },
{ customerId: '22', orderId: '502', orderNo: 'FL-502', inboundId: null, inboundCode: null, amount: 60 },
],
})
wrapper.unmount()
})
it.each(['success', 'failure'])('ignores a closed editor request that finishes late: %s', async (outcome) => {
const item = { id: '7', name: '操作费', feeCode: 'OPERATION', defaultPrice: 60, supplierCategory: null, warehouseReceivable: true, warehousePayable: false, pickupReceivable: false, pickupPayable: false }
let resolveOld!: (items: typeof item[]) => void
@@ -158,7 +200,15 @@ describe('ContainerFeesPanel contract', () => {
const item = { id: '2', loadingPlanId: '9', containerBookingId: '99', containerNo: null, feeItemId: '7', feeItemName: '拖车费', direction: 'PAYABLE' as const, supplierId: '41', supplierName: '承运商A', supplierCategory: 'TRUCKING' as const, businessDate: '2026-08-12', amount: 100, currency: 'CNY' as const, reviewStatus: 'APPROVED' as const, sourceStatus: 'ACTIVE' as const, sourceVersion: 3, remark: '原备注', reviewerName: null, reviewedAt: null, reviewReason: null }
const state = (wrapper.vm.$ as unknown as { setupState: { openAdjust(value: typeof item): void; save(): Promise<void>; form: { businessDate: string; amount: number } } }).setupState
state.openAdjust(item); await flushPromises()
const dialog = wrapper.findAllComponents(AppDialog).find(item => item.props('title').startsWith('调整已审核费用'))!
expect(dialog.props('title')).toBe('调整已审核费用 · CAB-9')
const fields = dialog.findAllComponents({ name: 'ElFormItem' })
expect(fields.find(field => field.props('label') === '费用项目')!.findComponent({ name: 'ElSelect' }).exists()).toBe(false)
expect(fields.find(field => field.props('label') === '供应商')!.text()).toContain('承运商A')
state.form.businessDate = '2026-08-13'; state.form.amount = 120
await dialog.get('[aria-label="展开全屏"]').trigger('click')
expect(dialog.find('.el-dialog.is-fullscreen').exists()).toBe(true)
expect(state.form.amount).toBe(120)
await state.save()
expect(adjustContainerFee).toHaveBeenCalledWith('9', '11', '2', { expectedSourceVersion: 3, businessDate: '2026-08-13', amount: 120 })
expect(wrapper.text()).toContain('已审核费用仍可调整日期与金额')