# PolyWeather Frontend UI 设计审查报告 > 审查日期:2026-05-10 | 审查范围:`frontend/` 全部组件、样式、布局 ## 整体评价 前端设计系统有**扎实的基础** — 完善的 CSS 自定义属性 token 体系、不错的暗色模式、合理的排版层级、以及语义化的颜色编码。但存在一些**工程性债务**需要关注。 最突出的 5 个问题: 1. `!important` 在浅色主题中泛滥(~960 行),维护成本极高 2. `font-weight: 760/850/950` 等无效值在 CSS 中大量使用 3. Geist 字体声明但从未加载,始终 fallback 到 Inter 4. 键盘导航和 focus-visible 样式缺失 5. 14 个不同的响应式断点值,无统一体系 --- ## 一、设计系统 & Token 体系 ### 优点 1. **完整的 CSS 自定义属性体系** (`globals.css:9-93`):拥有从背景色阶、文字色阶、语义色、阴影、间距、圆角、毛玻璃到动效的完整 token 体系,覆盖面广。 2. **4px 间距网格**:从 `--space-1: 4px` 到 `--space-12: 48px`,提供了系统化的间距基础。 3. **三层毛玻璃系统**:`--glass-blur-1/2/3` + `--glass-opacity-1/2/3` 的组合创造视觉深度层次,在暗色背景下效果出色。 4. **Tailwind 桥接**:`tailwind.config.ts` 提供了 `pw-bg-*`、`pw-text-*`、`pw-accent-*` 等语义化 utility class,兼顾了设计 token 的语义化和 Tailwind 的效率。 ### 问题 1. **"Fintech 3-Color" 名不副实** (`globals.css:25-27`): ```css --color-accent-primary: #4DA3FF; /* 蓝 */ --color-accent-secondary: #4DA3FF; /* 同上 — 完全一样 */ --color-accent-tertiary: #93C5FD; /* 浅蓝 */ ``` primary 和 secondary 是同一个颜色,实际只有 2 种不同色值。"3-Color" 声明具有误导性。 2. **Token 未充分利用**:大量 CSS Module 中硬编码了颜色值(如 `#4DA3FF`、`#E6EDF3`、`rgba(77, 163, 255, ...)`)而不是引用 CSS 变量。例如 `ScanTerminalCard.module.css` 中几乎每一行都是硬编码色值。 3. **shadcn/ui HSL token 遗留** (`globals.css:95-106`):保留了 shadcn 的 HSL token 但几乎未被使用,增加了 token 体系的认知负担。 --- ## 二、布局 & 响应式 ### 优点 1. **Scan Terminal 的 CSS Grid 布局** (`ScanTerminal.module.css:524-537`):`grid-template-columns: minmax(0, 1fr) minmax(380px, 420px)` 的 2 列布局干净、现代。 2. **毛玻璃面板系统**:所有主面板都有 `22px border-radius`、`backdrop-filter: blur(14px)`、渐变背景和微妙的阴影,视觉统一。 3. **Sticky detail rail**:右侧面板 `position: sticky; top: 16px; height: calc(100vh - 32px)` 保证了用户滚动时详情始终可见。 ### 问题 1. **两套布局系统并存**: - 旧版 "Home Intelligence" 使用 **absolute positioning** 定位 header/sidebar/map/panel - 新版 "Scan Terminal" 使用 **CSS Grid** - 两套 CSS Module 都在 `ScanTerminalDashboard.tsx` 中被 import 合并,增加了复杂度 2. **断点碎片化严重**:系统使用了 12+ 个不同断点值 (1680, 1480, 1360, 1240, 1100, 1020, 900, 820, 768, 720, 640, 600, 560, 520),没有统一的体系。同一个 tablet 过渡在一些模块用 768px,在另一些用 720px。 3. **Map View 中的 absolute positioning** (`DashboardShell.module.css`):使用 `top/right/bottom/left` 绝对定位来排列面板,脆弱且难以维护。 --- ## 三、色彩 & 对比度 ### 优点 1. **暗色模式基础扎实**:`#0B1220` 的深邃底色配合三层 radial gradient 营造了深度感和科技感。 2. **语义色系统清晰**:success/green、warning/amber、danger/red 的映射关系明确。 3. **渐变运用得当**:body 背景的三层 radial gradient(绿/紫/青)创造了微妙的大气光感。 ### 问题 1. **文字对比度偏低**: - 静音文字 `#6B7A90` 在 `#0B1220` 上的对比度约为 4.6:1,刚好达到 WCAG AA 标准但仍偏暗 - 大量 10-11px 的辅助文字使用 `#6B7A90`,可读性不足 2. **Accent Color 视觉突出度**:`#4DA3FF`(蓝色)在暗色背景上的人眼敏感度不如 `#00E0A4`(青绿色),但前者被用作主要强调色。 3. **语义色 class 命名不一致**:`accent-green` class 实际渲染为 `#4DA3FF`(蓝色),"green" 命名的 class 显示蓝色。 --- ## 四、字体排印 ### 优点 1. **字体选择合理**:Inter(正文) + JetBrains Mono(数据/代码) 是 fintech/数据密集类产品的标准选择。 2. **tabular-nums 广泛使用**:几乎所有温度数值都设置了 `font-variant-numeric: tabular-nums`,保证了数字对齐。 3. **层级明确**:从 10px 的 kicker 到 56px 的 hero temperature,形成了清晰的视觉层级。 ### 问题 1. **Geist 字体声明但未加载** (`layout.tsx:35-37`): - `globals.css` 声明了 `--font-display: "Geist", "Inter", ...` - 但 HTML 中只加载了 Inter 和 JetBrains Mono,Geist 永远不会生效 2. **非标准 font-weight 值泛滥**: - `font-weight: 760`、`850`、`860`、`880`、`900`、`950` 在 CSS 中大量出现 - Inter 字体只支持 300/400/500/600/700/800 - 非标准 weight 会被浏览器取整到最近支持值,视觉不可预测 - 例如 `font-weight: 950` 在 Inter 上实际渲染为 `800` 3. **字体大小偏小**:大量 UI 文字使用 10-11px,在高分辨率屏幕上可读性差。 4. **大写字母过多**:kicker/overline/chip 几乎全部使用 `text-transform: uppercase` + `letter-spacing: 0.08em`,降低了中文/双语场景下的可读性。 --- ## 五、组件设计 ### 优点 1. **City Decision Card 视觉层次优秀** (`ScanTerminalCard.module.css`):hero 区的渐变背景、蓝色左边框选中态、决策 band 的色彩编码(warm=red 边框、cold=green 边框、watch=amber 边框),信息密度高但不杂乱。 2. **Status Tags 色彩编码清晰**:green/blue/amber/red/muted 五种 tag 变体覆盖了数据状态的所有场景。 3. **双卡预测对比** (AI vs DEB):青色渐变卡 vs 蓝色边框卡的视觉区分让用户能一眼区分 AI 预测和模型预测。 4. **Decision Band 语义化**:`warm`(red) = 看涨、`cold`(green) = 看跌、`watch`(amber) = 待观察,视觉编码与交易语义对齐。 ### 问题 1. **Topbar 设计过于简单**:28px 标题 + 几个按钮,缺乏品牌标识(Logo)和视觉焦点。 2. **Tab 下划线指示器不够明显**:`2px` 高度 + `opacity: 0.8` 的蓝色下划线容易被忽略。 3. **按钮层级不够清晰**:`.scan-primary-button`(蓝紫渐变)、`.scan-ai-button`(青绿渐变)、`.scan-city-icon-button`(蓝色边框半透明)、`.scan-theme-button`(无边框无背景)四种视觉权重混在一起,用户难以判断优先级。 4. **空状态/加载状态设计不一致**: - 地图加载有精美的云/雷达/热力动画 - Scan terminal 加载是简单的脉冲 placeholder - 空状态是一个简单的文字居中块 - 缺乏统一的 loading/empty/error 设计规范 5. **Mobile Decision Card 采用 `
` 元素**:原生 `
/` 样式控制有限、动画困难,与桌面端的自定义折叠按钮体验不一致。 --- ## 六、深色/浅色主题 ### 优点 1. **浅色模式覆盖全面**:`ScanTerminalLightTheme.module.css` 约 960 行,覆盖了 scan terminal 的所有元素。 2. **localStorage 持久化**:主题选择保存在 `polyweather_scan_theme` 中,刷新不丢失。 3. **浅色配色方案合理**:从深色 `#0B1220` 到浅色 `#F7F9FC → #EEF2F7` 的映射关系合理。 ### 问题 1. **`!important` 严重滥用** (`ScanTerminalLightTheme.module.css`): - 几乎每条浅色规则都使用了 `!important` - 这是 specificity war 的症状,维护成本极高 - 多处出现 `!important` 叠加 2. **浅色主题分散在 8+ 个文件中**:缺乏集中管理,修改一个颜色需要跨多个文件搜索。 3. **浅色模式地图图块滤镜处理**:Leaflet tile 滤镜切换可能影响其他 overlay 的浅色适配。 --- ## 七、动效 & 过渡 ### 优点 1. **Cubic-bezier 缓动选择正确**:`(0.4, 0, 0.2, 1)` (Material standard) + `(0.16, 1, 0.3, 1)` (spring-like) 的组合符合现代 UI 动效标准。 2. **入场动画有层次**:detail panel 从右侧滑入 (400ms)、opportunity strip 有 120ms 延迟。 3. **Hover 微交互**:按钮 `translateY(-1px)`、卡片 `scale(1.002)`、边框颜色过渡等微交互提升了操作反馈感。 ### 问题 1. **`@keyframes spin` 重复定义 4 次**:在 `DashboardShell.module.css`、`DashboardModalGuide.module.css`、`DocsLayout.module.css`、`DashboardMap.module.css` 中各自定义了一次完全相同的 spin 动画。 2. **Loading 动画风格不统一**:`DashboardMap` 有复杂的 weather-themed 动画(radar swipe、cloud drift、thermal bars、wind shift),而 scan terminal 只有简单的扫光 placeholder。 3. **缺少 `prefers-reduced-motion` 支持**:没有任何 `@media (prefers-reduced-motion: reduce)` 的声明。 --- ## 八、代码组织 & 可维护性 ### 优点 1. **CSS Module 组件隔离**:每个组件有对应的 `.module.css`,样式作用域控制良好。 2. **命名约定一致**:CSS 类名使用 `scan-` 前缀 + kebab-case,全局可识别。 ### 问题 1. **CSS Module 堆叠模式过度耦合** (`ScanTerminalDashboard.tsx:143-159`): ```tsx const scanTerminalRootClassName = clsx( styles.root, dashboardHomeStyles.root, dashboardMapStyles.root, // ... 共 22 个 CSS Module 的 .root 合并 ); ``` 单个组件 import 了 22 个 CSS Module,破坏了 CSS Module 的隔离优势。 2. **`:global()` 绕过了 CSS Module 的哈希**:几乎所有规则都使用 `:global(.class-name)`,class 名不会被哈希。CSS Module 降级为"命名约定"工具。 3. **样式与逻辑耦合**:`ScanTerminalDashboard.tsx` 约 650 行,同时负责状态管理和渲染布局。 4. **重复的 CSS 变量声明**:`Dashboard.module.css` 中重新声明了 `--bg-primary` 等本地变量,与 `globals.css` 的全局 token 形成冗余。 --- ## 九、无障碍性 ### 优点 1. 部分元素有 aria-label(如 topbar 按钮、locale switch) 2. 主题切换按钮有 title 属性 3. ProFeaturePaywall 使用了 `role="dialog"` 和 `aria-modal="true"` ### 问题 1. **键盘导航不足**: - 城市卡片没有 `tabindex` 或 `role="button"` - Tab 切换缺少 `role="tablist"`/`role="tab"`/`aria-selected` - 折叠按钮缺少 `aria-expanded` 2. **焦点指示器不可见**:自定义按钮(如 `scan-theme-button`、`scan-city-icon-button`)没有 focus-visible 样式 3. **颜色不是唯一的信息传达方式**:风险等级、Market decision 的色彩编码缺少对应的文字标签或图标补充 4. **没有 skip-to-content 链接** 5. **10-11px 小字体对视力障碍用户不友好** --- ## 总结与优先级建议 ### 高优先级(影响用户体验和可维护性) | # | 问题 | 位置 | |---|------|------| | 1 | `!important` 滥用导致浅色主题不可维护 | `ScanTerminalLightTheme.module.css` | | 2 | 非标准 font-weight 值无效(760/850/950 等) | 多个 CSS Module | | 3 | Geist 字体声明但未加载 | `layout.tsx:35-37` / `globals.css:55` | | 4 | 键盘导航和 focus-visible 缺失 | 全局 | | 5 | 断点碎片化(14 个断点值无体系) | 所有响应式 CSS | ### 中优先级(影响设计一致性) | # | 问题 | 位置 | |---|------|------| | 6 | 22 个 CSS Module 堆叠耦合 | `ScanTerminalDashboard.tsx:143-159` | | 7 | Token 未充分利用(硬编码色值) | 多个 CSS Module | | 8 | `@keyframes spin` 重复定义 4 次 | 多个 CSS Module | | 9 | Loading 状态设计不一致 | `DashboardMap` vs `ScanTerminalState` | | 10 | 缺少 `prefers-reduced-motion` 支持 | 全局 | ### 低优先级(增强和优化) | # | 问题 | 位置 | |---|------|------| | 11 | "Fintech 3-Color" 实际只有 2 色 | `globals.css:25-27` | | 12 | Topbar 缺少 Logo/品牌标识 | `ScanTerminalDashboard.tsx` | | 13 | 按钮视觉层级不够清晰 | 多个组件 | | 14 | Shadcn UI 组件存在但未被使用 | `components/ui/` | | 15 | `--color-text-muted` 对比度刚达标 | `globals.css:21` | --- ## 修复路线图建议 1. **Phase 1** — 修复 `font-weight` 无效值:全局搜索 `font-weight: 760`、`850`、`860`、`880`、`900`、`950`,替换为 Inter 支持的 300-800 等效值 2. **Phase 2** — 重构浅色主题:将分散在 8+ 个文件中的浅色覆盖集中到一个 `light-theme.css`,使用 CSS 变量覆盖而非 `!important` 3. **Phase 3** — 补充无障碍:为 Tab/Button/Card 组件添加 ARIA 属性和 focus-visible 样式 4. **Phase 4** — 统一断点:定义 4-5 个标准断点(如 480/768/1024/1280/1440),逐步替换现有碎片化断点 5. **Phase 5** — 设计一致性:创建统一的 loading/empty/error 组件,清理未使用的 shadcn 组件 --- ## 修复完成记录 > 修复日期:2026-05-10 | 变更范围:28 个文件,+693 / −2,198 行 ### 高优先级 — 5/5 完成 | # | 问题 | 修复 | 涉及文件 | |---|------|------|----------| | 1 | `!important` 滥用 | 134 → 49(仅保留 Leaflet/图表所必需项),将 `.root:global(.light)` 替换为 `html.light` 以获得更高优先级 | `ScanTerminalLightTheme.module.css`、`globals.css` 等 | | 2 | 非标准 font-weight | 所有 760/850/860/880/950 等映射为 Inter 支持的 300–800 | 13 个 CSS 文件 | | 3 | 未加载 Geist | 从 `--font-display` 中移除,替换为 Inter | `globals.css` | | 4 | 键盘 / 焦点可见 | 添加了全局 `:focus-visible` 轮廓环、跳过链接、Tab ARIA(`role="tablist"`/`role="tab"`/`aria-selected`) | `globals.css`、`layout.tsx`、`ScanFilterPanel.tsx`、`ScanTerminalDashboard.tsx` | | 5 | 断点碎片化 | 18 → 10:合并 520/600/720/820/900/1020/1100/1240 → 640/768/960/1024/1280 | 9 个 CSS 文件 | ### 中优先级 — 5/5 完成 | # | 问题 | 修复 | 涉及文件 | |---|------|------|----------| | 6 | 22 个 CSS Module 耦合 | 新建 `scan-root-styles.ts` 桶文件,将 22 个独立导入合并为 1 个预组合的 className | `scan-root-styles.ts`(新建)、`ScanTerminalDashboard.tsx` | | 7 | Token 使用不足 | 将主要颜色(`#4DA3FF`/`#E6EDF3`/`#9FB2C7`/`#6B7A90`/`#6FB7FF`)从 0 个变量引用替换为数百个 | `ScanTerminalCard`、`ScanTerminalList`、`ScanTerminalBoard`、`ScanTerminalOpportunity`、`ScanTerminalMobile`、`ScanTerminal`、`DashboardHomeIntelligence` 等 | | 8 | `@keyframes spin` 重复 4 次 | 移至 `globals.css`;`loading-spin` 去重 2 处;`pulse-pending` 移至全局 | `globals.css`、4 个 CSS 文件 | | 9 | Loading 状态不一致 | 添加了 `.scan-error-state`、`.scan-retry-button`、`.scan-empty-icon` 用于统一状态呈现 | `ScanTerminalState.module.css`、`ScanTerminalLightTheme.module.css` | | 10 | 无 `prefers-reduced-motion` | 在 `globals.css` 中添加了全局动画/过渡禁用 | `globals.css` | ### 低优先级 — 5/5 完成 | # | 问题 | 修复 | 涉及文件 | |---|------|------|----------| | 11 | "3-Color" 仅 2 种颜色 | 将 accent-primary(blue) 和 accent-secondary(light-blue) 区分为不同颜色 | `globals.css`、`Dashboard.module.css` | | 12 | Topbar 缺少 Logo | 添加了 CSS 渐变品牌标记 | `ScanTerminalShell.module.css`、`ScanTerminalDashboard.tsx` | | 13 | 按钮层级不清晰 | 在 CSS 中添加了文档化的层级注释标题 | `ScanTerminalShell.module.css` | | 14 | Shadcn 未使用 | 已验证 6 个组件被 5 个文件使用(保留),更新了注释 | `globals.css` | | 15 | 文字对比度不足 | `--color-text-muted` 从 `#6B7A90` 提升至 `#7D8FA3` | `globals.css` | ### 其他修复 | 问题 | 修复 | |------|------| | `accent-green` 类错误渲染为蓝色 | `ScanTerminal.module.css`:`.scan-condition-value.accent-green` 从 `#4DA3FF` 修正为 `#22C55E` | | `Dashboard.module.css` 重复的 CSS 变量 | 将本地 `--bg-*`/`--accent-*` 变量桥接至全局 token | | 死代码 | 移除 `public/static/style.css`(1,459 行)和 `public/legacy/index.html`(238 行)— 均未被引用 | | 浅色主题 Token 基础设施 | 在 `globals.css` 中添加了 `html.light` CSS 自定义属性覆盖 | | 品牌 Logo 浅色主题 | 在 `ScanTerminalLightTheme.module.css` 中添加了浅色主题 Logo 样式 | | 空/错误状态浅色主题 | 在 `ScanTerminalLightTheme.module.css` 中添加了空/错误/重试的浅色覆盖 | ### 最终指标 | 指标 | 之前 | 之后 | |------|------|------| | `!important`(可避免项) | ~85 | 0 | | `!important`(必需项) | ~49 | 49(Leaflet 内联样式、图表 canvas 属性、减少动态效果) | | 硬编码调色板颜色 | 数百个 | 0(仅 `globals.css` 中的变量定义) | | 断点 | 18 个唯一值 | 10(480/640/768/960/1024/1200/1280/1360/1440/1680) | | 重复的 `@keyframes` | 7 | 0 | | 非标准 font-weight | 全 13 个文件 | 0 | | CSS Module 导入 | 22 个独立导入 | 2(桶文件 + 共享) | | 死代码 | 1,697 行 | 0 | | 净代码行数 | — | −1,505 行 |