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

26 KiB
Raw Blame History

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_视觉设计规范.mdPASS 才能进入下一个 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 appMSVC + 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 写入:

# 设计规范落地 —— 有意偏离记录(经用户确认的范围裁剪)
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 在匿名 namespacetoken/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 + 附录 ATheme.cppkTokens逐 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-618vtkBackground 实现)

  • 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 从令牌、styleSheetForModefillTokens

说明: 这是观感变化最大的一步(强调蓝 #2D6CB5#3B73EC,中性灰换规范阶)。遗留的 kDarkMap/themedQss/applyThemedStyleSheet 暂保留供尚未迁移的内联调用方TopBar/面板在本步后仍可用Task 45 迁移完后于 Task 5 删除。

  • Step 1: 把 kStyleSheet 内所有裸 hex 换成 {{token}} 占位

逐段替换(映射规则固定,便于校验):

原裸 hex 语义 → 占位
#F4F6FAQMainWindow/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/#EEF2FBhover 底) {{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 一致。若不一致,改为让 applyThemeModesetStyleSheet(fillTokens(...)) 前不依赖参数、统一以 ThemeManager 为准;并在本步明确 applyThemeModedark 参数仅用于 palette。实现者须先 grep applyThemeMode( 的所有调用点确认。

  • 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不再有任何裸 # hexQToolTip 注释除外),全部为 {{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 无裸 hexwhite 关键字允许记为「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(...)

  • bordertokenColor("border/strong")
  • boxBgtokenColor("bg/panel")
  • accenttokenColor("accent/primary")
  • selBgtoken("bg/selected")
  • selFgtoken("accent/primary-pressed")(深底对比的深蓝字)

hint_color:#9AA6B6applyTokenizedStyleSheet(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.3radius/md(8) 圆角、左 3px 状态色竖条、底为状态浅底、名称 text/primary-strong、右侧等级胶囊(radius/pill、状态浅底 + 状态主色字)、属性行 text/caption text/secondary 等宽数值、右侧显隐眼睛(开=text/secondary,关=text/disabled)。sizeHint 高度容纳双行(约 56px。所有颜色 tokenColor(...);主题切换时 viewport()->update()

实现细节cardpaintpaint()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.cppkTokens 表命中(唯一颜色来源)。其余文件命中即为漏迁移,逐个修。

  • Step 2: 终版规范一致性总校验(门禁)

派 subagentopus做一次全量复核docs/Geopro3.0_视觉设计规范.md 为准,遍历本计划范围内的所有改动文件,产出一份「规范条款 → 实现位置 → 一致/偏离」对照表。偏离项须落在 Task 0 记录的三条「有意偏离」内,否则修正。

  • Step 3: 用户最终验收截图(浅/深 × 工作台全貌 + 各面板)

  • Step 4: Commit 复核报告

git add docs/superpowers/plans/
git commit -m "docs: 设计规范落地终版一致性复核报告"