fix(ela): 登录窗主题化 + VTK 背景稳健随主题 + 扫清所有硬编码浅色 QSS 遗漏

- 登录窗: setStyleSheet → applyThemedStyleSheet(整窗随主题, 品牌带文字用 white 关键字恒白);
  refreshBtn/errorLabel 也随主题。修暗系统下浅窗+暗 Ela 控件割裂、占位文字看不清
- VTK: 改为主题切换时重跑 rebuildCentral/rebuildDetail(走完整渲染必重绘, 兼顾 syncSystemTheme
  异步切暗时序); rebuildDetail 也按 vtkBackground 设背景
- 主动扫描修掉遗漏: dockManager 分隔条、数据集/文件列表、对象树枝/hint、空状态标题/提示 全走主题
- themed() 公共助手(给需拼接 ADS 样式的 dockManager)
This commit is contained in:
gaozheng 2026-06-10 09:32:22 +08:00
parent f5eff9e185
commit 68d832c57b
5 changed files with 83 additions and 55 deletions

View File

@ -467,6 +467,11 @@ bool isDarkTheme()
return eTheme->getThemeMode() == ElaThemeType::Dark;
}
QString themed(const QString& designQss)
{
return themedQss(designQss, isDarkTheme());
}
void vtkBackground(double& r, double& g, double& b)
{
const QColor c = roleColor(isDarkTheme(), ElaThemeType::WindowBase);

View File

@ -101,4 +101,8 @@ void vtkBackground(double& r, double& g, double& b);
// (设计稿里用浅色令牌 #1F2A3D/#FFFFFF/#2D6CB5… 书写即可,与全局 QSS 同一套角色映射)。
void applyThemedStyleSheet(QWidget* w, const QString& designQss);
// 把一段「浅色设计稿 QSS」按当前 ElaTheme 配色着色后返回(供需要拼接/手动 setStyleSheet 的场景,
// 如 ADS dockManager 在其自带样式后追加规则)。不自动随主题切换,调用方需自行在切换时重取。
QString themed(const QString& designQss);
} // namespace geopro::app

View File

