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
@@ -1,25 +1,26 @@
import { mount, flushPromises } from '@vue/test-utils'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import ElementPlus from 'element-plus'
import ElementPlus, { ElFormItem, ElSelect } from 'element-plus'
import AppDialog from '@/shared/components/AppDialog.vue'
import ContainerLastLegFeesPanel from './ContainerLastLegFeesPanel.vue'
const mocks = vi.hoisted(() => ({ upload: vi.fn(), metadata: vi.fn(), download: vi.fn(), create: vi.fn(), fees: vi.fn() }))
vi.mock('@/core/access/accessPreview', () => ({ useAccessPreviewStore: () => ({ can: () => true }) }))
vi.mock('@/modules/finance', () => ({ getFinancePaymentVoucher: mocks.metadata, downloadFinancePaymentVoucher: mocks.download }))
vi.mock('@/modules/loading-lists', () => ({ uploadContainerFeePaymentVoucher: mocks.upload, listContainerFeeSupplierOptions: async () => [] }))
vi.mock('@/modules/loading-lists', () => ({ uploadContainerFeePaymentVoucher: mocks.upload, listContainerFeeSupplierOptions: async () => [{ id: '21', supplierName: '供应商甲', category: 'TERMINAL_DRAYAGE' }] }))
vi.mock('../api/overseasContainerFees', () => ({
listOverseasContainerFees: mocks.fees, listOverseasContainerCustomers: async () => [{ customerId: '11', customerName: '客户甲' }],
createOverseasContainerFee: mocks.create, adjustOverseasContainerFee: vi.fn(), reviewOverseasContainerFee: vi.fn(), submitOverseasContainerFee: vi.fn(), voidOverseasContainerFee: vi.fn(),
}))
async function setup(readonly = false) {
const wrapper = mount(ContainerLastLegFeesPanel, { props: { containerBookingId: '99', containerNo: 'CONT-99', readonly }, global: { plugins: [ElementPlus], stubs: {
async function setup(readonly = false, realDialog = false, containerNo: string | null = 'CONT-99') {
const wrapper = mount(ContainerLastLegFeesPanel, { props: { containerBookingId: '99', cabinetNo: 'CAB-99', containerNo, readonly }, global: { plugins: [ElementPlus], stubs: realDialog ? {} : {
AppDialog: { props: ['modelValue', 'title', 'confirmDisabled'], template: '<section v-if="modelValue"><h3>{{ title }}</h3><slot /><button :disabled="confirmDisabled" @click="$emit(\'confirm\')">保存</button></section>' },
} } })
await flushPromises()
return wrapper
}
type FormState = { form: { customerId: string; settlementMode: string }; dialogOpen: boolean }
type FormState = { form: { customerId: string; settlementMode: string; direction: string; amount: number | null; businessDate: string; supplierId: string; paidAt: string; paymentReferenceNo: string }; dialogOpen: boolean }
async function upload(wrapper: Awaited<ReturnType<typeof setup>>, name: string) {
const input = wrapper.get('input[type="file"]')
Object.defineProperty(input.element, 'files', { configurable: true, value: [new File(['image'], name, { type: 'image/png' })] })
@@ -37,6 +38,60 @@ describe('尾程付款凭证', () => {
mocks.download.mockResolvedValue(new Blob(['image']))
})
it('新增连续表切换付款字段和全屏后保留输入,保存仍使用真实集装箱号', async () => {
const wrapper = await setup(false, true)
await wrapper.get('button').trigger('click')
await flushPromises()
const state = (wrapper.vm.$ as unknown as { setupState: FormState }).setupState
const dialog = wrapper.findAllComponents(AppDialog).find(item => item.props('title').startsWith('新增尾程费用'))!
expect(dialog.props('title')).toBe('新增尾程费用 · CAB-99')
const labels = () => dialog.findAllComponents(ElFormItem).map(item => item.props('label'))
expect(labels()).toHaveLength(7)
state.form.settlementMode = 'DIRECT_PAID'
state.form.amount = 120.25
state.form.supplierId = '21'
state.form.businessDate = '2026-10-19'
state.form.paidAt = '2026-10-19T09:00:00'
state.form.paymentReferenceNo = ' PAY-001 '
await flushPromises()
expect(labels()).toHaveLength(11)
await dialog.get('[aria-label="展开全屏"]').trigger('click')
expect(dialog.find('.el-dialog.is-fullscreen').exists()).toBe(true)
expect(state.form.amount).toBe(120.25)
await dialog.get('[aria-label="退出全屏"]').trigger('click')
expect(state.form.paymentReferenceNo).toBe(' PAY-001 ')
dialog.vm.$emit('confirm')
await flushPromises()
expect(mocks.create).toHaveBeenCalledWith(expect.objectContaining({
customerId: '11', containerBookingId: '99', containerNo: 'CONT-99', businessNo: 'CONT-99',
amount: 120.25, supplierId: '21', settlementMode: 'DIRECT_PAID',
paidAt: '2026-10-19T09:00:00', paymentReferenceNo: 'PAY-001', currency: 'CNY',
}), expect.stringContaining('container-fee-create-'))
wrapper.unmount()
})
it('调整的所属客户显示只读名称,应收费用隐藏供应商和付款字段', async () => {
mocks.fees.mockResolvedValue([{ sourceFeeId: '81', sourceVersion: 7, customerId: '11', customerName: '客户甲', feeItemCode: 'PORT_FEE', amount: 10, businessDate: '2026-10-19', direction: 'RECEIVABLE', reviewStatus: 'APPROVED', sourceStatus: 'ACTIVE', paymentVoucherFileIds: [] }])
const wrapper = await setup(false, true)
await wrapper.findAll('button').find(button => button.text() === '调整')!.trigger('click')
await flushPromises()
const dialog = wrapper.findAllComponents(AppDialog).find(item => item.props('title').startsWith('调整尾程费用'))!
const fields = dialog.findAllComponents(ElFormItem)
expect(fields.map(item => item.props('label'))).toEqual(['客户', '费用项目', '费用方向', '业务日期', '金额(CNY)'])
const customer = fields[0]!
expect(customer.text()).toContain('客户甲')
expect(customer.findComponent(ElSelect).exists()).toBe(false)
wrapper.unmount()
})
it('只有柜次号仍不能维护费用,保留补录真实集装箱号门禁', async () => {
const wrapper = await setup(false, true, null)
expect(wrapper.get('button').attributes('disabled')).toBeDefined()
expect(wrapper.text()).toContain('尚未补录真实集装箱号')
expect(mocks.create).not.toHaveBeenCalled()
wrapper.unmount()
})
it('作废待财务确认时保留有效来源,停止调整和重复作废', async () => {
mocks.fees.mockResolvedValue([{ sourceFeeId: '81', customerId: '11', amount: 10, reviewStatus: 'APPROVED', sourceStatus: 'ACTIVE', voidStatus: 'PENDING_FINANCE', paymentVoucherFileIds: [] }])
const wrapper = await setup()