优化
This commit is contained in:
+45
-12
@@ -18,10 +18,10 @@
|
||||
--el-table-row-hover-bg-color: var(--admin-row-hover);
|
||||
font-size: var(--admin-font-body, 13px);
|
||||
}
|
||||
.admin-density .data-table .el-table .el-table__cell, .admin-density-dialog .el-table .el-table__cell { height: var(--admin-row-height, 36px); padding: var(--admin-space-1, 4px) 0; }
|
||||
.admin-density .el-table .el-table__cell, .admin-density .data-table .el-table .el-table__cell, .admin-density-dialog .el-table .el-table__cell { height: var(--admin-row-height, 36px); padding: var(--admin-space-1, 4px) 0; }
|
||||
.admin-density .el-table .cell, .admin-density-dialog .el-table .cell { line-height: 22px; }
|
||||
.admin-density .el-table .el-button--small, .admin-density-dialog .el-table .el-button--small { height: var(--admin-action-height, 28px); padding: var(--admin-space-1, 4px) var(--admin-space-3, 8px); }
|
||||
.admin-density .el-table__body tr.el-table__row--striped td.el-table__cell { background: #fcfcfd; }
|
||||
.admin-density .el-table__body tr.el-table__row--striped td.el-table__cell { background: var(--admin-row-stripe); }
|
||||
.admin-density .data-table .data-table__toolbar { min-height: 44px; padding: var(--admin-space-2, 6px) var(--admin-space-5, 12px); }
|
||||
.admin-density .data-table .data-table__pagination { min-height: 48px; padding: var(--admin-space-3, 8px) var(--admin-space-5, 12px); }
|
||||
.admin-density .el-empty { padding: var(--admin-space-5, 12px) 0; }
|
||||
@@ -29,7 +29,7 @@
|
||||
.admin-density .el-empty__description { margin-top: 0; }
|
||||
.admin-density .el-empty__description p { font-size: var(--admin-font-body, 13px); }
|
||||
.admin-density-dialog .el-form-item__label { line-height: 22px; }
|
||||
.admin-density-dialog .el-descriptions__label.el-descriptions__cell { font-size: var(--admin-font-caption, 12px); background: #f8fafc; }
|
||||
.admin-density-dialog .el-descriptions__label.el-descriptions__cell { font-size: var(--admin-font-caption, 12px); background: var(--admin-label-bg); }
|
||||
.admin-density-dialog .el-descriptions__content.el-descriptions__cell { font-size: var(--admin-font-body, 13px); overflow-wrap: anywhere; }
|
||||
.admin-density-dialog.el-dialog.is-fullscreen { width: 100%; max-width: 100%; height: 100vh; max-height: 100vh; margin: 0; border-radius: 0; }
|
||||
.admin-density-dialog.el-dialog.admin-density-workspace:not(.is-fullscreen) { height: 92vh; max-height: 92vh; margin-top: 4vh; }
|
||||
@@ -71,15 +71,16 @@
|
||||
:is(.admin-density, .admin-density-dialog) .el-button.density-action {
|
||||
--action-color: var(--admin-action-neutral);
|
||||
--action-border: var(--admin-action-neutral-border);
|
||||
--action-hover-bg: var(--admin-label-bg);
|
||||
--el-button-text-color: var(--action-color);
|
||||
--el-button-border-color: var(--action-border);
|
||||
--el-button-bg-color: var(--admin-surface);
|
||||
--el-button-hover-text-color: var(--action-color);
|
||||
--el-button-hover-border-color: var(--action-color);
|
||||
--el-button-hover-bg-color: var(--admin-label-bg);
|
||||
--el-button-hover-bg-color: var(--action-hover-bg);
|
||||
--el-button-active-text-color: var(--action-color);
|
||||
--el-button-active-border-color: var(--action-color);
|
||||
--el-button-active-bg-color: var(--admin-label-bg);
|
||||
--el-button-active-bg-color: var(--action-hover-bg);
|
||||
--el-button-disabled-text-color: #a1a1aa;
|
||||
--el-button-disabled-border-color: var(--admin-table-border);
|
||||
--el-button-disabled-bg-color: var(--admin-surface);
|
||||
@@ -89,15 +90,20 @@
|
||||
color: var(--action-color); background: var(--admin-surface);
|
||||
font-size: var(--admin-font-body); font-weight: 600; box-shadow: none;
|
||||
}
|
||||
:is(.admin-density, .admin-density-dialog) .density-action[data-tone="warning"] { --action-color: var(--admin-action-warning); --action-border: var(--admin-action-warning-border); }
|
||||
:is(.admin-density, .admin-density-dialog) .density-action[data-tone="purple"] { --action-color: var(--admin-action-purple); --action-border: var(--admin-action-purple-border); }
|
||||
:is(.admin-density, .admin-density-dialog) .density-action[data-tone="primary"] { --action-color: var(--admin-action-primary); --action-border: var(--admin-action-primary-border); }
|
||||
:is(.admin-density, .admin-density-dialog) .density-action[data-tone="success"] { --action-color: var(--admin-action-success); --action-border: var(--admin-action-success-border); }
|
||||
:is(.admin-density, .admin-density-dialog) .density-action[data-tone="danger"] { --action-color: var(--admin-action-danger); --action-border: var(--admin-action-danger-border); }
|
||||
:is(.admin-density, .admin-density-dialog) .el-button.density-action:hover:not(:disabled) { color: var(--action-color); border-color: var(--action-color); background: var(--admin-label-bg); box-shadow: none; }
|
||||
:is(.admin-density, .admin-density-dialog) .density-action[data-tone="warning"] { --action-color: var(--admin-action-warning); --action-border: var(--admin-action-warning-border); --action-hover-bg: var(--admin-action-warning-hover); }
|
||||
:is(.admin-density, .admin-density-dialog) .density-action[data-tone="purple"] { --action-color: var(--admin-action-purple); --action-border: var(--admin-action-purple-border); --action-hover-bg: var(--admin-action-purple-hover); }
|
||||
:is(.admin-density, .admin-density-dialog) .density-action[data-tone="primary"] { --action-color: var(--admin-action-primary); --action-border: var(--admin-action-primary-border); --action-hover-bg: var(--admin-action-primary-hover); }
|
||||
:is(.admin-density, .admin-density-dialog) .density-action[data-tone="success"] { --action-color: var(--admin-action-success); --action-border: var(--admin-action-success-border); --action-hover-bg: var(--admin-action-success-hover); }
|
||||
:is(.admin-density, .admin-density-dialog) .density-action[data-tone="danger"] { --action-color: var(--admin-action-danger); --action-border: var(--admin-action-danger-border); --action-hover-bg: var(--admin-action-danger-hover); }
|
||||
:is(.admin-density, .admin-density-dialog) .el-button.density-action:hover:not(:disabled) { color: var(--action-color); border-color: var(--action-color); background: var(--action-hover-bg); box-shadow: none; }
|
||||
:is(.admin-density, .admin-density-dialog) .el-button.density-action:disabled { color: var(--el-button-disabled-text-color); border-color: var(--el-button-disabled-border-color); background: var(--admin-surface); }
|
||||
:is(.admin-density, .admin-density-dialog) .el-button.density-action:focus-visible { outline: 2px solid var(--action-color); outline-offset: 2px; }
|
||||
|
||||
/* Back navigation stays in the detail heading, beside its business identifier. */
|
||||
.admin-density .el-button.density-back { height: var(--admin-control-height); padding: 0; border: 0; background: transparent; color: var(--cc-muted); font-size: var(--admin-font-body); font-weight: 400; box-shadow: none; }
|
||||
.admin-density .el-button.density-back:hover { color: var(--cc-primary); background: transparent; }
|
||||
.admin-density .el-button.density-back:focus-visible { outline: 2px solid var(--cc-primary); outline-offset: 2px; }
|
||||
|
||||
/* Navigation tabs shared by customer/order details. Layout-specific borders stay local. */
|
||||
.admin-density .density-tabs { display: flex; gap: 0; overflow-x: auto; overflow-y: hidden; background: var(--admin-surface); scrollbar-width: thin; }
|
||||
.admin-density .density-tabs > button { position: relative; display: inline-flex; min-width: max-content; height: var(--admin-tab-height); min-height: var(--admin-tab-height); flex: 0 0 auto; align-items: center; justify-content: center; gap: var(--admin-space-2); padding: 0 var(--admin-space-6); border: 0; background: transparent; color: #475467; font-size: var(--admin-font-body); font-weight: 500; white-space: nowrap; cursor: pointer; }
|
||||
@@ -113,9 +119,36 @@
|
||||
.admin-density-drawer .el-drawer__body { padding: var(--admin-space-5) var(--admin-space-6); }
|
||||
|
||||
/* Reusable row action group; no business action names in the shared layer. */
|
||||
.admin-density .data-table .density-actions { display: flex; align-items: center; gap: var(--admin-space-2); }
|
||||
.admin-density .data-table .density-actions { --cc-list-action-sibling-margin: 0px; display: flex; align-items: center; gap: var(--admin-space-2); }
|
||||
.admin-density .data-table .density-actions > :is(.el-button, .el-dropdown) { margin-left: 0; }
|
||||
|
||||
/* The same action spacing also applies to native tables and teleported content. */
|
||||
:is(.admin-density, .admin-density-dialog) .density-actions { display: flex; align-items: center; gap: var(--admin-space-2); }
|
||||
:is(.admin-density, .admin-density-dialog) .density-actions > :is(.el-button, .el-dropdown) { margin-left: 0; }
|
||||
|
||||
/* Element tabs retain their keyboard handling and use the shared detail-tab scale. */
|
||||
.admin-density .density-element-tabs { min-width: 0; background: var(--admin-surface); border-bottom: 1px solid var(--admin-table-border); }
|
||||
.admin-density .density-element-tabs > .el-tabs__header { margin: 0; }
|
||||
.admin-density .density-element-tabs > .el-tabs__header .el-tabs__item { height: var(--admin-tab-height); padding: 0 var(--admin-space-6); font-size: var(--admin-font-body); font-weight: 500; color: #475467; }
|
||||
.admin-density .density-element-tabs > .el-tabs__header .el-tabs__item.is-active { color: var(--cc-primary); font-weight: 600; }
|
||||
.admin-density .density-element-tabs > .el-tabs__header .el-tabs__item:hover { color: var(--cc-primary); }
|
||||
.admin-density .density-element-tabs > .el-tabs__header .el-tabs__active-bar { background-color: var(--cc-primary); }
|
||||
.admin-density .density-element-tabs > .el-tabs__content { display: none; }
|
||||
|
||||
/* Opted-in grouped forms use the same labels and spacing on pages and in dialogs. */
|
||||
:is(.admin-density, .admin-density-dialog) .density-form,
|
||||
.admin-density.density-form { --el-form-label-font-size: var(--admin-font-body); }
|
||||
:is(.admin-density, .admin-density-dialog) .density-form .el-form-item,
|
||||
.admin-density.density-form .el-form-item { margin-bottom: var(--admin-space-5); }
|
||||
|
||||
/* Existing native data tables keep their business column widths and sticky cells. */
|
||||
:is(.admin-density, .admin-density-dialog) .density-table { width: 100%; border-collapse: collapse; font-size: var(--admin-font-body); }
|
||||
:is(.admin-density, .admin-density-dialog) .density-table :is(th, td) { height: var(--admin-row-height); padding: var(--admin-space-1) var(--admin-space-3); border: 1px solid var(--admin-table-border); text-align: left; vertical-align: middle; overflow-wrap: anywhere; }
|
||||
:is(.admin-density, .admin-density-dialog) .density-table th { background: var(--admin-label-bg); color: var(--cc-muted); font-weight: 600; }
|
||||
:is(.admin-density, .admin-density-dialog) .density-table td { background: var(--admin-surface); }
|
||||
:is(.admin-density, .admin-density-dialog) .density-table tbody tr:nth-child(even) td { background: var(--admin-row-stripe); }
|
||||
:is(.admin-density, .admin-density-dialog) .density-table tbody tr:hover td { background: var(--admin-row-hover); }
|
||||
|
||||
/* Continuous description grid. Page owns column count and wide-field spans only. */
|
||||
:is(.admin-density, .admin-density-dialog) .density-fields { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 0; margin: 0; border-top: 1px solid var(--cc-border); border-left: 1px solid var(--cc-border); background: var(--admin-surface); }
|
||||
:is(.admin-density, .admin-density-dialog) .density-fields > div { display: grid; grid-template-columns: var(--admin-field-label-width, var(--admin-label-width)) minmax(0, 1fr); min-width: 0; min-height: var(--admin-row-height); gap: 0; padding: 0; border-right: 1px solid var(--cc-border); border-bottom: 1px solid var(--cc-border); border-radius: 0; background: var(--admin-surface); }
|
||||
|
||||
@@ -4,7 +4,19 @@ import { describe, expect, it } from 'vitest'
|
||||
import { compileStyle, parse } from 'vue/compiler-sfc'
|
||||
|
||||
// Approved entry/consumer inventory. Do not discover unrelated menus recursively.
|
||||
const cargoConsumers = [
|
||||
'ocean-bookings/pages/OceanBookingsView.vue', 'ocean-bookings/pages/OceanBookingDetailView.vue',
|
||||
'ocean-bookings/components/OceanBookingDocumentsPanel.vue', 'ocean-bookings/components/OceanBookingPrintDialog.vue',
|
||||
'trucking/pages/TruckAppointmentsView.vue', 'trucking/pages/TruckAppointmentCreateView.vue',
|
||||
'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/OceanBookingLinkDialog.vue', 'container-loading/components/ContainerOverviewPanel.vue',
|
||||
'container-loading/components/ContainerCustomsPanel.vue', 'container-loading/components/ContainerLastLegFeesPanel.vue',
|
||||
'loading-lists/components/ContainerFeesPanel.vue', 'port-schedules/components/CarrierScheduleQueryPanel.vue',
|
||||
]
|
||||
const consumers = [
|
||||
...cargoConsumers,
|
||||
'customers/pages/CustomersView.vue', 'customers/pages/CustomerDetailView.vue',
|
||||
'customers/pages/OverseasStorageFeesView.vue', 'customers/components/CustomerBillingRulesPanel.vue',
|
||||
'customers/CustomerOverseasStorageFeePanel.vue', 'unified-orders/pages/UnifiedOrdersView.vue',
|
||||
@@ -28,19 +40,25 @@ const domesticPages = [
|
||||
{ title: '托盘管理', path: '/master-data/pallets', file: 'warehouse-resources/pallets/pages/PalletsView.vue' },
|
||||
{ title: '整柜业务', path: '/domestic-containers', file: 'domestic-containers/pages/DomesticContainersView.vue' },
|
||||
]
|
||||
const customerPages = [
|
||||
{ title: '客户列表', path: '/customers', file: 'customers/pages/CustomersView.vue' },
|
||||
{ title: '海外仓阶梯费用', path: '/customers/overseas-storage-fees', file: 'customers/pages/OverseasStorageFeesView.vue' },
|
||||
]
|
||||
const densityPages = [...new Set([
|
||||
...cargoConsumers.filter(file => file.includes('/pages/') || ['ContainerOverviewPanel.vue', 'ContainerCustomsPanel.vue', 'ContainerLastLegFeesPanel.vue', 'ContainerFeesPanel.vue', 'OceanBookingDocumentsPanel.vue'].some(name => file.endsWith(name))),
|
||||
...domesticPages.map(page => page.file), 'loading-lists/pages/PreloadPlanDetailView.vue',
|
||||
'loading-lists/components/LoadingListDetailDrawer.vue', 'domestic-containers/pages/DomesticContainerInventoryDetailView.vue',
|
||||
'customers/pages/CustomersView.vue', 'customers/pages/CustomerDetailView.vue', 'customers/pages/OverseasStorageFeesView.vue',
|
||||
...customerPages.map(page => page.file), 'customers/pages/CustomerDetailView.vue', 'audit/components/CustomerAuditLogsPanel.vue',
|
||||
])]
|
||||
type TemplateNode = NonNullable<NonNullable<ReturnType<typeof parse>['descriptor']['template']>['ast']>['children'][number]
|
||||
type ElementNode = Extract<TemplateNode, { type: 1 }>
|
||||
const classesOf = (node: ElementNode, preload = true): string[] => node.props.flatMap(prop => {
|
||||
const classesOf = (node: ElementNode, preload = true, dense = preload, workbench = 'DOMESTIC'): string[] => node.props.flatMap(prop => {
|
||||
if (prop.type === 6 && prop.name === 'class') return prop.value?.content.split(/\s+/) ?? []
|
||||
if (prop.type !== 7 || prop.name !== 'bind' || prop.arg?.type !== 4 || prop.arg.content !== 'class' || prop.exp?.type !== 4 || !prop.exp.content.includes('density')) return []
|
||||
// Evaluate only the authored density class binding with the shared component's two modes.
|
||||
const value = new Function('mode', 'isPreload', 'dense', `return (${prop.exp.content})`)(preload ? 'preload' : 'confirmed', preload, preload) as Record<string, boolean>
|
||||
return Object.entries(value).filter(([, enabled]) => enabled).flatMap(([names]) => names.split(/\s+/))
|
||||
const value = new Function('mode', 'isPreload', 'dense', 'isDomestic', 'isOverseas', `return (${prop.exp.content})`)(preload ? 'preload' : 'confirmed', preload, dense, workbench === 'DOMESTIC', workbench === 'OVERSEAS') as Record<string, boolean>
|
||||
const flatten = (value: unknown): string[] => typeof value === 'string' ? value.split(/\s+/) : Array.isArray(value) ? value.flatMap(flatten) : value && typeof value === 'object' ? Object.entries(value).filter(([, enabled]) => enabled).flatMap(([names]) => names.split(/\s+/)) : []
|
||||
return flatten(value)
|
||||
})
|
||||
const styles = [
|
||||
...consumers.flatMap(file => parse(read(`modules/${file}`)).descriptor.styles.map(style => ({ file, css: style.content }))),
|
||||
@@ -55,6 +73,60 @@ const cssRoot = (css: string) => {
|
||||
}
|
||||
|
||||
describe('approved Admin density ownership', () => {
|
||||
it('covers four cargo menus, independent detail routes and both workbench modes', () => {
|
||||
const navigation = read('core/navigation/adminNavigation.ts')
|
||||
for (const path of ['/ocean-bookings', '/trucking', '/container-loading/domestic', '/container-loading/overseas']) expect(navigation).toContain(`path: '${path}'`)
|
||||
for (const [prefix, workbench] of [['Domestic', 'DOMESTIC'], ['Overseas', 'OVERSEAS']]) {
|
||||
for (const [suffix, component] of [['ContainersView', 'ContainersView'], ['ContainerDetailView', 'ContainerDetailView']]) {
|
||||
expect(read(`modules/container-loading/pages/${prefix}${suffix}.vue`)).toContain(`<${component} workbench="${workbench}"`)
|
||||
const ast = parse(read(`modules/container-loading/pages/${component}.vue`)).descriptor.template!.ast!
|
||||
const root = ast.children.find(node => node.type === 1) as ElementNode
|
||||
expect(classesOf(root, false, true, workbench)).toContain('admin-density')
|
||||
}
|
||||
}
|
||||
const detail = read('modules/container-loading/pages/ContainerDetailView.vue')
|
||||
for (const component of ['CarrierScheduleQueryPanel', 'LoadingListDetailDrawer', 'PalletLabelPrintDialog']) expect(detail).toContain(`<${component} dense`)
|
||||
expect(read('modules/container-loading/pages/ContainersView.vue')).toContain('<ContainerSimulationCreate dense')
|
||||
for (const file of cargoConsumers) expect(read(`modules/${file}`), `${file}: direct entry`).toContain('@/styles/adminDensity.css')
|
||||
})
|
||||
|
||||
it('enables confirmed loading content and schedule overlays only for opted-in callers', () => {
|
||||
for (const file of ['loading-lists/components/LoadingListDetailDrawer.vue', 'port-schedules/components/CarrierScheduleQueryPanel.vue']) {
|
||||
const ast = parse(read(`modules/${file}`)).descriptor.template!.ast!
|
||||
const root = ast.children.find(node => node.type === 1) as ElementNode
|
||||
expect(classesOf(root, false, true)).toContain('admin-density')
|
||||
expect(classesOf(root, false, false)).not.toContain('admin-density')
|
||||
if (file.includes('CarrierSchedule')) {
|
||||
const dialog = root.children.find(node => node.type === 1 && node.tag === 'AppDialog') as ElementNode
|
||||
expect(classesOf(dialog, false, true)).toContain('admin-density-dialog')
|
||||
expect(classesOf(dialog, false, false)).not.toContain('admin-density-dialog')
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
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) => {
|
||||
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))
|
||||
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') {
|
||||
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(ast.children)
|
||||
}
|
||||
})
|
||||
it('keeps shared list actions and detail content inside a density-enabled page ancestor', () => {
|
||||
for (const file of densityPages) {
|
||||
const template = parse(read(`modules/${file}`)).descriptor.template!.ast!
|
||||
@@ -63,7 +135,7 @@ describe('approved Admin density ownership', () => {
|
||||
for (const node of nodes) {
|
||||
if (node.type !== 1) continue
|
||||
const classes = classesOf(node)
|
||||
if (node.tag === 'DataTable' || node.tag === 'LoadingListDetailDrawer' || classes.some(name => ['density-action', 'density-actions', 'density-tabs', 'density-fields', 'density-tab-icon'].includes(name))) {
|
||||
if (node.tag === 'DataTable' || node.tag === 'LoadingListDetailDrawer' || classes.some(name => ['density-action', 'density-actions', 'density-tabs', 'density-fields', 'density-tab-icon', 'density-back'].includes(name))) {
|
||||
expect(insideDensity, `${file}: ${classes.join(' ')} lacks its density ancestor`).toBe(true)
|
||||
checked += 1
|
||||
}
|
||||
@@ -87,15 +159,27 @@ describe('approved Admin density ownership', () => {
|
||||
for (const file of densityPages) expect(read(`modules/${file}`), `${file}: direct entry CSS`).toContain('@/styles/adminDensity.css')
|
||||
})
|
||||
|
||||
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'"))
|
||||
expect([...customers.matchAll(/path: '/g)]).toHaveLength(customerPages.length)
|
||||
for (const page of customerPages) {
|
||||
expect(customers).toContain(`title: '${page.title}'`)
|
||||
expect(customers).toContain(`path: '${page.path}'`)
|
||||
}
|
||||
expect(read('modules/customers/pages/CustomerDetailView.vue')).toContain('class="back-button density-back"')
|
||||
})
|
||||
|
||||
it('uses shared outlined actions across domestic tables and explicit classes on teleported dialogs', () => {
|
||||
for (const file of densityPages) {
|
||||
for (const file of [...new Set([...densityPages, ...consumers])]) {
|
||||
const template = parse(read(`modules/${file}`)).descriptor.template!.ast!
|
||||
const visit = (nodes: typeof template.children, actionSlot = false, parentTag = '') => {
|
||||
for (const node of nodes) {
|
||||
if (node.type !== 1) continue
|
||||
const isActionSlot = actionSlot || (parentTag === 'DataTable' && node.props.some(prop => prop.type === 7 && prop.name === 'slot' && prop.arg?.type === 4 && prop.arg.content === 'actions'))
|
||||
if (isActionSlot && node.tag === 'el-button') expect(classesOf(node), `${file}: row action`).toContain('density-action')
|
||||
if (densityPages.includes(file) && isActionSlot && node.tag === 'el-button') expect(classesOf(node), `${file}: row action`).toContain('density-action')
|
||||
if (['AppDialog', 'el-dialog'].includes(node.tag)) expect(classesOf(node), `${file}: teleported dialog`).toContain('admin-density-dialog')
|
||||
if (['AppDrawer', 'el-drawer'].includes(node.tag)) expect(classesOf(node), `${file}: teleported drawer`).toContain('admin-density-drawer')
|
||||
visit(node.children, isActionSlot, node.tag)
|
||||
}
|
||||
}
|
||||
@@ -121,6 +205,22 @@ describe('approved Admin density ownership', () => {
|
||||
}
|
||||
})
|
||||
|
||||
it('connects configurable surface colors and semantic hover states to their consumers', () => {
|
||||
const root = cssRoot(shared)
|
||||
const rules = new Map<string, Record<string, string>>()
|
||||
root.walkRules(rule => {
|
||||
const declarations: Record<string, string> = {}
|
||||
rule.walkDecls(decl => { declarations[decl.prop] = decl.value })
|
||||
rules.set(rule.selector, declarations)
|
||||
})
|
||||
expect(rules.get('.admin-density .el-table__body tr.el-table__row--striped td.el-table__cell')?.background).toBe('var(--admin-row-stripe)')
|
||||
expect(rules.get('.admin-density-dialog .el-descriptions__label.el-descriptions__cell')?.background).toBe('var(--admin-label-bg)')
|
||||
for (const tone of ['warning', 'purple', 'primary', 'success', 'danger']) {
|
||||
expect(rules.get(`:is(.admin-density, .admin-density-dialog) .density-action[data-tone="${tone}"]`)?.['--action-hover-bg']).toBe(`var(--admin-action-${tone}-hover)`)
|
||||
}
|
||||
expect(rules.get(':is(.admin-density, .admin-density-dialog) .el-button.density-action:hover:not(:disabled)')?.background).toBe('var(--action-hover-bg)')
|
||||
})
|
||||
|
||||
it('prevents page copies of action paint, tab primitives and desktop density dimensions', () => {
|
||||
for (const { file, css } of styles) cssRoot(css).walkRules(rule => {
|
||||
expect(rule.selector, file).not.toMatch(/customer-action-button|order-action-button|\.density-action|\.density-tab-icon/)
|
||||
@@ -137,9 +237,22 @@ describe('approved Admin density ownership', () => {
|
||||
})
|
||||
|
||||
it('keeps conditional shared consumers opt-in instead of enabling other menus', () => {
|
||||
expect(read('shared/components/DataTable.vue')).toContain('margin-left: var(--cc-list-action-sibling-margin, 8px)')
|
||||
expect(shared).toContain('.density-actions { --cc-list-action-sibling-margin: 0px;')
|
||||
expect(read('modules/loading-lists/components/LoadingListsView.vue')).toContain("'admin-density': isPreload")
|
||||
expect(read('modules/loading-lists/components/PalletLabelPrintDialog.vue')).toContain("'admin-density-dialog': dense")
|
||||
expect(read('modules/container-loading/components/ContainerSimulationCreate.vue')).toContain("'admin-density-dialog': dense")
|
||||
for (const file of ['loading-lists/components/LoadingListsView.vue', 'loading-lists/components/LoadingListDetailDrawer.vue', 'loading-lists/components/PalletLabelPrintDialog.vue', 'container-loading/components/ContainerSimulationCreate.vue']) {
|
||||
const template = parse(read(`modules/${file}`)).descriptor.template!.ast!
|
||||
const visit = (nodes: typeof template.children) => {
|
||||
for (const node of nodes) {
|
||||
if (node.type !== 1) continue
|
||||
expect(classesOf(node, false).filter(name => /^(admin-density|density-)/.test(name)), `${file}: unopted consumer`).toEqual([])
|
||||
visit(node.children)
|
||||
}
|
||||
}
|
||||
visit(template.children)
|
||||
}
|
||||
for (const file of ['customers/pages/CustomersView.vue', 'customers/pages/OverseasStorageFeesView.vue', 'unified-orders/pages/UnifiedOrdersView.vue']) {
|
||||
expect(read(`modules/${file}`), file).toContain('density-action')
|
||||
}
|
||||
|
||||
@@ -31,12 +31,17 @@
|
||||
--admin-action-neutral-border: #dfe3ee;
|
||||
--admin-action-warning: #c76500;
|
||||
--admin-action-warning-border: #fed7aa;
|
||||
--admin-action-warning-hover: #fff7ed;
|
||||
--admin-action-purple: #7c3aed;
|
||||
--admin-action-purple-border: #ddd6fe;
|
||||
--admin-action-purple-hover: #f5f3ff;
|
||||
--admin-action-primary: #1677cf;
|
||||
--admin-action-primary-border: #bfdbfe;
|
||||
--admin-action-primary-hover: #eff6ff;
|
||||
--admin-action-success: #15803d;
|
||||
--admin-action-success-border: #bbf7d0;
|
||||
--admin-action-success-hover: #f0fdf4;
|
||||
--admin-action-danger: #dc2626;
|
||||
--admin-action-danger-border: #fecaca;
|
||||
--admin-action-danger-hover: #fef2f2;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user