@ -91,12 +91,15 @@ LoginWindow::LoginWindow(geopro::net::AuthService& auth, QWidget* parent)
// 仅外观:登录窗自带样式(沿用全局主题令牌,保证一脉相承)。
// QLineEdit 在所有状态都显式白底深字 + 边框,避免失焦时取调色板默认色与背景相近不可读。
// 字号引用 Theme 排版令牌:品牌名=display(24)、副标题/字段标签=caption(12)。
setStyleSheet(QStringLiteral(
// 登录窗整体随 ElaTheme 着色(与 Ela 化的输入/按钮一致,避免暗系统下浅窗+暗控件割裂)。
// 品牌带文字用 white 关键字(不入角色映射→恒为白),保证落在蓝色横幅上始终可读。
geopro::app::applyThemedStyleSheet(
this, QStringLiteral(
"QDialog { background: #F4F6FA; }"
"#headerBand {"
" background: qlineargradient(x1:0, y1:0, x2:1, y2:1,"
" stop:0 #2D6CB5, stop:1 #234F87); }"
"#brandTitle { color: #FFFFFF; font-size: %1px; font-weight: %2; }"
"#brandTitle { color: white; font-size: %1px; font-weight: %2; }"
"#brandSubtitle { color: rgba(255,255,255,0.82); font-size: %3px; }"
"#fieldLabel { color: #5A6B85; font-size: %4px; font-weight: %5; }"
// 输入框已 Ela 化(ElaLineEdit 自绘 Fluent + 自动明暗),不再写 QLineEdit QSS。
@ -179,7 +182,9 @@ LoginWindow::LoginWindow(geopro::net::AuthService& auth, QWidget* parent)
refreshBtn_ = new QPushButton(QStringLiteral("看不清?换一张"), body);
refreshBtn_->setFlat(true);
refreshBtn_->setCursor(Qt::PointingHandCursor);
refreshBtn_->setStyleSheet(QStringLiteral(
geopro::app::applyThemedStyleSheet(
refreshBtn_,
QStringLiteral(
"QPushButton { color: #2D6CB5; border: none; background: transparent; padding: 2px 0; }"
"QPushButton:hover { color: #234F87; text-decoration: underline; }"));
refreshRow->addWidget(refreshBtn_);
@ -192,8 +197,8 @@ LoginWindow::LoginWindow(geopro::net::AuthService& auth, QWidget* parent)
// 错误提示:固定占位高度,避免出现时整体布局跳动。
errorLabel_ = new QLabel(body);
errorLabel_->setStyleSheet(
QStringLiteral("color: #C0392B; font-size: %1px;").arg(type::kCaption));
geopro::app::applyThemedStyleSheet(
errorLabel_, QStringLiteral("color: #C0392B; font-size: %1px;").arg(type::kCaption));
errorLabel_->setWordWrap(true);
errorLabel_->setMinimumHeight(18);
form->addWidget(errorLabel_);

View File

@ -289,12 +289,19 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
window.setCentralWidget(dockManager);
// 覆盖 ADS 自带分隔条样式:其 default.css 用 palette(dark) 把面板间分隔画成深灰粗线,
// 且设在管理器自身、选择器更具体(优先级高于全局主题)。这里在其后追加同选择器规则覆盖为极淡分隔。
// 且设在管理器自身、选择器更具体(优先级高于全局主题)。在其后追加同选择器、按主题着色的极淡分隔覆盖。
// 捕获 ADS 基样式一次(避免每次切换重复追加而无限增长),切主题时用 base + 重新着色的覆盖。
const QString dockBaseQss = dockManager->styleSheet();
auto applyDockSplitter = [dockManager, dockBaseQss]() {
dockManager->setStyleSheet(
dockManager->styleSheet() +
QStringLiteral(
dockBaseQss +
geopro::app::themed(QStringLiteral(
"ads--CDockContainerWidget ads--CDockSplitter::handle { background: #EAEEF4; }"
"ads--CDockContainerWidget ads--CDockSplitter::handle:hover { background: #C7D2E0; }"));
"ads--CDockContainerWidget ads--CDockSplitter::handle:hover { background: #C7D2E0; }")));
};
applyDockSplitter();
QObject::connect(eTheme, &ElaTheme::themeModeChanged, dockManager,
[applyDockSplitter](ElaThemeType::ThemeMode) { applyDockSplitter(); });
// 面板包装:内容顶部加自绘表头(图标+标题+操作按钮ADS 自带标题栏随后隐藏,
// 从而让面板表头与原型完全一致。返回 [表头 + 内容] 容器供 setWidget。
@ -397,7 +404,8 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
auto* esTitle = new QLabel(QStringLiteral("选择左侧数据集开始分析"), emptyState);
esTitle->setAlignment(Qt::AlignCenter);
esTitle->setStyleSheet(QStringLiteral("color:#5A6B85; font-size:%1px; font-weight:%2;")
geopro::app::applyThemedStyleSheet(
esTitle, QStringLiteral("color:#5A6B85; font-size:%1px; font-weight:%2;")
.arg(geopro::app::type::kHeading)
.arg(geopro::app::type::kWeightSemibold));
@ -405,8 +413,8 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
"切到「三维视图」可叠加帘面、体素与地形图层"),
emptyState);
esHint->setAlignment(Qt::AlignCenter);
esHint->setStyleSheet(
QStringLiteral("color:#8A93A3; font-size:%1px;").arg(geopro::app::type::kBody));
geopro::app::applyThemedStyleSheet(
esHint, QStringLiteral("color:#8A93A3; font-size:%1px;").arg(geopro::app::type::kBody));
esLay->addWidget(esIcon);
esLay->addWidget(esTitle);
@ -443,17 +451,8 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
detailRenderWindow->AddRenderer(detailRenderer);
vtkRenderer* detailRendererPtr = detailRenderer.Get();
vtkGenericOpenGLRenderWindow* detailRenderWindowPtr = detailRenderWindow.Get();
// VTK 背景随主题切换ElaTheme 明/暗切换时,中央+详情渲染器重设底色并刷新。
QObject::connect(eTheme, &ElaTheme::themeModeChanged, detailWidget,
[rendererPtr, renderWindowPtr, detailRendererPtr, detailRenderWindowPtr]() {
double r, g, b;
geopro::app::vtkBackground(r, g, b);
rendererPtr->SetBackground(r, g, b);
renderWindowPtr->Render();
detailRendererPtr->SetBackground(r, g, b);
detailRenderWindowPtr->Render();
});
// 注VTK 背景随主题切换的连接放在 rebuildCentral/rebuildDetail 定义之后(直接重跑它们,
// 走完整渲染路径必重绘,比手动 SetBackground+Render 稳)。
// 数据详情容器:顶部「反演剖面/原数据」工具条 + 下方 QVTK 小视图。
auto* detailContainer = new QWidget();
@ -504,15 +503,16 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
// 左下 dock数据真实显示栏(选中测线后列其采集批次=数据集;tab 数据/文件)。
auto* datasetTabs = new QTabWidget();
auto* datasetList = new QListWidget();
// 简洁分割:去隔行变色,改为 item 间极淡分割线 + 内边距 + hover/选中反馈(专业、不误导)。
datasetList->setStyleSheet(QStringLiteral(
// 简洁分割:去隔行变色,改为 item 间极淡分割线 + 内边距 + hover/选中反馈(专业、不误导)。随主题着色。
const QString kListQss = QStringLiteral(
"QListWidget{ background:#FFFFFF; border:none; outline:none; }"
"QListWidget::item{ padding:9px 12px; border-bottom:1px solid #EEF1F5; color:#1F2A3D; }"
"QListWidget::item:hover{ background:#F5F8FD; }"
"QListWidget::item:selected{ background:#EAF1FB; color:#1F2A3D; }"));
"QListWidget::item:hover{ background:#EEF3FB; }"
"QListWidget::item:selected{ background:#EAF1FB; color:#1F2A3D; }");
geopro::app::applyThemedStyleSheet(datasetList, kListQss);
datasetTabs->addTab(datasetList, QStringLiteral("数据"));
auto* fileList = new QListWidget();
fileList->setStyleSheet(datasetList->styleSheet()); // 与数据页签同款简洁分割
geopro::app::applyThemedStyleSheet(fileList, kListQss); // 与数据页签同款简洁分割
datasetTabs->addTab(fileList, QStringLiteral("文件"));
auto* datasetDock = new ads::CDockWidget(QStringLiteral("数据真实显示栏"));
auto* datasetBox = wrapWithHeader(
@ -608,6 +608,11 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
fromCam->DeepCopy(detailRendererPtr->GetActiveCamera());
detailRendererPtr->RemoveAllViewProps();
{ // 背景随主题
double r, g, b;
geopro::app::vtkBackground(r, g, b);
detailRendererPtr->SetBackground(r, g, b);
}
if (currentDsId->isEmpty()) { // 未选数据集:清空即可
*prevDsId = *currentDsId;
detailRenderWindowPtr->Render();
@ -822,6 +827,14 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
// ── 启动:建立一次空背景中央视图(真实 sections 数据由下一轮接入)。
rebuildCentral();
// VTK 背景随主题切换:直接重跑 rebuildCentral/rebuildDetail走完整渲染路径、末尾必 Render
// 比手动 SetBackground+Render 稳;兼顾 syncSystemTheme 异步切暗的时序)。
QObject::connect(eTheme, &ElaTheme::themeModeChanged, &window,
[rebuildCentral, rebuildDetail]() {
rebuildCentral();
rebuildDetail();
});
// 顶部应用区(静态视觉壳,对齐原型):上=菜单栏(视图/项目管理/业务工具/设备),
// 下=工具条(工作空间切换 + 项目 + 帮助/通知/设置 + 用户)。纵向堆叠后挂到主窗口顶部。
geopro::app::TopBar* topBar = nullptr;

View File

@ -8,6 +8,7 @@
#include <QVBoxLayout>
#include "Glyphs.hpp"
#include "Theme.hpp"
#include "dto/NavDto.hpp"
namespace geopro::app {
@ -40,8 +41,8 @@ ObjectTreePanel::ObjectTreePanel(QWidget* parent) : QWidget(parent) {
{
const QString openArrow = writeChevronIcon(true, QColor("#8A93A3"));
const QString closedArrow = writeChevronIcon(false, QColor("#8A93A3"));
tree_->setStyleSheet(
QStringLiteral("QTreeView::branch { background: #FFFFFF; }"
geopro::app::applyThemedStyleSheet(
tree_, QStringLiteral("QTreeView::branch { background: #FFFFFF; }"
"QTreeView::branch:has-children:!has-siblings:closed,"
"QTreeView::branch:closed:has-children:has-siblings { image: url(%1); }"
"QTreeView::branch:open:has-children:!has-siblings,"
@ -52,7 +53,7 @@ ObjectTreePanel::ObjectTreePanel(QWidget* parent) : QWidget(parent) {
hint_ = new QLabel(QStringLiteral("正在加载对象…"), this);
hint_->setAlignment(Qt::AlignCenter);
hint_->setStyleSheet(QStringLiteral("color:#9AA6B6; padding:16px;"));
geopro::app::applyThemedStyleSheet(hint_, QStringLiteral("color:#9AA6B6; padding:16px;"));
hint_->setVisible(false);
lay->addWidget(hint_);