refactor(admin): use dialog for inbound relationships

This commit is contained in:
2026-08-30 18:21:12 +08:00
parent 8c1dc50cf3
commit abdea48e8d
6 changed files with 80 additions and 48 deletions
@@ -1,4 +1,4 @@
import { readFileSync } from 'node:fs'
import { existsSync, readFileSync } from 'node:fs'
import { resolve } from 'node:path'
import { defineComponent } from 'vue'
import { flushPromises, mount } from '@vue/test-utils'
@@ -9,7 +9,7 @@ import {
getOverseasInboundCodeRelationship,
getPlatformInboundCodeAssociation,
} from '@/shared/api/inboundCodeRelations'
import InboundCodeRelationshipDrawer from './InboundCodeRelationshipDrawer.vue'
import InboundCodeRelationshipDialog from './InboundCodeRelationshipDialog.vue'
vi.mock('@/shared/api/inboundCodeRelations', () => ({
getLoadingAssociationSummary: vi.fn(),
@@ -17,15 +17,15 @@ vi.mock('@/shared/api/inboundCodeRelations', () => ({
getPlatformInboundCodeAssociation: vi.fn(),
}))
const source = readFileSync(resolve(process.cwd(), 'src/shared/components/InboundCodeRelationshipDrawer.vue'), 'utf8')
const AppDrawerStub = defineComponent({
const source = readFileSync(resolve(process.cwd(), 'src/shared/components/InboundCodeRelationshipDialog.vue'), 'utf8')
const AppDialogStub = defineComponent({
props: { modelValue: Boolean, title: String, loading: Boolean },
emits: ['update:modelValue'],
template: '<aside><h2>{{ title }}</h2><slot /></aside>',
})
function mountDrawer() {
return mount(InboundCodeRelationshipDrawer, {
function mountDialog() {
return mount(InboundCodeRelationshipDialog, {
props: {
modelValue: true,
inboundCode: 'NYEXX2608300027',
@@ -35,9 +35,9 @@ function mountDrawer() {
},
global: {
stubs: {
AppDrawer: AppDrawerStub,
AppDialog: AppDialogStub,
ApiErrorMeta: true,
LoadErrorState: true,
LoadErrorState: { props: ['title', 'message'], template: '<div>{{ title }} · {{ message }}</div>' },
StatusBadge: true,
'el-alert': true,
'el-empty': { props: ['description'], template: '<p>{{ description }}</p>' },
@@ -48,7 +48,7 @@ function mountDrawer() {
})
}
describe('InboundCodeRelationshipDrawer contract', () => {
describe('InboundCodeRelationshipDialog contract', () => {
beforeEach(() => {
vi.clearAllMocks()
vi.mocked(getPlatformInboundCodeAssociation).mockResolvedValue({
@@ -73,7 +73,7 @@ describe('InboundCodeRelationshipDrawer contract', () => {
})
it('loads all three sections on an initially open mount and reloads after closing and reopening', async () => {
const wrapper = mountDrawer()
const wrapper = mountDialog()
await flushPromises()
expect(getPlatformInboundCodeAssociation).toHaveBeenCalledWith('NYEXX2608300027')
@@ -106,20 +106,30 @@ describe('InboundCodeRelationshipDrawer contract', () => {
inboundCode: 'NYEXX2608300027', found: false, customer: null, overseasWarehouse: null,
overseasReceivingStatus: null, taillegOrders: [], inboundPlans: [], palletCodes: [], inventory: [],
})
const wrapper = mountDrawer()
const wrapper = mountDialog()
await flushPromises()
expect(wrapper.text()).toContain('服务端未找到该入仓码的海外关联数据')
expect(wrapper.text()).toContain('FL2093975748931063810')
expect(wrapper.text()).toContain('LP-260830-0016')
})
it('keeps a failed section local while the other successful sections stay visible', async () => {
vi.mocked(getOverseasInboundCodeRelationship).mockRejectedValueOnce(new Error('overseas unavailable'))
const wrapper = mountDialog()
await flushPromises()
expect(wrapper.text()).toContain('海外关联加载失败')
expect(wrapper.text()).toContain('FL2093975748931063810')
expect(wrapper.text()).toContain('LP-260830-0016')
expect(wrapper.text()).not.toContain('洛杉矶海外仓')
})
it('distinguishes an unassigned overseas warehouse from a missing warehouse name', async () => {
vi.mocked(getOverseasInboundCodeRelationship).mockResolvedValueOnce({
inboundCode: 'NYEXX2608300027', found: true,
customer: { customerId: '90071992547409932', customerName: '纽约客户' }, overseasWarehouse: null,
overseasReceivingStatus: 'PENDING_ASSIGNMENT', taillegOrders: [], inboundPlans: [], palletCodes: [], inventory: [],
})
const unassigned = mountDrawer()
const unassigned = mountDialog()
await flushPromises()
expect(unassigned.text()).toContain('尚未分配海外仓')
expect(unassigned.text()).not.toContain('仓库名称待补充')
@@ -131,7 +141,7 @@ describe('InboundCodeRelationshipDrawer contract', () => {
overseasWarehouse: { warehouseId: '90071992547409939', warehouseCode: 'LAX', warehouseName: '' },
overseasReceivingStatus: 'PENDING_ASSIGNMENT', taillegOrders: [], inboundPlans: [], palletCodes: [], inventory: [],
})
const missingName = mountDrawer()
const missingName = mountDialog()
await flushPromises()
expect(missingName.text()).toContain('仓库名称待补充')
expect(missingName.text()).not.toContain('尚未分配海外仓')
@@ -165,10 +175,11 @@ describe('InboundCodeRelationshipDrawer contract', () => {
expect(source).toContain(':title="display(baseAssociation.inboundCode)"')
})
it('keeps sections and status badges within the drawer at desktop and narrow widths', () => {
expect(source).toContain('class="inbound-relationship-drawer"')
expect(source).toContain(':global(.inbound-relationship-drawer .el-drawer__body){box-sizing:border-box;min-width:0;max-width:100%;overflow-x:hidden;padding-right:calc(var(--el-drawer-padding-primary) + 8px)}')
expect(source).toContain(':global(.inbound-relationship-drawer .app-drawer__body){box-sizing:border-box;width:auto;max-width:100%;min-width:0;overflow-x:hidden}')
it('keeps sections and status badges within the dialog at desktop and narrow widths', () => {
expect(source).toContain('class="inbound-relationship-dialog"')
expect(source).toContain('width="min(1060px, 94vw)"')
expect(source).toContain(':global(.inbound-relationship-dialog .el-dialog__body){box-sizing:border-box;min-width:0;max-height:calc(90vh - 112px);overflow-x:hidden;overflow-y:auto;padding-right:28px}')
expect(source).toContain(':global(.inbound-relationship-dialog .app-dialog__body){box-sizing:border-box;width:auto;max-width:100%;min-width:0;overflow-x:hidden}')
expect(source).toContain('.relationship-sections{display:grid;width:auto;max-width:100%;min-width:0;grid-template-columns:minmax(0,1fr)')
expect(source).toContain('.relationship-section{box-sizing:border-box;width:100%;min-width:0')
expect(source).toContain('flex-wrap:wrap')
@@ -177,8 +188,8 @@ describe('InboundCodeRelationshipDrawer contract', () => {
expect(source).toContain('.inventory-table{width:100%;max-width:100%;min-width:0')
})
it('mounts the containment class through the real AppDrawer and Element Plus drawer DOM', async () => {
const wrapper = mount(InboundCodeRelationshipDrawer, {
it('mounts the containment class through the real AppDialog and Element Plus dialog DOM', async () => {
const wrapper = mount(InboundCodeRelationshipDialog, {
attachTo: document.body,
props: {
modelValue: true,
@@ -191,15 +202,36 @@ describe('InboundCodeRelationshipDrawer contract', () => {
})
await flushPromises()
const drawer = document.querySelector('.inbound-relationship-drawer')
const elementBody = drawer?.querySelector('.el-drawer__body')
const appBody = drawer?.querySelector('.app-drawer__body')
expect(drawer).not.toBeNull()
const dialog = document.querySelector('.inbound-relationship-dialog')
const elementBody = dialog?.querySelector('.el-dialog__body')
const appBody = dialog?.querySelector('.app-dialog__body')
expect(dialog).not.toBeNull()
expect(elementBody).not.toBeNull()
expect(appBody).not.toBeNull()
expect(appBody?.querySelectorAll('.relationship-section')).toHaveLength(3)
expect(appBody?.querySelectorAll('.relationship-status')).toHaveLength(3)
expect(dialog?.querySelector('.el-drawer__body')).toBeNull()
wrapper.unmount()
})
it('removes the retired Drawer component and all Admin consumer references', () => {
const root = process.cwd()
expect(existsSync(resolve(root, 'src/shared/components/InboundCodeRelationshipDrawer.vue'))).toBe(false)
expect(existsSync(resolve(root, 'src/shared/components/InboundCodeRelationshipDrawer.test.ts'))).toBe(false)
const consumerFiles = [
'src/modules/unified-orders/pages/UnifiedOrdersView.vue',
'src/modules/inbounds/pages/InboundOrdersView.vue',
'src/modules/inbounds/components/InboundTaskList.vue',
'src/modules/overseas-warehouse/pages/InventoryTransactionsView.vue',
]
const consumerSources = consumerFiles.map((file) => readFileSync(resolve(root, file), 'utf8'))
const consumers = consumerSources.join('\n')
expect(consumers).not.toContain('InboundCodeRelationshipDrawer')
expect(consumers).not.toMatch(/relationshipOpen|relationshipInboundCode|inboundRelationshipOpen/)
consumerSources.forEach((consumer) => {
expect(consumer).toContain("import InboundCodeRelationshipDialog from '@/shared/components/InboundCodeRelationshipDialog.vue'")
expect(consumer).toContain('<InboundCodeRelationshipDialog')
})
})
})
@@ -3,7 +3,7 @@ import { computed, ref, watch } from 'vue'
import { getApiErrorDetails, type ApiErrorDetails } from '@/shared/api/http'
import { getLoadingAssociationSummary, getOverseasInboundCodeRelationship, getPlatformInboundCodeAssociation, type LoadingAssociationSummary, type OverseasInboundCodeRelationship, type PlatformInboundCodeAssociation } from '@/shared/api/inboundCodeRelations'
import ApiErrorMeta from './ApiErrorMeta.vue'
import AppDrawer from './AppDrawer.vue'
import AppDialog from './AppDialog.vue'
import LoadErrorState from './LoadErrorState.vue'
import StatusBadge from './StatusBadge.vue'
@@ -50,7 +50,7 @@ watch(() => [props.modelValue, props.inboundCode, props.canReadBase, props.canRe
</script>
<template>
<AppDrawer class="inbound-relationship-drawer" :model-value="modelValue" :title="`入仓码关联数据 · ${normalizedCode || '未提供'}`" size="min(860px, 94vw)" :loading="loading" :show-footer="false" destroy-on-close @update:model-value="close">
<AppDialog class="inbound-relationship-dialog" :model-value="modelValue" :title="`入仓码关联数据 · ${normalizedCode || '未提供'}`" width="min(1060px, 94vw)" :loading="loading" :show-footer="false" destroy-on-close @update:model-value="close">
<el-alert v-if="!normalizedCode" type="warning" :closable="false" show-icon title="缺少入仓码,已停止查询关联数据。" />
<el-alert v-else-if="!canReadBase && !canReadLoading && !canReadOverseas" type="warning" :closable="false" show-icon title="当前账号无入仓关联数据查看权限。" />
@@ -90,9 +90,9 @@ watch(() => [props.modelValue, props.inboundCode, props.canReadBase, props.canRe
</template>
</section>
</div>
</AppDrawer>
</AppDialog>
</template>
<style scoped>
:global(.inbound-relationship-drawer .el-drawer__body){box-sizing:border-box;min-width:0;max-width:100%;overflow-x:hidden;padding-right:calc(var(--el-drawer-padding-primary) + 8px)}:global(.inbound-relationship-drawer .app-drawer__body){box-sizing:border-box;width:auto;max-width:100%;min-width:0;overflow-x:hidden}.relationship-sections{display:grid;width:auto;max-width:100%;min-width:0;grid-template-columns:minmax(0,1fr);gap:14px}.relationship-section{box-sizing:border-box;width:100%;min-width:0;border:1px solid var(--cc-border);border-radius:10px;padding:14px;background:var(--cc-surface)}.relationship-section>header{display:flex;min-width:0;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:8px 12px;margin-bottom:12px}.relationship-section h3{flex:1 1 auto;min-width:0;margin:0;font-size:15px}.relationship-status{flex:0 0 auto;max-width:100%}.compact-facts{display:grid;min-width:0;grid-template-columns:repeat(2,minmax(0,1fr));gap:1px;margin:0;background:var(--cc-border)}.compact-facts>div{display:grid;min-width:0;grid-template-columns:92px minmax(0,1fr);gap:8px;padding:9px 10px;background:var(--cc-surface)}.compact-facts dt{color:var(--cc-muted)}.compact-facts dd{min-width:0;margin:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.compact-facts small{display:block;overflow:hidden;color:var(--cc-muted);margin-top:2px;text-overflow:ellipsis;white-space:nowrap}.compact-list{display:grid;min-width:0;grid-template-columns:92px minmax(0,1fr);gap:8px;padding:9px 10px;border-top:1px solid var(--cc-border)}.compact-list span{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.inventory-table{width:100%;max-width:100%;min-width:0;margin-top:10px}@media(max-width:720px){.compact-facts{grid-template-columns:1fr}}
:global(.inbound-relationship-dialog.el-dialog){width:min(1060px,94vw);max-width:94vw}:global(.inbound-relationship-dialog .el-dialog__body){box-sizing:border-box;min-width:0;max-height:calc(90vh - 112px);overflow-x:hidden;overflow-y:auto;padding-right:28px}:global(.inbound-relationship-dialog .app-dialog__body){box-sizing:border-box;width:auto;max-width:100%;min-width:0;overflow-x:hidden}.relationship-sections{display:grid;width:auto;max-width:100%;min-width:0;grid-template-columns:minmax(0,1fr);gap:12px}.relationship-section{box-sizing:border-box;width:100%;min-width:0;border:1px solid var(--cc-border);border-radius:8px;padding:12px;background:var(--cc-surface)}.relationship-section>header{display:flex;min-width:0;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:8px 12px;margin-bottom:10px}.relationship-section h3{flex:1 1 auto;min-width:0;margin:0;font-size:15px}.relationship-status{flex:0 0 auto;max-width:100%}.compact-facts{display:grid;min-width:0;grid-template-columns:repeat(2,minmax(0,1fr));gap:1px;margin:0;background:var(--cc-border)}.compact-facts>div{display:grid;min-width:0;grid-template-columns:92px minmax(0,1fr);gap:8px;padding:8px 10px;background:var(--cc-surface)}.compact-facts dt{color:var(--cc-muted)}.compact-facts dd{min-width:0;margin:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.compact-facts small{display:block;overflow:hidden;color:var(--cc-muted);margin-top:2px;text-overflow:ellipsis;white-space:nowrap}.compact-list{display:grid;min-width:0;grid-template-columns:92px minmax(0,1fr);gap:8px;padding:8px 10px;border-top:1px solid var(--cc-border)}.compact-list span{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.inventory-table{width:100%;max-width:100%;min-width:0;margin-top:8px}@media(max-width:720px){:global(.inbound-relationship-dialog .el-dialog__body){padding-right:20px}.compact-facts{grid-template-columns:1fr}}
</style>