diff --git a/docs/superpowers/plans/2026-06-10-design-baseline.md b/docs/superpowers/plans/2026-06-10-design-baseline.md new file mode 100644 index 0000000..a0a7141 --- /dev/null +++ b/docs/superpowers/plans/2026-06-10-design-baseline.md @@ -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 + Ninja,preset `msvc-release`)。 +- 在 PowerShell 下 `& .\build.bat app` 会打印一行 `vswhere.exe is not recognized` 的 stderr 噪声,但 ninja 仍会运行——以最终的 ninja/cl 输出与 exit code 为准,不要被该行误导。 diff --git a/docs/superpowers/plans/2026-06-10-design-system-adoption.md b/docs/superpowers/plans/2026-06-10-design-system-adoption.md new file mode 100644 index 0000000..7d1b112 --- /dev/null +++ b/docs/superpowers/plans/2026-06-10-design-system-adoption.md @@ -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()` API:QSS 模板用 `{{token}}` 占位,运行时按当前明暗填充;`QPalette` 同一令牌表构建;主题切换时广播重填。逐步把内联 QSS 调用方迁移到令牌,最后删除遗留的 `kDarkMap` 字符串替换路径。 + +**Tech Stack:** C++17 / Qt 6 (QtWidgets) / Fusion QStyle + QSS + QPalette / VTK / ADS。构建:`build.bat app`(MSVC + Ninja,CMake 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: 规范一致性校验(门禁)** + +派 subagent(opus)执行:读取 `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 4–5 迁移完后于 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: 终版规范一致性总校验(门禁)** + +派 subagent(opus)做一次全量复核:以 `docs/Geopro3.0_视觉设计规范.md` 为准,遍历本计划范围内的所有改动文件,产出一份「规范条款 → 实现位置 → 一致/偏离」对照表。偏离项须落在 Task 0 记录的三条「有意偏离」内,否则修正。 + +- [ ] **Step 3: 用户最终验收截图**(浅/深 × 工作台全貌 + 各面板) + +- [ ] **Step 4: Commit 复核报告** + +```bash +git add docs/superpowers/plans/ +git commit -m "docs: 设计规范落地终版一致性复核报告" +```