feat(admin): apply shared blue-white self-operated workbench style

This commit is contained in:
2026-10-20 19:16:47 +08:00
parent 9bfce44988
commit 464b72cff9
38 changed files with 1851 additions and 340 deletions
+5 -4
View File
@@ -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', () => {
+597
View File
@@ -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;
}
}
+128
View File
@@ -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)/)
})
}
})
})
})
+43
View File
@@ -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
+69
View File
@@ -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;
}