This commit is contained in:
2026-10-20 23:34:11 +08:00
parent f155976f52
commit 13e4aa56ec
321 changed files with 6756 additions and 2110 deletions
@@ -1,4 +1,4 @@
import ElementPlus, { ElCheckboxButton, ElInputNumber, ElMessage, ElMessageBox } from 'element-plus'
import ElementPlus, { ElCheckboxButton, ElInputNumber, ElMessage, ElMessageBox, ElOption, ElSelect } from 'element-plus'
import { AxiosError } from 'axios'
import { createPinia, setActivePinia } from 'pinia'
import { flushPromises, mount } from '@vue/test-utils'
@@ -6,6 +6,7 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'
import { readFileSync } from 'node:fs'
import { resolve } from 'node:path'
import QueryToolbar from '@/shared/components/QueryToolbar.vue'
import { listPlatformTenants, type PlatformTenantResource } from '@/modules/iam'
import GlobalSmartLoadingView from './GlobalSmartLoadingView.vue'
import AppDialog from '@/shared/components/AppDialog.vue'
import type { ContainerBookingResource } from '../types'
@@ -35,7 +36,8 @@ vi.mock('vue-router', () => ({
vi.mock('@/core/access/accessPreview', () => ({
useAccessPreviewStore: () => ({ can: () => true }),
}))
vi.mock('@/modules/iam', () => ({
vi.mock('@/modules/iam', async importOriginal => ({
...await importOriginal<typeof import('@/modules/iam')>(),
listPlatformTenants: vi.fn().mockResolvedValue({ records: [], page: 1, size: 200, total: 0, pages: 0 }),
}))
vi.mock('../api/platformContainerLoading', () => ({
@@ -175,6 +177,7 @@ describe('GlobalSmartLoadingView', () => {
beforeEach(() => {
setActivePinia(createPinia())
vi.clearAllMocks()
vi.mocked(listPlatformTenants).mockReset().mockResolvedValue({ records: [], page: 1, size: 50, total: 0, pages: 0 })
mockContainerSearch([container])
vi.mocked(getSmartLoadingPlanByBooking).mockResolvedValue(plan)
vi.mocked(listSmartLoadingCandidates).mockResolvedValue(candidatePage([candidate]))
@@ -264,12 +267,13 @@ describe('GlobalSmartLoadingView', () => {
const option = wrapper.find('.container-option')
expect(option.text()).toContain('模拟柜次')
expect(option.text()).toContain('柜次号:待生成')
expect(option.text()).toContain('集装箱号:待补录')
expect(option.get('.container-option__top strong').text()).toBe('待生成')
expect(option.text()).not.toContain('空柜')
expect(option.text()).toContain('集装箱:待补录')
expect(option.text()).not.toContain('开始装柜前录入集装箱号')
expect(option.text()).toContain('渠道:美森正班')
expect(option.text()).toContain('仓库:义乌国内仓')
expect(option.text()).toContain('柜型:40HQ')
expect(option.get('.container-option__meta').text()).toContain('义乌国内仓')
expect(option.get('.container-option__meta').text()).toContain('40HQ')
expect(option.text()).not.toContain('2088475410134773762')
expect(wrapper.text()).not.toContain('真实柜号')
})
@@ -294,7 +298,7 @@ describe('GlobalSmartLoadingView', () => {
expect(wrapper.findAll('.container-option')).toHaveLength(1)
expect(wrapper.text()).toContain('MSCU-1')
expect(wrapper.text()).not.toContain('MSCU-2')
expect(wrapper.text()).toContain('柜子数量1 个')
expect(wrapper.text()).toContain('柜次1 个')
expect(wrapper.findAll('.channel-tags .el-checkbox-button').map((item) => item.text())).toEqual([
'美森正班', '美森加班', 'EXX', '合德', '洛杉矶普船', '芝加哥普船', '休斯顿普船', '萨凡纳普船', '纽约普船海卡',
@@ -306,7 +310,7 @@ describe('GlobalSmartLoadingView', () => {
expect(wrapper.findAll('.container-option')).toHaveLength(2)
expect(wrapper.text()).toContain('MSCU-1')
expect(wrapper.text()).toContain('MSCU-2')
expect(wrapper.text()).toContain('柜子数量2 个')
expect(wrapper.text()).toContain('柜次2 个')
})
it('selects only the double-clicked channel as a shortcut', async () => {
@@ -456,12 +460,35 @@ describe('GlobalSmartLoadingView', () => {
it('sends the active name-only customer selector to the server candidate query', () => {
const source = readFileSync(resolve(process.cwd(), 'src/modules/container-loading/pages/GlobalSmartLoadingView.vue'), 'utf8')
expect(source).toContain("listPlatformTenants({ page: 1, size: 200, status: 'ACTIVE' })")
expect(source).toContain('useActiveCustomerOptions(')
expect(source).toContain('remoteMethod: loadCustomers')
expect(source).toContain("label: customer.tenantName")
expect(source).toContain('params.customerId = customerId')
expect(source).not.toContain('customer.tenantCode')
})
it('searches customers remotely, loads more options, and applies the selected customer to candidates', async () => {
const target = { id: '9007199254740999', tenantName: '柏悦物流', status: 'ACTIVE' } as PlatformTenantResource
vi.mocked(listPlatformTenants).mockImplementation(async query => ({
records: query.keyword || query.page === 2 ? [target] : [{ id: '1', tenantName: '跨客户车次-测试客户', status: 'ACTIVE' } as PlatformTenantResource],
page: query.page, size: 50, total: query.keyword ? 1 : 350, pages: query.keyword ? 1 : 7,
}))
const wrapper = mount(GlobalSmartLoadingView, { global: { plugins: [ElementPlus] } })
await flushPromises()
const selector = wrapper.findAllComponents(ElSelect).find(item => item.props('placeholder') === '全部客户')!
expect(selector.props('remote')).toBe(true)
selector.vm.$emit('end-reached', 'bottom'); await flushPromises()
expect(listPlatformTenants).toHaveBeenLastCalledWith({ page: 2, size: 50, status: 'ACTIVE' })
await selector.props('remoteMethod')?.('柏悦'); await flushPromises()
expect(listPlatformTenants).toHaveBeenLastCalledWith({ page: 1, size: 50, status: 'ACTIVE', keyword: '柏悦' })
expect(selector.findAllComponents(ElOption).map(option => option.props('label'))).toContain('柏悦物流')
selector.vm.$emit('update:modelValue', target.id); await flushPromises()
const toolbar = wrapper.findComponent(QueryToolbar)
toolbar.vm.$emit('search', toolbar.props('modelValue')); await flushPromises()
expect(listSmartLoadingCandidates).toHaveBeenLastCalledWith(expect.objectContaining({ customerId: target.id }))
wrapper.unmount()
})
it('defaults ordinary candidate assignment to all available boxes', () => {
const source = readFileSync(resolve(process.cwd(), 'src/modules/container-loading/pages/GlobalSmartLoadingView.vue'), 'utf8')
expect(source).toContain(': candidate.availablePackageCount')
@@ -751,6 +778,8 @@ describe('GlobalSmartLoadingView', () => {
{ value: 'LOADING', label: '装柜中' },
])
expect(fields.find((field) => field.key === 'orderStatuses')?.props).toMatchObject({ multiple: true, collapseTags: true, maxCollapseTags: 2 })
expect(fields.find((field) => field.key === 'createdRange')?.props).toMatchObject({ startPlaceholder: '创建起始', endPlaceholder: '创建截止', 'aria-label': '创建日期范围' })
expect(fields.find((field) => field.key === 'expectedArrivalRange')?.props).toMatchObject({ startPlaceholder: '到货起始', endPlaceholder: '到货截止', 'aria-label': '预计到货日期范围' })
expect(source).not.toContain('目的仓')
expect(wrapper.text()).not.toContain('税率')
@@ -1073,7 +1102,7 @@ describe('GlobalSmartLoadingView', () => {
expect(source).toContain('size="small"')
expect(source).toContain('type="danger" plain')
expect(source).toContain(':max="adjustmentMax(row)"')
expect(source).toContain("{ key: 'customerName', prop: 'customerName', label: '客户', minWidth: 70, slotName: 'customerName' }")
expect(source).toContain("{ key: 'customerName', prop: 'customerName', label: '客户', minWidth: 120, slotName: 'customerName' }")
expect(source).not.toContain('link type="danger"')
expect(source).toContain('@media (max-width: 720px)')
expect(source.match(/<DataTable[\s\S]*?\bstripe\b/g)).toHaveLength(2)
@@ -1083,9 +1112,9 @@ describe('GlobalSmartLoadingView', () => {
const source = readFileSync(resolve(process.cwd(), 'src/modules/container-loading/pages/GlobalSmartLoadingView.vue'), 'utf8')
expect(source).toContain('height: clamp(520px, 68vh, 720px); min-height: 0;')
expect(source).toContain('grid-template-columns: minmax(0, 5fr) minmax(0, 2fr) minmax(0, 3fr);')
expect(source).toContain('grid-template-columns: minmax(0, 1.05fr) 240px minmax(0, 1fr);')
expect(source).toContain('.dispatch-pane { display: flex; min-width: 0; min-height: 0; flex-direction: column; }')
expect(source).toContain('.candidate-pane :deep(.data-table),.plan-pane :deep(.data-table) { min-height: 0; flex: 1; }')
expect(source).toContain('.candidate-pane :deep(.data-table),.plan-pane :deep(.data-table) { display: flex; min-height: 0; flex: 1; flex-direction: column; overflow: hidden; }')
expect(source).toContain(".candidate-pane :deep(.data-table__page-button[aria-label^='第']),.candidate-pane :deep(.data-table__page-ellipsis) { display: none; }")
expect(source).toContain('.candidate-pane :deep(.data-table__page-size .el-select) { width: 68px; }')
expect(source).toContain('.container-list { display: grid; min-height: 0; flex: 1;')
@@ -1093,11 +1122,12 @@ describe('GlobalSmartLoadingView', () => {
expect(source).toContain('overflow-wrap: anywhere;')
expect(source).toContain('.candidate-pane,.plan-pane { height: min(68vh, 560px); min-height: 420px; }')
expect(source).toContain('.container-pane { max-height: min(58vh, 460px); }')
expect(source).toContain('min-height: 112px')
expect(source).toContain('gap: var(--admin-space-1); padding: var(--admin-space-4);')
expect(source).toContain('.candidate-pane :deep(.data-table__pagination) { flex: 0 0 auto; }')
expect(source).toContain("label: '创建日期'")
expect(source).toContain("label: '预计到货时间'")
expect(source).toContain("label: '订单状态'")
expect(source).toContain('.smart-loading-query .query-toolbar__filters) { flex-wrap: nowrap; gap: 8px; overflow-x: auto; }')
expect(source).toContain('.smart-loading-query .query-toolbar__filters) { flex-wrap: wrap; gap: 8px; }')
expect(source).toContain('.smart-loading-query .query-toolbar__actions) { flex-wrap: nowrap; gap: 6px; }')
expect(source).toContain(':page-sizes="[20, 50, 100]"')
})
@@ -1112,10 +1142,10 @@ describe('GlobalSmartLoadingView', () => {
expect(source).toContain("ElMessage.warning('至少选择一个渠道')")
expect(source).toContain('class="compact-summary" aria-label="已选渠道装柜统计"')
expect(source).toContain('<dt>候选明细</dt>')
expect(source).toContain('<dt>柜子数量</dt>')
expect(source).toContain('<dt>已装</dt>')
expect(source).toContain('<dt>总重量</dt>')
expect(source).toContain('<dt>总体积</dt>')
expect(source).toContain('<dt>柜次</dt>')
expect(source).toContain('<dt>当前柜次分配</dt>')
expect(source).toContain('formatNumber(totalWeight, 1)')
expect(source).toContain('formatNumber(totalVolume)')
expect(source).toContain('.channel-tags :deep(.el-checkbox-button__inner)')
expect(source).toContain('flex-wrap: nowrap; gap: 6px;')
expect(source).toContain('height: 34px; padding: 0 10px;')