Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion admin-dashboard/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ Browser

## 技术与目录

运行时直接依赖只有 Next.js、React、Ant Design、Lucide 图标与 Ant SSR 样式运行时;没有 Ant Design Pro、Redux、Axios、Mock.js、Tailwind、Radix 或图表库。Lucide 与设计系统的图标契约一致,并按组件 tree-shaking。Pages Router 使用 `_document.tsx` 提取 Ant Design CSS-in-JS 样式,避免首屏闪烁。Next 16 的开发与生产构建显式使用 Webpack,以确保 Ant Design 与提取器共享同一个样式上下文;CI 会检查产物中存在真实 Ant 组件规则,而不只检查空的 style 标签
运行时直接依赖只有 Next.js、React、Ant Design、Lucide 图标与 Ant SSR 样式运行时;没有 Ant Design Pro、Redux、Axios、Mock.js、Tailwind、Radix 或图表库。Lucide 与设计系统的图标契约一致,并按组件 tree-shaking。Pages Router 使用 `_document.tsx` 提取 Ant Design CSS-in-JS 样式,避免首屏闪烁;浅色与深色使用独立的 Ant CSS variable scope,避免服务端浅色变量覆盖客户端深色状态。Next 16 的开发与生产构建显式使用 Webpack,以确保 Ant Design 与提取器共享同一个样式上下文;CI 会检查真实 Ant 组件规则、主题隔离和关键暗色文字对比度

```text
admin-dashboard/
Expand Down
33 changes: 32 additions & 1 deletion admin-dashboard/scripts/check-design-contract.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,16 @@ const fail = (message) => {
const expectText = (source, value, context) => {
if (!source.includes(value)) fail(`${context} is missing ${value}`);
};
const hexLuminance = (hex) => {
const channels = hex.match(/[\da-f]{2}/gi)?.map((value) => Number.parseInt(value, 16) / 255);
if (!channels || channels.length !== 3) fail(`invalid contrast color ${hex}`);
const linear = channels.map((value) => value <= 0.04045 ? value / 12.92 : ((value + 0.055) / 1.055) ** 2.4);
return 0.2126 * linear[0] + 0.7152 * linear[1] + 0.0722 * linear[2];
};
const contrast = (foreground, background) => {
const [lighter, darker] = [hexLuminance(foreground), hexLuminance(background)].sort((a, b) => b - a);
return (lighter + 0.05) / (darker + 0.05);
};

const globalCss = read('src/style/global.css');
const dashboardCss = read('src/style/dashboard.module.css');
Expand All @@ -21,7 +31,11 @@ expectText(documentSource, "matchMedia('(prefers-color-scheme: dark)')", 'pre-pa
expectText(documentSource, "localStorage.getItem('waitqueue-theme')", 'legacy theme migration');
expectText(documentSource, "dataset.themeReady=mode==='dark'?'false':'true'", 'dark first-paint guard');
expectText(globalCss, "[data-theme-ready='false']", 'dark first-paint CSS guard');
expectText(theme, 'colorTextPlaceholder: colors.foregroundSubtle', 'accessible placeholder mapping');
expectText(theme, 'colorTextPlaceholder: supportingText', 'accessible placeholder mapping');
expectText(theme, 'cssVar: { key: `waitqueue-${mode}` }', 'isolated light and dark Ant variable scopes');
expectText(theme, 'defaultColor: colors.foreground', 'readable Ant button mapping');
expectText(theme, 'itemColor: colors.foregroundMuted', 'readable Ant navigation mapping');
expectText(theme, 'lastItemColor: colors.foreground', 'readable Ant breadcrumb mapping');
expectText(theme, "dataset.themeReady = 'true'", 'hydrated theme readiness');

const requiredTokens = [
Expand All @@ -32,6 +46,7 @@ const requiredTokens = [
'--ui-primary:',
'--ui-ai:',
'--ui-border:',
'--ui-control-border:',
'--ui-font-sans:',
'--ui-space-4:',
'--ui-radius-lg:',
Expand All @@ -43,6 +58,22 @@ const requiredTokens = [
];
for (const token of requiredTokens) expectText(globalCss, token, 'semantic token contract');

const darkBlock = globalCss.match(/:root\[data-theme='dark'\]\s*{([\s\S]*?)\n}/)?.[1];
if (!darkBlock) fail('dark semantic token block is missing');
const darkToken = (name) => {
const value = darkBlock.match(new RegExp(`${name}:\\s*(#[\\da-f]{6})`, 'i'))?.[1];
if (!value) fail(`dark semantic token is missing ${name}`);
return value;
};
const darkSurface = darkToken('--ui-surface-raised');
for (const [role, minimum] of [['--ui-foreground', 7], ['--ui-foreground-muted', 4.5], ['--ui-primary', 4.5]]) {
const ratio = contrast(darkToken(role), darkSurface);
if (ratio < minimum) fail(`${role} dark contrast ${ratio.toFixed(2)} is below ${minimum}`);
}
if (contrast(darkToken('--ui-control-border'), darkSurface) < 3) {
fail('dark control boundary contrast is below 3');
}

