diff --git a/design-qa.md b/design-qa.md index 0dce641..c7bfb6c 100644 --- a/design-qa.md +++ b/design-qa.md @@ -158,3 +158,123 @@ The `type-check` script now uses project-reference mode (`vue-tsc -b --noEmit`) Design-QA final result: passed final result: passed + +--- + +# Service Dashboard Design QA + +## Evidence + +- Source visual truth: `docs/superpowers/specs/assets/service-dashboard-approved-1440.png` +- Browser-rendered implementation: `docs/superpowers/specs/assets/service-dashboard-implementation-1440.png` +- Viewport: 1440 × 1024 +- State: authenticated `admin` user on `/dashboard`; service dashboard navigation active; 7-day chart visible; four training tasks visible. + +## Full-view comparison + +The implementation preserves the approved composition: the product shell stays intact, the service dashboard is the active navigation item, the platform-health summary spans the top, the grouped training chart occupies the wide middle track, service health occupies the narrow track, and the training-task table spans the bottom. The three bar series, dual axes, dates, values, service counts, task names, statuses, progress, accuracy, and actions match the selected mock. + +A separate focused crop was not required because both source and implementation evidence are full-resolution desktop captures at a readable scale; the chart labels, axis units, service rows, and every task-table column are legible in the full-view comparison. + +## Required fidelity surfaces + +- Fonts and typography: the existing Inter/system/PingFang stack is retained. Heading, section title, metric, table header, and muted-copy weights and sizes match the selected direction. +- Spacing and layout rhythm: 24px page padding, 14px section gaps, 10px panel radii, light separators, and the wide-chart/narrow-status grid preserve the selected hierarchy. The implementation uses the repository's 240px sidebar and 60px header exactly. +- Colors and visual tokens: white page canvas, `#f3f5f8` shell, indigo `#4f46e5`, green `#10b981`, amber `#f59e0b`, red `#ef4444`, and slate text are aligned with the source and current product tokens. +- Image and icon fidelity: the page contains no decorative raster imagery. The supplied product logo is preserved and existing Font Awesome icons are used consistently; no emoji, handcrafted SVG, placeholder image, or CSS illustration was introduced. +- Copy and content: dashboard title, health summary, chart legend and units, service states, task names, task status, model names, progress, accuracy, timestamps, and action labels match the approved design. + +## Interaction and runtime checks + +- Login with the existing `admin` credentials navigated to `/dashboard`, confirming the requested default entry behavior. +- ECharts rendered one canvas; hovering 07/10 exposed the tooltip values: training count 18, GPU count 7, and average accuracy 91%. +- “查看全部任务” navigated to `/fine-tune` and browser back restored `/dashboard`. +- The first “查看详情” action navigated to `/training-log/103942` and browser back restored `/dashboard`. +- Browser console errors: none. +- `npm run test:default-dashboard`: passed. +- `npm run test:dashboard`: passed. +- `npx vite build`: passed. + +## Comparison history + +### Iteration 1 — passed + +No actionable P0/P1/P2 differences remain. The only intentional product constraint is that the sidebar active background follows the repository's current neutral active token instead of the slightly bluer tint produced by ImageGen; location, contrast, label, and active-state clarity remain equivalent. + +## Validation gap + +`npm run type-check` remains blocked by pre-existing TypeScript errors in the mock adapter, dataset mock typing, data-process list, evaluation tabs, and fine-tune views. No reported error points to `DashboardView.vue`, the ECharts registration, router defaults, login redirect, or dashboard regression scripts. The direct Vite production build succeeds. + +Design-QA final result: passed + +final result: passed + +## Compact dashboard revision + +- User annotation reference: `/var/folders/nk/yks07zp14wb4rv3jqq0pt_4h0000gn/T/codex-clipboard-43f63327-063f-47da-9e96-31b53cebc49d.png` +- Revised implementation screenshot: `docs/superpowers/specs/assets/service-dashboard-compact-1440.png` +- Viewport: 1440 × 1024 +- State: authenticated dashboard, compact layout, redundant title/action row removed. + +### Iteration 2 — passed + +The annotated header row containing the duplicate “服务看板” title, subtitle, and “查看告警” action was removed entirely. Section gaps, overview height, health icon, metric type, chart height, service rows, task heading, and task rows were reduced by roughly 10%–15%. The result preserves chart labels, dual-axis readability, service-state text, task progress, accuracy, and all task actions while bringing the primary content closer to the top of the page. + +- ECharts tooltip remains functional after the height reduction and reports all three 07/10 series values. +- The revised page contains no browser console errors. +- The full-resolution comparison makes the removed annotation target and the compact replacement legible; no focused crop is necessary. +- `npm run test:dashboard`, `npm run test:default-dashboard`, and `npx vite build` pass. + +Design-QA final result: passed + +final result: passed + +## One-screen dashboard revision + +- User annotation reference: `/var/folders/nk/yks07zp14wb4rv3jqq0pt_4h0000gn/T/codex-clipboard-3c166ffc-7243-4e57-94fc-48949599c4f1.png` +- Revised implementation screenshot: `docs/superpowers/specs/assets/service-dashboard-one-screen-1440x768.png` +- Viewport: 1440 × 768 +- State: authenticated dashboard with the desktop low-height compact rules active. + +### Iteration 3 — passed + +The platform-status block was reduced again, including its container padding, inner gap, health icon, status copy, metric labels, and metric values. The chart, service rows, task rows, and page-canvas padding now use a dedicated `max-height: 900px` desktop mode. The dashboard page canvas is constrained to the available application viewport so the outer content area does not introduce a vertical scrollbar. + +Browser measurements at 1440 × 768: + +- document overflow: false +- layout-content overflow: false +- page-canvas overflow: false +- dashboard overflow: false +- task section bottom: 653px within the 768px viewport +- ECharts tooltip: passed with all three series present +- browser console errors: none +- `npm run test:dashboard`, `npm run test:default-dashboard`, and `npx vite build`: passed + +Design-QA final result: passed + +final result: passed + +## Flexible middle-region revision + +- User annotation reference: `/var/folders/nk/yks07zp14wb4rv3jqq0pt_4h0000gn/T/codex-clipboard-daf2bbae-baea-418d-9962-7d0e1a1c219b.png` +- Revised implementation screenshot: `docs/superpowers/specs/assets/service-dashboard-flex-middle-1440x900.png` +- Viewport: 1440 × 900 +- State: authenticated dashboard with flexible middle-region growth. + +### Iteration 4 — passed + +The previous fixed-height middle row caused unused white space beneath the task table on taller screens. The dashboard now reserves compact intrinsic height for the platform summary and task table while allowing the chart/service row to consume all remaining viewport height. The ECharts canvas grows with that row, and the service-state rows distribute across the matching height. + +Browser measurements: + +- at 1440 × 768, chart height: 283px; no document, layout, canvas, or dashboard overflow +- at 1440 × 900, chart height: 415px; no document, layout, canvas, or dashboard overflow +- task table bottom at 1440 × 900: 868px within the 900px viewport +- ECharts tooltip: passed with all three series present +- browser console errors: none +- `npm run test:dashboard`, `npm run test:default-dashboard`, and `npx vite build`: passed + +Design-QA final result: passed + +final result: passed diff --git a/docs/superpowers/specs/assets/service-dashboard-approved-1440.png b/docs/superpowers/specs/assets/service-dashboard-approved-1440.png new file mode 100644 index 0000000..ffa4278 Binary files /dev/null and b/docs/superpowers/specs/assets/service-dashboard-approved-1440.png differ diff --git a/docs/superpowers/specs/assets/service-dashboard-compact-1440.png b/docs/superpowers/specs/assets/service-dashboard-compact-1440.png new file mode 100644 index 0000000..64a7736 Binary files /dev/null and b/docs/superpowers/specs/assets/service-dashboard-compact-1440.png differ diff --git a/docs/superpowers/specs/assets/service-dashboard-flex-middle-1440x900.png b/docs/superpowers/specs/assets/service-dashboard-flex-middle-1440x900.png new file mode 100644 index 0000000..dea5a69 Binary files /dev/null and b/docs/superpowers/specs/assets/service-dashboard-flex-middle-1440x900.png differ diff --git a/docs/superpowers/specs/assets/service-dashboard-implementation-1440.png b/docs/superpowers/specs/assets/service-dashboard-implementation-1440.png new file mode 100644 index 0000000..43e9894 Binary files /dev/null and b/docs/superpowers/specs/assets/service-dashboard-implementation-1440.png differ diff --git a/docs/superpowers/specs/assets/service-dashboard-one-screen-1440x768.png b/docs/superpowers/specs/assets/service-dashboard-one-screen-1440x768.png new file mode 100644 index 0000000..b46d89f Binary files /dev/null and b/docs/superpowers/specs/assets/service-dashboard-one-screen-1440x768.png differ diff --git a/frontend/package.json b/frontend/package.json index c0ef179..47da039 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -8,6 +8,8 @@ "build": "vue-tsc -b && vite build", "preview": "vite preview", "type-check": "vue-tsc -b --noEmit", + "test:default-dashboard": "node scripts/regression-default-dashboard.mjs", + "test:dashboard": "node scripts/regression-dashboard.mjs", "test:data-process-list": "node scripts/regression-data-process-list.mjs", "test:data-process-wizard": "node scripts/regression-data-process-wizard.mjs", "test:dataset-task-tab": "node scripts/regression-dataset-task-tab.mjs", diff --git a/frontend/scripts/regression-dashboard.mjs b/frontend/scripts/regression-dashboard.mjs new file mode 100644 index 0000000..15724a9 --- /dev/null +++ b/frontend/scripts/regression-dashboard.mjs @@ -0,0 +1,50 @@ +import assert from 'node:assert/strict' +import { readFile } from 'node:fs/promises' +import { fileURLToPath } from 'node:url' +import path from 'node:path' + +const scriptDir = path.dirname(fileURLToPath(import.meta.url)) +const [routerSource, dashboardSource, echartsSource, mainLayoutSource] = await Promise.all([ + readFile(path.resolve(scriptDir, '../src/router/index.ts'), 'utf8'), + readFile(path.resolve(scriptDir, '../src/views/dashboard/DashboardView.vue'), 'utf8'), + readFile(path.resolve(scriptDir, '../src/plugins/echarts.ts'), 'utf8'), + readFile(path.resolve(scriptDir, '../src/layouts/MainLayout.vue'), 'utf8'), +]) + +assert.match( + routerSource, + /path:\s*['"]dashboard['"][\s\S]*?DashboardView\.vue/, + '服务看板路由应使用独立 DashboardView', +) + +assert.match(echartsSource, /import\s*\{[^}]*BarChart[^}]*\}\s*from\s*['"]echarts\/charts['"]/, 'ECharts 未注册 BarChart') +assert.match(echartsSource, /use\(\[[\s\S]*?BarChart[\s\S]*?\]\)/, 'BarChart 未加入 ECharts 按需注册列表') + +for (const copy of [ + '平台运行状态', + '近 7 天训练统计', + '训练次数(次)', + 'GPU 使用数(个)', + '平均准确率(%)', + '服务状态', + '训练任务', + '查看全部任务', +]) { + assert.ok(dashboardSource.includes(copy), `服务看板缺少关键内容:${copy}`) +} + +assert.match(dashboardSource, /yAxis:\s*\[[\s\S]*?次数 \/ GPU 数[\s\S]*?准确率/, '柱状图应使用双 Y 轴表达不同单位') +assert.match(dashboardSource, /name:\s*['"]平均准确率(%)['"][\s\S]*?yAxisIndex:\s*1/, '准确率柱应绑定右侧百分比坐标轴') +assert.match(dashboardSource, /router\.push\(['"]\/fine-tune['"]\)/, '查看全部任务应进入模型微调列表') +assert.match(dashboardSource, /router\.push\(`\/training-log\/\$\{task\.id\}`\)/, '训练任务详情应进入训练日志页') +assert.doesNotMatch(dashboardSource, /class=["']dashboard-heading["']/, '服务看板不应重复展示页面标题栏') +assert.doesNotMatch(dashboardSource, /查看告警/, '服务看板不应保留冗余的顶部告警按钮') +assert.match(dashboardSource, /\.dashboard-view\s*\{[\s\S]*?gap:\s*10px;/, '服务看板应使用紧凑的区块间距') +assert.match(dashboardSource, /\.dashboard-middle\s*\{[\s\S]*?flex:\s*1 1 auto;[\s\S]*?min-height:\s*0;/, '中间区域应弹性占满剩余高度') +assert.match(dashboardSource, /\.training-chart\s*\{[\s\S]*?flex:\s*1 1 auto;[\s\S]*?height:\s*auto;[\s\S]*?min-height:\s*230px;/, '训练统计图应随可用空间自动拉伸') +assert.match(dashboardSource, /@media\s*\(max-height:\s*900px\)[\s\S]*?\.training-chart\s*\{[\s\S]*?min-height:\s*200px;/, '较矮桌面视口应保留图表最小可读高度') +assert.match(dashboardSource, /\.service-table\s*\{[\s\S]*?grid-template-rows:[^;]*repeat\(4,\s*minmax\(48px,\s*1fr\)\)/, '服务状态行应随中间区域同步拉伸') +assert.match(mainLayoutSource, /\.layout-content:has\(\.dashboard-view\)[\s\S]*?overflow-y:\s*hidden;/, '服务看板容器应避免产生页面纵向滚动条') +assert.match(mainLayoutSource, /\.page-canvas\s*\{[\s\S]*?flex:\s*1 1 auto;[\s\S]*?overflow:\s*hidden;/, '服务看板页面画布应约束在可用视口内') + +console.log('服务看板回归检查通过') diff --git a/frontend/scripts/regression-default-dashboard.mjs b/frontend/scripts/regression-default-dashboard.mjs new file mode 100644 index 0000000..f428c8f --- /dev/null +++ b/frontend/scripts/regression-default-dashboard.mjs @@ -0,0 +1,39 @@ +import { readFileSync } from 'node:fs' +import { resolve } from 'node:path' +import { fileURLToPath } from 'node:url' + +const root = resolve(fileURLToPath(new URL('..', import.meta.url))) + +function read(relativePath) { + return readFileSync(resolve(root, relativePath), 'utf8') +} + +function assert(condition, message) { + if (!condition) throw new Error(message) +} + +const router = read('src/router/index.ts') +const login = read('src/views/login/LoginView.vue') +const sidebar = read('src/components/AppSidebar.vue') + +assert( + router.includes("redirect: '/dashboard'"), + 'The authenticated root and fallback routes should default to 服务看板', +) + +assert( + router.includes("next('/dashboard')"), + 'An authenticated user visiting the login page should enter 服务看板', +) + +assert( + login.includes("router.push('/dashboard')"), + 'A successful login should navigate to 服务看板', +) + +assert( + sidebar.includes("route.path.split('/')[1] || 'dashboard'"), + 'The sidebar fallback active item should be 服务看板', +) + +console.log('default-dashboard regression checks passed') diff --git a/frontend/src/components/AppSidebar.vue b/frontend/src/components/AppSidebar.vue index 4016965..e19f269 100644 --- a/frontend/src/components/AppSidebar.vue +++ b/frontend/src/components/AppSidebar.vue @@ -1,5 +1,5 @@ + + + + diff --git a/frontend/src/views/login/LoginView.vue b/frontend/src/views/login/LoginView.vue index 9b5d799..bef2528 100644 --- a/frontend/src/views/login/LoginView.vue +++ b/frontend/src/views/login/LoginView.vue @@ -28,7 +28,7 @@ async function handleLogin() { try { await auth.login(loginForm.username, loginForm.password) ElMessage.success('登录成功') - router.push('/fine-tune') + router.push('/dashboard') } catch { // 拦截器已提示错误 } finally {