feat(admin): apply shared blue-white self-operated workbench style
This commit is contained in:
@@ -97,7 +97,7 @@ const notificationConsumers = [
|
||||
'notifications/components/TemplateEditorDialog.vue',
|
||||
]
|
||||
const systemConsumers = ['iam/pages/InternalUsersView.vue', 'iam/pages/RolesView.vue', 'iam/pages/PermissionsView.vue']
|
||||
const selfOperatedPages = ['self-operated/components/AnalyticsView.vue']
|
||||
const selfOperatedPages = ['self-operated/components/AnalyticsView.vue', 'self-operated/components/InventoryTurnoverView.vue']
|
||||
const selfOperatedConsumers = [...selfOperatedPages, 'self-operated/components/SkuTrend.vue', 'self-operated/pages/OverviewView.vue']
|
||||
const consumers = [
|
||||
...selfOperatedConsumers,
|
||||
@@ -444,7 +444,7 @@ describe('approved Admin density ownership', () => {
|
||||
it('registers the self-operated dashboard with an explicit density root and chart layout', () => {
|
||||
const source = read('modules/self-operated/pages/OverviewView.vue')
|
||||
expect(source).toContain("@/styles/adminDensity.css")
|
||||
expect(source).toContain('<AdminPage class="admin-density dashboard-page">')
|
||||
expect(source).toContain('<AdminPage class="admin-density admin-workbench dashboard-page">')
|
||||
expect(source).toContain('class="dashboard-kpis"')
|
||||
expect(source).toContain('class="dashboard-main-charts"')
|
||||
expect(source).not.toContain('density-fields')
|
||||
@@ -883,11 +883,12 @@ describe('approved Admin density ownership', () => {
|
||||
it('keeps the collection monitor in the Admin theme without legacy list controls', () => {
|
||||
const page = read('modules/self-operated/pages/CollectionView.vue')
|
||||
const monitor = read('modules/self-operated/components/CollectionAccountMonitor.vue')
|
||||
expect(page).toContain('class="admin-density"')
|
||||
expect(page).toContain('class="admin-density admin-workbench"')
|
||||
expect(page).toContain('@/styles/adminDensity.css')
|
||||
expect(page).toContain('@/styles/adminWorkbench.css')
|
||||
expect(page).toContain('<CollectionAccountMonitor')
|
||||
expect(page).not.toContain('<DataTable')
|
||||
expect(monitor).toContain('background: var(--el-bg-color)')
|
||||
expect(monitor).toContain('workbench-panel')
|
||||
expect(monitor).not.toMatch(/--el-[\w-]+\s*:/)
|
||||
})
|
||||
it('opts the confirmed self-operated pages into integrated text actions without changing older outlined consumers', () => {
|
||||
|
||||
@@ -0,0 +1,597 @@
|
||||
@import './adminWorkbenchTokens.css';
|
||||
|
||||
.admin-workbench {
|
||||
--el-color-primary: var(--workbench-primary);
|
||||
--el-color-primary-light-3: var(--workbench-primary-hover);
|
||||
--el-color-primary-light-5: var(--workbench-primary-disabled);
|
||||
--el-color-primary-light-7: var(--workbench-accent-border);
|
||||
--el-color-primary-light-8: var(--workbench-accent-border);
|
||||
--el-color-primary-light-9: var(--workbench-accent-soft);
|
||||
--el-color-primary-dark-2: var(--workbench-primary-active);
|
||||
}
|
||||
|
||||
/* Keep business grids in the page; these primitives only provide appearance. */
|
||||
.admin-workbench.admin-page {
|
||||
min-width: 0;
|
||||
padding: var(--workbench-page-space);
|
||||
background: var(--workbench-surface);
|
||||
color: var(--workbench-text);
|
||||
}
|
||||
|
||||
/* Override AdminPage's scoped normal spacing only for this explicit opt-in. */
|
||||
.admin-workbench.admin-page.admin-density.is-spacing-normal {
|
||||
gap: var(--workbench-page-space);
|
||||
}
|
||||
|
||||
.admin-workbench .workbench-heading {
|
||||
display: grid;
|
||||
min-width: 0;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.admin-workbench .workbench-title {
|
||||
margin: 0;
|
||||
color: var(--workbench-text);
|
||||
font-size: var(--workbench-title-size);
|
||||
font-weight: 700;
|
||||
line-height: 1.4;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.admin-workbench .workbench-meta {
|
||||
display: inline-flex;
|
||||
min-width: 0;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
color: var(--workbench-muted);
|
||||
font-size: var(--workbench-note-size);
|
||||
line-height: 1.6;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.admin-workbench .workbench-meta > i {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
flex: 0 0 auto;
|
||||
border-radius: 50%;
|
||||
background: var(--workbench-accent);
|
||||
}
|
||||
|
||||
.admin-workbench .workbench-section {
|
||||
display: grid;
|
||||
min-width: 0;
|
||||
gap: var(--workbench-section-gap);
|
||||
}
|
||||
|
||||
.admin-workbench .workbench-section-heading {
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.admin-workbench .workbench-section-title {
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
align-items: center;
|
||||
gap: 9px;
|
||||
margin: 0;
|
||||
color: var(--workbench-text);
|
||||
font-size: var(--workbench-section-title-size);
|
||||
font-weight: 600;
|
||||
line-height: 1.5;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.admin-workbench .workbench-section-title::before {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
flex: 0 0 auto;
|
||||
border-radius: 50%;
|
||||
background: var(--workbench-accent);
|
||||
content: '';
|
||||
}
|
||||
|
||||
.admin-workbench .workbench-badge {
|
||||
max-width: 100%;
|
||||
padding: 3px 10px;
|
||||
border-radius: 999px;
|
||||
background: var(--workbench-surface-soft);
|
||||
color: var(--workbench-muted);
|
||||
font-size: var(--workbench-note-size);
|
||||
line-height: 1.5;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.admin-workbench .workbench-badge.is-toned {
|
||||
border: 1px solid var(--workbench-accent-border);
|
||||
background: var(--workbench-accent-soft);
|
||||
color: var(--workbench-accent);
|
||||
}
|
||||
|
||||
.admin-workbench .workbench-metric,
|
||||
.admin-workbench .workbench-panel {
|
||||
min-width: 0;
|
||||
padding: var(--workbench-card-space);
|
||||
border: 1px solid var(--workbench-border);
|
||||
border-radius: var(--workbench-radius);
|
||||
background: var(--workbench-surface);
|
||||
box-shadow: var(--workbench-shadow);
|
||||
}
|
||||
|
||||
.admin-workbench .workbench-metric {
|
||||
min-height: var(--workbench-metric-min-height);
|
||||
box-sizing: border-box;
|
||||
background: linear-gradient(145deg, var(--workbench-surface) 58%, var(--workbench-surface-tint));
|
||||
}
|
||||
|
||||
.admin-workbench .workbench-panel.is-flush {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.admin-workbench .workbench-panel.is-compact {
|
||||
padding: var(--workbench-compact-card-space);
|
||||
}
|
||||
|
||||
.admin-workbench .workbench-metric.is-compact {
|
||||
min-height: 0;
|
||||
padding: var(--workbench-compact-card-space);
|
||||
}
|
||||
|
||||
.admin-workbench .workbench-metric.is-compact .workbench-metric-heading {
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.admin-workbench .workbench-metric.is-compact .workbench-metric-label {
|
||||
font-size: var(--workbench-note-size);
|
||||
}
|
||||
|
||||
.admin-workbench .workbench-metric.is-compact .workbench-metric-value {
|
||||
margin: 10px 0 6px;
|
||||
}
|
||||
|
||||
.admin-workbench .workbench-metric.is-compact .workbench-metric-value > strong {
|
||||
font-size: var(--workbench-compact-number-size);
|
||||
}
|
||||
|
||||
.admin-workbench .workbench-icon.is-compact {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: var(--workbench-tab-radius);
|
||||
}
|
||||
|
||||
.admin-workbench .workbench-icon.is-compact > svg {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
}
|
||||
|
||||
.admin-workbench .workbench-metric-heading {
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.admin-workbench .workbench-icon {
|
||||
display: inline-flex;
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
flex: 0 0 auto;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: var(--workbench-icon-radius);
|
||||
background: var(--workbench-accent-soft);
|
||||
color: var(--workbench-accent);
|
||||
}
|
||||
|
||||
.admin-workbench .workbench-icon > svg {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.admin-workbench .workbench-metric-label {
|
||||
min-width: 0;
|
||||
margin: 0;
|
||||
color: var(--workbench-text);
|
||||
font-size: var(--workbench-label-size);
|
||||
font-weight: 600;
|
||||
line-height: 1.5;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.admin-workbench .workbench-metric-value {
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
flex-wrap: wrap;
|
||||
align-items: baseline;
|
||||
gap: 4px 6px;
|
||||
margin: 20px 0 12px;
|
||||
}
|
||||
|
||||
.admin-workbench .workbench-metric-value > strong {
|
||||
min-width: 0;
|
||||
color: var(--workbench-number);
|
||||
font-size: var(--workbench-number-size);
|
||||
font-weight: 700;
|
||||
font-variant-numeric: tabular-nums;
|
||||
line-height: 1.2;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.admin-workbench .workbench-metric-value > span {
|
||||
color: var(--workbench-muted);
|
||||
font-size: var(--workbench-note-size);
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.admin-workbench .workbench-note,
|
||||
.admin-workbench .workbench-status {
|
||||
display: block;
|
||||
margin: 4px 0;
|
||||
color: var(--workbench-muted);
|
||||
font-size: var(--workbench-note-size);
|
||||
line-height: 1.6;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.admin-workbench .workbench-panel.workbench-tone {
|
||||
border-color: var(--workbench-accent-border);
|
||||
}
|
||||
|
||||
.admin-workbench .workbench-chart {
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
height: 100%;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.admin-workbench .workbench-chart-heading {
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
padding-bottom: 12px;
|
||||
}
|
||||
|
||||
.admin-workbench .workbench-chart-title {
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin: 0;
|
||||
color: var(--workbench-text);
|
||||
font-size: var(--workbench-label-size);
|
||||
font-weight: 600;
|
||||
line-height: 1.5;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.admin-workbench .workbench-chart-title::before {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
flex: 0 0 auto;
|
||||
border-radius: 50%;
|
||||
background: var(--workbench-accent);
|
||||
content: '';
|
||||
}
|
||||
|
||||
.admin-workbench .workbench-chart-actions {
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: 10px;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.admin-workbench .workbench-chart-actions .el-button {
|
||||
--el-button-text-color: var(--workbench-primary);
|
||||
--el-button-hover-text-color: var(--workbench-primary-active);
|
||||
--el-button-disabled-text-color: var(--workbench-primary-disabled);
|
||||
font-size: var(--workbench-note-size);
|
||||
}
|
||||
|
||||
.admin-workbench .workbench-chart-actions .el-button + .el-button {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.admin-workbench .workbench-chart-canvas {
|
||||
min-width: 0;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.admin-workbench .workbench-chart-empty {
|
||||
display: flex;
|
||||
min-height: 240px;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 12px;
|
||||
padding: var(--workbench-card-space);
|
||||
border-radius: var(--workbench-icon-radius);
|
||||
background: var(--workbench-surface-soft);
|
||||
color: var(--workbench-muted);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.admin-workbench .workbench-chart-empty.is-compact {
|
||||
min-height: 0;
|
||||
padding: var(--workbench-compact-card-space);
|
||||
}
|
||||
|
||||
.admin-workbench .workbench-chart-empty-icon {
|
||||
display: grid;
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
flex: 0 0 auto;
|
||||
place-items: center;
|
||||
border: 1px solid var(--workbench-border);
|
||||
border-radius: var(--workbench-icon-radius);
|
||||
background: var(--workbench-surface);
|
||||
color: var(--workbench-muted);
|
||||
}
|
||||
|
||||
.admin-workbench .workbench-chart-empty p {
|
||||
max-width: 28em;
|
||||
margin: 0;
|
||||
color: var(--workbench-muted);
|
||||
font-size: var(--workbench-note-size);
|
||||
line-height: 1.7;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
/* DataTable retains ownership of toolbar size, controls and query layout. */
|
||||
.admin-workbench .workbench-toolbar,
|
||||
.admin-workbench .workbench-table {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
/* DataTable forwards attrs to its inner table, so opt in through this wrapper. */
|
||||
.admin-workbench .workbench-toolbar > .data-table,
|
||||
.admin-workbench .workbench-table > .data-table {
|
||||
--list-card-accent: var(--workbench-primary);
|
||||
--list-card-border: var(--workbench-border);
|
||||
--list-card-muted-bg: var(--workbench-surface-soft);
|
||||
--list-card-radius: var(--workbench-radius);
|
||||
min-width: 0;
|
||||
border-color: var(--workbench-border);
|
||||
border-radius: var(--workbench-radius);
|
||||
background: var(--workbench-surface);
|
||||
box-shadow: var(--workbench-shadow);
|
||||
}
|
||||
|
||||
.admin-workbench .workbench-toolbar > .data-table > .data-table__toolbar,
|
||||
.admin-workbench .workbench-table > .data-table > .data-table__toolbar {
|
||||
border-color: var(--workbench-border);
|
||||
background: var(--workbench-surface);
|
||||
}
|
||||
|
||||
/* Only title-free list toolbars opt in; keep controls and wrapping intact. */
|
||||
.admin-workbench .workbench-table.is-compact-toolbar > .data-table > .data-table__toolbar {
|
||||
min-height: 52px;
|
||||
padding-top: 8px;
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
|
||||
.admin-workbench .workbench-table > .data-table > .el-table {
|
||||
--el-table-border-color: var(--workbench-border);
|
||||
--el-table-header-bg-color: var(--workbench-surface-soft);
|
||||
--el-table-header-text-color: var(--workbench-muted);
|
||||
--el-table-row-hover-bg-color: var(--workbench-accent-soft);
|
||||
--el-table-current-row-bg-color: var(--workbench-accent-soft);
|
||||
--el-table-text-color: var(--workbench-text);
|
||||
color: var(--workbench-text);
|
||||
}
|
||||
|
||||
.admin-workbench .workbench-table .el-table th.el-table__cell {
|
||||
color: var(--workbench-muted);
|
||||
}
|
||||
|
||||
.admin-workbench .workbench-table .el-table .density-action:not([data-tone]),
|
||||
.admin-workbench .workbench-table .el-table .density-action[data-tone='primary'] {
|
||||
--action-color: var(--workbench-primary);
|
||||
--action-border: var(--workbench-accent-border);
|
||||
--action-hover-bg: var(--workbench-accent-soft);
|
||||
}
|
||||
|
||||
.admin-workbench .workbench-table .data-table .data-table__page-button[aria-current='page']:not(:disabled),
|
||||
.admin-workbench .workbench-table .data-table__business-actions .el-button--primary:not(:disabled) {
|
||||
box-shadow: var(--workbench-shadow);
|
||||
}
|
||||
|
||||
/* Keep selection, focus and disabled states consistent for existing native tabs. */
|
||||
.admin-workbench .workbench-tabs {
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.admin-workbench .workbench-tabs > button {
|
||||
display: inline-flex;
|
||||
min-height: var(--admin-tab-height);
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 6px;
|
||||
padding: 6px 14px;
|
||||
border: 1px solid transparent;
|
||||
border-radius: var(--workbench-tab-radius);
|
||||
background: transparent;
|
||||
color: var(--workbench-muted);
|
||||
font: inherit;
|
||||
font-weight: 500;
|
||||
line-height: 1.5;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.admin-workbench .workbench-tabs > button:hover:not(:disabled):not([aria-pressed='true']):not(.is-active) {
|
||||
background: var(--workbench-surface-soft);
|
||||
color: var(--workbench-primary);
|
||||
}
|
||||
|
||||
.admin-workbench .workbench-tabs > button[aria-pressed='true'],
|
||||
.admin-workbench .workbench-tabs > button.is-active {
|
||||
border-color: var(--workbench-accent-border);
|
||||
background: var(--workbench-accent-soft);
|
||||
color: var(--workbench-primary);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.admin-workbench .workbench-tabs > button:focus-visible,
|
||||
.admin-workbench .workbench-input:focus-visible {
|
||||
outline: 2px solid var(--workbench-primary);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
.admin-workbench .workbench-tabs > button:disabled {
|
||||
color: var(--workbench-primary-disabled);
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.admin-workbench .workbench-input {
|
||||
min-height: var(--admin-control-height);
|
||||
box-sizing: border-box;
|
||||
padding: 6px 8px;
|
||||
border: 1px solid var(--workbench-border);
|
||||
border-radius: var(--workbench-tab-radius);
|
||||
background: var(--workbench-surface);
|
||||
color: var(--workbench-text);
|
||||
font: inherit;
|
||||
}
|
||||
|
||||
.admin-workbench .workbench-input:disabled {
|
||||
background: var(--workbench-surface-soft);
|
||||
color: var(--workbench-muted);
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* Teleported dialogs opt in on their own root; retain the density frame geometry. */
|
||||
.admin-workbench.is-dialog.el-dialog:not(.is-fullscreen) {
|
||||
border-radius: var(--workbench-radius);
|
||||
}
|
||||
|
||||
.admin-workbench.is-dialog.el-dialog .el-dialog__header,
|
||||
.admin-workbench.is-dialog.el-dialog .el-dialog__footer {
|
||||
border-color: var(--workbench-border);
|
||||
background: var(--workbench-surface);
|
||||
}
|
||||
|
||||
.admin-workbench.is-dialog.el-dialog .el-dialog__title {
|
||||
color: var(--workbench-text);
|
||||
}
|
||||
|
||||
.admin-workbench .workbench-evidence table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: var(--admin-font-body);
|
||||
}
|
||||
|
||||
.admin-workbench .workbench-evidence th,
|
||||
.admin-workbench .workbench-evidence td {
|
||||
border: 1px solid var(--workbench-border);
|
||||
text-align: left;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.admin-workbench .workbench-evidence table:not(.density-table) :is(th, td) {
|
||||
padding: var(--admin-space-1) var(--admin-space-3);
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.admin-workbench .workbench-evidence th {
|
||||
background: var(--workbench-surface-soft);
|
||||
color: var(--workbench-muted);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.admin-workbench .workbench-notice {
|
||||
margin: 0;
|
||||
padding: 10px 14px;
|
||||
border: 1px solid var(--workbench-accent-border);
|
||||
border-radius: var(--workbench-icon-radius);
|
||||
background: var(--workbench-accent-soft);
|
||||
color: var(--workbench-text);
|
||||
font-size: var(--workbench-note-size);
|
||||
line-height: 1.6;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
/* State labels remain the source of meaning; color only reinforces them. */
|
||||
.admin-workbench .workbench-step {
|
||||
min-width: 0;
|
||||
padding: var(--workbench-compact-card-space);
|
||||
border: 1px solid var(--workbench-border);
|
||||
border-radius: var(--workbench-icon-radius);
|
||||
background: var(--workbench-surface-soft);
|
||||
color: var(--workbench-muted);
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.admin-workbench .workbench-step-index {
|
||||
display: grid;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
flex: 0 0 auto;
|
||||
place-items: center;
|
||||
border: 1px solid var(--workbench-border);
|
||||
border-radius: 50%;
|
||||
background: var(--workbench-surface);
|
||||
color: var(--workbench-muted);
|
||||
font-size: var(--workbench-note-size);
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.admin-workbench .workbench-step[data-state='active'],
|
||||
.admin-workbench .workbench-step[data-state='done'] {
|
||||
border-color: var(--workbench-accent-border);
|
||||
background: var(--workbench-accent-soft);
|
||||
color: var(--workbench-accent);
|
||||
}
|
||||
|
||||
.admin-workbench .workbench-step[data-state='active'] .workbench-step-index,
|
||||
.admin-workbench .workbench-step[data-state='done'] .workbench-step-index {
|
||||
border-color: var(--workbench-accent);
|
||||
background: var(--workbench-accent);
|
||||
color: var(--workbench-surface);
|
||||
}
|
||||
|
||||
.admin-workbench .el-button--primary:not(.is-plain):not(.is-link):not(.is-text) {
|
||||
--el-button-bg-color: var(--workbench-primary);
|
||||
--el-button-border-color: var(--workbench-primary);
|
||||
--el-button-hover-bg-color: var(--workbench-primary-hover);
|
||||
--el-button-hover-border-color: var(--workbench-primary-hover);
|
||||
--el-button-active-bg-color: var(--workbench-primary-active);
|
||||
--el-button-active-border-color: var(--workbench-primary-active);
|
||||
--el-button-disabled-bg-color: var(--workbench-primary-disabled);
|
||||
--el-button-disabled-border-color: var(--workbench-primary-disabled);
|
||||
}
|
||||
|
||||
.admin-workbench .workbench-skeleton {
|
||||
display: block;
|
||||
min-width: 0;
|
||||
height: var(--workbench-metric-min-height);
|
||||
border: 1px solid var(--workbench-border);
|
||||
border-radius: var(--workbench-radius);
|
||||
background: var(--workbench-surface-soft);
|
||||
animation: admin-workbench-loading 1.6s ease-in-out infinite alternate;
|
||||
}
|
||||
|
||||
@keyframes admin-workbench-loading {
|
||||
from { opacity: 0.55; }
|
||||
to { opacity: 1; }
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.admin-workbench .workbench-skeleton {
|
||||
animation: none;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
import { readFileSync } from 'node:fs'
|
||||
import { resolve } from 'node:path'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { compileStyle, parse } from 'vue/compiler-sfc'
|
||||
import { adminWorkbenchChartTheme } from './adminWorkbenchChartTheme'
|
||||
|
||||
const read = (path: string) => readFileSync(resolve(process.cwd(), 'src', path), 'utf8')
|
||||
const tokens = read('styles/adminWorkbenchTokens.css')
|
||||
const styles = read('styles/adminWorkbench.css')
|
||||
const page = read('modules/self-operated/pages/OverviewView.vue')
|
||||
const listPages = [
|
||||
'modules/self-operated/components/AnalyticsView.vue',
|
||||
'modules/self-operated/components/InventoryTurnoverView.vue',
|
||||
]
|
||||
const consumers = [
|
||||
...listPages,
|
||||
'modules/self-operated/pages/InventoryView.vue',
|
||||
'modules/self-operated/components/SkuTrend.vue',
|
||||
'modules/self-operated/components/ChartDetailsDialog.vue',
|
||||
'modules/self-operated/pages/CollectionView.vue',
|
||||
'modules/self-operated/components/CollectionAccountMonitor.vue',
|
||||
'modules/self-operated/components/CollectionProgressGauge.vue',
|
||||
'modules/self-operated/components/CollectionTaskProgress.vue',
|
||||
]
|
||||
const root = (source: string) => {
|
||||
const result = compileStyle({ source, filename: 'workbench.css', id: 'workbench-contract' })
|
||||
expect(result.errors).toEqual([])
|
||||
return result.rawResult!.root
|
||||
}
|
||||
|
||||
describe('Admin workbench opt-in styles', () => {
|
||||
it('keeps canvas chart colors aligned with the public CSS palette', () => {
|
||||
const declarations = new Map<string, string>()
|
||||
root(tokens).walkRules(rule => {
|
||||
if (rule.selector !== '.admin-workbench') return
|
||||
rule.walkDecls(declaration => { declarations.set(declaration.prop, declaration.value) })
|
||||
})
|
||||
for (const name of ['primary', 'text', 'muted', 'border', 'surface'] as const) {
|
||||
expect(adminWorkbenchChartTheme.colors[name]).toBe(declarations.get(`--workbench-${name}`))
|
||||
}
|
||||
expect(adminWorkbenchChartTheme.colors.primarySoft).toBe(declarations.get('--workbench-accent-soft'))
|
||||
})
|
||||
|
||||
it('keeps lazy-loaded styles scoped to opted-in pages and dialog roots', () => {
|
||||
for (const source of [tokens, styles]) {
|
||||
root(source).walkRules(rule => {
|
||||
if (rule.parent?.type === 'atrule' && rule.parent.name.endsWith('keyframes')) return
|
||||
for (const selector of rule.selectors) {
|
||||
expect(selector).toMatch(/^\.admin-workbench(?:\s|\.|:|\[|$)/)
|
||||
expect(selector).not.toMatch(/:global|:root/)
|
||||
if (selector.includes('.el-dialog')) expect(selector).toMatch(/^\.admin-workbench\.is-dialog\.el-dialog/)
|
||||
}
|
||||
})
|
||||
root(source).walkDecls(declaration => {
|
||||
expect(declaration.prop).not.toMatch(/^--admin-/)
|
||||
expect(declaration.important).toBeFalsy()
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
it('resolves every workbench variable from the public tokens instead of page defaults', () => {
|
||||
const declared = new Set<string>()
|
||||
root(tokens).walkDecls(declaration => {
|
||||
if (declaration.prop.startsWith('--workbench-')) declared.add(declaration.prop)
|
||||
})
|
||||
const pageStyles = [page, ...consumers.map(read)].flatMap(source => parse(source).descriptor.styles.map(style => style.content)).join('\n')
|
||||
for (const source of [styles, pageStyles]) {
|
||||
root(source).walkDecls(declaration => {
|
||||
expect(declaration.prop).not.toMatch(/^--workbench-/)
|
||||
})
|
||||
for (const match of source.matchAll(/var\((--workbench-[\w-]+)/g)) {
|
||||
expect(declared.has(match[1]!)).toBe(true)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
it('opts list cards and teleported dialogs in on their actual rendering containers', () => {
|
||||
for (const file of listPages) {
|
||||
const source = read(file)
|
||||
expect(source).toContain("import '@/styles/adminWorkbench.css'")
|
||||
const template = parse(source).descriptor.template!.ast!
|
||||
type TemplateElement = Extract<(typeof template.children)[number], { type: 1 }>
|
||||
const classesOf = (node?: TemplateElement) => node?.props.flatMap(prop => prop.type === 6 && prop.name === 'class' ? prop.value?.content.split(/\s+/) ?? [] : []) ?? []
|
||||
let tables = 0
|
||||
const visit = (nodes: typeof template.children, ancestors: TemplateElement[]) => {
|
||||
for (const node of nodes) {
|
||||
if (node.type !== 1) continue
|
||||
if (node.tag === 'AdminPage') expect(classesOf(node)).toEqual(expect.arrayContaining(['admin-density', 'admin-workbench']))
|
||||
if (node.tag === 'DataTable') {
|
||||
expect(classesOf(ancestors.at(-1))).toContain('workbench-table')
|
||||
expect(classesOf(node)).not.toContain('workbench-table')
|
||||
tables++
|
||||
}
|
||||
if (node.tag === 'AppDialog') expect(classesOf(node)).toEqual(expect.arrayContaining(['admin-density-dialog', 'admin-workbench', 'is-dialog']))
|
||||
visit(node.children, [...ancestors, node])
|
||||
}
|
||||
}
|
||||
visit(template.children, [])
|
||||
expect(tables).toBe(1)
|
||||
}
|
||||
})
|
||||
|
||||
it('preserves complete metric values and the existing toolbar layout ownership', () => {
|
||||
root(styles).walkRules(rule => {
|
||||
if (rule.selector.includes('.workbench-metric')) {
|
||||
rule.walkDecls(declaration => {
|
||||
expect(['text-overflow', 'line-clamp', '-webkit-line-clamp']).not.toContain(declaration.prop)
|
||||
if (declaration.prop === 'overflow') expect(declaration.value).not.toBe('hidden')
|
||||
expect(declaration.prop).not.toBe('height')
|
||||
})
|
||||
}
|
||||
if (rule.selector.includes('.data-table__toolbar')) {
|
||||
rule.walkDecls(declaration => {
|
||||
if (rule.selector.includes('.workbench-table.is-compact-toolbar')) {
|
||||
expect(['min-height', 'padding-top', 'padding-bottom']).toContain(declaration.prop)
|
||||
} else {
|
||||
expect(declaration.prop).not.toMatch(/^(?:padding|height|min-height|display|gap)/)
|
||||
}
|
||||
})
|
||||
}
|
||||
if (rule.selector.includes('.is-dialog')) {
|
||||
rule.walkDecls(declaration => {
|
||||
expect(declaration.prop).not.toMatch(/^(?:width|height|min-height|max-height|padding|margin|overflow)/)
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,43 @@
|
||||
const fontFamily = '-apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif'
|
||||
|
||||
const colors = {
|
||||
primary: '#3784fa',
|
||||
primarySoft: '#eff6ff',
|
||||
primaryFaint: 'rgba(55,132,250,0.03)',
|
||||
primaryLight: '#5b9bfb',
|
||||
teal: '#169b9b',
|
||||
tealLight: '#46b5bc',
|
||||
green: '#18a46f',
|
||||
purple: '#8b5cf6',
|
||||
amber: '#bf850e',
|
||||
rose: '#e85e70',
|
||||
muted: '#64748b',
|
||||
text: '#1f2937',
|
||||
border: '#e5eaf2',
|
||||
axis: '#d8e2ef',
|
||||
unknown: '#a2adbf',
|
||||
surface: '#fff',
|
||||
}
|
||||
|
||||
/** Explicitly consumed by workbench charts; never registers a global ECharts theme. */
|
||||
export const adminWorkbenchChartTheme = {
|
||||
fontFamily,
|
||||
colors,
|
||||
tooltip: {
|
||||
backgroundColor: colors.surface,
|
||||
borderColor: colors.border,
|
||||
borderWidth: 1,
|
||||
borderRadius: 10,
|
||||
padding: 12,
|
||||
extraCssText: 'box-shadow: 0 8px 24px rgba(31, 55, 90, 0.1);',
|
||||
textStyle: { color: colors.text, fontSize: 12, fontFamily },
|
||||
},
|
||||
axisLabel: { color: colors.muted, fontSize: 12 },
|
||||
axisName: { color: colors.muted, fontSize: 12 },
|
||||
splitLine: {
|
||||
show: true,
|
||||
lineStyle: { type: 'dashed' as const, color: colors.border, opacity: 0.8 },
|
||||
},
|
||||
}
|
||||
|
||||
export type AdminWorkbenchChartTheme = typeof adminWorkbenchChartTheme
|
||||
@@ -0,0 +1,69 @@
|
||||
/* Blue-and-white workbench preview. Pages opt in without changing admin density. */
|
||||
.admin-workbench {
|
||||
--workbench-primary: #3784fa;
|
||||
--workbench-primary-hover: #5b9bfb;
|
||||
--workbench-primary-active: #276dd5;
|
||||
--workbench-primary-disabled: #a9c9fc;
|
||||
--workbench-number: #1890ff;
|
||||
--workbench-text: #1f2937;
|
||||
--workbench-muted: #64748b;
|
||||
--workbench-surface: #fff;
|
||||
--workbench-surface-soft: #f5f7fb;
|
||||
--workbench-surface-tint: #f8faff;
|
||||
--workbench-border: #e5eaf2;
|
||||
--workbench-shadow: 0 4px 18px rgb(31 55 90 / 4%);
|
||||
--workbench-radius: 16px;
|
||||
--workbench-icon-radius: 12px;
|
||||
--workbench-page-space: 24px;
|
||||
--workbench-section-gap: 16px;
|
||||
--workbench-grid-gap: 18px;
|
||||
--workbench-card-space: 20px;
|
||||
--workbench-metric-min-height: 186px;
|
||||
--workbench-title-size: 24px;
|
||||
--workbench-section-title-size: 18px;
|
||||
--workbench-label-size: 15px;
|
||||
--workbench-number-size: 34px;
|
||||
--workbench-compact-number-size: 26px;
|
||||
--workbench-compact-card-space: 14px;
|
||||
--workbench-tab-radius: 8px;
|
||||
--workbench-note-size: 13px;
|
||||
--workbench-accent: #3784fa;
|
||||
--workbench-accent-soft: #eff6ff;
|
||||
--workbench-accent-border: #dbeafe;
|
||||
}
|
||||
|
||||
.admin-workbench .workbench-tone[data-tone='blue'] {
|
||||
--workbench-accent: #3784fa;
|
||||
--workbench-accent-soft: #eff6ff;
|
||||
--workbench-accent-border: #dbeafe;
|
||||
}
|
||||
|
||||
.admin-workbench .workbench-tone[data-tone='teal'] {
|
||||
--workbench-accent: #169b9b;
|
||||
--workbench-accent-soft: #effcfa;
|
||||
--workbench-accent-border: #c7eeea;
|
||||
}
|
||||
|
||||
.admin-workbench .workbench-tone[data-tone='green'] {
|
||||
--workbench-accent: #18a46f;
|
||||
--workbench-accent-soft: #ecfdf5;
|
||||
--workbench-accent-border: #c7efdd;
|
||||
}
|
||||
|
||||
.admin-workbench .workbench-tone[data-tone='purple'] {
|
||||
--workbench-accent: #8b5cf6;
|
||||
--workbench-accent-soft: #f5f3ff;
|
||||
--workbench-accent-border: #e6ddfc;
|
||||
}
|
||||
|
||||
.admin-workbench .workbench-tone[data-tone='amber'] {
|
||||
--workbench-accent: #bf850e;
|
||||
--workbench-accent-soft: #fffbeb;
|
||||
--workbench-accent-border: #f5e2ad;
|
||||
}
|
||||
|
||||
.admin-workbench .workbench-tone[data-tone='rose'] {
|
||||
--workbench-accent: #e85e70;
|
||||
--workbench-accent-soft: #fff1f3;
|
||||
--workbench-accent-border: #f8d7df;
|
||||
}
|
||||
Reference in New Issue
Block a user