if (/#[\da-f]{3,8}\b/i.test(dashboardCss) || /#[\da-f]{3,8}\b/i.test(page)) {
fail('product layout must use semantic variables instead of raw palette values');
}
Expand Down
74 changes: 41 additions & 33 deletions admin-dashboard/src/style/dashboard.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@
width: 100%;
margin: 0 !important;
overflow: visible;
font-size: 10px;
font-size: 12px;
font-weight: 600;
line-height: 1.2;
text-align: center;
Expand Down Expand Up @@ -182,13 +182,13 @@
.catalogHeaderCopy small {
margin-top: 1px;
color: var(--ui-foreground-muted);
font-size: 11px;
font-size: 12px;
}

.catalogHeader :global(.ant-badge-count) {
color: var(--ui-primary-foreground);
box-shadow: none;
font-size: 11px;
font-size: 12px;
}

.catalogSearch {
Expand All @@ -197,7 +197,7 @@

.catalogSearch :global(.ant-input-affix-wrapper) {
background: var(--ui-surface-raised);
border-color: var(--ui-border);
border-color: var(--ui-control-border);
}

.catalogSearch :global(.ant-input-prefix) {
Expand All @@ -210,7 +210,7 @@
justify-content: space-between;
padding: 10px 18px 7px;
color: var(--ui-foreground-muted);
font-size: 11px;
font-size: 12px;
font-weight: 650;
}

Expand Down Expand Up @@ -253,7 +253,7 @@
.queueNav > button.queueNavActive {
color: var(--ui-foreground);
background: var(--ui-surface-raised);
border-color: var(--ui-border);
border-color: var(--ui-control-border);
box-shadow: inset 2px 0 var(--ui-primary), var(--ui-shadow-sm);
}

Expand Down Expand Up @@ -305,7 +305,7 @@
overflow: hidden;
margin-top: 3px;
color: var(--ui-foreground-muted);
font-size: 11px;
font-size: 12px;
text-overflow: ellipsis;
white-space: nowrap;
}
Expand All @@ -325,7 +325,7 @@

.queueNavCount small {
color: var(--ui-foreground-muted);
font-size: 10px;
font-size: 12px;
}

.catalogEmpty {
Expand All @@ -349,7 +349,7 @@
justify-content: center;
gap: 5px;
color: var(--ui-foreground-muted);
font-size: 11px;
font-size: 12px;
}

.mainLayout {
Expand Down Expand Up @@ -406,8 +406,8 @@
}

.updated {
color: var(--ui-foreground-subtle);
font-size: 11px;
color: var(--ui-foreground-muted);
font-size: 12px;
white-space: nowrap;
}

Expand Down Expand Up @@ -496,7 +496,7 @@
align-items: center;
gap: 10px;
margin-top: 12px;
color: var(--ui-foreground-subtle);
color: var(--ui-foreground-muted);
font-size: 12px;
}

Expand Down Expand Up @@ -612,15 +612,15 @@

.metricCard :global(.ant-statistic-content-suffix) {
margin-left: 5px;
color: var(--ui-foreground-subtle);
color: var(--ui-foreground-muted);
font-size: 12px;
font-weight: 500;
}

