refactor(theme): 登录/设置迁移令牌 + 删除遗留 kDarkMap 字符串替换路径(暗色全由令牌双值驱动)(规范§13.1)

This commit is contained in:
gaozheng 2026-06-10 16:57:10 +08:00
parent 8f31f043df
commit b78969471e
4 changed files with 15 additions and 91 deletions

View File

@ -74,8 +74,8 @@ QWidget* buildAppearancePage() {
rlay->setContentsMargins(96 + 12, 0, 0, 0); // 与控件列对齐 rlay->setContentsMargins(96 + 12, 0, 0, 0); // 与控件列对齐
rlay->setSpacing(10); rlay->setSpacing(10);
auto* hint = new QLabel(QStringLiteral("界面字号将在重启后生效"), restartRow); auto* hint = new QLabel(QStringLiteral("界面字号将在重启后生效"), restartRow);
geopro::app::applyThemedStyleSheet( geopro::app::applyTokenizedStyleSheet(
hint, QStringLiteral("color:#5A6B85; font-size:%1px;") hint, QStringLiteral("color:{{text/secondary}}; font-size:%1px;")
.arg(geopro::app::scaledPx(geopro::app::type::kCaption))); .arg(geopro::app::scaledPx(geopro::app::type::kCaption)));
auto* restartBtn = new QPushButton(QStringLiteral("立即重启"), restartRow); auto* restartBtn = new QPushButton(QStringLiteral("立即重启"), restartRow);
rlay->addWidget(hint); rlay->addWidget(hint);

View File

@ -449,60 +449,6 @@ ads--CDockWidgetTab[activeTab="true"] QLabel {
} }
)QSS"; )QSS";
// ── 主题桥配色:工作台标准控件的 QSS/调色板颜色直接取自 ElaTheme
// 保证里外ElaWindow 外壳 ↔ 内部工作台)在明/暗两模下完全一致——这是关键,
// 否则外壳一种灰、工作台另一种灰,明暗都显得割裂。
// 做法:把浅色设计稿里每个色令牌按语义角色替换为 ElaTheme 当前模式的真实颜色。
// 浅→暗 颜色映射(全 UI 唯一颜色来源)。明色 = 令牌本身QSS 直接写的就是明色);
// 暗色 = 此表对应值。覆盖全部 QSS 用色,缺一即在暗色下露浅色。改色只改这一处。
struct DarkPair {
const char* light;
const char* dark;
};
const DarkPair kDarkMap[] = {
// 背景(外壳→面板→抬升)
{"#F4F6FA", "#1E1F22"}, {"#FFFFFF", "#2B2D30"}, {"#EDF1F7", "#34373C"},
{"#F0F2F6", "#2B2D30"}, {"#EAEEF4", "#3A3D42"}, {"#EAEEF5", "#34373C"},
{"#EEF2FB", "#34373C"}, {"#E6EBF3", "#34373C"}, {"#EEF3FB", "#34373C"},
{"#EAF1FB", "#2F4257"}, {"#DCE6F4", "#2A3A4F"}, {"#DCE9F8", "#33527A"},
{"#FBEAD2", "#46371F"},
// 文字
{"#1F2A3D", "#E6E8EB"}, {"#5A6B85", "#A0A8B4"}, {"#3A475C", "#C4CCD8"},
{"#8A93A3", "#828B98"}, {"#9AA6B6", "#6E7681"}, {"#1B3D67", "#E8F1FB"},
// 边框
{"#D5DBE5", "#3A3D42"}, {"#C2CCDA", "#484C52"}, {"#C7D2E0", "#484C52"},
{"#E1E6EE", "#34373C"}, {"#E6EAF1", "#34373C"}, {"#EEF1F5", "#34373C"},
{"#DCE0E7", "#3A3D42"}, {"#A7B4C7", "#4A4E54"},
// 强调(品牌蓝)
{"#2D6CB5", "#5E9BD6"}, {"#2862A6", "#6FA8DD"}, {"#234F87", "#4E89C4"},
// 语义
{"#C0392B", "#E06A5E"}, {"#B45309", "#E0964A"}, {"#15803D", "#5BBF7A"},
};
QString darkOf(const QString& lightHex)
{
for (const auto& p : kDarkMap)
if (lightHex.compare(QLatin1String(p.light), Qt::CaseInsensitive) == 0)
return QString::fromLatin1(p.dark);
return lightHex;
}
// 设计令牌(浅色 hex) → 当前明暗的真实色。
QColor roleColor(bool dark, const char* lightHex)
{
return dark ? QColor(darkOf(QString::fromLatin1(lightHex))) : QColor(QLatin1String(lightHex));
}
// 把一段浅色设计稿 QSS 按当前明暗着色:明色原样;暗色把每个浅色令牌替换为暗色。
QString themedQss(const QString& designQss, bool dark)
{
if (!dark) return designQss;
QString s = designQss;
for (const auto& p : kDarkMap)
s.replace(QString::fromLatin1(p.light), QString::fromLatin1(p.dark), Qt::CaseInsensitive);
return s;
}
// 当前模式的全局 QSS。 // 当前模式的全局 QSS。
QString styleSheetForMode(bool /*dark*/) QString styleSheetForMode(bool /*dark*/)
{ {
@ -662,11 +608,6 @@ bool isDarkTheme()
return ThemeManager::instance().isDark(); return ThemeManager::instance().isDark();
} }
QString themed(const QString& designQss)
{
return themedQss(designQss, isDarkTheme());
}
void vtkBackground(double& r, double& g, double& b) void vtkBackground(double& r, double& g, double& b)
{ {
// 规范 §0.5/§11数据画布永远深色不随明暗切换。取 canvas/bg。 // 规范 §0.5/§11数据画布永远深色不随明暗切换。取 canvas/bg。
@ -676,14 +617,6 @@ void vtkBackground(double& r, double& g, double& b)
b = c.blueF(); b = c.blueF();
} }
void applyThemedStyleSheet(QWidget* w, const QString& designQss)
{
if (!w) return;
w->setStyleSheet(themedQss(designQss, isDarkTheme()));
QObject::connect(&ThemeManager::instance(), &ThemeManager::changed, w,
[w, designQss]() { w->setStyleSheet(themedQss(designQss, isDarkTheme())); });
}
QString token(const char* name) { return tokenHex(name, isDarkTheme()); } QString token(const char* name) { return tokenHex(name, isDarkTheme()); }
QColor tokenColor(const char* name) { return QColor(token(name)); } QColor tokenColor(const char* name) { return QColor(token(name)); }

