This commit is contained in:
2026-09-14 17:54:21 +08:00
parent 7e1b9b4cee
commit 25825685c3
40 changed files with 850 additions and 705 deletions
+91 -34
View File
@@ -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; }
+147
View File
@@ -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')
}
})
})
+42
View File
@@ -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;
}