优化
This commit is contained in:
+91
-34
@@ -1,66 +1,123 @@
|
||||
@import './adminDensityTokens.css';
|
||||
|
||||
/* Explicit opt-in for approved dense business workspaces and teleported dialogs. */
|
||||
.admin-density, .admin-density-dialog {
|
||||
--el-font-size-base: 13px;
|
||||
--el-component-size: 32px;
|
||||
--cc-list-table-font-size: 13px;
|
||||
--cc-list-action-font-size: 13px;
|
||||
font-size: 13px;
|
||||
.admin-density, .admin-density-dialog, .admin-density-drawer {
|
||||
--el-font-size-base: var(--admin-font-body);
|
||||
--el-component-size: var(--admin-control-height);
|
||||
--cc-list-table-font-size: var(--admin-font-body);
|
||||
--cc-list-action-font-size: var(--admin-font-body);
|
||||
--cc-list-query-font-size: var(--admin-font-body);
|
||||
font-size: var(--admin-font-body, 13px);
|
||||
}
|
||||
.admin-density .surface-panel, .admin-density .query-toolbar {
|
||||
border-radius: 4px; box-shadow: none;
|
||||
border-radius: var(--admin-radius, 4px); box-shadow: none;
|
||||
}
|
||||
.admin-density .el-table, .admin-density-dialog .el-table {
|
||||
--el-table-header-bg-color: #f8fafc;
|
||||
--el-table-border-color: #e5e7eb;
|
||||
--el-table-row-hover-bg-color: #f3f7fb;
|
||||
font-size: 13px;
|
||||
--el-table-header-bg-color: var(--admin-label-bg);
|
||||
--el-table-border-color: var(--admin-table-border);
|
||||
--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: 36px; padding: 4px 0; }
|
||||
.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: 28px; padding: 4px 8px; }
|
||||
.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 .data-table .data-table__toolbar { min-height: 44px; padding: 6px 12px; }
|
||||
.admin-density .data-table .data-table__pagination { min-height: 48px; padding: 8px 12px; }
|
||||
.admin-density .el-empty { padding: 12px 0; }
|
||||
.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; }
|
||||
.admin-density .el-empty__image { display: none; }
|
||||
.admin-density .el-empty__description { margin-top: 0; }
|
||||
.admin-density .el-empty__description p { font-size: 13px; }
|
||||
.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: 12px; background: #f8fafc; }
|
||||
.admin-density-dialog .el-descriptions__content.el-descriptions__cell { font-size: 13px; overflow-wrap: anywhere; }
|
||||
.admin-density-dialog .el-descriptions__label.el-descriptions__cell { font-size: var(--admin-font-caption, 12px); background: #f8fafc; }
|
||||
.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; }
|
||||
.admin-density-dialog .el-dialog__headerbtn { top: 0; }
|
||||
.admin-density-dialog .app-dialog__native-fullscreen-control { top: 6px; }
|
||||
.admin-density .density-tab-icon { display: inline-flex; align-items: center; justify-content: center; width: 18px; font: 17px/1 "Segoe UI Emoji", "Apple Color Emoji", "Noto Color Emoji", sans-serif; }
|
||||
.admin-density-confirm.el-message-box { --el-font-size-base: 13px; border-radius: 4px; padding: 12px 16px; }
|
||||
.admin-density-confirm .el-message-box__title { font-size: 16px; }
|
||||
.admin-density-confirm.el-message-box { --el-font-size-base: var(--admin-font-body); border-radius: var(--admin-radius, 4px); padding: var(--admin-space-5, 12px) var(--admin-space-6, 16px); }
|
||||
.admin-density-confirm .el-message-box__title { font-size: var(--admin-font-title, 16px); }
|
||||
|
||||
/* Dialogs are teleported; opt in explicitly instead of styling all AppDialog instances. */
|
||||
.admin-density-dialog.el-dialog {
|
||||
--el-font-size-base: 13px;
|
||||
--el-component-size: 32px;
|
||||
--el-font-size-base: var(--admin-font-body);
|
||||
--el-component-size: var(--admin-control-height);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
border-radius: 4px;
|
||||
border-radius: var(--admin-radius, 4px);
|
||||
}
|
||||
.admin-density-dialog.el-dialog:not(.is-fullscreen) { margin-top: 6vh; max-height: 88vh; }
|
||||
.admin-density-dialog.el-dialog .el-dialog__header { flex: 0 0 auto; margin: 0; padding: 10px 48px 10px 16px; border-bottom: 1px solid var(--cc-border); }
|
||||
.admin-density-dialog.el-dialog .el-dialog__title { font-size: 16px; line-height: 24px; font-weight: 600; }
|
||||
.admin-density-dialog.el-dialog .el-dialog__body { min-height: 0; overflow-y: auto; overscroll-behavior: contain; padding: 12px 16px; }
|
||||
.admin-density-dialog.el-dialog .el-dialog__footer { flex: 0 0 auto; padding: 8px 16px; border-top: 1px solid var(--cc-border); }
|
||||
.admin-density-dialog.el-dialog .el-table { font-size: 13px; }
|
||||
.admin-density-dialog.el-dialog .el-table__cell { height: 36px; padding: 4px 0; }
|
||||
.admin-density-dialog.el-dialog .el-dialog__header { flex: 0 0 auto; margin: 0; padding: var(--admin-space-4, 10px) 48px var(--admin-space-4, 10px) var(--admin-space-6, 16px); border-bottom: 1px solid var(--cc-border); }
|
||||
.admin-density-dialog.el-dialog .el-dialog__title { font-size: var(--admin-font-title, 16px); line-height: 24px; font-weight: 600; }
|
||||
.admin-density-dialog.el-dialog .el-dialog__body { min-height: 0; overflow-y: auto; overscroll-behavior: contain; padding: var(--admin-space-5, 12px) var(--admin-space-6, 16px); }
|
||||
.admin-density-dialog.el-dialog .el-dialog__footer { flex: 0 0 auto; padding: var(--admin-space-3, 8px) var(--admin-space-6, 16px); border-top: 1px solid var(--cc-border); }
|
||||
.admin-density-dialog.el-dialog .el-table { font-size: var(--admin-font-body, 13px); }
|
||||
.admin-density-dialog.el-dialog .el-table__cell { height: var(--admin-row-height, 36px); padding: var(--admin-space-1, 4px) 0; }
|
||||
.admin-density-dialog.el-dialog .el-table .cell { line-height: 22px; }
|
||||
.admin-density-dialog.el-dialog .el-form-item { margin-bottom: 14px; }
|
||||
.admin-density-dialog.el-dialog .el-form-item__label { font-size: 13px; }
|
||||
.admin-density-dialog.el-dialog .el-alert { padding: 8px 10px; border-radius: 3px; }
|
||||
.admin-density-dialog.el-dialog .el-empty { padding: 12px 0; }
|
||||
.admin-density-dialog.el-dialog .el-form-item__label { font-size: var(--admin-font-body, 13px); }
|
||||
.admin-density-dialog.el-dialog .el-alert { padding: var(--admin-space-3, 8px) var(--admin-space-4, 10px); border-radius: 3px; }
|
||||
.admin-density-dialog.el-dialog .el-empty { padding: var(--admin-space-5, 12px) 0; }
|
||||
.admin-density-dialog.el-dialog .el-empty__image { display: none; }
|
||||
.admin-density-dialog.el-dialog .el-empty__description { margin-top: 0; }
|
||||
|
||||
/* Headers must keep units visible even when a saved column width is narrow. */
|
||||
.admin-density .el-table th.el-table__cell .cell,
|
||||
.admin-density-dialog .el-table th.el-table__cell .cell { white-space: normal; overflow-wrap: anywhere; text-overflow: clip; line-height: 18px; }
|
||||
|
||||
/* Shared outlined business actions. Pages choose tone, never redefine paint/size. */
|
||||
:is(.admin-density, .admin-density-dialog) .el-button.density-action {
|
||||
--action-color: var(--admin-action-neutral);
|
||||
--action-border: var(--admin-action-neutral-border);
|
||||
--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-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-disabled-text-color: #a1a1aa;
|
||||
--el-button-disabled-border-color: var(--admin-table-border);
|
||||
--el-button-disabled-bg-color: var(--admin-surface);
|
||||
height: var(--admin-action-height); min-height: var(--admin-action-height);
|
||||
padding: var(--admin-space-1) var(--admin-space-4); margin: 0;
|
||||
border: 1px solid var(--action-border); border-radius: var(--admin-action-radius);
|
||||
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) .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; }
|
||||
|
||||
/* 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; }
|
||||
.admin-density .density-tabs > button::after { position: absolute; right: var(--admin-space-5); bottom: 0; left: var(--admin-space-5); height: 2px; background: transparent; content: ''; }
|
||||
.admin-density .density-tabs > button:hover { background: #fafbff; color: #344054; }
|
||||
.admin-density .density-tabs > button.active { background: #f7f8ff; color: var(--cc-primary); font-weight: 600; }
|
||||
.admin-density .density-tabs > button.active::after { background: var(--cc-primary); }
|
||||
.admin-density .density-tabs > button:focus-visible { outline: 2px solid var(--cc-primary); outline-offset: -2px; }
|
||||
.admin-density .density-tab-icon { display: inline-flex; width: var(--admin-tab-icon-slot); height: var(--admin-tab-icon-slot); flex: 0 0 var(--admin-tab-icon-slot); align-items: center; justify-content: center; font: var(--admin-tab-icon-size)/1 "Segoe UI Emoji", "Apple Color Emoji", "Noto Color Emoji", sans-serif; }
|
||||
|
||||
.admin-density-drawer .el-drawer__header { margin-bottom: 0; padding: var(--admin-space-5) var(--admin-space-6); border-bottom: 1px solid var(--cc-border); }
|
||||
.admin-density-drawer .el-drawer__title { font-size: var(--admin-font-title); }
|
||||
.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 > :is(.el-button, .el-dropdown) { margin-left: 0; }
|
||||
|
||||
/* 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); }
|
||||
:is(.admin-density, .admin-density-dialog) .density-fields dt { display: flex; align-items: center; margin: 0; padding: var(--admin-space-2) var(--admin-space-3); color: var(--cc-muted); background: var(--admin-label-bg); font-size: var(--admin-font-caption); line-height: 20px; }
|
||||
:is(.admin-density, .admin-density-dialog) .density-fields dd { align-self: center; min-width: 0; margin: 0; padding: var(--admin-space-2) var(--admin-space-3); color: #25324a; font-size: var(--admin-font-body); font-weight: 400; line-height: 20px; overflow-wrap: anywhere; white-space: normal; }
|
||||
|
||||
@@ -0,0 +1,147 @@
|
||||
import { readFileSync } from 'node:fs'
|
||||
import { resolve } from 'node:path'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { compileStyle, parse } from 'vue/compiler-sfc'
|
||||
|
||||
// Approved entry/consumer inventory. Do not discover unrelated menus recursively.
|
||||
const consumers = [
|
||||
'customers/pages/CustomersView.vue', 'customers/pages/CustomerDetailView.vue',
|
||||
'customers/pages/OverseasStorageFeesView.vue', 'customers/components/CustomerBillingRulesPanel.vue',
|
||||
'customers/CustomerOverseasStorageFeePanel.vue', 'unified-orders/pages/UnifiedOrdersView.vue',
|
||||
'inbounds/components/InboundTaskList.vue', 'inbounds/measurement/InboundMeasurementDialog.vue',
|
||||
'inbounds/measurement/InboundMeasurementPanel.vue', 'inbounds/review/MeasurementReviewPanel.vue',
|
||||
'inbounds/weight/components/WeightOcrPanel.vue', 'loading-lists/pages/PreloadPlanDetailView.vue',
|
||||
'loading-lists/components/LoadingListsView.vue', 'loading-lists/components/LoadingListDetailDrawer.vue',
|
||||
'loading-lists/components/PalletLabelPrintDialog.vue', 'warehouse-resources/locations/pages/LocationsView.vue',
|
||||
'warehouse-resources/pallets/pages/PalletsView.vue', 'domestic-containers/pages/DomesticContainersView.vue',
|
||||
'domestic-containers/pages/DomesticContainerInventoryDetailView.vue',
|
||||
'container-loading/components/ContainerSimulationCreate.vue', 'audit/components/CustomerAuditLogsPanel.vue',
|
||||
]
|
||||
const moduleCss = ['customers/customerDensity.css', 'unified-orders/styles/orderDensity.css', 'inbounds/measurement/measurementDensity.css', 'domestic-containers/pages/domesticContainersSummary.css']
|
||||
const read = (file: string) => readFileSync(resolve(process.cwd(), 'src', file), 'utf8')
|
||||
const domesticPages = [
|
||||
{ title: '订单列表', path: '/orders', file: 'unified-orders/pages/UnifiedOrdersView.vue' },
|
||||
{ title: '订单测量', path: '/inbounds/measurements', file: 'inbounds/components/InboundTaskList.vue', entry: 'inbounds/pages/InboundMeasurementsView.vue', component: 'InboundTaskList', mode: 'measurement' },
|
||||
{ title: '测量数据审核', path: '/inbounds/measurement-reviews', file: 'inbounds/components/InboundTaskList.vue', entry: 'inbounds/pages/MeasurementReviewsView.vue', component: 'InboundTaskList', mode: 'review' },
|
||||
{ title: '预装清单', path: '/loading-plans', file: 'loading-lists/components/LoadingListsView.vue', entry: 'loading-lists/pages/PreloadPlansView.vue', component: 'LoadingListsView', mode: 'preload' },
|
||||
{ title: '库位管理', path: '/master-data/locations', file: 'warehouse-resources/locations/pages/LocationsView.vue' },
|
||||
{ title: '托盘管理', path: '/master-data/pallets', file: 'warehouse-resources/pallets/pages/PalletsView.vue' },
|
||||
{ title: '整柜业务', path: '/domestic-containers', file: 'domestic-containers/pages/DomesticContainersView.vue' },
|
||||
]
|
||||
const densityPages = [...new Set([
|
||||
...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',
|
||||
])]
|
||||
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 => {
|
||||
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 styles = [
|
||||
...consumers.flatMap(file => parse(read(`modules/${file}`)).descriptor.styles.map(style => ({ file, css: style.content }))),
|
||||
...moduleCss.map(file => ({ file, css: read(`modules/${file}`) })),
|
||||
]
|
||||
const shared = read('styles/adminDensity.css')
|
||||
const tokens = read('styles/adminDensityTokens.css')
|
||||
const cssRoot = (css: string) => {
|
||||
const result = compileStyle({ source: css, filename: 'density.css', id: 'density-contract', trim: false })
|
||||
expect(result.errors).toEqual([])
|
||||
return result.rawResult!.root
|
||||
}
|
||||
|
||||
describe('approved Admin density ownership', () => {
|
||||
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!
|
||||
let checked = 0
|
||||
const visit = (nodes: typeof template.children, insideDensity = false) => {
|
||||
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))) {
|
||||
expect(insideDensity, `${file}: ${classes.join(' ')} lacks its density ancestor`).toBe(true)
|
||||
checked += 1
|
||||
}
|
||||
visit(node.children, insideDensity || classes.some(name => ['admin-density', 'admin-density-dialog'].includes(name)))
|
||||
}
|
||||
}
|
||||
visit(template.children)
|
||||
expect(checked, `${file}: no shared styles checked`).toBeGreaterThan(0)
|
||||
}
|
||||
})
|
||||
|
||||
it('covers all seven domestic menu entries and loads density CSS on direct entry', () => {
|
||||
const navigation = read('core/navigation/adminNavigation.ts')
|
||||
const domestic = navigation.slice(navigation.indexOf("key: 'domestic-warehouse'"), navigation.indexOf("key: 'overseas-delivery'"))
|
||||
expect([...domestic.matchAll(/path: '/g)]).toHaveLength(domesticPages.length)
|
||||
for (const page of domesticPages) {
|
||||
expect(domestic).toContain(`title: '${page.title}'`)
|
||||
expect(domestic).toContain(`path: '${page.path}'`)
|
||||
if (page.entry) expect(read(`modules/${page.entry}`)).toContain(`<${page.component} mode="${page.mode}"`)
|
||||
}
|
||||
for (const file of densityPages) expect(read(`modules/${file}`), `${file}: direct entry CSS`).toContain('@/styles/adminDensity.css')
|
||||
})
|
||||
|
||||
it('uses shared outlined actions across domestic tables and explicit classes on teleported dialogs', () => {
|
||||
for (const file of densityPages) {
|
||||
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 (['AppDialog', 'el-dialog'].includes(node.tag)) expect(classesOf(node), `${file}: teleported dialog`).toContain('admin-density-dialog')
|
||||
visit(node.children, isActionSlot, node.tag)
|
||||
}
|
||||
}
|
||||
visit(template.children)
|
||||
}
|
||||
})
|
||||
|
||||
it('keeps tokens and shared rules opt-in and free of business selectors', () => {
|
||||
expect(shared).toContain("@import './adminDensityTokens.css'")
|
||||
for (const css of [tokens, shared]) cssRoot(css).walkRules(rule => {
|
||||
expect(rule.selector, `unscoped rule: ${rule.selector}`).toContain('.admin-density')
|
||||
expect(rule.selector).not.toMatch(/customer-|order-|inbound-|preload-/)
|
||||
})
|
||||
})
|
||||
|
||||
it('defines every referenced density variable once, with local field width as the documented exception', () => {
|
||||
const definitions = [...tokens.matchAll(/(--admin-[\w-]+)\s*:/g)].map(match => match[1])
|
||||
expect(new Set(definitions).size).toBe(definitions.length)
|
||||
const declared = new Set([...definitions, '--admin-field-label-width'])
|
||||
for (const { file, css } of [...styles, { file: 'adminDensity.css', css: shared }]) {
|
||||
for (const match of css.matchAll(/var\((--admin-[\w-]+)/g)) expect(declared.has(match[1]!), `${file}: ${match[1]}`).toBe(true)
|
||||
for (const match of css.matchAll(/(--admin-[\w-]+)\s*:/g)) expect(match[1], `${file}: token redefinition`).toBe('--admin-field-label-width')
|
||||
}
|
||||
})
|
||||
|
||||
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/)
|
||||
let desktop = true
|
||||
for (let parent: typeof rule.parent | ReturnType<typeof cssRoot>['parent'] = rule.parent; parent; parent = parent.parent) {
|
||||
if (parent.type === 'atrule' && /max-width|print/.test('params' in parent ? String(parent.params) : '')) desktop = false
|
||||
}
|
||||
rule.walkDecls(decl => {
|
||||
expect(decl.prop, file).not.toMatch(/^--el-button-/)
|
||||
if (desktop && decl.prop === 'font-size') expect(decl.value, `${file}: ${rule.selector}`).not.toMatch(/^(10|11|12|13|14|16)px$/)
|
||||
if (desktop && /^(height|min-height)$/.test(decl.prop)) expect(decl.value, `${file}: ${rule.selector}`).not.toMatch(/^(28|32|36|38|64)px$/)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('keeps conditional shared consumers opt-in instead of enabling other menus', () => {
|
||||
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 ['customers/pages/CustomersView.vue', 'customers/pages/OverseasStorageFeesView.vue', 'unified-orders/pages/UnifiedOrdersView.vue']) {
|
||||
expect(read(`modules/${file}`), file).toContain('density-action')
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,42 @@
|
||||
/* Single source for the approved domestic-warehouse/customer density scale.
|
||||
* Opt-in only: loading this stylesheet never changes unrelated pages. */
|
||||
:where(.admin-density, .admin-density-dialog, .admin-density-drawer, .admin-density-confirm) {
|
||||
--admin-font-body: 13px;
|
||||
--admin-font-caption: 12px;
|
||||
--admin-font-section: 14px;
|
||||
--admin-font-title: 16px;
|
||||
--admin-control-height: 32px;
|
||||
--admin-row-height: 36px;
|
||||
--admin-action-height: 28px;
|
||||
--admin-tab-height: 38px;
|
||||
--admin-tab-icon-size: 17px;
|
||||
--admin-tab-icon-slot: 18px;
|
||||
--admin-radius: 4px;
|
||||
--admin-action-radius: 6px;
|
||||
--admin-space-1: 4px;
|
||||
--admin-space-2: 6px;
|
||||
--admin-space-3: 8px;
|
||||
--admin-space-4: 10px;
|
||||
--admin-space-5: 12px;
|
||||
--admin-space-6: 16px;
|
||||
--admin-space-7: 24px;
|
||||
--admin-label-width: 112px;
|
||||
--admin-empty-height: 64px;
|
||||
--admin-surface: #fff;
|
||||
--admin-label-bg: #f8fafc;
|
||||
--admin-table-border: #e5e7eb;
|
||||
--admin-row-hover: #f3f7fb;
|
||||
--admin-row-stripe: #fcfcfd;
|
||||
--admin-action-neutral: #52525b;
|
||||
--admin-action-neutral-border: #dfe3ee;
|
||||
--admin-action-warning: #c76500;
|
||||
--admin-action-warning-border: #fed7aa;
|
||||
--admin-action-purple: #7c3aed;
|
||||
--admin-action-purple-border: #ddd6fe;
|
||||
--admin-action-primary: #1677cf;
|
||||
--admin-action-primary-border: #bfdbfe;
|
||||
--admin-action-success: #15803d;
|
||||
--admin-action-success-border: #bbf7d0;
|
||||
--admin-action-danger: #dc2626;
|
||||
--admin-action-danger-border: #fecaca;
|
||||
}
|
||||
Reference in New Issue
Block a user