View File

@ -19,7 +19,7 @@ class QWidget;
namespace geopro::app { namespace geopro::app {
// 主题管理器(纯 Qt替代 ElaTheme持有当前明暗 + 是否跟随系统;切换发 changed() 信号, // 主题管理器(纯 Qt替代 ElaTheme持有当前明暗 + 是否跟随系统;切换发 changed() 信号,
// 全 UI全局 QSS 由 main 重应用、内联 chrome 由 applyThemedStyleSheet据此热切重着色。 // 全 UI全局 QSS 由 main 重应用、内联 chrome 由 applyTokenizedStyleSheet据此热切重着色。
class ThemeManager : public QObject { class ThemeManager : public QObject {
Q_OBJECT Q_OBJECT
public: public:
@ -103,7 +103,7 @@ inline constexpr const char* kWarningFill = "#FBEAD2"; // 警告底纹(配 kW
} // namespace semantic } // namespace semantic
// 应用专业主题Fusion + 调色板 + 全局样式表。dark=true 走暗色P2 主题桥用)。 // 应用专业主题Fusion + 调色板 + 全局样式表。dark=true 走暗色P2 主题桥用)。
// 暗色复用同一 QSS 结构,仅按 kDarkMap 换色;幂等,可随主题切换重复调用。 // 暗色复用同一 QSS 结构,颜色全由 kTokens 双值fillTokens/tokenHex驱动;幂等,可随主题切换重复调用。
void applyThemeMode(QApplication& app, bool dark); void applyThemeMode(QApplication& app, bool dark);
// 浅色主题快捷入口(= applyThemeMode(app,false))。经典壳启动调用一次。 // 浅色主题快捷入口(= applyThemeMode(app,false))。经典壳启动调用一次。
@ -128,15 +128,6 @@ bool isDarkTheme();
// VTK 渲染器背景色(随当前主题,取 ElaTheme 窗口底色)。写入 r/g/b01 // VTK 渲染器背景色(随当前主题,取 ElaTheme 窗口底色)。写入 r/g/b01
void vtkBackground(double& r, double& g, double& b); void vtkBackground(double& r, double& g, double& b);
// 把一段「浅色设计稿 QSS」按当前 ElaTheme 配色着色应用到 widget并随明/暗切换自动重着色。
// 用于 TopBar/PanelHeader/浮层 等带内联 setStyleSheet 的自定义 chrome——让它们也跟随主题
// (设计稿里用浅色令牌 #1F2A3D/#FFFFFF/#2D6CB5… 书写即可,与全局 QSS 同一套角色映射)。
void applyThemedStyleSheet(QWidget* w, const QString& designQss);
// 把一段「浅色设计稿 QSS」按当前 ElaTheme 配色着色后返回(供需要拼接/手动 setStyleSheet 的场景,
// 如 ADS dockManager 在其自带样式后追加规则)。不自动随主题切换,调用方需自行在切换时重取。
QString themed(const QString& designQss);
// ── 语义令牌(单一事实来源,取值见 Theme.cpp kTokens规范 §1.5 + 附录 A + §1.3)── // ── 语义令牌(单一事实来源,取值见 Theme.cpp kTokens规范 §1.5 + 附录 A + §1.3)──
// 组件只引语义 token禁止散落硬编码 hex。token 名形如 "bg/panel"、"accent/primary"。 // 组件只引语义 token禁止散落硬编码 hex。token 名形如 "bg/panel"、"accent/primary"。
QString token(const char* name); // 当前明暗下的 hex未知名返回品红 "#FF00FF" 以便一眼发现漏配) QString token(const char* name); // 当前明暗下的 hex未知名返回品红 "#FF00FF" 以便一眼发现漏配)

View File

@ -89,17 +89,17 @@ LoginWindow::LoginWindow(geopro::net::AuthService& auth, QWidget* parent)
// 字号引用 Theme 排版令牌:品牌名=display(24)、副标题/字段标签=caption(12)。 // 字号引用 Theme 排版令牌:品牌名=display(24)、副标题/字段标签=caption(12)。
// 登录窗整体随 ElaTheme 着色(与 Ela 化的输入/按钮一致,避免暗系统下浅窗+暗控件割裂)。 // 登录窗整体随 ElaTheme 着色(与 Ela 化的输入/按钮一致,避免暗系统下浅窗+暗控件割裂)。
// 品牌带文字用 white 关键字(不入角色映射→恒为白),保证落在蓝色横幅上始终可读。 // 品牌带文字用 white 关键字(不入角色映射→恒为白),保证落在蓝色横幅上始终可读。
geopro::app::applyThemedStyleSheet( geopro::app::applyTokenizedStyleSheet(
this, QStringLiteral( this, QStringLiteral(
"QDialog { background: #F4F6FA; }" "QDialog { background: {{bg/app}}; }"
"#headerBand {" "#headerBand {"
" background: qlineargradient(x1:0, y1:0, x2:1, y2:1," " background: qlineargradient(x1:0, y1:0, x2:1, y2:1,"
" stop:0 #2D6CB5, stop:1 #234F87); }" " stop:0 {{accent/primary}}, stop:1 {{accent/primary-pressed}}); }"
"#brandTitle { color: white; font-size: %1px; font-weight: %2; }" "#brandTitle { color: {{text/on-primary}}; font-size: %1px; font-weight: %2; }"
"#brandSubtitle { color: rgba(255,255,255,0.82); font-size: %3px; }" "#brandSubtitle { color: rgba(255,255,255,0.82); font-size: %3px; }"
"#fieldLabel { color: #5A6B85; font-size: %4px; font-weight: %5; }" "#fieldLabel { color: {{text/secondary}}; font-size: %4px; font-weight: %5; }"
// 输入框已 Ela 化(ElaLineEdit 自绘 Fluent + 自动明暗),不再写 QLineEdit QSS。 // 输入框已 Ela 化(ElaLineEdit 自绘 Fluent + 自动明暗),不再写 QLineEdit QSS。
"#captchaImg { border: 1px solid #C7D2E0; border-radius: 8px; background: #EEF2FB; }") "#captchaImg { border: 1px solid {{border/strong}}; border-radius: 8px; background: {{bg/hover}}; }")
.arg(scaledPx(type::kDisplay)) .arg(scaledPx(type::kDisplay))
.arg(type::kWeightBold) .arg(type::kWeightBold)
.arg(scaledPx(type::kCaption)) .arg(scaledPx(type::kCaption))
@ -178,11 +178,11 @@ LoginWindow::LoginWindow(geopro::net::AuthService& auth, QWidget* parent)
refreshBtn_ = new QPushButton(QStringLiteral("看不清?换一张"), body); refreshBtn_ = new QPushButton(QStringLiteral("看不清?换一张"), body);
refreshBtn_->setFlat(true); refreshBtn_->setFlat(true);
refreshBtn_->setCursor(Qt::PointingHandCursor); refreshBtn_->setCursor(Qt::PointingHandCursor);
geopro::app::applyThemedStyleSheet( geopro::app::applyTokenizedStyleSheet(
refreshBtn_, refreshBtn_,
QStringLiteral( QStringLiteral(
"QPushButton { color: #2D6CB5; border: none; background: transparent; padding: 2px 0; }" "QPushButton { color: {{accent/primary}}; border: none; background: transparent; padding: 2px 0; }"
"QPushButton:hover { color: #234F87; text-decoration: underline; }")); "QPushButton:hover { color: {{accent/primary-pressed}}; text-decoration: underline; }"));
refreshRow->addWidget(refreshBtn_); refreshRow->addWidget(refreshBtn_);
form->addLayout(refreshRow); form->addLayout(refreshRow);
@ -193,8 +193,8 @@ LoginWindow::LoginWindow(geopro::net::AuthService& auth, QWidget* parent)
// 错误提示:固定占位高度,避免出现时整体布局跳动。 // 错误提示:固定占位高度,避免出现时整体布局跳动。
errorLabel_ = new QLabel(body); errorLabel_ = new QLabel(body);
geopro::app::applyThemedStyleSheet( geopro::app::applyTokenizedStyleSheet(
errorLabel_, QStringLiteral("color: #C0392B; font-size: %1px;").arg(scaledPx(type::kCaption))); errorLabel_, QStringLiteral("color: {{status/danger}}; font-size: %1px;").arg(scaledPx(type::kCaption)));
errorLabel_->setWordWrap(true); errorLabel_->setWordWrap(true);
errorLabel_->setMinimumHeight(18); errorLabel_->setMinimumHeight(18);
form->addWidget(errorLabel_); form->addWidget(errorLabel_);