.metricCard > :global(.ant-card-body) > p {
min-height: 20px;
margin: 8px 0 0;
color: var(--ui-foreground-subtle);
color: var(--ui-foreground-muted);
font-size: 12px;
}

Expand Down Expand Up @@ -689,8 +689,8 @@

.cardHeading small {
margin-top: 2px;
color: var(--ui-foreground-subtle);
font-size: 11px;
color: var(--ui-foreground-muted);
font-size: 12px;
font-weight: 400;
}

Expand All @@ -708,6 +708,14 @@
border-start-end-radius: 0 !important;
}

.queueHealthCard :global(.ant-btn-link) {
color: var(--ui-primary);
}

.queueHealthCard :global(.ant-btn-link:hover) {
color: var(--ui-primary-hover);
}

.queueIdentity {
display: flex;
min-width: 0;
Expand Down Expand Up @@ -737,8 +745,8 @@

.queueIdentity span {
overflow: hidden;
color: var(--ui-foreground-subtle);
font-size: 11px;
color: var(--ui-foreground-muted);
font-size: 12px;
text-overflow: ellipsis;
white-space: nowrap;
}
Expand All @@ -764,8 +772,8 @@

.tableMetric small {
margin-top: 2px;
color: var(--ui-foreground-subtle);
font-size: 11px;
color: var(--ui-foreground-muted);
font-size: 12px;
}

.capacityCell {
Expand All @@ -786,7 +794,7 @@
}

.dangerText {
color: var(--ui-danger) !important;
color: color-mix(in srgb, var(--ui-danger) 92%, var(--ui-foreground)) !important;
}

.mobileQueueList,
Expand Down Expand Up @@ -835,7 +843,7 @@
color: var(--ui-foreground-muted);
background: var(--ui-surface-inset);
border-radius: 10px;
font-size: 11px;
font-size: 12px;
line-height: 1.5;
}

Expand Down Expand Up @@ -877,8 +885,8 @@
}

.capacitySummary small {
color: var(--ui-foreground-subtle);
font-size: 11px;
color: var(--ui-foreground-muted);
font-size: 12px;
}

.scheduleList {
Expand Down Expand Up @@ -989,8 +997,8 @@
justify-content: space-between;
gap: 12px;
margin: 13px 0;
color: var(--ui-foreground-subtle);
font-size: 11px;
color: var(--ui-foreground-muted);
font-size: 12px;
}

.pagination {
Expand Down Expand Up @@ -1062,9 +1070,9 @@
.healthCopy small {
overflow: hidden;
margin-top: 2px;
color: var(--ui-foreground-subtle);
color: var(--ui-foreground-muted);
font-family: var(--ui-font-mono);
font-size: 10px;
font-size: 12px;
text-overflow: ellipsis;
white-space: nowrap;
}
Expand Down Expand Up @@ -1114,13 +1122,13 @@
gap: 20px;
margin-top: 28px;
padding: 14px 2px 0;
color: var(--ui-foreground-subtle);
color: var(--ui-foreground-muted);
border-top: 1px solid var(--ui-border);
font-size: 11px;
font-size: 12px;
}

.footer code {
font-size: 10px;
font-size: 11px;
}

.formGrid {
Expand Down Expand Up @@ -1379,8 +1387,8 @@
}

.mobileQueueMetrics small {
color: var(--ui-foreground-subtle);
font-size: 10px;
color: var(--ui-foreground-muted);
font-size: 12px;
}

.mobileQueueMetrics b {
Expand Down
2 changes: 2 additions & 0 deletions admin-dashboard/src/style/global.css
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
--ui-foreground-subtle: #6c6e67;
--ui-border: #d9d4ca;
--ui-border-strong: #c6c1b7;
--ui-control-border: #929088;
--ui-primary: #5f735e;
--ui-primary-hover: #50644f;
--ui-primary-foreground: #fbfdf8;
Expand Down Expand Up @@ -87,6 +88,7 @@
--ui-foreground-subtle: #93948c;
--ui-border: #3f423c;
--ui-border-strong: #555950;
--ui-control-border: #737b6e;
--ui-primary: #a1b69d;
--ui-primary-hover: #b2c6ae;
--ui-primary-foreground: #1c251c;
Expand Down
Loading