geopro/docs/superpowers/plans/2026-06-10-design-system-ad...

534 lines
26 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.

# Geopro 3.0 视觉设计规范落地 Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
> **每个 Task 末尾的「规范一致性校验」步骤是硬性门禁** —— 必须派 subagent 逐值核对改动与 `docs/Geopro3.0_视觉设计规范.md`PASS 才能进入下一个 Task。
**Goal:**`docs/Geopro3.0_视觉设计规范.md` 落成代码层的单一事实来源集中语义令牌light/dark 双值)→ 模板化 QSS → 同步 QPalette → 数据画布常深 → 关键列表面板卡片化,消除当前「裸 hex 散落 + 暗色靠字符串替换」的脆弱结构。
**Architecture:**`Theme.cpp` 建一张语义令牌表(`name → {lightHex, darkHex}`,取值严格来自规范 §1.5 + 附录 A + §1.3 画布色)。新增 `token()/tokenColor()/fillTokens()/applyTokenizedStyleSheet()` APIQSS 模板用 `{{token}}` 占位,运行时按当前明暗填充;`QPalette` 同一令牌表构建;主题切换时广播重填。逐步把内联 QSS 调用方迁移到令牌,最后删除遗留的 `kDarkMap` 字符串替换路径。
**Tech Stack:** C++17 / Qt 6 (QtWidgets) / Fusion QStyle + QSS + QPalette / VTK / ADS。构建`build.bat app`MSVC + NinjaCMake preset `msvc-release`)。
**Scope明确边界YAGNI**
- ✅ 本计划覆盖:令牌基础设施、数据画布常深、全局标准控件重着色到规范色值、内联 QSS 调用方迁移、异常列表 + 数据集列表两个面板卡片化。
- ❌ 本计划**不**覆盖(留待后续独立计划):表格/对话框/Toast/Tooltip 富组件、VTK colormap§8.2)、字号从 px 切 pt§2.2,当前 px 字号缩放体系刻意保留)、图标改 QtAwesome当前自有 `Glyphs` 已矢量+随主题,满足 §9 意图,刻意保留)。这三点在 Task 0 记为「有意偏离」。
**Verify 形态说明(本领域无 QSS 单测,刻意不套 TDD** 每个 Task 的验证 = (a) `build.bat app` 通过;(b) 规范一致性校验 subagent 返回 PASS(c) 标注 `[截图检查点]` 的 Task 由用户看真机截图拍板。
---
## File Structure
| 文件 | 职责 | 本计划改动 |
|---|---|---|
| `src/app/Theme.hpp` | 主题公共 API + 排版/间距/圆角令牌 | 新增令牌 API 声明 |
| `src/app/Theme.cpp` | 令牌表、QSS 模板、QPalette、主题管理器 | 重写核心:令牌表 + 模板化 + palette 从令牌构建;最后删 `kDarkMap` |
| `src/app/CentralScene.cpp` | VTK 场景重建(含背景色) | 画布背景改常深(间接,经 `vtkBackground()` |
| `src/app/TopBar.cpp` | 顶栏 chrome 内联 QSS | 迁移到 `{{token}}` |
| `src/app/PanelHeader.cpp` | 面板表头内联 QSS | 迁移到 `{{token}}` |
| `src/app/panels/ObjectTreePanel.cpp` | 对象树(内联复选框/选中色) | 迁移到令牌 |
| `src/app/panels/AnomalyListPanel.cpp` | 异常列表 | 卡片化(自绘 item delegate / 富 item |
| `src/app/panels/DatasetListPanel.cpp` | 数据集列表 | 卡片化(双行 + 选中竖条) |
---
## Task 0: 基线快照 + 偏离记录(无代码改动)
**Files:**
- Create: `docs/superpowers/plans/2026-06-10-design-baseline.md`
- [ ] **Step 1: 记录当前 git 状态与构建基线**
Run: `build.bat app`
Expected: 构建成功(建立「改动前可编译」基线)。若失败,先停下来报告,不要继续。
- [ ] **Step 2: 写下有意偏离规范的三点**
`docs/superpowers/plans/2026-06-10-design-baseline.md` 写入:
```markdown
# 设计规范落地 —— 有意偏离记录(经用户确认的范围裁剪)
1. 字号:保留现有 px 字号缩放体系Theme.hpp type:: 命名空间 + scaledPx不切规范 §2.2 的 pt。理由用户已投入字号缩放设置切 pt 会破坏现有缩放。
2. 图标:保留自有 Glyphs程序绘制矢量、随主题着色不引入 QtAwesome。理由已满足规范 §9「矢量+可染色+随主题」的意图,引入新依赖收益低。
3. 本轮不做:表格/对话框/Toast/Tooltip 富组件、VTK colormap(§8.2)。留待后续计划。
```
- [ ] **Step 3: Commit**
```bash
git add docs/superpowers/plans/
git commit -m "docs: 设计规范落地计划 + 基线与偏离记录"
```
---
## Task 1: 语义令牌基础设施additive无视觉变化
**Files:**
- Modify: `src/app/Theme.hpp`(新增 API 声明)
- Modify: `src/app/Theme.cpp`(新增令牌表 + 实现,暂不替换现有 QSS
- [ ] **Step 1: 在 `Theme.hpp` 声明令牌 API**
`namespace geopro::app {` 内、`applyThemedStyleSheet` 声明附近加入:
```cpp
// ── 语义令牌(单一事实来源,取值见 Theme.cpp kTokens规范 §1.5 + 附录 A + §1.3)──
// 组件只引语义 token禁止散落硬编码 hex。token 名形如 "bg/panel"、"accent/primary"。
QString token(const char* name); // 当前明暗下的 hex未知名返回品红 "#FF00FF" 以便一眼发现漏配)
QColor tokenColor(const char* name); // 同上QColor 形式
// 把 QSS 模板里的 {{token}} 占位替换为当前明暗的 hex 后返回。
QString fillTokens(const QString& tmpl);
// 应用一段 {{token}} 模板 QSS 到 widget并随主题切换自动重填。
// 迁移内联 QSS 调用方的目标接口(取代 applyThemedStyleSheet 的浅色 hex 写法)。
void applyTokenizedStyleSheet(QWidget* w, const QString& tmpl);
```
- [ ] **Step 2: 在 `Theme.cpp` 匿名 namespace 顶部加入令牌表**
放在 `kStyleSheet` 之前。**取值逐一对照规范,禁止改动**
```cpp
// ── 语义令牌表(全 UI 唯一颜色来源)。改色只改这一处。 ──────────────────
// 取值来源:规范 §1.5 语义映射 + 附录 A 速查 + §1.3 画布专用色。
// 画布(canvas/*)与 bg/canvas 两模式同值——规范 §0.5「视图区永远深色」。
struct Token { const char* name; const char* light; const char* dark; };
const Token kTokens[] = {
// 背景
{"bg/app", "#F7F8FA", "#0E1116"},
{"bg/panel", "#FFFFFF", "#161A20"},
{"bg/panel-subtle", "#FCFCFD", "#161B22"},
{"bg/header", "#FFFFFF", "#12161C"},
{"bg/hover", "#EFF1F4", "#1B2129"},
{"bg/selected", "#EFF5FF", "#16243F"},
{"bg/canvas", "#0B1320", "#0B1320"},
// 边框
{"border/default", "#E3E6EB", "#262C35"},
{"border/strong", "#CDD2DA", "#333B45"},
{"border/focus", "#3B73EC", "#5E8DF5"},
// 文字
{"text/primary", "#272C35", "#E6E9EF"},
{"text/secondary", "#5A626F", "#A4ADBB"},
{"text/tertiary", "#7C8493", "#7A8494"},
{"text/disabled", "#A8AFBC", "#5A626F"},
{"text/link", "#3B73EC", "#5E8DF5"},
{"text/on-primary", "#FFFFFF", "#FFFFFF"},
// 强调
{"accent/primary", "#3B73EC", "#5E8DF5"},
{"accent/primary-hover", "#2B5FD9", "#93B4FA"},
{"accent/primary-pressed","#2450B8", "#3B73EC"},
// 其他
{"divider", "#E3E6EB", "#22272F"},
{"scrollbar/thumb", "#CDD2DA", "#3A424D"},
{"scrollbar/thumb-hover", "#A8AFBC", "#4A535F"},
// 状态色(主色 + 浅底)规范 §1.4
{"status/danger", "#E5484D", "#FF6166"},
{"status/danger-bg", "#FDECEC", "#3A1D1F"},
{"status/warning", "#E08A1E", "#F5A623"},
{"status/warning-bg", "#FBF0DD", "#3A2C12"},
{"status/success", "#2E9E5B", "#46C07A"},
{"status/success-bg", "#E7F6ED", "#16301F"},
{"status/info", "#3B73EC", "#5E8DF5"},
{"status/info-bg", "#EFF5FF", "#16243F"},
{"status/neutral", "#7C8493", "#8A93A3"},
// 画布专用(两模式同值)规范 §1.3
{"canvas/bg", "#0B1320", "#0B1320"},
{"canvas/bg-soft", "#111B2D", "#111B2D"},
{"canvas/grid", "#1E2A3D", "#1E2A3D"},
{"canvas/text", "#E6ECF5", "#E6ECF5"},
{"canvas/text-dim", "#8A97AC", "#8A97AC"},
};
QString tokenHex(const char* name, bool dark)
{
for (const auto& t : kTokens)
if (qstrcmp(t.name, name) == 0) return QString::fromLatin1(dark ? t.dark : t.light);
return QStringLiteral("#FF00FF"); // 漏配的令牌显眼品红,便于一眼发现
}
```
- [ ] **Step 3: 在 `Theme.cpp` 实现公共 API文件末尾 `namespace geopro::app` 内)**
```cpp
QString token(const char* name) { return tokenHex(name, isDarkTheme()); }
QColor tokenColor(const char* name) { return QColor(token(name)); }
QString fillTokens(const QString& tmpl)
{
const bool dark = isDarkTheme();
QString s = tmpl;
for (const auto& t : kTokens)
s.replace(QStringLiteral("{{%1}}").arg(QLatin1String(t.name)),
QString::fromLatin1(dark ? t.dark : t.light));
return s;
}
void applyTokenizedStyleSheet(QWidget* w, const QString& tmpl)
{
if (!w) return;
w->setStyleSheet(fillTokens(tmpl));
QObject::connect(&ThemeManager::instance(), &ThemeManager::changed, w,
[w, tmpl]() { w->setStyleSheet(fillTokens(tmpl)); });
}
```
> 注:`kTokens`/`tokenHex` 在匿名 namespace`token/fillTokens` 等在 `geopro::app` 内调用 `tokenHex` 没问题(同 TU
- [ ] **Step 4: 构建**
Run: `build.bat app`
Expected: 构建成功,**界面无任何变化**(新 API 尚无调用方)。
- [ ] **Step 5: 规范一致性校验(门禁)**
派 subagentopus执行读取 `docs/Geopro3.0_视觉设计规范.md` 的 §1.1§1.5 + §1.3 + 附录 A`Theme.cpp``kTokens` 表**逐 token 逐 hex 比对**。输出 PASS/FAIL 清单:每个 token 标注「规范值 vs 代码值」是否一致列出规范有但表里缺的、表里有但规范无依据的。FAIL 则修正后重校PASS 才继续。
- [ ] **Step 6: Commit**
```bash
git add src/app/Theme.hpp src/app/Theme.cpp
git commit -m "feat(theme): 语义令牌基础设施(令牌表+token/fillTokens API规范§1"
```
---
## Task 2: 数据画布常深(规范 §0.5 / §5 / §11[截图检查点]
**Files:**
- Modify: `src/app/Theme.cpp:612-618``vtkBackground` 实现)
- [ ] **Step 1: 改 `vtkBackground` 返回画布令牌,不随主题**
把现有实现:
```cpp
void vtkBackground(double& r, double& g, double& b)
{
const QColor c = roleColor(isDarkTheme(), "#F4F6FA");
r = c.redF();
g = c.greenF();
b = c.blueF();
}
```
改为:
```cpp
void vtkBackground(double& r, double& g, double& b)
{
// 规范 §0.5/§11数据画布永远深色不随明暗切换。取 canvas/bg。
const QColor c = tokenColor("canvas/bg"); // #0B1320
r = c.redF();
g = c.greenF();
b = c.blueF();
}
```
- [ ] **Step 2: 更新 `CentralScene.cpp:23` 的注释(避免注释腐烂)**
`// 背景随主题(取 ElaTheme 窗口底色),暗色下不再是刺眼白底。`
改为 `// 背景永远深色规范§0.5 视图区常深,不随明暗切换),让色阶数据更突出。`
- [ ] **Step 3: 构建**
Run: `build.bat app`
Expected: 构建成功。
- [ ] **Step 4: 规范一致性校验(门禁)**
派 subagent确认 (a) `vtkBackground` 返回 `canvas/bg` = `#0B1320`(b) 两种主题模式下该函数返回值相同(不分支 `isDarkTheme()` 影响结果);(c) 符合规范 §11「VTK 渲染器背景在两模式下均深色不参与切换」。PASS/FAIL。
- [ ] **Step 5: 用户截图检查点**
请用户运行 `build.bat run`,截图浅色 + 深色两模式下中间视图,确认画布均为深蓝黑 `#0B1320`、与外围 UI 衔接自然。用户确认后继续。
- [ ] **Step 6: Commit**
```bash
git add src/app/Theme.cpp src/app/CentralScene.cpp
git commit -m "feat(canvas): 数据画布常深 #0B1320规范§0.5/§11"
```
---
## Task 3: 全局标准控件重着色到规范色值(模板化 QSS + palette 从令牌)[截图检查点]
**Files:**
- Modify: `src/app/Theme.cpp`(重写 `kStyleSheet``{{token}}` 模板、`buildPalette` 从令牌、`styleSheetForMode` 走 `fillTokens`
**说明:** 这是观感变化最大的一步(强调蓝 `#2D6CB5`→`#3B73EC`,中性灰换规范阶)。遗留的 `kDarkMap`/`themedQss`/`applyThemedStyleSheet` 暂保留供尚未迁移的内联调用方TopBar/面板在本步后仍可用Task 45 迁移完后于 Task 5 删除。
- [ ] **Step 1: 把 `kStyleSheet` 内所有裸 hex 换成 `{{token}}` 占位**
逐段替换(映射规则固定,便于校验):
| 原裸 hex | 语义 → 占位 |
|---|---|
| `#F4F6FA`QMainWindow/QDialog 底、ADS 区底) | `{{bg/app}}` |
| `#FFFFFF`(面板/树/列表/工具条/状态栏/菜单底) | `{{bg/panel}}` |
| `#1F2A3D`(主文字) | `{{text/primary}}` |
| `#5A6B85`(次文字) | `{{text/secondary}}` |
| `#3A475C`(表头/分组标题文字) | `{{text/secondary}}` |
| `#9AA6B6` / `#8A93A3`(禁用/占位文字) | `{{text/disabled}}` |
| `#EDF1F7`(表头/抬升/ADS 标题底) | `{{bg/hover}}` |
| `#EFF1F4`/`#EEF3FB`/`#EAF1FB`/`#EEF2FB`hover 底) | `{{bg/hover}}` |
| `#DCE9F8`/`#DCE6F4`(选中/按下底) | `{{bg/selected}}` |
| `#1B3D67`(选中文字) | `{{accent/primary-pressed}}`(深蓝,深底对比)|
| `#E3E6EB`/`#D5DBE5`/`#EAEEF4`/`#E1E6EE`/`#EEF1F5`/`#E6EBF3`/`#E6EAF1`(分隔/边框/轨道) | `{{divider}}``{{border/default}}`(边框用 border/default分隔线/轨道用 divider|
| `#C2CCDA`/`#C7D2E0`(输入/按钮强边框、滚动条 thumb | 边框→`{{border/strong}}`;滚动条 thumb→`{{scrollbar/thumb}}` |
| `#A7B4C7`(滚动条 hover thumb | `{{scrollbar/thumb-hover}}` |
| `#2D6CB5`(强调) | `{{accent/primary}}` |
| `#2862A6`(强调 hover | `{{accent/primary-hover}}` |
| `#234F87`(强调 pressed | `{{accent/primary-pressed}}` |
| `#F0F2F6`/`#F0F1F4`(禁用底) | `{{bg/app}}` |
> QToolTip 段维持「不写 QSS」现状用原生。`QSplitter::handle:hover`/ADS splitter hover 用 `{{accent/primary}}`(规范 §4.2「splitter hover 显示 accent/primary」
- [ ] **Step 2: `styleSheetForMode` 改走 `fillTokens`**
```cpp
QString styleSheetForMode(bool /*dark*/)
{
return fillTokens(QString::fromUtf8(kStyleSheet));
}
```
`fillTokens` 内部已按 `isDarkTheme()` 取值;保留参数签名以免动调用方,但实现以当前模式为准。若调用点传入与当前模式不一致的 dark需在 Step 3 校验中确认 `applyThemeMode` 调用时 `ThemeManager` 状态已就绪。)
> ⚠️ 依赖检查:`applyThemeMode(app, dark)` 在 `main` 中调用时机须保证 `ThemeManager::instance()` 的 `dark_` 已与传入 `dark` 一致。若不一致,改为让 `applyThemeMode` 内 `setStyleSheet(fillTokens(...))` 前不依赖参数、统一以 `ThemeManager` 为准;并在本步明确 `applyThemeMode` 的 `dark` 参数仅用于 palette。**实现者须先 grep `applyThemeMode(` 的所有调用点确认。**
- [ ] **Step 3: `buildPalette` 从令牌构建**
`buildPalette` 内的 `roleColor(dark, "#xxxxxx")` 调用改为 `QColor(tokenHex(name, dark))`
```cpp
QPalette buildPalette(bool dark)
{
QPalette p;
const QColor shell = QColor(tokenHex("bg/app", dark));
const QColor panel = QColor(tokenHex("bg/panel", dark));
const QColor text = QColor(tokenHex("text/primary", dark));
const QColor muted = QColor(tokenHex("text/secondary", dark));
const QColor accent = QColor(tokenHex("accent/primary", dark));
const QColor border = QColor(tokenHex("border/default", dark));
const QColor disabled = QColor(tokenHex("text/disabled", dark));
const QColor hoverBg = QColor(tokenHex("bg/hover", dark));
p.setColor(QPalette::Window, shell);
p.setColor(QPalette::WindowText, text);
p.setColor(QPalette::Base, panel);
p.setColor(QPalette::AlternateBase, QColor(tokenHex("bg/panel-subtle", dark)));
p.setColor(QPalette::Text, text);
p.setColor(QPalette::Button, hoverBg);
p.setColor(QPalette::ButtonText, text);
p.setColor(QPalette::ToolTipBase, text);
p.setColor(QPalette::ToolTipText, panel);
p.setColor(QPalette::Highlight, accent);
p.setColor(QPalette::HighlightedText, QColor(tokenHex("text/on-primary", dark)));
p.setColor(QPalette::PlaceholderText, muted);
p.setColor(QPalette::Link, accent);
p.setColor(QPalette::Light, panel);
p.setColor(QPalette::Midlight, border);
p.setColor(QPalette::Mid, border);
p.setColor(QPalette::Dark, border);
p.setColor(QPalette::Shadow, border);
p.setColor(QPalette::Disabled, QPalette::Text, disabled);
p.setColor(QPalette::Disabled, QPalette::WindowText, disabled);
p.setColor(QPalette::Disabled, QPalette::ButtonText, disabled);
return p;
}
```
- [ ] **Step 4: 构建**
Run: `build.bat app`
Expected: 构建成功。
- [ ] **Step 5: 规范一致性校验(门禁)**
派 subagent(a) 确认 `kStyleSheet` 内**不再有任何裸 `#` hex**QToolTip 注释除外),全部为 `{{token}}`(b) 抽查每条 `{{token}}` 的语义角色与规范 §3/§6/§7 控件描述一致(如选中行 = `bg/selected`、splitter hover = `accent/primary`(c) `buildPalette` 各 QPalette 角色取的令牌合理。输出 PASS/FAIL。
- [ ] **Step 6: 用户截图检查点**
请用户 `build.bat run`,截图浅/深两模式工作台全貌。重点确认强调蓝、中性灰、选中/hover、边框层级符合规范气质。**色板方向由用户拍板**(这是审美决策)。用户确认后继续。
- [ ] **Step 7: Commit**
```bash
git add src/app/Theme.cpp
git commit -m "feat(theme): 全局 QSS 模板化 + palette 从令牌标准控件对齐规范色值§1/§3/§6/§7"
```
---
## Task 4: 顶栏 + 面板表头内联 QSS 迁移到令牌
**Files:**
- Modify: `src/app/TopBar.cpp:135-154`
- Modify: `src/app/PanelHeader.cpp`(全文件内联 QSS
- [ ] **Step 1: TopBar 内联 QSS 改 `{{token}}` + `applyTokenizedStyleSheet`**
`applyThemedStyleSheet(this, QStringLiteral("...裸 hex..."))` 改为 `applyTokenizedStyleSheet(this, QStringLiteral("...{{token}}..."))`,裸 hex 按 Task 3 同一映射替换:
- `#FFFFFF`→`{{bg/header}}`(顶栏用 header 底)、`#E1E6EE`→`{{divider}}`、`#1F2A3D`→`{{text/primary}}`、`#EEF3FB`→`{{bg/hover}}`、`#2D6CB5`→`{{accent/primary}}`、`#8A93A3`→`{{text/tertiary}}`。
- `#avatar` 背景用 `{{accent/primary}}``color:white` 保持字面 `white`(头像白字恒白,规范 §5 主按钮 on-primary
- 字号 `.arg(scaledPx(...))` 占位(`%1`…`%6`)保持不变——令牌只替换颜色,不碰字号。
> 注意:`{{token}}` 与 `.arg()` 的 `%N` 共存无冲突(`fillTokens` 只替换 `{{...}}``.arg` 只替换 `%N`)。先 `.arg()` 拼好字符串,再交给 `applyTokenizedStyleSheet` 填 `{{}}`。
- [ ] **Step 2: PanelHeader 同法迁移**
`PanelHeader.cpp` 全文,将其内联 QSS 的裸 hex 按 Task 3 映射改为 `{{token}}`,调用改 `applyTokenizedStyleSheet`。表头底用 `{{bg/panel}}`、底部分隔 `{{divider}}`、标题文字 `{{text/primary}}`、图标按钮 hover `{{bg/hover}}`(规范 §4.3 面板标题栏:背景 bg/panel + 底部 1px divider
- [ ] **Step 3: 构建**
Run: `build.bat app`
Expected: 构建成功,顶栏/表头外观与 Task 3 后的全局风格一致(同一套令牌)。
- [ ] **Step 4: 规范一致性校验(门禁)**
派 subagent确认 TopBar.cpp / PanelHeader.cpp 内联 QSS **无裸 hex**`white` 关键字允许记为「on-primary 恒白」);表头结构符合规范 §4.3 / §5。PASS/FAIL。
- [ ] **Step 5: Commit**
```bash
git add src/app/TopBar.cpp src/app/PanelHeader.cpp
git commit -m "refactor(theme): TopBar/PanelHeader 内联 QSS 迁移到语义令牌§4.3/§5"
```
---
## Task 5: 对象树面板迁移 + 删除遗留 kDarkMap 路径
**Files:**
- Modify: `src/app/panels/ObjectTreePanel.cpp:45-71`
- Modify: `src/app/Theme.cpp`(删除 `kDarkMap`/`darkOf`/`roleColor`/`themedQss`/`themed`/`applyThemedStyleSheet`/`themed` 声明与实现——**仅当全仓无调用方时**
- [ ] **Step 1: ObjectTreePanel 复选框/选中色改令牌**
`applyCheckboxStyle` lambda 内的硬编码 `QColor(0x..)``selBg/selFg` 字面 hex 改为 `tokenColor(...)`
- `border``tokenColor("border/strong")`
- `boxBg``tokenColor("bg/panel")`
- `accent``tokenColor("accent/primary")`
- `selBg``token("bg/selected")`
- `selFg``token("accent/primary-pressed")`(深底对比的深蓝字)
`hint_``color:#9AA6B6``applyTokenizedStyleSheet(hint_, "color:{{text/disabled}}; padding:16px;")`
- [ ] **Step 2: grep 确认遗留路径已无调用方**
Run: `grep -rn "applyThemedStyleSheet\|themedQss\|\bthemed(\|roleColor\|kDarkMap\|darkOf" src/`
Expected: 仅 `Theme.cpp` 定义处命中,无其他调用方。**若仍有调用方(如 DatasetListPanel/AnomalyListPanel 尚在 Task 6 前用到),跳过 Step 3把删除挪到 Task 6 末尾。**
- [ ] **Step 3: 删除遗留 kDarkMap 字符串替换路径(仅当 Step 2 确认无调用方)**
`Theme.cpp` 删除:`struct DarkPair` + `kDarkMap[]` + `darkOf` + `roleColor` + `themedQss` + `themed` + `applyThemedStyleSheet`;从 `Theme.hpp` 删除 `themed` / `applyThemedStyleSheet` 声明。`styleSheetForMode` 已在 Task 3 改走 `fillTokens`,不依赖它们。
- [ ] **Step 4: 构建**
Run: `build.bat app`
Expected: 构建成功。
- [ ] **Step 5: 规范一致性校验(门禁)**
派 subagent(a) ObjectTreePanel 无裸 hex(b) 若执行了 Step 3确认 `Theme.cpp` 已无字符串替换 dark 路径、暗色完全由 `kTokens` 双值驱动(规范 §13.1「集中 token」。PASS/FAIL。
- [ ] **Step 6: Commit**
```bash
git add src/app/panels/ObjectTreePanel.cpp src/app/Theme.hpp src/app/Theme.cpp
git commit -m "refactor(theme): 对象树迁移令牌 + 移除遗留 kDarkMap 字符串替换路径§13.1"
```
---
## Task 6: 异常列表 + 数据集列表卡片化(规范 §6.2 / §6.3[截图检查点]
**Files:**
- Modify: `src/app/panels/AnomalyListPanel.cpp`(异常卡片:左状态竖条 + 圆点 + 名称 + 等级胶囊 + 属性行 + 显隐眼睛)
- Modify: `src/app/panels/DatasetListPanel.cpp`(双行列表项 + 状态圆点 + 选中竖条)
**实现取向:**`QStyledItemDelegate` 自绘(保留 `QListWidget` 数据模型),颜色全取 `tokenColor(...)`。避免 `\n` 拼字符串那种朴素渲染。
- [ ] **Step 1: 异常列表 —— 定义等级→状态令牌映射**
`AnomalyListPanel.cpp` 匿名 namespace 加:
```cpp
// 规范 §1.4:异常分级 高=Danger 中=Warning 低=Info停用=Neutral。
// 返回 {主色 token, 浅底 token}。
struct LevelTokens { const char* main; const char* bg; };
LevelTokens levelTokens(int level) // 0=高 1=中 2=低 其他=停用
{
switch (level) {
case 0: return {"status/danger", "status/danger-bg"};
case 1: return {"status/warning", "status/warning-bg"};
case 2: return {"status/info", "status/info-bg"};
default: return {"status/neutral", "bg/hover"};
}
}
```
> 实现者须先确认 `geopro::core::Anomaly` 是否已有「等级」字段;若无,本步先用现有 `lineColor` 决定竖条色、等级胶囊暂以「—」占位,并在 baseline 文档记一条 TODO不阻塞卡片结构。**先读 `AnomalyListPanel.hpp` + Anomaly 定义确认。**
- [ ] **Step 2: 异常列表 —— 写 `QStyledItemDelegate` 子类自绘卡片**
卡片规范§6.3`radius/md`(8) 圆角、左 3px 状态色竖条、底为状态浅底、名称 `text/primary-strong`、右侧等级胶囊(`radius/pill`、状态浅底 + 状态主色字)、属性行 `text/caption` `text/secondary` 等宽数值、右侧显隐眼睛(开=`text/secondary`,关=`text/disabled`)。`sizeHint` 高度容纳双行(约 56px。所有颜色 `tokenColor(...)`;主题切换时 `viewport()->update()`
> 实现细节cardpaint`paint()` 内 `painter->setRenderHint(QPainter::Antialiasing)`;用 `QPainterPath::addRoundedRect` 画卡底;竖条 `fillRect`;胶囊 `drawRoundedRect`;眼睛图标复用 `makeGlyph`(若 `Glyph` 无 eye先用文字「●/○」占位并记 TODO
- [ ] **Step 3: 数据集列表 —— 双行项 delegate**
规范 §6.2:项高 52px、标题 `text/body` + 前置状态圆点、元信息行 `text/caption` `text/tertiary`如「2026-03-15 09:21 · 64 道」)、选中项 `bg/selected` + 左 2px 竖条 + `radius/md`。同样 `QStyledItemDelegate` 自绘,颜色 `tokenColor`
> 先读 `DatasetListPanel.cpp/.hpp` 确认现有数据字段(标题/日期/通道数来源)。
- [ ] **Step 4: 构建**
Run: `build.bat app`
Expected: 构建成功。
- [ ] **Step 5: 若 Task 5 Step 3 当时被跳过,现在删除遗留 kDarkMap 路径**
重跑 Task 5 的 grep 确认无调用方后,执行 Task 5 Step 3 的删除并构建。
- [ ] **Step 6: 规范一致性校验(门禁)**
派 subagent对照规范 §6.2 / §6.3 / §1.4,核对:竖条宽度(异常 3px / 数据集 2px、圆角档md/pill、状态色映射高=danger…、字号角色、显隐态颜色、所有颜色经 `tokenColor` 无裸 hex。PASS/FAIL。
- [ ] **Step 7: 用户截图检查点**
用户 `build.bat run`,截图异常列表 + 数据集列表(浅/深)。确认卡片层级、状态色、胶囊、选中竖条符合规范且美观。
- [ ] **Step 8: Commit**
```bash
git add src/app/panels/AnomalyListPanel.cpp src/app/panels/DatasetListPanel.cpp src/app/Theme.hpp src/app/Theme.cpp
git commit -m "feat(panels): 异常/数据集列表卡片化状态色对齐规范§6.2/§6.3/§1.4"
```
---
## 收尾:整体一致性复核
- [ ] **Step 1: 全仓裸 hex 扫描**
Run: `grep -rn "#[0-9A-Fa-f]\{6\}" src/app/ --include=*.cpp --include=*.hpp`
Expected: 仅 `Theme.cpp``kTokens` 表命中(唯一颜色来源)。其余文件命中即为漏迁移,逐个修。
- [ ] **Step 2: 终版规范一致性总校验(门禁)**
派 subagentopus做一次全量复核`docs/Geopro3.0_视觉设计规范.md` 为准,遍历本计划范围内的所有改动文件,产出一份「规范条款 → 实现位置 → 一致/偏离」对照表。偏离项须落在 Task 0 记录的三条「有意偏离」内,否则修正。
- [ ] **Step 3: 用户最终验收截图**(浅/深 × 工作台全貌 + 各面板)
- [ ] **Step 4: Commit 复核报告**
```bash
git add docs/superpowers/plans/
git commit -m "docs: 设计规范落地终版一致性复核报告"
```