26 KiB
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 写入:
# 设计规范落地 —— 有意偏离记录(经用户确认的范围裁剪)
1. 字号:保留现有 px 字号缩放体系(Theme.hpp type:: 命名空间 + scaledPx),不切规范 §2.2 的 pt。理由:用户已投入字号缩放设置,切 pt 会破坏现有缩放。
2. 图标:保留自有 Glyphs(程序绘制矢量、随主题着色),不引入 QtAwesome。理由:已满足规范 §9「矢量+可染色+随主题」的意图,引入新依赖收益低。
3. 本轮不做:表格/对话框/Toast/Tooltip 富组件、VTK colormap(§8.2)。留待后续计划。
- Step 3: Commit
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 声明附近加入:
// ── 语义令牌(单一事实来源,取值见 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 之前。取值逐一对照规范,禁止改动:
// ── 语义令牌表(全 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内)
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
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返回画布令牌,不随主题
把现有实现:
void vtkBackground(double& r, double& g, double& b)
{
const QColor c = roleColor(isDarkTheme(), "#F4F6FA");
r = c.redF();
g = c.greenF();
b = c.blueF();
}
改为:
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
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
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。实现者须先 grepapplyThemeMode(的所有调用点确认。
- Step 3:
buildPalette从令牌构建
把 buildPalette 内的 roleColor(dark, "#xxxxxx") 调用改为 QColor(tokenHex(name, dark)):
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
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
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
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 加:
// 规范 §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
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 复核报告
git add docs/superpowers/plans/
git commit -m "docs: 设计规范落地终版一致性复核报告"