fix(admin): 完善弹窗滚动与表单交互布局

This commit is contained in:
2026-10-21 07:40:08 +08:00
parent 2f9a3330e5
commit 0a1968a134
41 changed files with 614 additions and 131 deletions
@@ -1,14 +1,15 @@
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'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { DOMWrapper, enableAutoUnmount, flushPromises, mount } from '@vue/test-utils'
import { afterEach, 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 SmartLoadingDialog from '../components/SmartLoadingDialog.vue'
import type { ContainerBookingResource } from '../types'
import {
assignPendingReviewSmartLoadingOrder,
@@ -27,6 +28,8 @@ import {
removeSmartLoadingPallet,
} from '../api/platformContainerLoading'
enableAutoUnmount(afterEach)
const { routerPush } = vi.hoisted(() => ({ routerPush: vi.fn() }))
vi.mock('vue-router', () => ({
@@ -120,7 +123,50 @@ function mockContainerSearch(records: ContainerBookingResource[]) {
))))
}
function assignmentDialog() {
const root = document.body.querySelector<HTMLElement>('.smart-loading-assignment-dialog')
if (!root) throw new Error('assignment dialog is missing')
return new DOMWrapper(root)
}
describe('GlobalSmartLoadingView', () => {
it('mounts assignment outside the parent workbench and keeps fullscreen and reopen states independent', async () => {
const wrapper = mount(SmartLoadingDialog, { attachTo: document.body, props: { modelValue: true }, global: { plugins: [ElementPlus] } })
try {
await flushPromises()
const workbench = wrapper.getComponent(GlobalSmartLoadingView)
const openAssignment = async () => {
await workbench.get('.candidate-pane .el-table__body-wrapper').findAll('button').find(button => button.text() === '加入')!.trigger('click')
await flushPromises()
}
await openAssignment()
const popup = workbench.getComponent(AppDialog)
const outer = document.querySelector('.smart-loading-dialog')!
const child = assignmentDialog()
expect(document.body.contains(child.element)).toBe(true)
expect(outer.contains(child.element)).toBe(false)
expect(child.element.closest('.global-loading-page')).toBeNull()
expect(child.classes()).toEqual(expect.arrayContaining(['admin-density-dialog', 'admin-workbench', 'is-dialog']))
popup.getComponent(ElInputNumber).vm.$emit('update:modelValue', 4)
await child.get('[aria-label="展开全屏"]').trigger('click')
expect(child.classes()).toContain('is-fullscreen')
expect(outer.classList.contains('is-fullscreen')).toBe(true)
await child.findAll('button').find(button => button.text() === '取消')!.trigger('click')
await flushPromises()
expect(popup.props('modelValue')).toBe(false)
expect(wrapper.getComponent(AppDialog).props('modelValue')).toBe(true)
expect(workbench.get('.container-option.selected').attributes('aria-pressed')).toBe('true')
await openAssignment()
expect(popup.props('modelValue')).toBe(true)
expect(assignmentDialog().classes()).not.toContain('is-fullscreen')
expect(popup.getComponent(ElInputNumber).props('modelValue')).toBe(candidate.availablePackageCount)
expect(outer.classList.contains('is-fullscreen')).toBe(true)
expect(assignSmartLoadingItem).not.toHaveBeenCalled()
} finally {
wrapper.unmount()
}
})
it.each(['PACKAGE', 'SEALED_PALLET'] as const)('分配窗口全屏保留来源及输入,按%s原接口提交', async (unitType) => {
if (unitType === 'SEALED_PALLET') {
vi.mocked(listSmartLoadingCandidates).mockResolvedValue(candidatePage([{
@@ -138,7 +184,7 @@ describe('GlobalSmartLoadingView', () => {
await wrapper.get('.candidate-pane .el-table__body-wrapper').findAll('button').find(button => button.text() === '加入')!.trigger('click')
await flushPromises()
const popup = wrapper.getComponent(AppDialog)
expect(popup.get('table[aria-label="全局装柜分配来源"]').text()).toContain('CB-1')
expect(assignmentDialog().get('table[aria-label="全局装柜分配来源"]').text()).toContain('CB-1')
if (unitType === 'PACKAGE') {
const input = popup.getComponent(ElInputNumber)
expect(input.props('modelValue')).toBe(candidate.availablePackageCount)
@@ -146,11 +192,11 @@ describe('GlobalSmartLoadingView', () => {
input.vm.$emit('update:modelValue', 4)
} else {
expect(popup.findComponent(ElInputNumber).exists()).toBe(false)
expect(popup.get('details').text()).toContain('I181:9 箱')
expect(assignmentDialog().get('details').text()).toContain('I181:9 箱')
expect(popup.props('title')).toBe('整托加入当前柜次 · DP-TEST-1')
}
await popup.get('[aria-label="展开全屏"]').trigger('click')
expect(popup.find('.el-dialog.is-fullscreen').exists()).toBe(true)
await assignmentDialog().get('[aria-label="展开全屏"]').trigger('click')
expect(assignmentDialog().classes()).toContain('is-fullscreen')
if (unitType === 'PACKAGE') expect(popup.getComponent(ElInputNumber).props('modelValue')).toBe(4)
popup.vm.$emit('confirm'); await flushPromises()
if (unitType === 'PACKAGE') {
@@ -572,11 +618,11 @@ describe('GlobalSmartLoadingView', () => {
expect(document.body.textContent).toContain('不能部分输入、单独分配或单独移除')
const popup = wrapper.getComponent(AppDialog)
expect(popup.props('title')).toBe('整组加入当前柜次 · CLG-2093539779795795969')
const contextRows = popup.findAll('table[aria-label="全局装柜分配来源"] tr')
const contextRows = assignmentDialog().findAll('table[aria-label="全局装柜分配来源"] tr')
expect(contextRows[0]!.findAll('td').map(cell => cell.text())).toEqual(['CB-1', 'MSCU-1'])
expect(contextRows[1]!.findAll('td').map(cell => cell.text())).toEqual(['义乌仓', '美森正班'])
await popup.get('[aria-label="展开全屏"]').trigger('click')
expect(popup.get('table[aria-label="全局装柜同柜组成员"]').text()).toContain('NYEXX2608290051-B')
await assignmentDialog().get('[aria-label="展开全屏"]').trigger('click')
expect(assignmentDialog().get('table[aria-label="全局装柜同柜组成员"]').text()).toContain('NYEXX2608290051-B')
expect(document.body.textContent).not.toContain('本次分配箱数')
;([...document.body.querySelectorAll('button')].find((button) => button.textContent?.includes('确认整组分配')) as HTMLButtonElement).click()
await flushPromises()
@@ -612,9 +658,9 @@ describe('GlobalSmartLoadingView', () => {
await flushPromises()
const popup = wrapper.getComponent(AppDialog)
expect(popup.props('title')).toBe('加入当前柜次 · EXXHK2609020361')
const measurements = popup.findAll('table[aria-label="全局装柜分配填写"] tr').find(row => row.text().includes('平均单箱毛重'))!
const measurements = assignmentDialog().findAll('table[aria-label="全局装柜分配填写"] tr').find(row => row.text().includes('平均单箱毛重'))!
expect(measurements.findAll('td').map(cell => cell.text())).toEqual(['待测量', '待测量'])
const channel = popup.findAll('table[aria-label="全局装柜分配来源"] tr').find(row => row.text().includes('订单渠道'))!
const channel = assignmentDialog().findAll('table[aria-label="全局装柜分配来源"] tr').find(row => row.text().includes('订单渠道'))!
expect(channel.findAll('td')[0]!.text()).toBe('合德海卡')
expect(document.body.textContent).toContain('这是人工装柜分配,确认后仍会加入')
expect(document.body.textContent).toContain('智能推荐不会跨渠道')
@@ -907,10 +953,10 @@ describe('GlobalSmartLoadingView', () => {
await flushPromises()
// 箱数与上方资料使用同一标签列,保留输入名称与原范围。
const label = popup.get('label[for="global-loading-assignment-count"]')
const label = assignmentDialog().get('label[for="global-loading-assignment-count"]')
expect(label.element.parentElement?.tagName).toBe('TH')
expect(label.element.parentElement?.nextElementSibling?.getAttribute('colspan')).toBe('3')
expect(popup.find('input#global-loading-assignment-count').exists()).toBe(true)
expect(assignmentDialog().find('input#global-loading-assignment-count').exists()).toBe(true)
expect(input.props('max')).toBe(candidate.availablePackageCount)
popup.vm.$emit('confirm')
await flushPromises()
@@ -918,11 +964,12 @@ describe('GlobalSmartLoadingView', () => {
expect(assignSmartLoadingItem).toHaveBeenCalledWith(plan.id, { inboundId: candidate.inboundId, packageCount: 7, expectedVersion: plan.version })
expect(popup.props('modelValue')).toBe(true)
expect(input.props('modelValue')).toBe(7)
expect(popup.text()).toContain('加入当前柜次失败')
expect(popup.text()).toContain('当前账号无权访问该数据')
expect(popup.text()).toContain('assign-request')
expect(popup.text()).toContain('assign-trace')
expect(wrapper.get('.global-loading-page').findAll('.load-error-state')).toHaveLength(1)
expect(assignmentDialog().text()).toContain('加入当前柜次失败')
expect(assignmentDialog().text()).toContain('当前账号无权访问该数据')
expect(assignmentDialog().text()).toContain('assign-request')
expect(assignmentDialog().text()).toContain('assign-trace')
expect(assignmentDialog().findAll('.load-error-state')).toHaveLength(1)
expect(wrapper.get('.global-loading-page').findAll('.load-error-state')).toHaveLength(0)
wrapper.unmount()
})