docs: 设计规范落地计划 + 基线与偏离记录
This commit is contained in:
parent
6c34f71177
commit
0edfa56ec6
|
|
@ -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 为准,不要被该行误导。
|
||||||
|
|
@ -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: 设计规范落地终版一致性复核报告"
|
||||||
|
```
|
||||||
Loading…
Reference in New Issue