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
+651 -4
View File
@@ -13,7 +13,10 @@ const cargoConsumers = [
'trucking/pages/TruckAppointmentDetailView.vue', 'trucking/components/TruckAppointmentForm.vue',
'trucking/components/TruckAttachmentDialog.vue', 'container-loading/pages/ContainersView.vue',
'container-loading/pages/ContainerDetailView.vue', 'container-loading/pages/SmartLoadingWorkbenchView.vue',
'container-loading/components/SmartLoadingDialog.vue', 'container-loading/pages/GlobalSmartLoadingView.vue',
'loading-lists/pages/PreloadPlanDetailView.vue',
'container-loading/components/OceanBookingLinkDialog.vue', 'container-loading/components/ContainerOverviewPanel.vue',
'port-schedules/components/ContainerPortSchedulePanel.vue',
'container-loading/components/ContainerCustomsPanel.vue', 'container-loading/components/ContainerLastLegFeesPanel.vue',
'loading-lists/components/ContainerFeesPanel.vue', 'port-schedules/components/CarrierScheduleQueryPanel.vue',
]
@@ -32,6 +35,8 @@ const overseasConsumers = [
'overseas-warehouse/pages/RelabelOrderDetailView.vue',
'overseas-warehouse/pages/PalletsView.vue',
'overseas-warehouse/pages/PalletDetailView.vue',
'overseas-warehouse/pages/LocationsView.vue',
'overseas-warehouse/pages/CargoDiscardsView.vue',
'overseas-warehouse/pages/InventoryView.vue',
'overseas-warehouse/pages/InventoryDetailView.vue',
'overseas-warehouse/pages/InventoryTransactionsView.vue',
@@ -172,6 +177,139 @@ const cssRoot = (css: string) => {
}
describe('approved Admin density ownership', () => {
it('shares the measurement palette through explicit dialog sections without changing other dialog bodies', () => {
const tokenValues: Record<string, string> = {}
cssRoot(tokens).walkDecls(decl => { tokenValues[decl.prop] = decl.value })
const rules = new Map<string, Record<string, string>>()
cssRoot(shared).walkRules(rule => {
const values: Record<string, string> = {}
rule.walkDecls(decl => { values[decl.prop] = decl.value })
rules.set(rule.selector, values)
})
for (const [tone, color] of [['summary', '#f4fbf8'], ['source', '#f7f9ff'], ['work', '#fffbf4']]) {
expect(tokenValues[`--admin-dialog-${tone}-bg`]).toBe(color)
expect(rules.get(`:is(.admin-density-dialog, .admin-density-drawer:where(.admin-density-drawer--detail, .admin-density-drawer--editor)) .density-dialog-section[data-tone="${tone}"]`)).toMatchObject({
background: `var(--admin-dialog-${tone}-bg)`,
'border-color': `var(--admin-dialog-${tone}-border)`,
})
}
expect(rules.get(':is(.admin-density-dialog, .admin-density-drawer:where(.admin-density-drawer--detail, .admin-density-drawer--editor)) .density-dialog-section')).not.toHaveProperty('height')
expect(rules.get(':is(.admin-density-dialog, .admin-density-drawer:where(.admin-density-drawer--detail, .admin-density-drawer--editor)) .density-dialog-note')).toMatchObject({ color: 'var(--cc-muted)', 'font-size': 'var(--admin-font-caption)' })
expect(rules.get(':is(.admin-density-dialog, .admin-density-drawer:where(.admin-density-drawer--editor)) .density-actions--footer')).toMatchObject({ 'justify-content': 'flex-end', 'flex-wrap': 'wrap', gap: 'var(--admin-space-3)' })
for (const [selector, declarations] of rules) {
if (selector.includes('density-dialog-section')) expect(Object.keys(declarations).filter(name => name.startsWith('border-left')), selector).toEqual([])
}
const editorRule = rules.get(':is(.admin-density, .admin-density-dialog) .density-table--editable :is(.el-input, .el-textarea)')
expect(editorRule).toMatchObject({ '--el-input-border-color': 'transparent', '--el-input-hover-border-color': 'var(--admin-table-border)' })
expect(editorRule).not.toHaveProperty('--el-input-focus-border-color')
expect(rules.get(':is(.admin-density, .admin-density-dialog) .density-table--editable .el-form-item')).toMatchObject({ 'margin-bottom': '0' })
expect(rules.get(':is(.admin-density, .admin-density-dialog) .density-table--editable .el-form-item.is-error')).toMatchObject({ 'margin-bottom': 'var(--admin-space-5)' })
for (const selector of rules.keys()) if (selector.includes('density-dialog-section')) expect(selector.startsWith(':is(.admin-density-dialog, .admin-density-drawer:where(.admin-density-drawer--detail, .admin-density-drawer--editor)) ')).toBe(true)
const measured = read('modules/inbounds/measurement/measurementDensity.css')
expect(measured).not.toMatch(/border-left(?:-color|-width)?\s*:/)
for (const color of ['#f4fbf8', '#f7f9ff', '#fffbf4']) expect(measured).not.toContain(color)
for (const file of ['unified-orders/pages/UnifiedOrdersView.vue', 'unified-orders/components/OrderDeclarationEditorDialog.vue', 'unified-orders/components/InboundFeeDetailManager.vue', 'inbounds/measurement/InboundMeasurementPanel.vue', 'inbounds/weight/components/WeightOcrPanel.vue', 'domestic-containers/pages/DomesticContainersView.vue', 'sailing-calendar/pages/SailingCalendarView.vue', 'ocean-bookings/components/OceanBookingPrintDialog.vue', 'ocean-bookings/pages/OceanBookingDetailView.vue', 'trucking/components/TruckAttachmentDialog.vue', 'container-loading/pages/ContainersView.vue', 'container-loading/pages/ContainerDetailView.vue', 'container-loading/components/OceanBookingLinkDialog.vue']) {
const source = read('modules/' + file)
expect(parse(source).errors, file).toEqual([])
const ast = parse(source).descriptor.template!.ast!
const visit = (nodes: typeof ast.children, inDialog = false) => { for (const node of nodes) {
if (node.type !== 1) continue
const inside = inDialog || ['AppDialog', 'el-dialog'].includes(node.tag)
if (classesOf(node).some(name => ['density-dialog-section', 'density-table--editable'].includes(name))) expect(inside, file + ': sections and cell editors belong to an explicit dialog').toBe(true)
visit(node.children, inside)
} }
visit(ast.children)
}
})
it('opts the audit detail drawer into the same palette without styling unselected drawers or clipping snapshots', () => {
const rules = new Map<string, Record<string, string>>()
cssRoot(shared).walkRules(rule => {
const declarations: Record<string, string> = {}
rule.walkDecls(decl => { declarations[decl.prop] = decl.value })
rules.set(rule.selector, declarations)
})
const snapshot = rules.get(':is(.admin-density-dialog, .admin-density-drawer:where(.admin-density-drawer--detail, .admin-density-drawer--editor)) .density-code-block')!
expect(snapshot).toMatchObject({ 'white-space': 'pre-wrap', 'overflow-wrap': 'anywhere' })
for (const key of ['height', 'min-height', 'max-height', 'overflow', 'overflow-y']) expect(snapshot).not.toHaveProperty(key)
for (const selector of rules.keys()) {
if (selector.includes('admin-density-drawer') && (selector.includes('density-dialog-section') || selector.includes('density-table'))) expect(selector).toContain('.admin-density-drawer:where(.admin-density-drawer--detail, .admin-density-drawer--editor)')
if (selector.includes('.el-drawer__header') || selector.includes('.el-drawer__body')) expect(selector).not.toContain('admin-density-drawer--detail')
}
const audit = parse(read('modules/audit/components/CustomerAuditLogsPanel.vue')).descriptor
const ast = audit.template!.ast!
const visit = (nodes: typeof ast.children, inside = false) => {
for (const node of nodes) {
if (node.type !== 1) continue
const inDrawer = inside || (node.tag === 'AppDrawer' && classesOf(node).includes('admin-density-drawer--detail') && classesOf(node).includes('admin-density-drawer'))
if (classesOf(node).some(value => ['density-dialog-section', 'density-table', 'density-code-block'].includes(value))) expect(inDrawer).toBe(true)
visit(node.children, inDrawer)
}
}
visit(audit.template!.ast!.children)
for (const style of audit.styles) expect(style.content).not.toMatch(/snapshot[^{}]*\{[^}]*max-height|border-left:/)
})
it('keeps review presentation in the shared dialog and resource editors on the shared form scale', () => {
const review = parse(read('modules/inbounds/review/MeasurementReviewPanel.vue')).descriptor
for (const style of review.styles) {
expect(style.content).not.toContain('linear-gradient')
expect(style.content).not.toMatch(/box-shadow:\s*inset/)
}
const host = parse(read('modules/inbounds/components/InboundTaskList.vue')).descriptor.template!.ast!
let reviewHostFound = false
const visit = (nodes: typeof host.children) => { for (const node of nodes) {
if (node.type !== 1) continue
if (classesOf(node).includes('inbound-task-dialog')) {
expect(node.tag).toBe('AppDialog')
expect(classesOf(node)).toContain('admin-density-dialog')
expect(node.props.some(prop => prop.type === 6 && prop.name === 'fullscreenable')).toBe(true)
expect(node.children.some(child => child.type === 1 && child.props.some(prop => prop.type === 7 && prop.name === 'slot' && prop.arg?.type === 4 && prop.arg.content === 'footer'))).toBe(true)
reviewHostFound = true
}
visit(node.children)
} }
visit(host.children)
expect(reviewHostFound).toBe(true)
for (const file of ['locations/pages/LocationsView.vue', 'pallets/pages/PalletsView.vue']) {
const source = read('modules/warehouse-resources/' + file)
expect(source).toContain('class="editor-form density-form density-form--flush-end"')
for (const style of parse(source).descriptor.styles) {
expect(style.content).not.toContain('.editor-form :deep(.el-form-item__label)')
expect(style.content).not.toContain('.editor-form :deep(.el-form-item)')
}
}
expect(shared).toContain('.admin-density-dialog .density-form--flush-end > .el-form-item:last-child')
expect(shared).not.toContain('.admin-density-dialog .density-form > .el-form-item:last-child')
})
it('keeps batch approval overflow tooltips single-line without forcing its headers or error tags to truncate', () => {
const view = parse(read('modules/unified-orders/pages/UnifiedOrdersView.vue')).descriptor.template!.ast!
const tooltipColumns: string[] = []
const visit = (nodes: typeof view.children, inBatch = false) => { for (const node of nodes) {
if (node.type !== 1) continue
const inside = inBatch || classesOf(node).includes('batch-approval-table')
if (inside && node.tag === 'el-table-column') {
const label = node.props.find(prop => prop.type === 6 && prop.name === 'label')
if (label?.type === 6 && ['客户', '中文品名'].includes(label.value?.content || '')) {
expect(node.props.some(prop => prop.type === 6 && prop.name === 'show-overflow-tooltip')).toBe(true)
tooltipColumns.push(label.value!.content)
}
}
visit(node.children, inside)
} }
visit(view.children)
expect(tooltipColumns).toEqual(['客户', '中文品名'])
const rules = new Map<string, Record<string, string>>()
cssRoot(read('modules/unified-orders/styles/orderDensity.css')).walkRules(rule => {
const declarations: Record<string, string> = {}
rule.walkDecls(decl => { declarations[decl.prop] = decl.value })
rules.set(rule.selector, declarations)
})
expect(rules.has('.admin-density-dialog.el-dialog .batch-approval-table .cell')).toBe(false)
expect(rules.get('.admin-density-dialog.el-dialog .batch-approval-table td .cell.el-tooltip')).toMatchObject({
'white-space': 'nowrap', 'text-overflow': 'ellipsis', overflow: 'hidden', 'overflow-wrap': 'normal',
})
expect(rules.get('.admin-density-dialog.el-dialog .batch-approval-table .el-tag')?.['white-space']).toBe('normal')
})
it('opts transfer approval into the shared centered confirmation appearance', () => {
const dialog = read('modules/overseas-warehouse/components/WarehouseTransferReviewDialog.vue')
const css = read('styles/adminDensity.css')
@@ -203,6 +341,26 @@ describe('approved Admin density ownership', () => {
expect(panel.template!.content).toContain('v-if="canCreate"')
expect(panel.template!.content).toContain('新增预约')
})
it('keeps calendar creation, its two consumers and arrival editing on shared dialog sections', () => {
const view = parse(read('modules/appointment-calendar/AppointmentCalendarView.vue')).descriptor
const create = parse(read('modules/appointment-calendar/AppointmentCreatePanel.vue')).descriptor
const work = parse(read('modules/appointment-calendar/WorkCreateAppointmentForm.vue')).descriptor
expect(view.scriptSetup!.content).toContain("import '@/styles/adminDensity.css'")
const dialogs = view.template!.content.match(/<AppDialog\b[^>]+>/g)!
expect(dialogs).toHaveLength(3)
for (const dialog of dialogs) { expect(dialog).toContain('admin-density-dialog'); expect(dialog).toContain('fullscreenable') }
expect(view.template!.content).toContain('density-table density-table--editable')
expect(view.template!.content).toContain('label-position="left" label-width="110px"')
expect(view.styles.map(style => style.content).join('\n')).not.toContain('arrival-field')
expect(create.template!.content).toContain('aria-label="预约保存回执"')
expect(create.template!.content).not.toContain('<FbaAppointmentFields')
expect(create.template!.content).toContain('<LineClampText :content="row.customerName"')
expect(create.template!.content).toContain('data-tone="source"')
expect(work.template!.content).toContain('<FbaAppointmentFields continuous')
expect(work.template!.content).toContain('density-actions density-actions--footer')
for (const source of [create.template!.content, read('modules/appointment-calendar/WorkDetailPanel.vue')]) expect(source).toContain('<WorkCreateAppointmentForm')
expect(create.styles).toHaveLength(0); expect(work.styles).toHaveLength(0)
})
it('shares list status and category tab styles across registered list consumers', () => {
for (const file of [
'unified-orders/pages/UnifiedOrdersView.vue', 'overseas-warehouse/pages/LastMileOrdersView.vue',
@@ -310,11 +468,156 @@ describe('approved Admin density ownership', () => {
}
})
it('keeps warehouse editors and import results on shared sections and cell editors', () => {
const source = read('modules/warehouses/pages/WarehousesView.vue')
expect(source).toContain('class="density-form density-dialog-sections"')
expect(source).toContain('density-table density-table--editable warehouse-editor-table')
expect(source).toContain('class="warehouse-import-dialog admin-density-dialog"')
expect(source).toContain('class="density-table warehouse-import-summary"')
expect(source).toContain('class="density-actions density-actions--footer"')
expect(source).not.toContain('<FormSection')
expect(source).not.toContain('<el-dialog')
expect(source).not.toContain(':global(.warehouse-editor-dialog .el-dialog__body)')
})
it('keeps role and permission editors on shared sections with one body scroll and fixed codes', () => {
const role = read('modules/iam/pages/RolesView.vue')
const permission = read('modules/iam/pages/PermissionsView.vue')
for (const source of [role, permission]) {
expect(source).toContain('fullscreenable :confirm-loading="saving"')
expect(source).toContain('density-table--editable')
expect(source).toContain('data-tone="work"')
expect(source).not.toContain('<FormSection')
expect(source).not.toContain('label-position="top"')
const css = parse(source).descriptor.styles.map(style => style.content).join('')
expect(css).not.toContain('el-dialog__body')
expect(css).not.toContain('border-left')
}
expect(role).toContain('data-tone="source"')
expect(role).not.toContain('max-height: 380px')
expect(role).toContain('align-items: start')
expect(permission).not.toContain('class="form-callout"')
})
it('keeps internal user forms on shared cells and native fullscreen with original geometry', () => {
const source = read('modules/iam/pages/InternalUsersView.vue')
expect(source).toContain('class="density-form density-dialog-sections"')
expect(source).toContain('class="density-table density-table--editable user-editor-table"')
expect(source).toContain('data-tone="work"')
expect(source).toContain('fullscreenable destroy-on-close')
expect(source).toContain('width="min(650px, calc(100vw - 28px))"')
expect(source).not.toContain('<FormSection')
expect(source).not.toContain('label-position="top"')
const css = parse(source).descriptor.styles.map(style => style.content).join('')
expect(css).not.toMatch(/font-size|background|border|el-dialog__body|el-dialog__footer/)
expect(css).toContain(':not(.is-fullscreen)')
})
it('opts fee item editing into drawer sections and existing cell editors without changing other drawers', () => {
const source = read('modules/fee-items/pages/FeeItemsView.vue')
const ast = parse(source).descriptor.template!.ast!
let editorFound = false
const visit = (nodes: typeof ast.children, inEditor = false) => { for (const node of nodes) {
if (node.type !== 1) continue
const inside = inEditor || node.tag === 'AppDrawer' && classesOf(node).includes('admin-density-drawer--editor')
if (node.tag === 'AppDrawer') {
editorFound = true
for (const name of ['admin-density', 'admin-density-drawer', 'admin-density-drawer--editor']) expect(classesOf(node)).toContain(name)
}
if (classesOf(node).some(name => ['density-dialog-section', 'density-table--editable'].includes(name))) expect(inside).toBe(true)
visit(node.children, inside)
} }
visit(ast.children)
expect(editorFound).toBe(true)
expect(source).not.toContain('<FormSection')
expect(source).not.toContain('label-position="top"')
expect(source).toContain('class="density-actions density-actions--footer"')
expect(source).toContain('size="min(620px, 94vw)"')
expect(shared).toContain(':where(.admin-density-drawer--detail, .admin-density-drawer--editor)')
const css = parse(source).descriptor.styles.map(style => style.content).join('')
expect(css).not.toContain('direction-grid')
expect(css).not.toContain('delete-message')
})
it('keeps download task detail on shared facts, error and footer primitives', () => {
const source = read('modules/downloads/pages/DownloadCenterView.vue')
const dialog = source.slice(source.indexOf('<AppDialog'), source.indexOf('</AppDialog>'))
expect(dialog).toContain('class="admin-density-dialog"')
expect(dialog).toContain('fullscreenable')
expect(dialog).toContain('data-tone="summary"')
expect(dialog).toContain('class="task-detail-grid density-fields"')
expect(dialog).toContain('<LoadErrorState v-if="detailError" compact')
expect(dialog).toContain('class="density-actions density-actions--footer"')
expect(dialog).not.toContain('requestedUsername || detailTask.requestedBy')
const css = parse(source).descriptor.styles.map(style => style.content).join('')
expect(css).not.toMatch(/\.task-(?:detail-summary|failure)\b|\.detail-load-error\b/)
})
it('keeps ship price book upload compact with shared table and confirmation styles', () => {
const list = read('modules/fba-pricing/carrier-rates/pages/CarrierRatesView.vue')
const detail = read('modules/fba-pricing/carrier-rates/pages/CarrierRateDetailView.vue')
const dialog = list.slice(list.indexOf('<AppDialog'), list.indexOf('</AppDialog>'))
expect(dialog).toContain('class="admin-density-dialog"')
expect(dialog).toContain('width="min(560px, calc(100vw - 28px))"')
expect(dialog).toContain(':confirm-loading="uploading"')
expect(dialog).toContain('class="density-table density-table--editable"')
expect(dialog).toContain('label-position="left"')
expect(dialog).toContain('class="density-dialog-note"')
expect(dialog).not.toContain('upload-scope-field')
expect(list).not.toContain('.upload-file-name strong')
for (const source of [list, detail]) expect(source).toContain("customClass: 'admin-density-confirm'")
})
it('uses shared frames, continuous tables and readonly facts for fleet quote dialogs', () => {
const editor = read('modules/overseas-master-data/components/FleetQuoteEditor.vue')
const importer = read('modules/overseas-master-data/components/FleetQuoteImport.vue')
for (const source of [editor, importer]) {
expect(source).toContain('class="admin-density-dialog" fullscreenable :confirm-loading="saving"')
expect(source).toContain('density-table--editable')
expect(source).toContain('data-tone="work"')
expect(source).toContain('data-tone="source"')
expect(source).toContain('class="density-actions density-actions--footer"')
expect(source).not.toContain('label-position="top"')
const css = parse(source).descriptor.styles.map(style => style.content).join('')
expect(css).not.toMatch(/font-size|background|border|el-dialog__body|max-height/)
}
expect(editor).toContain('v-if="readonly"')
expect(editor).toContain('class="density-fields fleet-quote-facts"')
expect(read('modules/overseas-master-data/pages/FleetQuotesView.vue')).toContain("customClass: 'admin-density-confirm'")
})
it('keeps pricing dialogs on shared tables and sections without local form paint', () => {
const rate = read('modules/overseas-master-data/pages/ExchangeRatesView.vue')
const quote = read('modules/overseas-master-data/pages/OverseasQuotesView.vue')
for (const source of [rate, quote]) {
const dialogs = parse(source).descriptor.template!.content.match(/<AppDialog[\s\S]*?<\/AppDialog>/g)!
expect(dialogs.length).toBeGreaterThan(0)
for (const dialog of dialogs) {
expect(dialog).toContain('class="admin-density-dialog"')
expect(dialog).not.toContain('label-position="top"')
expect(dialog).not.toContain('#header')
}
expect(source).toContain('density-table--editable')
expect(source).not.toContain('line-grid')
expect(source).not.toContain('form-grid')
}
expect(quote.match(/fullscreenable/g)).toHaveLength(2)
expect(quote).toContain('data-tone="summary"')
expect(quote).toContain('data-tone="source"')
expect(quote).toContain('data-tone="work"')
const css = parse(quote).descriptor.styles.map(style => style.content).join('')
expect(css).not.toMatch(/font-size|background|border|el-dialog__body|max-height/)
})
it('keeps the supplier license dialog styled at its own teleported root', () => {
const source = read('modules/suppliers/components/SupplierLicenseDialog.vue')
expect(source).toContain('<AppDialog class="admin-density-dialog"')
expect(source).toContain('width="min(560px, calc(100vw - 24px))"')
expect(source).toContain('aria-label="营业执照信息"')
expect(source).toContain('class="density-dialog-section" data-tone="source"')
expect(source).toContain('class="density-table license-info-table"')
expect(source).not.toContain('license-card')
expect(source).not.toContain('box-shadow')
})
it('uses identical authored tab paint for native domestic and Element overseas tabs', () => {
@@ -384,6 +687,7 @@ describe('approved Admin density ownership', () => {
}
expect(read('modules/overseas-warehouse/components/OverseasReadOnlyDetail.vue')).toContain(':dense="dense"')
expect(read('modules/overseas-warehouse/pages/InventoryTransactionsView.vue')).toContain('<InboundCodeRelationshipDialog dense')
expect(read('modules/inbounds/pages/InboundOrdersView.vue')).toContain('<InboundCodeRelationshipDialog dense')
for (const file of ['CartonDetailView', 'ProductDetailView']) expect(read('modules/overseas-warehouse/pages/' + file + '.vue')).not.toContain(' dense')
})
@@ -421,21 +725,22 @@ describe('approved Admin density ownership', () => {
it('keeps cargo native tables, row actions and detail tabs connected to shared styles', () => {
for (const file of cargoConsumers) {
const ast = parse(read(`modules/${file}`)).descriptor.template!.ast!
const visit = (nodes: typeof ast.children, inCell = false, insideDensity = false) => {
const visit = (nodes: typeof ast.children, inCell = false, insideDensity = false, insideEditor = false) => {
for (const node of nodes) {
if (node.type !== 1) continue
const classes = classesOf(node)
const enabled = insideDensity || classes.some(name => ['admin-density', 'admin-density-dialog'].includes(name))
const editable = insideEditor || classes.includes('density-table--editable')
if (node.tag === 'table') {
expect(classes, `${file}: native table`).toContain('density-table')
expect(insideDensity, `${file}: native table ancestor`).toBe(true)
}
if (node.tag === 'el-tabs') expect(classes).toContain('density-element-tabs')
if (inCell && node.tag === 'el-button') {
if (inCell && !editable && node.tag === 'el-button') {
expect(classes, `${file}: native row action`).toContain('density-action')
expect(node.children.some(child => child.type === 2 && child.content.trim() || child.type === 5), `${file}: empty action`).toBe(true)
}
visit(node.children, inCell || node.tag === 'td', enabled)
visit(node.children, inCell || node.tag === 'td', enabled, editable)
}
}
visit(ast.children)
@@ -474,6 +779,30 @@ describe('approved Admin density ownership', () => {
for (const file of densityPages) expect(read(`modules/${file}`), `${file}: direct entry CSS`).toContain('@/styles/adminDensity.css')
})
it('keeps customer dialogs on shared sections, editors and preview geometry', () => {
const customers = read('modules/customers/pages/CustomersView.vue')
const detail = read('modules/customers/pages/CustomerDetailView.vue')
expect(customers).toContain('density-table density-table--editable customer-editor-table')
expect(customers).toContain('density-actions density-actions--footer')
expect(customers).not.toContain('<FormSection')
expect(customers).not.toContain('<el-dialog')
for (const source of [customers, detail]) {
expect(source).toContain('customer-qualification-preview')
expect(source).toContain('data-tone="source"')
expect(source).not.toMatch(/\.qualification-preview \{[^}]*overflow:auto/)
}
})
it('shares dense editable sections between both customer fee entries and billing rules', () => {
for (const file of ['customers/CustomerOverseasStorageFeePanel.vue', 'customers/pages/OverseasStorageFeesView.vue', 'customers/components/CustomerBillingRulesPanel.vue']) {
const source = read(`modules/${file}`)
expect(source, file).toContain('density-table density-table--editable')
expect(source, file).toContain('class="density-dialog-section" data-tone="work"')
expect(source, file).toContain('fullscreenable')
expect(source, file).not.toContain('class="dialog-actions"')
}
})
it('covers both customer menu entries and their shared detail primitives', () => {
const navigation = read('core/navigation/adminNavigation.ts')
const customers = navigation.slice(navigation.indexOf("key: 'customers'"), navigation.indexOf("key: 'domestic-warehouse'"))
@@ -619,7 +948,7 @@ describe('approved Admin density ownership', () => {
sectionStyles.set(rule.selector, declarations)
})
for (const [section, background] of [
['summary-section', '#f4fbf8'], ['declaration', '#f7f9ff'], ['items-section', '#fffbf4'],
['summary-section', 'var(--admin-dialog-summary-bg)'], ['declaration', 'var(--admin-dialog-source-bg)'], ['items-section', 'var(--admin-dialog-work-bg)'],
]) {
expect(sectionStyles.get(`.inbound-measurement-workspace-dialog.inbound-density .measurement-${section}`)).toMatchObject({
padding: 'var(--admin-space-4, 10px) var(--admin-space-5, 12px)',
@@ -719,6 +1048,324 @@ describe('approved Admin density ownership', () => {
'var(--cc-row-action-gap, var(--admin-space-2))',
])
})
it('keeps active last-mile order dialogs on shared sections without internal vertical caps', () => {
const source = read('modules/overseas-warehouse/pages/LastMileOrdersView.vue')
for (const model of ['transferOpen', 'containerConvertOpen', 'inboundPlanOpen', 'holdOpen', 'manualNodesOpen', 'reworkOpen']) {
const frame = source.match(new RegExp('<AppDialog[^>]*v-model="' + model + '"[\\s\\S]*?(?=\\n <|\\n <|$)'))?.[0] || ''
expect(frame, model).toContain('fullscreenable')
}
expect(source).toContain('data-tone="summary"')
expect(source).toContain('data-tone="source"')
expect(source).toContain('data-tone="work"')
expect(source).toContain('class="frozen-pallet-count"')
expect(source).not.toContain('max-height: 48vh')
expect(source).not.toContain('.manual-node-table { max-height: 420px')
expect(source).not.toContain('class="container-preview__metric')
const detail = read('modules/overseas-warehouse/pages/LastMileOrderDetailView.vue')
expect(detail).toContain('density-table density-table--editable fee-editor-table')
expect(detail).toContain('label-position="left" label-width="110px" :disabled="saving"')
expect(detail).not.toContain('.fee-form-grid')
expect(read('modules/overseas-warehouse/components/WarehouseTransferReviewDialog.vue')).toContain('data-tone="source"')
})
it('keeps new dispatch settings and resources on shared continuous sections with body scrolling', () => {
const source = read('modules/overseas-warehouse/components/SmartDispatchDialog.vue')
const fields = read('modules/overseas-warehouse/components/DispatchResourceFields.vue')
expect(source).toContain('fullscreenable')
expect(source).toContain('density-table density-table--editable dispatch-create-table')
expect(source).toContain('<DispatchResourceFields continuous')
expect(source).toContain('label-position="left" label-width="130px"')
for (const tone of ['summary', 'source', 'work']) expect(source).toContain(`data-tone="${tone}"`)
expect(source).toContain('class="cargo-workspace"')
expect(source).toContain('<LineClampText')
expect(source).toContain('class="dispatch-source-group"')
expect(source).not.toContain('height="420"')
expect(source).not.toContain('class="create-grid"')
expect(fields).toContain('continuous?: boolean')
expect(fields).toContain('v-if="continuous"')
expect(fields).toContain('density-table density-table--editable dispatch-resource-table')
expect(fields).toContain('class="resource-grid"')
})
it('keeps dispatch workflow, delivery arrangements and owned evidence on shared compact sections', () => {
const workflow = read('modules/overseas-warehouse/components/DispatchWorkflowDialog.vue')
const arrangement = read('modules/overseas-warehouse/components/DispatchArrangeFields.vue')
const evidence = read('modules/overseas-warehouse/components/DispatchEvidenceFiles.vue')
expect(workflow).toContain(':fullscreenable="width !== \'560px\'"')
expect(workflow).toContain('<DispatchResourceFields continuous')
expect(workflow).toContain('density-table density-table--editable workflow-editor-table')
expect(workflow).toContain('label-position="left" label-width="130px"')
expect(workflow).toContain(':confirm-loading="busy || uploadingEvidence"')
expect(workflow).toContain('<LineClampText')
for (const tone of ['source', 'work']) {
expect(workflow).toContain(`data-tone="${tone}"`)
expect(arrangement).toContain(`data-tone="${tone}"`)
}
expect(workflow).not.toMatch(/max-height="(?:380|420)"/)
expect(arrangement).not.toContain('max-height="240"')
expect(arrangement).toContain('density-table density-table--editable arrange-editor-table')
expect(arrangement).toContain('本组新增费用')
expect(evidence).toContain('class="density-table dispatch-evidence-table"')
expect(evidence).toContain('fullscreenable :loading="previewLoading"')
expect(evidence).toContain('<LoadErrorState')
expect(evidence).toContain('<LineClampText')
for (const source of [workflow, arrangement, evidence]) {
const styles = parse(source).descriptor.styles.map(style => style.content).join('\n')
expect(styles).not.toMatch(/background:|border-left:|font-size:/)
}
})
it('keeps outbound creation modes compact with one body scroll and complete candidate controls', () => {
const source = read('modules/overseas-warehouse/pages/OutboundsView.vue')
expect(source).toContain('width="min(1760px, calc(100vw - 32px))" fullscreenable')
expect(source).toContain('label-position="left" label-width="120px" :disabled="createLocked"')
expect(source).toContain('density-table density-table--editable outbound-editor-table')
expect(source).toContain('data-tone="source"')
expect(source).toContain('data-tone="work"')
expect(source).toContain(':confirm-loading="saving || inventoryLoading"')
expect(source).toContain('aria-label="全选本页订单"')
expect(source).toContain('class="return-list-card density-dialog-section"')
expect(source).toContain(':page-sizes="[5, 10, 20, 50]"')
expect(source).toContain('<ApiErrorMeta :details="candidateError"')
expect(source).not.toMatch(/max-height="(?:360|380|400)"/)
const styles = parse(source).descriptor.styles.map(style => style.content).join('\n')
expect(styles).not.toMatch(/background:|border-left:|font-size:|max-height:|overflow: auto/)
})
it('keeps delivery operations and documents on compact sections without read-only inputs', () => {
const source = read('modules/overseas-warehouse/pages/DeliveryOrdersView.vue')
const documents = read('modules/overseas-warehouse/components/DeliveryDocumentsDialog.vue')
expect(source).toContain('density-table density-table--editable delivery-return-sources')
expect(source).toContain('label-position="left" label-width="100px"')
expect(source).toContain('label-position="left" label-width="110px"')
expect(source).toContain(':confirm-loading="returnSaving || returnLoading"')
expect(source).toContain('width="min(720px, calc(100vw - 28px))" fullscreenable')
expect(documents).toContain('width="min(1040px, calc(100vw - 40px))" fullscreenable')
expect(documents).toContain('data-tone="summary"')
expect(documents).toContain('data-tone="source"')
expect(documents).toContain('data-tone="work"')
expect(documents).toContain('<LineClampText :content="file.fileName"')
expect(documents).not.toContain(':model-value="deliveryNo" readonly')
expect(documents).not.toContain('max-height=')
const styles = parse(documents).descriptor.styles.map(style => style.content).join('\n')
expect(styles).not.toMatch(/background:|border-left:|font-size:|overflow: auto/)
})
it('shares relationship sections and fullscreen only through the explicit dense profile', () => {
const content = read('shared/components/InboundCodeRelationshipDialog.vue')
expect(content).toContain(':fullscreenable="dense"')
expect(content).toContain("{ 'density-dialog-sections': dense }")
expect(content.match(/'density-dialog-section': dense/g)).toHaveLength(3)
expect(content.match(/'density-dialog-section__title': dense/g)).toHaveLength(3)
expect(content).toContain(':data-tone="dense ? \'source\' : undefined"')
expect(content).toContain(':data-tone="dense ? \'summary\' : undefined"')
expect(content).not.toContain('.inbound-relationship-dialog.admin-density-dialog .relationship-section')
const styles = parse(content).descriptor.styles.map(style => style.content).join('\n')
cssRoot(styles).walkRules(rule => {
rule.walkDecls(decl => {
if (!['padding', 'background', 'border-radius', 'font-size', 'max-height'].includes(decl.prop)) return
expect(rule.selector, 'legacy visual declarations must stay outside dense').toMatch(/:not\(\.admin-density-dialog\)|:not\(\.density-dialog-section\)|:not\(\.density-fields\)/)
})
})
})
it('keeps return packing and carton dialogs on shared sections and one body scroll', () => {
const content = read('modules/overseas-warehouse/pages/ReturnInventoryManagementView.vue')
const dialogs = content.slice(content.indexOf('<AppDialog'))
expect(dialogs.match(/fullscreenable/g)).toHaveLength(3)
expect(dialogs).toContain('1380px')
expect(dialogs).toContain('1400px')
expect(dialogs).toContain('1080px')
expect(dialogs).toContain('label-position="left" label-width="110px"')
expect(dialogs).toContain('density-table density-table--editable')
expect(dialogs).toContain('data-tone="source"')
expect(dialogs).toContain('data-tone="summary"')
expect(dialogs).toContain('data-tone="work"')
expect(dialogs).not.toContain('<el-descriptions')
expect(dialogs).not.toContain('label-position="top"')
const styles = parse(content).descriptor.styles.map(style => style.content).join('\n')
expect(styles).not.toMatch(/max-height:|resize:|overflow: auto|border-radius:|font-size:|background:/)
expect(content).not.toContain('.packing-form :deep(.el-input__inner)')
})
it('keeps discard readonly sections and confirmation compact while retaining photo geometry', () => {
const content = read('modules/overseas-warehouse/pages/CargoDiscardsView.vue')
const dialog = content.slice(content.indexOf('<AppDialog'))
expect(dialog).toContain('class="admin-density-dialog"')
expect(dialog).toContain('fullscreenable')
expect(dialog).toContain('920px')
expect(dialog).toContain('data-tone="summary"')
expect(dialog).toContain('data-tone="source"')
expect(dialog).toContain('data-tone="work"')
expect(dialog).toContain('preview-teleported')
expect(dialog).toContain('<LineClampText')
expect(dialog).not.toContain('<el-timeline')
expect(content).toContain("customClass: 'admin-density-confirm'")
expect(content).toContain('height: 150px')
const styles = parse(content).descriptor.styles.map(style => style.content).join('\n')
expect(styles).not.toMatch(/background:|border-left:|font-size:|border-radius:/)
})
it('keeps overseas location editing and native confirmations on the shared dialog contract', () => {
const content = read('modules/overseas-warehouse/pages/LocationsView.vue')
const dialog = content.slice(content.indexOf('<AppDialog'))
expect(dialog).toContain('class="admin-density-dialog"')
expect(dialog).toContain('fullscreenable')
expect(dialog).toContain('density-table density-table--editable')
expect(dialog).toContain('label-position="left" label-width="110px" :disabled="formLocked"')
expect(dialog).toContain('data-tone="work"')
expect(dialog).not.toContain('label-position="top"')
expect(content).not.toContain('editor-grid')
expect(content.match(/customClass: 'admin-density-confirm'/g)).toHaveLength(2)
expect(parse(content).descriptor.styles).toHaveLength(0)
})
it('keeps overseas pallet editing on one shared continuous table with native fullscreen', () => {
const content = read('modules/overseas-warehouse/pages/PalletsView.vue')
const dialog = content.slice(content.indexOf('<AppDialog'))
expect(dialog).toContain('fullscreenable')
expect(dialog).toContain('label-position="left" label-width="120px" :disabled="formLocked"')
expect(dialog).toContain('density-table density-table--editable pallet-editor-table')
expect(dialog).toContain('data-tone="work"')
expect(dialog).not.toContain('label-position="top"')
expect(dialog).not.toContain('<el-row')
expect(content).not.toContain('.el-form :deep(')
const styles = parse(content).descriptor.styles.map(style => style.content).join('\n')
const editorRules = new Map<string, Record<string, string>>()
cssRoot(styles).walkRules(rule => {
if (!rule.selector.includes('pallet-editor-table')) return
const values: Record<string, string> = {}
rule.walkDecls(decl => { values[decl.prop] = decl.value })
editorRules.set(rule.selector, values)
})
expect([...editorRules]).toEqual([
['.pallet-editor-table', { 'table-layout': 'fixed' }],
['.pallet-editor-table td:not([colspan])', { width: '50%' }],
])
})
it('keeps relabel creation, review and PDF previews compact without replacing document sizing', () => {
const list = read('modules/overseas-warehouse/pages/RelabelOrdersView.vue')
const detail = read('modules/overseas-warehouse/pages/RelabelOrderDetailView.vue')
expect(list).toContain('width="min(760px, calc(100vw - 28px))" fullscreenable')
expect(list).toContain('density-table density-table--editable relabel-editor-table')
expect(list).toContain('aria-label="换标审核原始资料"')
for (const tone of ['summary', 'source', 'work']) expect(list).toContain(`data-tone="${tone}"`)
expect(list).not.toContain('label-position="top"')
expect(list).not.toContain('packing-plan-dialog')
expect(detail).toContain('width="min(900px, calc(100vw - 28px))" fullscreenable')
expect(detail).toContain(':loading="previewLoading"')
expect(detail).toContain('height:min(72vh,760px)')
expect(detail).toContain('<th class="relabel-pdf-actions">操作(明细PDF)</th>')
expect(detail).not.toContain('item.destinationFbaWarehouseCode || item.destinationWarehouseId')
expect(parse(list).descriptor.styles.map(style => style.content).join('\n')).not.toMatch(/background:|border-left:|font-size:|max-height:/)
})
it('keeps inbound appointment, plan and move editors on opt-in continuous forms', () => {
const list = read('modules/overseas-warehouse/pages/InboundOrdersView.vue')
const detail = read('modules/overseas-warehouse/pages/InboundOrderDetailView.vue')
const fields = read('modules/overseas-warehouse/components/FbaAppointmentFields.vue')
expect(list).toContain('<FbaAppointmentFields continuous')
expect(list.match(/fullscreenable/g)).toHaveLength(2)
expect(list).toContain('data-tone="summary"')
expect(list).toContain('data-tone="source"')
expect(list).toContain('data-tone="work"')
expect(list).not.toContain('v-model="planEditForm.version"')
expect(fields).toContain('continuous?: boolean')
expect(fields).toContain('v-if="continuous"')
expect(fields).toContain('density-table density-table--editable appointment-editor-table')
expect(fields).toContain('label-position="left" label-width="140px"')
expect(fields).toContain('label-position="top" class="appointment-fields density-form"')
const move = detail.slice(detail.indexOf('<AppDialog'))
expect(move).toContain('fullscreenable')
expect(move).toContain('aria-label="移库来源"')
expect(move).toContain('label-position="left" label-width="100px" :disabled="moveSaving || moveLocationsLoading"')
expect(move).not.toContain(':model-value="moveRow?.palletNo"')
})
it('keeps transfer allocation and receipt popups on shared sections and the native submit footer', () => {
const allocation = read('modules/overseas-warehouse/components/WarehouseTransferFulfillmentPanel.vue')
const receipt = read('modules/overseas-warehouse/components/WarehouseTransferReceiptPanel.vue')
for (const source of [allocation, receipt]) {
expect(source).toContain("import '@/styles/adminDensity.css'")
expect(source).toContain('class="density-dialog-sections"')
expect(source).toContain('data-tone="summary"')
expect(source).toContain('data-tone="source"')
expect(source).not.toMatch(/max-height:|overflow-y:/)
}
expect(allocation.match(/fullscreenable/g)).toHaveLength(1)
expect(receipt.match(/fullscreenable/g)).toHaveLength(3)
expect(allocation).toContain('row-key="inventoryPositionId" max-height=""')
expect(receipt).toContain('row-key="cartonMark" max-height=""')
expect(receipt).toContain('row-key="sku" max-height=""')
const forecast = receipt.split('v-model="increaseOpen"')[1]!.split('</AppDialog>')[0]!
expect(forecast).toContain(':confirm-loading="increasing"')
expect(forecast).toContain('@confirm="saveIncreaseForecast"')
expect(forecast).toContain('label-position="left" label-width="100px" :disabled="increasing"')
expect(forecast).toContain('density-table density-table--editable')
expect(forecast).not.toContain(':show-footer="false"')
expect(forecast).not.toContain('<el-button')
})
it('keeps settlement generation and cashiers on continuous forms with native fullscreen', () => {
const source = read('modules/finance/pages/FinanceFormalSettlementPage.vue')
const popups = source.slice(source.indexOf('<AppDialog'))
expect(popups.match(/fullscreenable/g)).toHaveLength(3)
expect(popups.match(/density-table density-table--editable/g)).toHaveLength(3)
expect(popups).toContain('label-position="left" label-width="120px" :disabled="createBusy"')
for (const flag of ['paymentBusy', 'receiptBusy']) expect(popups).toContain(`label-position="left" label-width="140px" :disabled="${flag}"`)
expect(popups).toContain('data-tone="summary"')
expect(popups).toContain('data-tone="work"')
expect(popups).toContain(':key="paymentUploadKey"')
expect(popups).toContain(':key="receiptUploadKey"')
expect(popups).not.toContain('label-position="top"')
expect(source).not.toContain('.remaining-preview')
expect(source).not.toContain('.settlement-operation-form { display: grid')
})
it('keeps fee and statement popup editors on the shared density and main body scroll', () => {
const arap = read('modules/finance/pages/FinanceCustomerArapView.vue').split('<AppDialog')[1]!
expect(arap).toContain('fullscreenable')
expect(arap).toContain('data-tone="source"')
expect(arap).toContain('data-tone="work"')
expect(arap).toContain('density-table density-table--editable')
expect(arap).toContain('label-position="left" label-width="110px" :disabled="reviewSaving"')
const statements = read('modules/finance/pages/FinanceCustomerStatementsView.vue')
const popups = statements.slice(statements.indexOf('<AppDialog'))
expect(popups.match(/fullscreenable/g)).toHaveLength(2)
expect(popups).toContain('label-position="left" label-width="90px" :disabled="createBusy"')
expect(popups).toContain('label-width="110px" :disabled="adjustBusy"')
expect(popups).toContain('<OperationErrorToast :details="adjustError"')
expect(popups).toContain('data-tone="summary"')
expect(popups).not.toContain('max-height="420"')
expect(popups).not.toContain('label-position="top"')
expect(statements).not.toContain('.create-period-row')
})
it('keeps wallet dialogs compact while preserving financial warnings and preview space', () => {
const content = read('modules/finance/pages/FinanceCustomerAccountsView.vue')
const popups = content.slice(content.indexOf('<AppDialog'))
expect(popups.match(/fullscreenable/g)).toHaveLength(4)
expect(popups.match(/class="density-table density-table--editable/g)).toHaveLength(3)
expect(popups.match(/label-position="left" label-width="100px"/g)).toHaveLength(3)
for (const flag of ['adjustmentSaving', 'creditLimitSaving', 'reviewSaving']) expect(popups).toContain(`:disabled="${flag}"`)
expect(popups).toContain('data-tone="summary"')
expect(popups).toContain('data-tone="source"')
expect(popups).toContain('data-tone="work"')
expect(popups).toContain('不足部分再使用可用授信')
expect(popups).toContain('次日授信失效')
expect(popups).not.toContain(' readonly')
expect(content).not.toContain('.wallet-form__grid')
expect(content).toContain('max-height: 70vh')
expect(content).toContain('onBeforeUnmount(clearVoucherPreview)')
})
it('keeps notification editors and detail drawers on the shared popup palette', () => {
for (const file of ['MessageComposerDialog', 'TemplateEditorDialog']) {
const content = read(`modules/notifications/components/${file}.vue`)
expect(content).toContain('fullscreenable :confirm-loading="submitting"')
expect(content).toContain('class="density-form density-dialog-sections" label-position="left"')
expect(content).toContain('density-table density-table--editable')
expect(content).toContain('density-actions density-actions--footer')
expect(content).toContain('data-tone="work"')
expect(content).toContain('<InlineHelp')
expect(content).not.toContain('class="dialog-actions"')
expect(content).not.toContain('model-value="站内信" disabled')
}
for (const file of ['PlatformNotificationsView', 'MessageManagementView']) {
const content = read(`modules/notifications/pages/${file}.vue`)
expect(content).toContain('admin-density-drawer--detail')
expect(content).toContain('data-tone="summary"')
expect(content).toContain('data-tone="source"')
expect(content).toContain('@retry="detailRow && openDetail(detailRow)"')
const styles = parse(content).descriptor.styles.map(style => style.content).join('\n')
expect(styles).not.toMatch(/background:|border-left:|font-size:|max-height:/)
}
})
it('keeps container evidence and customs summaries compact without duplicating field styles', () => {
const overview = read('modules/container-loading/components/ContainerOverviewPanel.vue')
const customs = read('modules/container-loading/components/ContainerCustomsPanel.vue')