优化
This commit is contained in:
@@ -0,0 +1,261 @@
|
||||
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 { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import InboundOrdersView from './InboundOrdersView.vue'
|
||||
import { listPlatformTenants, type PlatformTenantResource } from '@/modules/iam'
|
||||
import QueryToolbar from '@/shared/components/QueryToolbar.vue'
|
||||
import DataTable from '@/shared/components/DataTable.vue'
|
||||
import { listPlatformWarehouses } from '@/modules/warehouses'
|
||||
import { getPlatformInbound, listPlatformInbounds } from '../core/api'
|
||||
import type { InboundOrderDetail, InboundOrderSummary } from '../core/types'
|
||||
|
||||
vi.mock('../core/api', () => ({
|
||||
getPlatformInbound: vi.fn(),
|
||||
listPlatformInbounds: vi.fn(),
|
||||
}))
|
||||
|
||||
vi.mock('@/modules/warehouses', () => ({
|
||||
listPlatformWarehouses: vi.fn(),
|
||||
}))
|
||||
|
||||
vi.mock('@/modules/iam', () => ({
|
||||
listPlatformTenants: vi.fn(),
|
||||
}))
|
||||
|
||||
const customers: PlatformTenantResource[] = [
|
||||
{ id: '10001', tenantCode: 'CUS-001', tenantName: '示例企业', status: 'ACTIVE', legalName: '示例企业有限公司', legalRepresentative: null, unifiedSocialCreditCode: null, contactName: null, contactPhone: null, contactEmail: null, servicePackage: null, contactWechat: null, salesOwnerName: null, paymentTermDays: null, enterpriseType: null, establishedDate: null, registeredCapital: null, registeredCapitalCurrency: null, registeredAddress: null, internalRemark: null, createdAt: null, approvedAt: null },
|
||||
{ id: '10002', tenantCode: 'CUS-002', tenantName: '第二客户', status: 'ACTIVE', legalName: null, legalRepresentative: null, unifiedSocialCreditCode: null, contactName: null, contactPhone: null, contactEmail: null, servicePackage: null, contactWechat: null, salesOwnerName: null, paymentTermDays: null, enterpriseType: null, establishedDate: null, registeredCapital: null, registeredCapitalCurrency: null, registeredAddress: null, internalRemark: null, createdAt: null, approvedAt: null },
|
||||
]
|
||||
|
||||
const summary: InboundOrderSummary = {
|
||||
id: '19001', orderId: '18001', orderNo: 'ORD-001', inboundCode: 'RK-001', processStatus: 'WAIT_MEASURE',
|
||||
orderStatusSnapshot: 'APPROVED', measureReviewStatus: 'PENDING', feeStatus: 'UNPRICED', feeReviewStatus: null,
|
||||
notificationStatus: null, version: 1, expectedArrivalAt: '2026-08-04T10:00:00', customerId: '10001', customerName: '示例客户',
|
||||
companyId: '10001', companyName: '示例企业', warehouseId: '20001', warehouseName: '义乌收货仓', deliveryWarehouseId: null,
|
||||
deliveryWarehouseName: null, businessType: 'FBA', channelCode: 'SEA', channelName: '海运', taxMode: 'DDP', containerMode: null,
|
||||
customsInspection: false, destinationType: 'FBA', destinationWarehouseId: '30001', destinationCode: 'ONT8', destinationName: 'ONT8',
|
||||
holdFlag: false, currentHoldReason: null, totalPackageCount: 6, actualGrossWeightKg: 120, actualNetWeightKg: 110,
|
||||
actualVolumeCbm: 1.2, volumeWeightKg: 200, chargeableWeightKg: 200, chargeableVolumeCbm: null, palletCount: 1, locationCodes: 'A-01',
|
||||
measureCompletedAt: null, inboundedAt: null, expressCompanyCode: null, expressCompanyName: null, expressTrackingNo: null,
|
||||
loadingStatus: null, containerNo: null, cabinetNo: null, vesselName: null, voyageNo: null, sailingAt: null, portArrivalAt: null,
|
||||
createdAt: '2026-08-01T10:00:00', updatedAt: '2026-08-01T10:00:00',
|
||||
}
|
||||
|
||||
const detail: InboundOrderDetail = {
|
||||
summary,
|
||||
publicRemark: '请优先安排测量',
|
||||
palletLocations: [{ allocationNo: 1, warehouseId: '20001', palletId: '21001', palletCode: 'P-001', palletCapacityCbm: 2, allocatedVolumeCbm: 1.2, allocatedPackageCount: 6, locationId: '22001', locationCode: 'A-01', reservationNo: 'RES-1', allocationStatus: 'PREPARED' }],
|
||||
files: [], internalNotes: [], statusHistory: [{ fromStatus: null, toStatus: 'WAIT_MEASURE', action: 'CREATE', reason: null, operatorId: '2', operatorName: '管理员', occurredAt: '2026-08-01T10:00:00' }],
|
||||
}
|
||||
|
||||
enableAutoUnmount(afterEach)
|
||||
|
||||
function mountPage() {
|
||||
return mount(InboundOrdersView, {
|
||||
attachTo: document.body,
|
||||
global: { plugins: [ElementPlus] },
|
||||
})
|
||||
}
|
||||
|
||||
async function selectCustomer(wrapper: ReturnType<typeof mountPage>, customerId = '10001') {
|
||||
await flushPromises()
|
||||
const toolbar = wrapper.findComponent(QueryToolbar)
|
||||
toolbar.vm.$emit('update:modelValue', { ...toolbar.props('modelValue'), customerId })
|
||||
await flushPromises()
|
||||
return toolbar
|
||||
}
|
||||
|
||||
describe('InboundOrdersView', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
setActivePinia(createPinia())
|
||||
vi.mocked(listPlatformInbounds).mockResolvedValue({ records: [summary], page: 1, size: 20, total: 1, pages: 1 })
|
||||
vi.mocked(getPlatformInbound).mockResolvedValue(detail)
|
||||
vi.mocked(listPlatformTenants).mockResolvedValue({ records: customers, page: 1, size: 200, total: 2, pages: 1 })
|
||||
vi.mocked(listPlatformWarehouses).mockResolvedValue({
|
||||
records: [
|
||||
{ id: '20001', tenantId: '10001', warehouseCode: 'YIWU', warehouseName: '义乌收货仓', warehouseType: 'DOMESTIC', countryCode: 'CN', timezone: 'Asia/Shanghai', contactName: null, contactPhone: null, contactEmail: null, status: 'ACTIVE', remark: null },
|
||||
{ id: 20002, tenantId: '10001', warehouseCode: 'B001', warehouseName: '交货仓 B001', warehouseType: 'DOMESTIC', countryCode: 'CN', timezone: 'Asia/Shanghai', contactName: null, contactPhone: null, contactEmail: null, status: 'ACTIVE', remark: null },
|
||||
],
|
||||
page: 1, size: 200, total: 2, pages: 1,
|
||||
})
|
||||
})
|
||||
|
||||
it('loads the customer-scoped list and renders the first-column view action', async () => {
|
||||
const wrapper = mountPage()
|
||||
await flushPromises()
|
||||
const toolbar = wrapper.findComponent(QueryToolbar)
|
||||
const customerField = (toolbar.props('fields') as Array<{ key: string; placeholder?: string }>).find((field) => field.key === 'customerId')
|
||||
expect(customerField?.placeholder).toBe('全部客户')
|
||||
expect((toolbar.props('modelValue') as { customerId: string }).customerId).toBe('')
|
||||
expect(listPlatformInbounds).toHaveBeenCalledWith(undefined, expect.objectContaining({ page: 1, size: 20 }))
|
||||
expect(listPlatformWarehouses).not.toHaveBeenCalled()
|
||||
const requestsBeforeSearch = vi.mocked(listPlatformInbounds).mock.calls.length
|
||||
toolbar.vm.$emit('search')
|
||||
await flushPromises()
|
||||
expect(vi.mocked(listPlatformInbounds).mock.calls).toHaveLength(requestsBeforeSearch + 1)
|
||||
expect(listPlatformInbounds).toHaveBeenLastCalledWith(undefined, expect.objectContaining({ page: 1, size: 20 }))
|
||||
|
||||
await selectCustomer(wrapper)
|
||||
expect(listPlatformInbounds).toHaveBeenCalledWith('10001', expect.objectContaining({ page: 1, size: 20 }))
|
||||
expect(listPlatformWarehouses).toHaveBeenCalledWith('10001', 1, 200)
|
||||
expect(listPlatformTenants).toHaveBeenCalledWith({ page: 1, size: 200 })
|
||||
expect(document.body.textContent).toContain('RK-001')
|
||||
expect(document.body.textContent).toContain('查看')
|
||||
expect(document.body.textContent).toContain('所属企业 示例企业有限公司')
|
||||
expect(document.body.textContent).not.toContain('租户')
|
||||
expect(wrapper.find('.inbound-tabs .is-active').text()).toContain('全部')
|
||||
})
|
||||
|
||||
it('uses backend-supported warehouse IDs from tenant-scoped options without coercing Long IDs', async () => {
|
||||
const wrapper = mountPage()
|
||||
const toolbar = await selectCustomer(wrapper)
|
||||
toolbar.vm.$emit('update:modelValue', {
|
||||
...toolbar.props('modelValue'),
|
||||
warehouseId: '20001',
|
||||
deliveryWarehouseId: '20002',
|
||||
})
|
||||
toolbar.vm.$emit('search')
|
||||
await flushPromises()
|
||||
expect(listPlatformInbounds).toHaveBeenLastCalledWith('10001', expect.objectContaining({
|
||||
warehouseId: '20001',
|
||||
deliveryWarehouseId: '20002',
|
||||
}))
|
||||
})
|
||||
|
||||
it('keeps common filters compact and clears hidden advanced filters when collapsed', async () => {
|
||||
const wrapper = mountPage()
|
||||
const toolbar = await selectCustomer(wrapper)
|
||||
const fieldKeys = () => (toolbar.props('fields') as Array<{ key: string }>).map((field) => field.key)
|
||||
const widths = Object.fromEntries((toolbar.props('fields') as Array<{ key: string; width?: number }>).map((field) => [field.key, field.width]))
|
||||
expect(widths.customerId).toBe(260)
|
||||
expect(widths.processStatus).toBe(160)
|
||||
expect(widths.inboundCode).toBe(220)
|
||||
expect(widths.expectedArrivalRange).toBe(320)
|
||||
expect(fieldKeys()).toContain('expectedArrivalRange')
|
||||
expect(fieldKeys()).not.toContain('feeReviewStatus')
|
||||
|
||||
const advancedButton = () => wrapper.findAll('button').find((button) => button.text().includes('更多筛选') || button.text().includes('收起筛选'))
|
||||
await advancedButton()?.trigger('click')
|
||||
expect(fieldKeys()).toContain('feeReviewStatus')
|
||||
|
||||
toolbar.vm.$emit('update:modelValue', {
|
||||
...toolbar.props('modelValue'),
|
||||
feeReviewStatus: 'PENDING',
|
||||
destinationCode: 'ONT8',
|
||||
})
|
||||
await flushPromises()
|
||||
await advancedButton()?.trigger('click')
|
||||
await flushPromises()
|
||||
expect(fieldKeys()).not.toContain('feeReviewStatus')
|
||||
expect(listPlatformInbounds).toHaveBeenLastCalledWith('10001', expect.objectContaining({
|
||||
feeReviewStatus: undefined,
|
||||
destinationCode: undefined,
|
||||
}))
|
||||
})
|
||||
|
||||
it('passes the selected backend tab and opens an aggregate read-only detail', async () => {
|
||||
const wrapper = mountPage()
|
||||
await selectCustomer(wrapper)
|
||||
;[...document.querySelectorAll<HTMLButtonElement>('.inbound-tab')].find((button) => button.textContent?.includes('待审核'))?.click()
|
||||
await flushPromises()
|
||||
expect(listPlatformInbounds).toHaveBeenLastCalledWith('10001', expect.objectContaining({ tab: 'WAIT_REVIEW', page: 1 }))
|
||||
;[...document.querySelectorAll<HTMLButtonElement>('button')].filter((button) => button.textContent?.trim() === '查看').at(-1)?.click()
|
||||
await flushPromises()
|
||||
expect(getPlatformInbound).toHaveBeenCalledWith('10001', '19001')
|
||||
expect(document.body.textContent).toContain('托盘与库位')
|
||||
expect(document.body.textContent).toContain('请优先安排测量')
|
||||
wrapper.unmount()
|
||||
})
|
||||
|
||||
it('uses the app header as the page title and exposes compact accessible status tabs', async () => {
|
||||
const wrapper = mountPage()
|
||||
await flushPromises()
|
||||
expect(wrapper.find('.inbound-page-heading').exists()).toBe(false)
|
||||
expect(wrapper.text()).not.toContain('所有处理均进入单票工作台完成')
|
||||
const tabList = wrapper.find('[role="tablist"]')
|
||||
const tabButtons = tabList.findAll('[role="tab"]')
|
||||
const queryToolbar = wrapper.findComponent(QueryToolbar)
|
||||
expect(tabList.attributes('aria-label')).toBe('入库单处理状态')
|
||||
expect(tabList.element.parentElement?.classList.contains('table-toolbar')).toBe(true)
|
||||
expect(queryToolbar.element.compareDocumentPosition(tabList.element) & Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy()
|
||||
expect(tabButtons).toHaveLength(5)
|
||||
expect(wrapper.find('.table-toolbar').text()).toContain('共 1 条 · 所属企业 全部客户')
|
||||
expect(wrapper.find('.table-toolbar').text()).toContain('刷新')
|
||||
expect(wrapper.text()).not.toContain('全部入库单')
|
||||
expect(tabButtons[0].attributes('aria-selected')).toBe('true')
|
||||
expect(tabButtons[0].attributes('tabindex')).toBe('0')
|
||||
await tabButtons[0].trigger('keydown', { key: 'ArrowRight' })
|
||||
await flushPromises()
|
||||
expect(tabButtons[1].attributes('aria-selected')).toBe('true')
|
||||
expect(listPlatformInbounds).toHaveBeenLastCalledWith(undefined, expect.objectContaining({ tab: 'WAIT_MEASURE', page: 1 }))
|
||||
})
|
||||
|
||||
it('shows an actionable list error and recovers on retry', async () => {
|
||||
vi.mocked(listPlatformInbounds).mockRejectedValueOnce(new Error('network down')).mockResolvedValueOnce({ records: [summary], page: 1, size: 20, total: 1, pages: 1 })
|
||||
const wrapper = mountPage()
|
||||
await flushPromises()
|
||||
expect(document.body.textContent).toContain('入库单暂时无法加载')
|
||||
;[...document.querySelectorAll<HTMLButtonElement>('button')].find((button) => button.textContent?.trim() === '重新加载')?.click()
|
||||
await flushPromises()
|
||||
expect(listPlatformInbounds).toHaveBeenCalledTimes(2)
|
||||
expect(document.body.textContent).toContain('RK-001')
|
||||
})
|
||||
|
||||
it('clears the selected list and detail when the selected customer changes', async () => {
|
||||
const wrapper = mountPage()
|
||||
const toolbar = await selectCustomer(wrapper)
|
||||
toolbar.vm.$emit('update:modelValue', { ...toolbar.props('modelValue'), customerId: '10002' })
|
||||
await flushPromises()
|
||||
expect(listPlatformInbounds).toHaveBeenLastCalledWith('10002', expect.objectContaining({ page: 1 }))
|
||||
expect(listPlatformWarehouses).toHaveBeenLastCalledWith('10002', 1, 200)
|
||||
})
|
||||
|
||||
it('resets to all customers without sending an empty backend tenant ID', async () => {
|
||||
const wrapper = mountPage()
|
||||
const toolbar = await selectCustomer(wrapper)
|
||||
const requestsBeforeReset = vi.mocked(listPlatformInbounds).mock.calls.length
|
||||
const resetButton = wrapper.findAll('button').find((button) => button.text().includes('重置'))
|
||||
await resetButton?.trigger('click')
|
||||
await flushPromises()
|
||||
expect((toolbar.props('modelValue') as { customerId: string }).customerId).toBe('')
|
||||
expect(vi.mocked(listPlatformInbounds).mock.calls).toHaveLength(requestsBeforeReset + 1)
|
||||
expect(listPlatformInbounds).toHaveBeenLastCalledWith(undefined, expect.objectContaining({ page: 1 }))
|
||||
expect(document.body.textContent).not.toContain('请选择所属企业')
|
||||
expect(document.body.textContent).not.toContain('请选择客户')
|
||||
})
|
||||
|
||||
it('loads another full-data page without a customer filter', async () => {
|
||||
vi.mocked(listPlatformInbounds).mockResolvedValue({ records: [summary], page: 1, size: 20, total: 60, pages: 3 })
|
||||
const wrapper = mountPage()
|
||||
await flushPromises()
|
||||
wrapper.findComponent(DataTable).vm.$emit('update:page', 2)
|
||||
await flushPromises()
|
||||
expect(listPlatformInbounds).toHaveBeenLastCalledWith(undefined, expect.objectContaining({ page: 2, size: 20 }))
|
||||
})
|
||||
|
||||
it('keeps the operation column first and follows the prototype field order with backend-backed columns', () => {
|
||||
const source = readFileSync(resolve(process.cwd(), 'src/modules/inbounds/pages/InboundOrdersView.vue'), 'utf8')
|
||||
const toolbarSource = readFileSync(resolve(process.cwd(), 'src/shared/components/QueryToolbar.vue'), 'utf8')
|
||||
const columnSource = source.slice(source.indexOf('const columns:'))
|
||||
const operation = columnSource.indexOf("key: 'actions'")
|
||||
const inboundCode = columnSource.indexOf("key: 'inboundCode'")
|
||||
const orderStatus = columnSource.indexOf("key: 'orderStatus'")
|
||||
const measureReview = columnSource.indexOf("key: 'measureReviewStatus'")
|
||||
const feeStatus = columnSource.indexOf("key: 'feeStatus'")
|
||||
const customer = columnSource.indexOf("key: 'customerName'")
|
||||
const warehouse = columnSource.indexOf("key: 'warehouseName'")
|
||||
expect(operation).toBeGreaterThan(-1)
|
||||
expect(operation).toBeLessThan(inboundCode)
|
||||
expect(inboundCode).toBeLessThan(orderStatus)
|
||||
expect(orderStatus).toBeLessThan(measureReview)
|
||||
expect(measureReview).toBeLessThan(feeStatus)
|
||||
expect(feeStatus).toBeLessThan(customer)
|
||||
expect(customer).toBeLessThan(warehouse)
|
||||
expect(columnSource).toContain("fixed: 'left'")
|
||||
expect(toolbarSource).toMatch(/@media \(max-width: 720px\)[\s\S]*width: 100%/)
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user