docs: 设计规范落地计划 + 基线与偏离记录

This commit is contained in:
gaozheng 2026-06-10 15:14:44 +08:00
parent 6c34f71177
commit 0edfa56ec6
2 changed files with 547 additions and 0 deletions

View File

@ -0,0 +1,14 @@
# 设计规范落地 —— 基线与有意偏离记录
**基线(改动前):** `build.bat app` 通过;`build/release/src/app/geopro_desktop.exe` 现行可执行ninja: no work to do = 源码与产物同步)。分支 `refactor/pure-qt-ui`
## 有意偏离规范的三点(经用户确认的范围裁剪)
1. **字号**:保留现有 px 字号缩放体系(`Theme.hpp` `type::` 命名空间 + `scaledPx`),不切规范 §2.2 的 pt。理由用户已投入字号缩放设置切 pt 会破坏现有缩放与持久化。
2. **图标**:保留自有 `Glyphs`(程序绘制矢量、随主题着色),不引入 QtAwesome。理由已满足规范 §9「矢量 + 可染色 + 随主题」的意图,引入新依赖收益低。
3. **本轮不做**:表格 / 对话框 / Toast / Tooltip 富组件、VTK colormap§8.2)。留待后续独立计划。
## 构建说明(供实现者)
- 命令:项目根目录执行 `build.bat app`MSVC + Ninjapreset `msvc-release`)。
- 在 PowerShell 下 `& .\build.bat app` 会打印一行 `vswhere.exe is not recognized` 的 stderr 噪声,但 ninja 仍会运行——以最终的 ninja/cl 输出与 exit code 为准,不要被该行误导。

View File

@ -0,0 +1,533 @@
# 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: 设计规范落地终版一致性复核报告"
```