Files
PolyWeather/docs/frontend-ui-design-review.md

348 lines
17 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 MonoGeist 永远不会生效
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 采用 `<details>` 元素**:原生 `<details>/<summary>` 样式控制有限、动画困难,与桌面端的自定义折叠按钮体验不一致。
---
## 六、深色/浅色主题
### 优点
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 支持的 300800 | 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 | 49Leaflet 内联样式、图表 canvas 属性、减少动态效果) |
| 硬编码调色板颜色 | 数百个 | 0(仅 `globals.css` 中的变量定义) |
| 断点 | 18 个唯一值 | 10480/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 行 |