refactor(ui): 方案A — 移除 ElaWidgetTools,统一为标准 Qt + 单一设计系统
根因: 此前 Fusion+自定义QSS+ElaWidgetTools(自绘控件) 三套样式系统并存、互相打架, 是各种丑/不一致的来源。本次彻底收敛为一套: - 移除 ElaWidgetTools 依赖(CMake FetchContent + 链接 + spike);所有 Ela* 控件 → 标准 Qt: ElaWindow→QMainWindow(原生标题栏)、ElaMenu/MenuBar→QMenu/QMenuBar、ElaLineEdit/ComboBox/ CheckBox/PushButton/ToolButton/Text/TableWidget→对应 Qt、ElaIconButton→QToolButton+glyph - 主题系统: 自建 ThemeManager(替代 ElaTheme,QStyleHints 检测系统明暗、持久化、changed 信号热切) + 单一「浅→暗」颜色映射(kDarkMap,全 UI 唯一颜色来源) + 单份 QSS(明色基线,暗色按表替换) - 主题: 跟随系统/浅/深(持久化, 启动应用→登录与主页统一); 字号缩放经 scaledPx 覆盖内联 chrome - NOTICE/关于 同步去掉 ElaWidgetTools - ctest 53/53
This commit is contained in:
parent
a13b58e09f
commit
9010b20b57
|
|
@ -63,24 +63,7 @@ FetchContent_Declare(qtkeychain
|
|||
GIT_TAG v0.14.0)
|
||||
FetchContent_MakeAvailable(qtkeychain)
|
||||
|
||||
# 【ElaWidgetTools 评估 spike — 仅 feat/elawidgettools 分支】Fluent UI for QWidget。
|
||||
# 用 RainbowCandyX fork(支持 Qt6.10+,对 6.11 有条件修补)。SOURCE_SUBDIR 仅编库子目录,
|
||||
# 跳过其示例/PySide bindings。静态链接(MIT 许可,static 合规且省 DLL)。库子目录自带
|
||||
# find_package(Qt6 Widgets/WidgetsPrivate) 与自身 .qrc(靠全局 AUTORCC)。仅隔离评估,不影响产品。
|
||||
set(ELAWIDGETTOOLS_BUILD_STATIC_LIB ON CACHE BOOL "" FORCE)
|
||||
FetchContent_Declare(elawidgettools
|
||||
GIT_REPOSITORY https://github.com/RainbowCandyX/ElaWidgetTools.git
|
||||
GIT_TAG b80eadc4a199186e14656dce09959b3216a593be # 钉定提交,构建可复现(review H1)
|
||||
SOURCE_SUBDIR ElaWidgetTools)
|
||||
FetchContent_MakeAvailable(elawidgettools)
|
||||
|
||||
add_subdirectory(src)
|
||||
|
||||
# ElaWidgetTools 评估 spike(隔离 demo,默认不编;评估用 -DGEOPRO_BUILD_ELA_SPIKE=ON 开启,review M4)。
|
||||
option(GEOPRO_BUILD_ELA_SPIKE "Build ElaWidgetTools evaluation spike" OFF)
|
||||
if(GEOPRO_BUILD_ELA_SPIKE)
|
||||
add_subdirectory(spike/ela)
|
||||
endif()
|
||||
|
||||
enable_testing()
|
||||
add_subdirectory(tests)
|
||||
|
|
|
|||
|
|
@ -8,21 +8,16 @@
|
|||
| **Qt 6** (6.11.x) | GUI 框架(Widgets/Gui/Core/Network/Svg/OpenGL 等) | LGPL-3.0(亦提供商业许可) | https://www.qt.io |
|
||||
| **VTK** (9.x) | 三维/二维可视化渲染 | BSD-3-Clause | https://vtk.org |
|
||||
| **Qt-Advanced-Docking-System** (4.3.1) | 停靠面板布局 | LGPL-2.1 | https://github.com/githubuser0xFFFF/Qt-Advanced-Docking-System |
|
||||
| **ElaWidgetTools** | Fluent UI 控件(菜单/列表/表单/图标等) | MIT | https://github.com/Liniyous/ElaWidgetTools (Qt6.10+ 支持取自 fork https://github.com/RainbowCandyX/ElaWidgetTools) |
|
||||
| **QtKeychain** (0.14.0) | 凭证安全存取("记住登录") | BSD-3-Clause | https://github.com/frankosterfeld/qtkeychain |
|
||||
|
||||
> UI 为标准 Qt Widgets(Fusion 风格 + 项目自带 QSS 主题),未使用第三方 UI 控件库。
|
||||
|
||||
## 许可证要点
|
||||
|
||||
- **Qt 6 / Qt-Advanced-Docking-System(LGPL)**:本项目以动态链接方式使用 Qt 与 ADS,符合 LGPL
|
||||
对动态链接的要求;如需替换这些库,最终用户可自行替换对应动态库。若改为静态链接或分发修改版,
|
||||
需遵循 LGPL 的相应义务(提供目标代码/重新链接能力)。
|
||||
- **ElaWidgetTools(MIT)**:以静态库方式集成;MIT 允许静态链接与闭源分发,需保留版权声明与许可证文本。
|
||||
- **VTK / QtKeychain(BSD-3-Clause)**:需在分发物中保留版权声明与许可证文本,不得用作者名背书。
|
||||
|
||||
> 各组件完整许可证文本随其源码分发(FetchContent 拉取于 build 目录的 `_deps/<组件>-src/`,
|
||||
> 或随 Qt/VTK 安装目录)。如需在发行包中附带完整 LICENSE 文本,请从对应来源复制。
|
||||
|
||||
## ElaWidgetTools 版本钉定
|
||||
|
||||
为保证可复现构建,ElaWidgetTools 钉定到提交
|
||||
`b80eadc4a199186e14656dce09959b3216a593be`(见 `CMakeLists.txt` 的 `FetchContent_Declare(elawidgettools ...)`)。
|
||||
|
|
|
|||
|
|
@ -0,0 +1,669 @@
|
|||
# Geopro 3.0 桌面客户端 — 视觉设计规范(Design System)
|
||||
|
||||
**版本 v1.0** · 适用范围:Geopro 3.0 桌面客户端全部界面
|
||||
**技术载体**:Qt 6(QtWidgets)+ Fusion QStyle + QSS + QPalette + QtAwesome + VTK
|
||||
**模式**:Light(依据原型还原)/ Dark(同一设计语言派生)
|
||||
|
||||
> 本规范是客户端视觉的**单一事实来源**。Claude Code 在实现任何界面时,颜色、间距、字号、圆角、控件尺寸、状态表达一律引用本文档的 token,禁止在各 widget 中即兴硬编色值。所有 token 应集中定义在一个主题模块(见 §13),全局通过主题对象访问。
|
||||
|
||||
---
|
||||
|
||||
## 0. 设计原则
|
||||
|
||||
1. **数据为主,UI 退后**:中间的 2D/3D 视图与剖面图是视觉焦点,外围面板(树、列表、属性)使用克制的中性色,不与数据争夺注意力。
|
||||
2. **浅色为默认,深色为派生**:原型为浅色界面,Light 为基准模式;Dark 按相同色相、相同层级关系派生,保证两套是「同一语言的明暗版本」。
|
||||
3. **信息密度优先**:勘探软件信息密集,控件紧凑、间距节制、对齐严谨,而非消费级 App 的宽松留白。
|
||||
4. **强调色克制**:主强调色(科技蓝)只用于可交互的主操作、选中态、链接、聚焦;状态色只用于状态表达,不作装饰。
|
||||
5. **视图区永远深色**:无论 Light/Dark 模式,中间的 2D 地图 / 3D 视图 / 剖面图画布**始终是深色衬底**(原型即如此),让色阶数据更突出。这意味着「模式切换」主要影响外围 UI,视图画布的深色基调保持稳定。
|
||||
6. **双模式同构**:同一组件在两种模式下结构、间距、字号完全一致,仅颜色 token 取值不同。
|
||||
|
||||
---
|
||||
|
||||
## 1. 色彩系统(Color Tokens)
|
||||
|
||||
色彩采用**语义化分层**:原始色板(Primitives)→ 语义 token(Semantic)。组件只引用语义 token,不直接引用原始色板,便于换肤。
|
||||
|
||||
### 1.1 原始色板 · 主强调色(Brand / Primary)
|
||||
|
||||
科技蓝,取自原型导航高亮、主按钮、链接、选中态。
|
||||
|
||||
| Token | 色值 | 用途 |
|
||||
|---|---|---|
|
||||
| `--primary-50` | `#EFF5FF` | 最浅,选中行背景、hover 底 |
|
||||
| `--primary-100` | `#DBE8FE` | 浅强调背景 |
|
||||
| `--primary-200` | `#BFD4FD` | |
|
||||
| `--primary-300` | `#93B4FA` | |
|
||||
| `--primary-400` | `#5E8DF5` | |
|
||||
| `--primary-500` | `#3B73EC` | **主强调色(Light 主按钮、链接)** |
|
||||
| `--primary-600` | `#2B5FD9` | 主按钮 hover |
|
||||
| `--primary-700` | `#2450B8` | 主按钮 pressed |
|
||||
| `--primary-800` | `#21478F` | |
|
||||
| `--primary-900` | `#1B3A6E` | |
|
||||
|
||||
> Dark 模式主强调略微提亮以保证深底对比度:Dark 主强调用 `--primary-400` (`#5E8DF5`),hover 用 `--primary-300`。
|
||||
|
||||
### 1.2 原始色板 · 中性灰阶(Neutral)
|
||||
|
||||
界面骨架色,决定整体气质。
|
||||
|
||||
| Token | 色值 | 说明 |
|
||||
|---|---|---|
|
||||
| `--neutral-0` | `#FFFFFF` | 纯白 |
|
||||
| `--neutral-25` | `#FCFCFD` | 面板底 |
|
||||
| `--neutral-50` | `#F7F8FA` | 应用背景(Light 工作区底) |
|
||||
| `--neutral-100` | `#EFF1F4` | 次级背景、表头底、斑马纹 |
|
||||
| `--neutral-200` | `#E3E6EB` | 边框、分隔线 |
|
||||
| `--neutral-300` | `#CDD2DA` | 输入框边框、禁用边框 |
|
||||
| `--neutral-400` | `#A8AFBC` | 占位文字、禁用文字 |
|
||||
| `--neutral-500` | `#7C8493` | 次要文字、图标默认 |
|
||||
| `--neutral-600` | `#5A626F` | 正文次级 |
|
||||
| `--neutral-700` | `#3E4551` | 正文 |
|
||||
| `--neutral-800` | `#272C35` | 标题文字 |
|
||||
| `--neutral-900` | `#161A20` | 最深文字、Dark 面板底 |
|
||||
| `--neutral-950` | `#0E1116` | Dark 应用背景 |
|
||||
|
||||
### 1.3 原始色板 · 视图画布专用深色(Canvas)
|
||||
|
||||
2D/3D/剖面画布的衬底,两种模式通用。
|
||||
|
||||
| Token | 色值 | 说明 |
|
||||
|---|---|---|
|
||||
| `--canvas-bg` | `#0B1320` | 视图画布主背景(原型深蓝黑) |
|
||||
| `--canvas-bg-soft` | `#111B2D` | 画布内浮层(如「列表显示栏」浮窗)底 |
|
||||
| `--canvas-grid` | `#1E2A3D` | 画布网格线、坐标轴 |
|
||||
| `--canvas-overlay` | `rgba(10,17,28,0.82)` | 画布上的标签底(如 ERT1 标注牌) |
|
||||
| `--canvas-text` | `#E6ECF5` | 画布上文字 |
|
||||
| `--canvas-text-dim` | `#8A97AC` | 画布上次要文字(坐标、比例尺) |
|
||||
|
||||
### 1.4 语义色 · 状态色(Status)
|
||||
|
||||
来自原型异常分级(红=高/低阻异常、橙=中等、蓝=边界/信息)与通用反馈。每个状态含 `主色 / 浅底 / 边框` 三档。
|
||||
|
||||
| 语义 | 主色(Light) | 主色(Dark) | 浅底(Light) | 浅底(Dark) | 用途 |
|
||||
|---|---|---|---|---|---|
|
||||
| **Danger / 高(红)** | `#E5484D` | `#FF6166` | `#FDECEC` | `#3A1D1F` | 高等级异常、错误、删除 |
|
||||
| **Warning / 中(橙)** | `#E08A1E` | `#F5A623` | `#FBF0DD` | `#3A2C12` | 中等异常、警告 |
|
||||
| **Success(绿)** | `#2E9E5B` | `#46C07A` | `#E7F6ED` | `#16301F` | 成功、在线、就绪 |
|
||||
| **Info / 低(蓝)** | `#3B73EC` | `#5E8DF5` | `#EFF5FF` | `#16243F` | 信息、低等级、边界过渡 |
|
||||
| **Neutral / 离线(灰)** | `#7C8493` | `#8A93A3` | `#F0F1F4` | `#23282F` | 离线、停用、未知 |
|
||||
|
||||
> **异常分级专用**(原型异常列表左侧圆点 + 标签「高/中/低」):高=Danger、中=Warning、低=Info,停用/隐藏=Neutral。三维视图与剖面图中的异常标注牌也用同一组色。
|
||||
|
||||
### 1.5 语义 token 映射表(组件取此层)
|
||||
|
||||
| 语义 token | Light 取值 | Dark 取值 |
|
||||
|---|---|---|
|
||||
| `bg/app` | `neutral-50` | `neutral-950` |
|
||||
| `bg/panel` | `neutral-0` | `neutral-900` |
|
||||
| `bg/panel-subtle` | `neutral-25` | `#161B22` |
|
||||
| `bg/header` | `neutral-0` | `#12161C` |
|
||||
| `bg/hover` | `neutral-100` | `#1B2129` |
|
||||
| `bg/selected` | `primary-50` | `#16243F` |
|
||||
| `bg/canvas` | `canvas-bg` | `canvas-bg` |
|
||||
| `border/default` | `neutral-200` | `#262C35` |
|
||||
| `border/strong` | `neutral-300` | `#333B45` |
|
||||
| `border/focus` | `primary-500` | `primary-400` |
|
||||
| `text/primary` | `neutral-800` | `#E6E9EF` |
|
||||
| `text/secondary` | `neutral-600` | `#A4ADBB` |
|
||||
| `text/tertiary` | `neutral-500` | `#7A8494` |
|
||||
| `text/disabled` | `neutral-400` | `#5A626F` |
|
||||
| `text/link` | `primary-500` | `primary-400` |
|
||||
| `text/on-primary` | `neutral-0` | `neutral-0` |
|
||||
| `accent/primary` | `primary-500` | `primary-400` |
|
||||
| `accent/primary-hover` | `primary-600` | `primary-300` |
|
||||
| `accent/primary-pressed` | `primary-700` | `primary-500` |
|
||||
| `divider` | `neutral-200` | `#22272F` |
|
||||
| `scrollbar/thumb` | `neutral-300` | `#3A424D` |
|
||||
| `scrollbar/thumb-hover` | `neutral-400` | `#4A535F` |
|
||||
|
||||
---
|
||||
|
||||
## 2. 字体与排版(Typography)
|
||||
|
||||
### 2.1 字族
|
||||
|
||||
| 用途 | 字体栈 |
|
||||
|---|---|
|
||||
| 中文 UI | `"Microsoft YaHei UI", "PingFang SC", "Source Han Sans SC", "Noto Sans CJK SC", sans-serif` |
|
||||
| 西文/数字 | `"Segoe UI", "Inter", "Helvetica Neue", Arial, sans-serif` |
|
||||
| 等宽(坐标/数值/编号/日志) | `"Cascadia Code", "JetBrains Mono", "Consolas", monospace` |
|
||||
|
||||
> macOS 优先 PingFang SC + SF Pro。数值、坐标(如 `103.85°E · 36.72°N · z=1.0x`)、批次号、深度刻度一律用等宽字体,保证对齐。
|
||||
|
||||
### 2.2 字号阶梯(pt,桌面端基准)
|
||||
|
||||
| Token | 字号 | 字重 | 行高 | 用途 |
|
||||
|---|---|---|---|---|
|
||||
| `text/display` | 18 | 600 | 26 | 空状态大标题(少用) |
|
||||
| `text/title` | 14 | 600 | 22 | 对话框标题、首选项分组标题 |
|
||||
| `text/heading` | 12.5 | 600 | 20 | 面板标题栏(「对象显示栏」「属性」等) |
|
||||
| `text/body` | 11.5 | 400 | 18 | 正文、列表项、表单值 |
|
||||
| `text/body-strong` | 11.5 | 600 | 18 | 强调正文、选中项 |
|
||||
| `text/label` | 11 | 400 | 16 | 表单标签、次要说明 |
|
||||
| `text/caption` | 10 | 400 | 14 | 辅助信息(日期、计数、单位) |
|
||||
| `text/mono` | 11 | 400 | 16 | 数值/坐标/编号 |
|
||||
|
||||
> 桌面端字号偏小(信息密度优先)。如需适配高 DPI,按系统缩放因子整体放大,不单独改 token。
|
||||
|
||||
---
|
||||
|
||||
## 3. 间距、圆角、阴影、边框(Spacing / Radius / Elevation)
|
||||
|
||||
### 3.1 间距阶梯(px,4 的倍数节奏)
|
||||
|
||||
| Token | 值 | 典型用途 |
|
||||
|---|---|---|
|
||||
| `space/3xs` | 2 | 图标与文字微距 |
|
||||
| `space/2xs` | 4 | 紧凑控件内边距 |
|
||||
| `space/xs` | 6 | 列表项上下内边距 |
|
||||
| `space/sm` | 8 | 控件内边距、小间隔 |
|
||||
| `space/md` | 12 | 面板内边距、表单行距 |
|
||||
| `space/lg` | 16 | 分组间距 |
|
||||
| `space/xl` | 24 | 对话框内边距 |
|
||||
| `space/2xl` | 32 | 大区块分隔 |
|
||||
|
||||
### 3.2 圆角
|
||||
|
||||
| Token | 值 | 用途 |
|
||||
|---|---|---|
|
||||
| `radius/none` | 0 | 表格、树(贴合密集布局) |
|
||||
| `radius/sm` | 4 | 按钮、输入框、标签 |
|
||||
| `radius/md` | 6 | 卡片、列表项、浮层 |
|
||||
| `radius/lg` | 8 | 对话框、画布浮窗 |
|
||||
| `radius/pill` | 999 | 胶囊标签、开关、计数徽标 |
|
||||
|
||||
### 3.3 边框
|
||||
|
||||
- 默认边框宽度 `1px`,颜色 `border/default`。
|
||||
- 聚焦态边框 `1px` `border/focus` + 外发光 `0 0 0 2px primary-100`(Light)/ `primary-900 透明度` 描边(Dark)。
|
||||
- 分隔线用 `divider`,`1px`。
|
||||
|
||||
### 3.4 阴影(仅浮层使用,界面整体扁平)
|
||||
|
||||
| Token | Light | Dark |
|
||||
|---|---|---|
|
||||
| `shadow/popover` | `0 4px 16px rgba(20,30,50,0.12)` | `0 4px 16px rgba(0,0,0,0.5)` |
|
||||
| `shadow/dialog` | `0 12px 40px rgba(20,30,50,0.18)` | `0 12px 40px rgba(0,0,0,0.6)` |
|
||||
| `shadow/dropdown` | `0 2px 10px rgba(20,30,50,0.10)` | `0 2px 10px rgba(0,0,0,0.45)` |
|
||||
|
||||
> 树、列表、表格、面板**不使用阴影**(扁平、紧凑)。仅菜单、下拉、tooltip、对话框、画布浮窗使用。
|
||||
|
||||
---
|
||||
|
||||
## 4. 布局框架(依据原型还原)
|
||||
|
||||
原型即客户端「项目分析视图」工作台,整体为 **顶栏 + 三栏主体 + 中列上下分割** 的多面板结构(用 ADS 实现停靠)。
|
||||
|
||||
### 4.1 整体栅格
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────────┐
|
||||
│ 顶栏 TopBar (高 48px) │
|
||||
├────────────┬───────────────────────────────┬─────────────────┤
|
||||
│ 左栏 │ 中列上:2D/3D 视图画布 │ 右栏上:异常列表 │
|
||||
│ 对象显示栏 │ (深色 canvas,含浮窗/工具) │ /对象属性 (Tab) │
|
||||
│ (树) ├───────────────────────────────┤ │
|
||||
│ │ 中列下:数据详情 ├─────────────────┤
|
||||
│ 数据集显示栏 │ (Tab + 工具条 + 剖面图) │ 右栏下:属性 │
|
||||
│ (列表) │ │ (键值表) │
|
||||
└────────────┴───────────────────────────────┴─────────────────┘
|
||||
```
|
||||
|
||||
### 4.2 各区尺寸建议
|
||||
|
||||
| 区域 | 尺寸 | 说明 |
|
||||
|---|---|---|
|
||||
| 顶栏 TopBar | 高 `48px` | 固定 |
|
||||
| 左栏 | 默认宽 `280px`,可拖拽 `220–400px` | 上下两段:对象树 + 数据集列表 |
|
||||
| 右栏 | 默认宽 `340px`,可拖拽 `280–460px` | 上下两段:异常/属性 Tab + 属性键值表 |
|
||||
| 中列 | 自适应填充 | 上下分割:视图画布(占比大)+ 数据详情 |
|
||||
| 面板标题栏 | 高 `36px` | 含图标 + 标题 + 右侧动作按钮 |
|
||||
| 面板间分隔条(splitter) | `4px` 命中区,视觉 `1px` | hover 显示 `accent/primary` |
|
||||
|
||||
### 4.3 面板标题栏规范(所有可停靠面板统一)
|
||||
|
||||
- 左:`14px` 图标(QtAwesome)+ `text/heading` 标题,可带计数(如「异常列表 2/3」)。
|
||||
- 右:动作按钮区(如 筛选漏斗、+、展开/折叠、全屏),图标按钮 `24×24px`,hover 显示 `bg/hover` 底。
|
||||
- 底部 `1px` `divider`。
|
||||
- 背景 `bg/panel`。
|
||||
|
||||
---
|
||||
|
||||
## 5. 顶栏 TopBar(依据原型)
|
||||
|
||||
从左到右的元素与规范:
|
||||
|
||||
| 元素 | 规范 |
|
||||
|---|---|
|
||||
| **Logo** | 左起,高 `24px`,含下拉箭头(切换工作空间/企业) |
|
||||
| **项目名 + 区域** | 图标 + 项目名(`text/body-strong`)+ 区域下拉胶囊(`primary` 文字 + 浅底)|
|
||||
| **主导航**(视图分析/项目管理/业务工具) | 文字 tab,含图标;当前项 `accent/primary` + 底部 `2px` 高亮条;业务工具带下拉箭头 |
|
||||
| **右侧工具组** | 设备(主按钮样式,`accent/primary` 填充)、帮助(?)、通知(铃铛,含红点徽标)、设置(齿轮)、用户头像+姓名+角色 |
|
||||
| 背景 | `bg/header`,底部 `1px` `divider` |
|
||||
| 图标按钮尺寸 | `32×32px`,hover `bg/hover` |
|
||||
| 通知红点 | `8px` 圆点,`Danger` 色,右上角 |
|
||||
|
||||
---
|
||||
|
||||
## 6. 核心组件规范
|
||||
|
||||
### 6.1 对象树(左栏上 · 对象显示栏)
|
||||
|
||||
原型特征:多级树,每行含「复选框 + 状态圆点 + 类型图标 + 名称 + 右侧计数」。
|
||||
|
||||
| 元素 | 规范 |
|
||||
|---|---|
|
||||
| 行高 | `28px` |
|
||||
| 缩进 | 每级 `16px`,展开箭头 `12px` |
|
||||
| 复选框 | `14px`,选中 `accent/primary` 填充 + 白勾;三态(半选)用横线 |
|
||||
| 状态圆点 | `8px` 实心圆,颜色映射对象状态/数据集色(蓝/绿/橙/红,与右侧异常色一致)|
|
||||
| 类型图标 | `14px` QtAwesome,`text/secondary` 色 |
|
||||
| 名称 | `text/body`;选中行 `text/body-strong` |
|
||||
| 计数徽标 | 右对齐,`text/caption`,`text/tertiary` 色;可加 `pill` 浅底 |
|
||||
| 选中行 | 底 `bg/selected`,左侧 `2px` `accent/primary` 竖条 |
|
||||
| hover 行 | 底 `bg/hover` |
|
||||
| 分组节点(GS) | 名称略强,可加定位图标 |
|
||||
|
||||
### 6.2 数据集列表(左栏下 · 数据集显示栏)
|
||||
|
||||
原型特征:顶部 Tab(数据 / 文件,带计数)+ 列表项(含状态点、标题、日期·通道数)。
|
||||
|
||||
| 元素 | 规范 |
|
||||
|---|---|
|
||||
| 段 Tab | 「数据 2 / 文件 3」,胶囊式分段控件(见 6.9)|
|
||||
| 列表项高 | `52px`(双行:标题 + 元信息)|
|
||||
| 标题 | `text/body`,含前置状态圆点 |
|
||||
| 元信息行 | `text/caption` `text/tertiary`,如「2026-03-15 09:21 · 64 道」|
|
||||
| 选中项 | `bg/selected` + 左 `2px` 竖条 + `radius/md` |
|
||||
| 右上工具 | 筛选漏斗、导出图标 |
|
||||
|
||||
### 6.3 异常列表(右栏上 Tab1)
|
||||
|
||||
原型特征:每条含左侧状态竖条 + 圆点 + 名称 + 等级标签 + 多行属性 + 右侧眼睛(显隐)。
|
||||
|
||||
| 元素 | 规范 |
|
||||
|---|---|
|
||||
| 卡片项 | `radius/md`,左 `3px` 状态色竖条,内距 `space/sm` |
|
||||
| 卡片底 | 对应状态浅底(高=Danger 浅底、中=Warning 浅底、低=Info 浅底)|
|
||||
| 名称 | `text/body-strong` + 圆点 |
|
||||
| 等级标签 | 胶囊标签,状态色(见 6.8)|
|
||||
| 属性行 | `text/caption`,如「140m · 18m / 32 Ω·m」,数值用等宽 |
|
||||
| 显隐开关 | 右侧眼睛图标,开=`text/secondary`,关=`text/disabled` + 斜杠眼 |
|
||||
| 标题计数 | 「异常列表 2/3」=可见/总数 |
|
||||
|
||||
### 6.4 属性键值表(右栏下 · 属性)
|
||||
|
||||
原型特征:两列键值,左键右值,多组。
|
||||
|
||||
| 元素 | 规范 |
|
||||
|---|---|
|
||||
| 行高 | `28px` |
|
||||
| 键 | 左列,`text/label` `text/secondary`,定宽约 `72px` |
|
||||
| 值 | 右列,`text/body` `text/primary`,数值/日期用等宽;可右对齐 |
|
||||
| 分组标题 | `text/heading`,上留 `space/md` |
|
||||
| 可编辑值 | hover 显示编辑图标;进入编辑变为内联输入框 |
|
||||
| 链接值 | 指向其他数据集的属性显示为 `text/link`,点击跳转新建详情页 |
|
||||
|
||||
### 6.5 视图画布(中列上 · 2D/3D)
|
||||
|
||||
**始终深色**(`bg/canvas`),不随模式切换变浅。
|
||||
|
||||
| 元素 | 规范 |
|
||||
|---|---|
|
||||
| 视图切换 | 左上「二维地图 / 三维视图」分段 tab,深色版分段控件 |
|
||||
| 画布浮窗(列表显示栏) | 左上浮层,底 `canvas-bg-soft` + `radius/lg` + `shadow/popover`,半透明 |
|
||||
| 画布内文字 | `canvas-text` / `canvas-text-dim` |
|
||||
| 标注牌(ERT1 等) | 底 `canvas-overlay`,白字,`radius/sm` |
|
||||
| 右上控件 | 底图切换下拉(天地图等)、缩放 +/-,深色按钮 |
|
||||
| 右下状态条 | 坐标/比例尺,`canvas-text-dim`,等宽字体 |
|
||||
| 缩放按钮 | `28×28px`,深色半透明底,hover 提亮 |
|
||||
| 顶部状态徽标 | 如「3/4 测线可见」,圆点 + `text/caption` |
|
||||
|
||||
### 6.6 数据详情区(中列下 · 剖面图)
|
||||
|
||||
原型特征:Tab(采集批次)+ 二级 Tab(原数据/网格数据)+ 工具条 + 剖面图 + 色阶条。
|
||||
|
||||
| 元素 | 规范 |
|
||||
|---|---|
|
||||
| 标题栏 | 图标 +「数据详情」+ 右侧设置/下载图标 |
|
||||
| 批次 Tab | 可关闭的多 Tab(数据集详情以 Tab 呈现,见 6.10)|
|
||||
| 二级 Tab | 「原数据 / 网格数据」文字 tab |
|
||||
| 工具条 | 一排工具按钮(异常标注/色阶配置/白化/滤波处理)= 次按钮(描边)样式,带图标;右侧为复选框组(显示异常/等值线等)+ 滑块(简化容差)+ 右端图标按钮(网格/另存为)|
|
||||
| 复选框组 | 行内排列,`14px` 复选框 + `text/label` |
|
||||
| 滑块 | 见 6.13 |
|
||||
| 剖面图画布 | 深色衬底;色阶填充用数据色阶(见 §8)|
|
||||
| 色阶条(legend) | 底部水平色带 + 刻度(等宽数字)+ 单位标签 |
|
||||
| 深度/距离轴 | 轴标题 `text/caption`,刻度等宽 |
|
||||
|
||||
### 6.7 按钮(Button)
|
||||
|
||||
| 类型 | Light | Dark | 用途 |
|
||||
|---|---|---|---|
|
||||
| **Primary(主)** | 底 `accent/primary` 白字,hover `accent/primary-hover` | 同(用 Dark 强调取值) | 设备、确定、主操作;每个区域≤1个 |
|
||||
| **Secondary(次/描边)** | 描边 `border/strong` + `text/primary`,hover `bg/hover` | 同 | 工具条按钮、取消 |
|
||||
| **Tertiary(文字/幽灵)** | 无边无底 `text/secondary`,hover `bg/hover` | 同 | 次要动作、图标按钮 |
|
||||
| **Danger** | 底/描边 `Danger` | 同 | 删除等破坏性操作 |
|
||||
| **Link** | `text/link` 无底 | 同 | 内联跳转 |
|
||||
|
||||
- 高度:标准 `28px`,紧凑(工具条)`26px`,大(对话框主操作)`32px`。
|
||||
- 内距:水平 `space/md`,图标与文字间距 `space/xs`。
|
||||
- 圆角 `radius/sm`。禁用态:`text/disabled` + 不变底色 + 光标禁用。
|
||||
|
||||
### 6.8 标签 / 徽标(Tag / Badge)
|
||||
|
||||
| 类型 | 规范 |
|
||||
|---|---|
|
||||
| 状态标签(高/中/低) | 胶囊 `radius/pill`,状态浅底 + 状态主色文字,`text/caption`,内距 `space/2xs space/sm` |
|
||||
| 计数徽标 | 圆形/胶囊,`text/tertiary` + `neutral-100` 底;通知红点为纯 `Danger` 圆点 |
|
||||
| 类型徽标 | 中性,`neutral-100` 底 + `text/secondary` |
|
||||
|
||||
### 6.9 分段控件(Segmented / 视图切换、数据/文件 Tab)
|
||||
|
||||
- 容器:`neutral-100` 底(Dark `#1B2129`)+ `radius/md`,内距 `2px`。
|
||||
- 选中段:`bg/panel` 底 + `text/primary` + 轻阴影(浮起感)。
|
||||
- 未选段:透明 + `text/secondary`,hover `text/primary`。
|
||||
- 深色画布上的分段控件(视图切换)用深色版:容器半透明深底,选中段 `accent/primary` 文字。
|
||||
|
||||
### 6.10 标签页(Tabs · 数据集详情多 Tab)
|
||||
|
||||
- 标签:`text/body`,当前项 `text/primary` + 底部 `2px` `accent/primary`;非当前 `text/secondary`。
|
||||
- 可关闭:每个 Tab 右侧 `×`(hover 显示),底色 hover `bg/hover`。
|
||||
- 新建详情页 = 新增 Tab;列表选中与 Tab 双向联动(选中 Tab 高亮)。
|
||||
- 溢出:超出宽度显示左右滚动箭头或下拉列出全部 Tab。
|
||||
|
||||
### 6.11 大视图模式(剖面/属性页全屏)
|
||||
|
||||
- 触发后该详情页覆盖「标题菜单以下区域」,其余面板隐藏。
|
||||
- 右上角显示「按 Esc 退出大视图」提示条(`canvas-overlay` 底,`text/caption`,2 秒后淡出)。
|
||||
|
||||
### 6.12 复选框 / 单选 / 开关
|
||||
|
||||
| 控件 | 规范 |
|
||||
|---|---|
|
||||
| 复选框 | `14px`,未选描边 `border/strong`;选中 `accent/primary` 填充 + 白勾;半选横线;禁用降透明 |
|
||||
| 单选框 | `14px` 圆,选中 `accent/primary` 圆环 + 实心点 |
|
||||
| 开关 Switch | 宽 `36px` 高 `20px` 胶囊,关=`neutral-300` 底,开=`accent/primary` 底,滑块白色 `16px` |
|
||||
|
||||
### 6.13 滑块(Slider · 如简化容差)
|
||||
|
||||
- 轨道 `4px` `neutral-200`(Dark `#2A313B`),已填充段 `accent/primary`。
|
||||
- 滑块手柄 `14px` 白圆 + `1px` `border/strong` + 轻阴影;hover 放大到 `16px`。
|
||||
- 当前值标签:右侧等宽数字。
|
||||
|
||||
---
|
||||
|
||||
## 7. 表单、表格、对话框等通用组件(补充 · 原型未全部出现但客户端必备)
|
||||
|
||||
### 7.1 输入框(Text Input)
|
||||
|
||||
| 状态 | 规范 |
|
||||
|---|---|
|
||||
| 默认 | 高 `28px`,底 `bg/panel`,描边 `1px border/default`,`radius/sm`,内距 `space/sm`,`text/body` |
|
||||
| hover | 描边 `border/strong` |
|
||||
| focus | 描边 `border/focus` + 外发光 |
|
||||
| 禁用 | 底 `neutral-50`(Dark `#1A1F26`)+ `text/disabled` |
|
||||
| 错误 | 描边 `Danger` + 下方 `text/caption` `Danger` 错误说明 |
|
||||
| 占位符 | `text/disabled` |
|
||||
| 前/后缀 | 单位、图标置于框内两端,`text/tertiary` |
|
||||
|
||||
### 7.2 下拉选择(ComboBox / Select)
|
||||
|
||||
- 外观同输入框 + 右侧 `12px` 下拉箭头。
|
||||
- 展开菜单:`bg/panel` + `radius/md` + `shadow/dropdown`;项高 `28px`,hover `bg/hover`,选中项 `bg/selected` + 勾。
|
||||
- 可搜索下拉:顶部带搜索输入框。
|
||||
- 多选下拉:选中项以 6.8 标签形式回填到框内。
|
||||
|
||||
### 7.3 数字输入 / 步进器(SpinBox)
|
||||
|
||||
- 输入框 + 右侧上下步进按钮(各 `14px` 高);数值等宽右对齐;支持单位后缀。
|
||||
|
||||
### 7.4 表格(Table / DataGrid)
|
||||
|
||||
| 元素 | 规范 |
|
||||
|---|---|
|
||||
| 表头 | 底 `neutral-100`(Dark `#161B22`),`text/label` `text/secondary`,高 `32px`,可排序列带箭头 |
|
||||
| 行高 | 标准 `32px`,紧凑 `28px` |
|
||||
| 斑马纹 | 偶数行 `bg/panel-subtle`(可关闭) |
|
||||
| 行 hover | `bg/hover` |
|
||||
| 行选中 | `bg/selected` + 左 `2px` 竖条(多选用复选框列) |
|
||||
| 单元格 | 内距 `space/sm`,文本 `text/body`,数值等宽右对齐 |
|
||||
| 边框 | 仅横向 `1px divider`(无竖线,保持轻盈);密集模式可加竖线 |
|
||||
| 固定列/表头 | 横向滚动时首列与表头固定 |
|
||||
| 空状态 | 居中插画/图标 + `text/secondary` 说明 + 可选操作按钮 |
|
||||
| 分页/加载 | 底部分页器或无限滚动;加载中显示骨架行 |
|
||||
|
||||
### 7.5 对话框(Dialog / Modal)
|
||||
|
||||
| 元素 | 规范 |
|
||||
|---|---|
|
||||
| 遮罩 | 全屏 `rgba(10,16,26,0.45)`(Dark `rgba(0,0,0,0.6)`) |
|
||||
| 容器 | `bg/panel` + `radius/lg` + `shadow/dialog`;宽按内容(小 `420px` / 中 `560px` / 大 `720px`) |
|
||||
| 标题栏 | `text/title` + 右上 `×`;底 `1px divider`;内距 `space/xl` |
|
||||
| 内容区 | 内距 `space/xl`,可滚动 |
|
||||
| 底部操作栏 | 右对齐:次按钮(取消)+ 主按钮(确定);破坏性操作主按钮用 Danger;内距 `space/lg space/xl` |
|
||||
| 进入动画 | 淡入 + 轻微上移(120ms) |
|
||||
|
||||
### 7.6 确认 / 警示框(Confirm / Alert)
|
||||
|
||||
- 小对话框,左侧状态图标(Danger/Warning/Info 圆形图标)+ 标题 + 说明文 + 操作按钮。
|
||||
- 删除类:图标 Danger,主按钮 Danger,文案明确后果。
|
||||
|
||||
### 7.7 提示框(Toast / Notification)
|
||||
|
||||
| 类型 | 规范 |
|
||||
|---|---|
|
||||
| Toast(瞬时) | 右上或底部居中浮出,`bg/panel` + 状态色左竖条 + 图标 + 文案;`radius/md` + `shadow/popover`;3–4s 自动消失;可手动关闭 |
|
||||
| 行内提示(Inline alert) | 块状,状态浅底 + 状态色左竖条 + 图标 + 文案;用于表单顶部、面板内提示 |
|
||||
| 通知中心(铃铛) | 顶栏铃铛下拉面板,列表项含图标/标题/时间/已读态;未读左侧圆点 |
|
||||
|
||||
### 7.8 Tooltip / Popover
|
||||
|
||||
- Tooltip:深色小气泡(Light 也用深色 `neutral-800` 底 + 白字),`text/caption`,`radius/sm`,延迟 `400ms` 显示。
|
||||
- Popover:`bg/panel` + `radius/md` + `shadow/popover`,可含富内容(如对象快速属性 tip)。
|
||||
|
||||
### 7.9 菜单(右键菜单 / 下拉菜单)
|
||||
|
||||
| 元素 | 规范 |
|
||||
|---|---|
|
||||
| 容器 | `bg/panel` + `radius/md` + `shadow/dropdown`,内距 `space/2xs` |
|
||||
| 项 | 高 `28px`,左图标 `14px` + 文字 + 右侧快捷键(`text/tertiary` 等宽)|
|
||||
| hover | `bg/hover` |
|
||||
| 分隔 | `1px divider`,上下 `space/2xs` |
|
||||
| 危险项 | `Danger` 文字(如删除)|
|
||||
| 子菜单 | 右侧箭头,hover 展开 |
|
||||
| 禁用项 | `text/disabled` |
|
||||
|
||||
> 对象树/数据集右键菜单按规约功能:显示/隐藏、定位、属性、异常详情、编辑、新建 GS/TM、导入数据集、删除等。
|
||||
|
||||
### 7.10 首选项 / 设置(Preferences)
|
||||
|
||||
原型顶栏齿轮入口,客户端必备完整设置界面。
|
||||
|
||||
| 元素 | 规范 |
|
||||
|---|---|
|
||||
| 结构 | 左侧分类导航(垂直列表)+ 右侧设置项面板(主从布局)|
|
||||
| 分类项 | 高 `32px`,图标 + 名称,选中 `bg/selected` + 左竖条 |
|
||||
| 设置项行 | 左:标题(`text/body`)+ 说明(`text/caption text/tertiary`);右:控件(开关/下拉/输入)|
|
||||
| 分组 | 分组标题 `text/heading` + `divider` |
|
||||
| 建议分类 | 外观(**主题:跟随系统/浅色/深色**、字体、字号缩放)、启动画面与停留时间、默认大屏、语言(中/英)、坐标系默认、底图与缓存、更新(检查/通道)、账户、关于(版本/许可/Qt 源码声明)|
|
||||
|
||||
### 7.11 空状态 / 加载 / 骨架屏
|
||||
|
||||
- **空状态**:居中 `48px` 灰度图标 + `text/body secondary` 主文案 + `text/caption` 辅助 + 可选主操作按钮。
|
||||
- **加载**:局部用旋转 spinner(`accent/primary`);区块用骨架屏(`neutral-100`/`#1B2129` 矩形微动画)。
|
||||
- **进度**:长任务(如在线更新、VTK 大数据加载)用进度条 `accent/primary` + 百分比等宽数字。
|
||||
|
||||
### 7.12 滚动条
|
||||
|
||||
- 细滚动条:宽 `8px`,thumb `scrollbar/thumb` + `radius/pill`,hover `scrollbar/thumb-hover`,轨道透明;overlay 模式(悬停才显)。
|
||||
|
||||
### 7.13 树/列表筛选器(漏斗)
|
||||
|
||||
- 面板标题栏漏斗图标 → 弹出 Popover:含搜索框 + 多选类型复选组 + 日期范围 + 「应用/重置」。
|
||||
- 激活筛选时漏斗图标显示 `accent/primary` + 角标计数。
|
||||
|
||||
---
|
||||
|
||||
## 8. VTK 视图配色(与 UI 对齐)
|
||||
|
||||
> **关键约束**:QSS/QPalette 不作用于 VTK 渲染窗口。VTK 画布配色须通过 VTK API 单独设置,并**与 §1.3 画布 token 手动对齐**,避免「UI 深色、视图另一种灰」的割裂。VTK 画布在两种模式下**都用深色**。
|
||||
|
||||
### 8.1 渲染窗口基础
|
||||
|
||||
| VTK 元素 | 取值(对齐 token) |
|
||||
|---|---|
|
||||
| 渲染器背景(renderer background) | `canvas-bg` `#0B1320`;可用上下渐变到 `#0E1626` |
|
||||
| 三维网格/地面网格 | `canvas-grid` `#1E2A3D` |
|
||||
| 坐标轴(axes actor) | X/Y/Z 用低饱和 红/绿/蓝,标签 `canvas-text-dim` |
|
||||
| 文字标注(vtkTextActor) | `canvas-text` `#E6ECF5`,等宽字体 |
|
||||
| 比例尺/方位 | `canvas-text-dim` |
|
||||
| 拾取高亮 | `accent/primary` `#5E8DF5` 描边 |
|
||||
| 选中对象包围盒 | `accent/primary` 虚线框 |
|
||||
|
||||
### 8.2 数据色阶(Colormap / Lookup Table)
|
||||
|
||||
原型剖面图色阶为**电阻率经典彩虹色阶**(紫蓝→青绿→黄→橙红→深红,对数刻度 5–1000 Ω·m)。规范:
|
||||
|
||||
| 色阶用途 | 推荐 colormap | 说明 |
|
||||
|---|---|---|
|
||||
| 电阻率/极化率(默认) | 彩虹/Jet 类(紫→蓝→青→绿→黄→橙→红) | 与原型一致;对数刻度;端点可标注异常 |
|
||||
| 连续物理量(通用) | Viridis / Turbo | 感知均匀,科学制图推荐 |
|
||||
| 发散量(正负、相对基线) | Coolwarm(蓝-白-红) | 如电位差、变化量 |
|
||||
| 地层/分类(离散) | 离散调色板(命名色板) | 类别明确,高区分度 |
|
||||
| 单色强度 | 单色渐变(如蓝系) | 单一属性强度 |
|
||||
|
||||
- 色阶须**可配置、可命名保存**(规约「色阶定义工具」),用户调整后视图实时刷新。
|
||||
- 色阶条(legend)渲染在 UI 侧或画布侧均可,但**刻度数字用等宽字体**,单位明确。
|
||||
- 等值线(contour):默认 `canvas-text-dim` 细线;标注牌用 `canvas-overlay` 底。
|
||||
|
||||
### 8.3 异常标注(三维视图 + 剖面图)
|
||||
|
||||
- 异常圈/标注牌颜色 = §1.4 异常分级色(高=Danger、中=Warning、低=Info),与右栏异常列表**严格一致**。
|
||||
- 三维视图中的测线:可见用亮色(红/绿区分极性或测线编号),不可见降透明。
|
||||
|
||||
---
|
||||
|
||||
## 9. 图标系统(QtAwesome)
|
||||
|
||||
- **统一用 QtAwesome**(图标字体),全局禁止混用位图图标,保证矢量、可染色、随主题变色。
|
||||
- 图标字体集:优先 **Material Design Icons (mdi)** 或 **Font Awesome**,全项目统一一套。
|
||||
- 标准尺寸:行内 `14px`,按钮内 `16px`,面板标题 `14px`,空状态 `48px`。
|
||||
- 默认色 `text/secondary`;激活/选中 `accent/primary`;禁用 `text/disabled`。
|
||||
- 图标语义映射(建议固定,供 Claude Code 一致引用):
|
||||
|
||||
| 语义 | 图标(mdi 名参考) |
|
||||
|---|---|
|
||||
| 对象树/层级 | `file-tree` / `sitemap` |
|
||||
| GS 分组/定位 | `map-marker` |
|
||||
| 数据集 | `database` / `chart-line` |
|
||||
| 文件 | `file-document` |
|
||||
| 二维地图 | `map` |
|
||||
| 三维视图 | `cube-outline` |
|
||||
| 异常 | `alert-circle` |
|
||||
| 属性 | `information-outline` |
|
||||
| 筛选 | `filter-variant` |
|
||||
| 新增 | `plus` |
|
||||
| 导出/下载 | `download` / `export` |
|
||||
| 显隐 | `eye` / `eye-off` |
|
||||
| 全屏 | `fullscreen` / `fullscreen-exit` |
|
||||
| 设置 | `cog` |
|
||||
| 通知 | `bell` |
|
||||
| 帮助 | `help-circle` |
|
||||
| 设备 | `access-point` / `chip` |
|
||||
| 色阶配置 | `palette` |
|
||||
| 白化/滤波 | `blur` / `wave` |
|
||||
| 网格 | `grid` |
|
||||
| 缩放 | `magnify-plus` / `magnify-minus` |
|
||||
|
||||
---
|
||||
|
||||
## 10. 状态与交互反馈
|
||||
|
||||
| 交互态 | 表现 |
|
||||
|---|---|
|
||||
| hover | 背景 `bg/hover` 或控件提亮,`120ms` 过渡 |
|
||||
| pressed | 加深一档 |
|
||||
| focus(键盘) | `border/focus` + 外发光,**键盘可达性必须保留** |
|
||||
| selected | `bg/selected` + 左竖条 |
|
||||
| disabled | `text/disabled` + 降透明 + 禁用光标 |
|
||||
| loading | spinner / 骨架 / 进度条 |
|
||||
| drag(异常合并、图层排序) | 拖拽项半透明跟随 + 目标位置 `accent/primary` 插入线 |
|
||||
| 实时刷新(色阶/图例调整) | 视图即时重绘,无整页闪烁 |
|
||||
|
||||
> 动效克制:仅用 `120–200ms` 的 hover/展开/淡入过渡,专业工具避免花哨动画。
|
||||
|
||||
---
|
||||
|
||||
## 11. 双模式切换规范
|
||||
|
||||
- 三种选项:**跟随系统 / 浅色 / 深色**(首选项 §7.10 + 顶栏快捷切换可选)。
|
||||
- 切换时:外围 UI(顶栏、面板、树、列表、表单、对话框)整体换肤;**视图画布与剖面图保持深色基调不变**(仅 UI 边框等微调)。
|
||||
- 切换应即时、无重启;通过统一主题对象广播刷新所有 widget 的 QSS + QPalette。
|
||||
- VTK 渲染器背景在两模式下均深色,不参与切换(仅当用户在首选项单独设置画布背景时才改)。
|
||||
|
||||
---
|
||||
|
||||
## 12. 可访问性与适配
|
||||
|
||||
- **对比度**:正文文字与背景对比度 ≥ 4.5:1,大字 ≥ 3:1(两种模式都须校验,Dark 模式尤其注意状态浅底上的文字)。
|
||||
- **不以颜色为唯一信息**:异常分级除颜色外必须带文字标签(高/中/低)或图标,照顾色觉障碍。
|
||||
- **高 DPI**:所有尺寸用逻辑像素,随系统缩放因子整体放大;图标用矢量字体。
|
||||
- **键盘导航**:Tab 焦点环必须可见;树/列表/表格支持方向键。
|
||||
- **最小命中区**:可点击图标按钮命中区 ≥ `24×24px`。
|
||||
|
||||
---
|
||||
|
||||
## 13. 实现约定(给 Claude Code)
|
||||
|
||||
1. **集中定义 token**:建一个主题模块(如 `src/ui/theme/`),包含 `Theme` 对象,集中持有所有颜色/间距/字号 token 的两套取值(light/dark)。所有 widget 通过 `theme.color("bg/panel")` 一类接口取值,**禁止散落硬编色值**。
|
||||
2. **QSS 模板化**:QSS 写成带占位符的模板,运行时用当前 token 填充生成最终 QSS 字符串并 `qApp->setStyleSheet()`;切换模式时重新生成并应用。
|
||||
3. **QPalette 同步**:除 QSS 外**必须同步设置 QPalette**(Window/Base/Text/Highlight/ButtonText 等角色),否则原生绘制控件(菜单、tooltip、部分原生项)颜色不统一。两者取值来自同一 token。
|
||||
4. **Fusion 为底座**:`QApplication::setStyle("Fusion")`,在其上叠加 QSS + QPalette,不替换为第三方 QStyle。
|
||||
5. **VTK 配色独立设置**:VTK 渲染器背景、坐标轴、文字、色阶用 VTK API 设置,取值引用 §8 / §1.3,与 UI token 对齐;模式切换时画布保持深色。
|
||||
6. **图标统一走 QtAwesome**:封装一个图标工具函数 `icon(name, role)`,role 决定染色(默认/激活/禁用/状态色),全局复用 §9 映射。
|
||||
7. **主题切换广播**:主题切换时通过信号通知所有顶层窗口重应用 QSS/QPalette 并刷新 QtAwesome 图标颜色。
|
||||
8. **可视化校验**:实现后用 light/dark 各截图核对本规范(对比度、状态色一致性、视图画布与 UI 的衔接)。
|
||||
|
||||
---
|
||||
|
||||
## 附录 A:核心语义色速查(开发对照)
|
||||
|
||||
### Light
|
||||
|
||||
| 用途 | 色值 |
|
||||
|---|---|
|
||||
| 应用背景 | `#F7F8FA` |
|
||||
| 面板背景 | `#FFFFFF` |
|
||||
| hover | `#EFF1F4` |
|
||||
| 选中 | `#EFF5FF` |
|
||||
| 边框 | `#E3E6EB` |
|
||||
| 主文字 | `#272C35` |
|
||||
| 次文字 | `#5A626F` |
|
||||
| 主强调 | `#3B73EC` |
|
||||
| 视图画布 | `#0B1320` |
|
||||
| 高/Danger | `#E5484D` |
|
||||
| 中/Warning | `#E08A1E` |
|
||||
| 低/Info | `#3B73EC` |
|
||||
| 成功 | `#2E9E5B` |
|
||||
|
||||
### Dark
|
||||
|
||||
| 用途 | 色值 |
|
||||
|---|---|
|
||||
| 应用背景 | `#0E1116` |
|
||||
| 面板背景 | `#161A20` |
|
||||
| hover | `#1B2129` |
|
||||
| 选中 | `#16243F` |
|
||||
| 边框 | `#262C35` |
|
||||
| 主文字 | `#E6E9EF` |
|
||||
| 次文字 | `#A4ADBB` |
|
||||
| 主强调 | `#5E8DF5` |
|
||||
| 视图画布 | `#0B1320`(同 Light) |
|
||||
| 高/Danger | `#FF6166` |
|
||||
| 中/Warning | `#F5A623` |
|
||||
| 低/Info | `#5E8DF5` |
|
||||
| 成功 | `#46C07A` |
|
||||
|
||||
---
|
||||
|
||||
*本规范依据客户提供的 Web 原型(浅色)提取并派生深色模式,覆盖项目分析视图工作台及客户端通用组件。色值为初始建议值,落地后应结合实机截图微调对比度与一致性,并随设计迭代维护版本。*
|
||||
|
|
@ -36,7 +36,6 @@ target_link_libraries(geopro_desktop PRIVATE
|
|||
Qt6::Core Qt6::Gui Qt6::Widgets Qt6::Svg
|
||||
${VTK_LIBRARIES}
|
||||
ads::qt6advanceddocking
|
||||
ElaWidgetTools # Fluent UI 迁移(feat/elawidgettools):ElaWindow/ElaTheme/Ela* 控件
|
||||
qt6keychain
|
||||
nlohmann_json::nlohmann_json
|
||||
geopro_core # Phase 1:ColorScale 上色
|
||||
|
|
|
|||
|
|
@ -14,9 +14,6 @@
|
|||
#include <QString>
|
||||
#include <QSvgRenderer>
|
||||
|
||||
#include <ElaDef.h>
|
||||
#include <ElaTheme.h>
|
||||
|
||||
#include "Theme.hpp"
|
||||
|
||||
namespace geopro::app {
|
||||
|
|
@ -203,8 +200,7 @@ namespace {
|
|||
// 当前主题下的 chrome 图标色:取主题主文本色(暗=浅、亮=深),保证两种模式都清晰。
|
||||
QColor themedIconColor()
|
||||
{
|
||||
return eTheme->getThemeColor(
|
||||
isDarkTheme() ? ElaThemeType::Dark : ElaThemeType::Light, ElaThemeType::BasicText);
|
||||
return isDarkTheme() ? QColor(0xE6, 0xE8, 0xEB) : QColor(0x1F, 0x2A, 0x3D); // 主文字明/暗
|
||||
}
|
||||
} // namespace
|
||||
|
||||
|
|
@ -215,8 +211,7 @@ void setThemedGlyph(QLabel* label, Glyph type, int px)
|
|||
label->setPixmap(makeGlyph(type, themedIconColor(), px).pixmap(px, px));
|
||||
};
|
||||
apply();
|
||||
QObject::connect(eTheme, &ElaTheme::themeModeChanged, label,
|
||||
[apply](ElaThemeType::ThemeMode) { apply(); });
|
||||
QObject::connect(&ThemeManager::instance(), &ThemeManager::changed, label, [apply]() { apply(); });
|
||||
}
|
||||
|
||||
void setThemedGlyph(QAbstractButton* button, Glyph type, int px)
|
||||
|
|
@ -224,8 +219,7 @@ void setThemedGlyph(QAbstractButton* button, Glyph type, int px)
|
|||
if (!button) return;
|
||||
auto apply = [button, type, px]() { button->setIcon(makeGlyph(type, themedIconColor(), px)); };
|
||||
apply();
|
||||
QObject::connect(eTheme, &ElaTheme::themeModeChanged, button,
|
||||
[apply](ElaThemeType::ThemeMode) { apply(); });
|
||||
QObject::connect(&ThemeManager::instance(), &ThemeManager::changed, button, [apply]() { apply(); });
|
||||
}
|
||||
|
||||
} // namespace geopro::app
|
||||
|
|
|
|||
|
|
@ -2,10 +2,6 @@
|
|||
|
||||
#include "Theme.hpp"
|
||||
|
||||
#include <ElaDef.h>
|
||||
#include <ElaIconButton.h>
|
||||
#include <ElaToolButton.h>
|
||||
|
||||
#include <QButtonGroup>
|
||||
#include <QColor>
|
||||
#include <QHBoxLayout>
|
||||
|
|
@ -41,6 +37,8 @@ QString headerQss()
|
|||
" padding:1px 7px; font-size:%2px; font-weight:%3; }"
|
||||
"#panelBadgeWarn { background:%4; color:%5; border-radius:9px;"
|
||||
" padding:1px 7px; font-size:%2px; font-weight:%3; }"
|
||||
"QToolButton#panelAction { border:none; border-radius:7px; padding:5px; }"
|
||||
"QToolButton#panelAction:hover { background:#EEF3FB; }"
|
||||
"QToolButton#tabBtn { border:none; border-bottom:2px solid transparent; color:#5A6B85;"
|
||||
" padding:8px 6px; font-size:%6px; }"
|
||||
"QToolButton#tabBtn:hover { color:#1F2A3D; }"
|
||||
|
|
@ -65,25 +63,16 @@ QLabel* makeBadge(QWidget* parent)
|
|||
return badge;
|
||||
}
|
||||
|
||||
// 表头操作 Glyph → Ela 字体图标(Fluent,清晰且随主题;不再用位图 makeGlyph 以免发糊)。
|
||||
ElaIconType::IconName actionElaIcon(Glyph g)
|
||||
{
|
||||
switch (g) {
|
||||
case Glyph::Filter: return ElaIconType::Filter;
|
||||
case Glyph::Upload: return ElaIconType::Upload;
|
||||
case Glyph::Plus: return ElaIconType::Plus;
|
||||
case Glyph::Download: return ElaIconType::Download;
|
||||
case Glyph::Collapse: return ElaIconType::ChevronUp;
|
||||
default: return ElaIconType::Ellipsis;
|
||||
}
|
||||
}
|
||||
|
||||
// 表头操作按钮(Fluent ElaIconButton 字体图标,固定 28×28,与顶栏图标按钮一致)。
|
||||
// 表头操作按钮(QToolButton + 项目 glyph 图标,随主题着色;悬停底由 #panelAction QSS 给)。
|
||||
QWidget* makeActionButton(QWidget* parent, const HeaderAction& a)
|
||||
{
|
||||
auto* btn = new ElaIconButton(actionElaIcon(a.first), 16, 28, 28, parent);
|
||||
auto* btn = new QToolButton(parent);
|
||||
btn->setObjectName(QStringLiteral("panelAction"));
|
||||
setThemedGlyph(btn, a.first, kActionIcon);
|
||||
btn->setIconSize(QSize(kActionIcon, kActionIcon));
|
||||
btn->setCursor(Qt::PointingHandCursor);
|
||||
btn->setToolTip(a.second + QStringLiteral("(占位)"));
|
||||
btn->setAutoRaise(true);
|
||||
return btn;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -16,10 +16,6 @@
|
|||
|
||||
#include "Theme.hpp"
|
||||
|
||||
#include <ElaComboBox.h>
|
||||
#include <ElaLineEdit.h>
|
||||
#include <ElaPushButton.h>
|
||||
#include <ElaTableWidget.h>
|
||||
|
||||
namespace geopro::app {
|
||||
namespace {
|
||||
|
|
@ -50,24 +46,24 @@ ProjectListDialog::ProjectListDialog(data::IProjectRepository& repo, QWidget* pa
|
|||
|
||||
auto* filter = new QHBoxLayout();
|
||||
filter->addWidget(new QLabel(QStringLiteral("项目名称"), this));
|
||||
nameEdit_ = new ElaLineEdit(this);
|
||||
nameEdit_ = new QLineEdit(this);
|
||||
nameEdit_->setPlaceholderText(QStringLiteral("输入项目名称"));
|
||||
nameEdit_->setFixedWidth(200);
|
||||
filter->addWidget(nameEdit_);
|
||||
filter->addSpacing(8);
|
||||
filter->addWidget(new QLabel(QStringLiteral("项目类型"), this));
|
||||
typeCombo_ = new ElaComboBox(this);
|
||||
typeCombo_ = new QComboBox(this);
|
||||
typeCombo_->setFixedWidth(160);
|
||||
filter->addWidget(typeCombo_);
|
||||
filter->addSpacing(8);
|
||||
auto* searchBtn = new ElaPushButton(QStringLiteral("搜索"), this);
|
||||
auto* resetBtn = new ElaPushButton(QStringLiteral("重置"), this);
|
||||
auto* searchBtn = new QPushButton(QStringLiteral("搜索"), this);
|
||||
auto* resetBtn = new QPushButton(QStringLiteral("重置"), this);
|
||||
filter->addWidget(searchBtn);
|
||||
filter->addWidget(resetBtn);
|
||||
filter->addStretch();
|
||||
root->addLayout(filter);
|
||||
|
||||
table_ = new ElaTableWidget(this); // Ela item 版表格(继承 QTableWidget),直替
|
||||
table_ = new QTableWidget(this); // Ela item 版表格(继承 QTableWidget),直替
|
||||
table_->setColumnCount(8);
|
||||
table_->setHorizontalHeaderLabels(QStringList{
|
||||
QStringLiteral("序号"), QStringLiteral("项目名称"), QStringLiteral("项目编号"),
|
||||
|
|
@ -86,8 +82,8 @@ ProjectListDialog::ProjectListDialog(data::IProjectRepository& repo, QWidget* pa
|
|||
pageLabel_ = new QLabel(this);
|
||||
bottom->addWidget(pageLabel_);
|
||||
bottom->addStretch();
|
||||
prevBtn_ = new ElaPushButton(QStringLiteral("上一页"), this);
|
||||
nextBtn_ = new ElaPushButton(QStringLiteral("下一页"), this);
|
||||
prevBtn_ = new QPushButton(QStringLiteral("上一页"), this);
|
||||
nextBtn_ = new QPushButton(QStringLiteral("下一页"), this);
|
||||
bottom->addWidget(prevBtn_);
|
||||
bottom->addWidget(nextBtn_);
|
||||
root->addLayout(bottom);
|
||||
|
|
|
|||
|
|
@ -1,19 +1,17 @@
|
|||
#include "SettingsDialog.hpp"
|
||||
|
||||
#include <QComboBox>
|
||||
#include <QCoreApplication>
|
||||
#include <QHBoxLayout>
|
||||
#include <QLabel>
|
||||
#include <QListWidget>
|
||||
#include <QProcess>
|
||||
#include <QPushButton>
|
||||
#include <QStackedWidget>
|
||||
#include <QTextBrowser>
|
||||
#include <QVBoxLayout>
|
||||
#include <QWidget>
|
||||
|
||||
#include <ElaComboBox.h>
|
||||
#include <ElaPushButton.h>
|
||||
#include <ElaText.h>
|
||||
|
||||
#include "Theme.hpp"
|
||||
|
||||
namespace geopro::app {
|
||||
|
|
@ -34,9 +32,10 @@ QWidget* makeRow(const QString& label, QWidget* control) {
|
|||
}
|
||||
|
||||
// 区段标题。
|
||||
ElaText* sectionTitle(const QString& text, QWidget* parent) {
|
||||
auto* t = new ElaText(text, parent);
|
||||
t->setTextPixelSize(geopro::app::scaledPx(geopro::app::type::kHeading));
|
||||
QLabel* sectionTitle(const QString& text, QWidget* parent) {
|
||||
auto* t = new QLabel(text, parent);
|
||||
t->setStyleSheet(QStringLiteral("font-size:%1px; font-weight:600;")
|
||||
.arg(geopro::app::scaledPx(geopro::app::type::kHeading)));
|
||||
return t;
|
||||
}
|
||||
|
||||
|
|
@ -48,7 +47,7 @@ QWidget* buildAppearancePage() {
|
|||
v->addWidget(sectionTitle(QStringLiteral("外观"), page));
|
||||
|
||||
// 主题:跟随系统 / 浅色 / 深色(热切)。
|
||||
auto* themeCombo = new ElaComboBox(page);
|
||||
auto* themeCombo = new QComboBox(page);
|
||||
themeCombo->addItem(QStringLiteral("跟随系统"), QStringLiteral("system"));
|
||||
themeCombo->addItem(QStringLiteral("浅色"), QStringLiteral("light"));
|
||||
themeCombo->addItem(QStringLiteral("深色"), QStringLiteral("dark"));
|
||||
|
|
@ -60,7 +59,7 @@ QWidget* buildAppearancePage() {
|
|||
v->addWidget(makeRow(QStringLiteral("主题"), themeCombo));
|
||||
|
||||
// 界面字号:小/标准/大/特大(重启生效)。
|
||||
auto* fontCombo = new ElaComboBox(page);
|
||||
auto* fontCombo = new QComboBox(page);
|
||||
fontCombo->addItem(QStringLiteral("小"), 90);
|
||||
fontCombo->addItem(QStringLiteral("标准"), 100);
|
||||
fontCombo->addItem(QStringLiteral("大"), 115);
|
||||
|
|
@ -74,9 +73,11 @@ QWidget* buildAppearancePage() {
|
|||
auto* rlay = new QHBoxLayout(restartRow);
|
||||
rlay->setContentsMargins(96 + 12, 0, 0, 0); // 与控件列对齐
|
||||
rlay->setSpacing(10);
|
||||
auto* hint = new ElaText(QStringLiteral("界面字号将在重启后生效"), restartRow);
|
||||
hint->setTextPixelSize(geopro::app::scaledPx(geopro::app::type::kCaption));
|
||||
auto* restartBtn = new ElaPushButton(QStringLiteral("立即重启"), restartRow);
|
||||
auto* hint = new QLabel(QStringLiteral("界面字号将在重启后生效"), restartRow);
|
||||
geopro::app::applyThemedStyleSheet(
|
||||
hint, QStringLiteral("color:#5A6B85; font-size:%1px;")
|
||||
.arg(geopro::app::scaledPx(geopro::app::type::kCaption)));
|
||||
auto* restartBtn = new QPushButton(QStringLiteral("立即重启"), restartRow);
|
||||
rlay->addWidget(hint);
|
||||
rlay->addWidget(restartBtn);
|
||||
rlay->addStretch();
|
||||
|
|
@ -104,8 +105,9 @@ QWidget* buildAboutPage() {
|
|||
v->setSpacing(12);
|
||||
v->addWidget(sectionTitle(QStringLiteral("关于"), page));
|
||||
|
||||
auto* ver = new ElaText(QStringLiteral("Geopro 3.0 — 项目分析视图 (M1)"), page);
|
||||
ver->setTextPixelSize(geopro::app::scaledPx(geopro::app::type::kTitle));
|
||||
auto* ver = new QLabel(QStringLiteral("Geopro 3.0 — 项目分析视图 (M1)"), page);
|
||||
ver->setStyleSheet(QStringLiteral("font-size:%1px; font-weight:600;")
|
||||
.arg(geopro::app::scaledPx(geopro::app::type::kTitle)));
|
||||
v->addWidget(ver);
|
||||
|
||||
auto* license = new QTextBrowser(page);
|
||||
|
|
@ -116,7 +118,6 @@ QWidget* buildAboutPage() {
|
|||
"<tr><td>Qt 6</td><td>GUI 框架</td><td>LGPL-3.0</td></tr>"
|
||||
"<tr><td>VTK 9</td><td>二维/三维渲染</td><td>BSD-3-Clause</td></tr>"
|
||||
"<tr><td>Qt-Advanced-Docking-System</td><td>停靠布局</td><td>LGPL-2.1</td></tr>"
|
||||
"<tr><td>ElaWidgetTools</td><td>Fluent 控件</td><td>MIT</td></tr>"
|
||||
"<tr><td>QtKeychain</td><td>凭证安全存取</td><td>BSD-3-Clause</td></tr>"
|
||||
"</table>"
|
||||
"<p style='margin-top:8px'>完整声明见随附 <i>NOTICE.md</i>。</p>"));
|
||||
|
|
|
|||
|
|
@ -7,11 +7,9 @@
|
|||
#include <QPalette>
|
||||
#include <QSettings>
|
||||
#include <QStyleFactory>
|
||||
#include <QStyleHints>
|
||||
#include <QWidget>
|
||||
|
||||
#include <ElaDef.h>
|
||||
#include <ElaTheme.h>
|
||||
|
||||
namespace geopro::app {
|
||||
|
||||
namespace {
|
||||
|
|
@ -243,8 +241,45 @@ QStatusBar QLabel {
|
|||
padding: 0 4px;
|
||||
}
|
||||
|
||||
/* ── 菜单栏 / 菜单:已全部改用 ElaMenuBar/ElaMenu(自绘透明圆角弹窗+随主题),
|
||||
这里不再写 QMenuBar/QMenu 的 QSS——否则 border-radius 会让弹窗圆角露出后面的直角。 */
|
||||
/* ── 菜单栏 / 菜单(标准 QMenuBar/QMenu):刻意不设 border-radius——弹窗圆角靠系统(Win11
|
||||
原生圆角),QSS 设圆角会露出后面的直角。仅设底/字/选中,干净不刺眼。 */
|
||||
QMenuBar {
|
||||
background: #FFFFFF;
|
||||
color: #1F2A3D;
|
||||
border-bottom: 1px solid #EEF1F5;
|
||||
padding: 2px 6px;
|
||||
}
|
||||
QMenuBar::item {
|
||||
background: transparent;
|
||||
padding: 6px 12px;
|
||||
border-radius: 6px;
|
||||
}
|
||||
QMenuBar::item:selected {
|
||||
background: #EAF1FB;
|
||||
color: #2D6CB5;
|
||||
}
|
||||
QMenuBar::item:pressed {
|
||||
background: #DCE6F4;
|
||||
}
|
||||
QMenu {
|
||||
background: #FFFFFF;
|
||||
color: #1F2A3D;
|
||||
border: 1px solid #D5DBE5;
|
||||
padding: 4px;
|
||||
}
|
||||
QMenu::item {
|
||||
padding: 6px 24px 6px 14px;
|
||||
border-radius: 6px;
|
||||
}
|
||||
QMenu::item:selected {
|
||||
background: #EAF1FB;
|
||||
color: #2D6CB5;
|
||||
}
|
||||
QMenu::separator {
|
||||
height: 1px;
|
||||
background: #E1E6EE;
|
||||
margin: 4px 8px;
|
||||
}
|
||||
|
||||
/* ── 下拉框(按需出现时也与主题一致)──────────────────────── */
|
||||
QComboBox {
|
||||
|
|
@ -339,54 +374,53 @@ ads--CDockWidgetTab[activeTab="true"] QLabel {
|
|||
// 保证里外(ElaWindow 外壳 ↔ 内部工作台)在明/暗两模下完全一致——这是关键,
|
||||
// 否则外壳一种灰、工作台另一种灰,明暗都显得割裂。
|
||||
// 做法:把浅色设计稿里每个色令牌按语义角色替换为 ElaTheme 当前模式的真实颜色。
|
||||
struct RoleMap {
|
||||
const char* token; // 浅色设计稿中的占位 hex
|
||||
ElaThemeType::ThemeColor role; // 对应 ElaTheme 颜色角色
|
||||
// 浅→暗 颜色映射(全 UI 唯一颜色来源)。明色 = 令牌本身(QSS 直接写的就是明色);
|
||||
// 暗色 = 此表对应值。覆盖全部 QSS 用色,缺一即在暗色下露浅色。改色只改这一处。
|
||||
struct DarkPair {
|
||||
const char* light;
|
||||
const char* dark;
|
||||
};
|
||||
const RoleMap kRoleMap[] = {
|
||||
{"#F4F6FA", ElaThemeType::WindowBase}, // 外壳/停靠区底
|
||||
{"#FFFFFF", ElaThemeType::BasicBase}, // 面板/输入/菜单 底
|
||||
{"#EDF1F7", ElaThemeType::BasicBaseDeep}, // 抬升/表头底
|
||||
{"#1F2A3D", ElaThemeType::BasicText}, // 主文字
|
||||
{"#5A6B85", ElaThemeType::BasicTextNoFocus}, // 次要文字
|
||||
{"#3A475C", ElaThemeType::BasicTextCategory}, // 表头文字
|
||||
{"#1B3D67", ElaThemeType::BasicText}, // 选中行文字
|
||||
{"#DCE9F8", ElaThemeType::BasicPress}, // 选中行/按下 底
|
||||
{"#EEF3FB", ElaThemeType::BasicHover}, // 悬停底
|
||||
{"#EAEEF4", ElaThemeType::BasicBorder}, // 细分隔/边框
|
||||
{"#D5DBE5", ElaThemeType::BasicBorder}, // 边框
|
||||
{"#C2CCDA", ElaThemeType::BasicBorderDeep}, // 强边框/滚动条柄
|
||||
{"#C7D2E0", ElaThemeType::BasicBorder}, // 输入边框
|
||||
{"#2D6CB5", ElaThemeType::PrimaryNormal}, // 强调
|
||||
{"#2862A6", ElaThemeType::PrimaryHover}, // 强调悬停
|
||||
{"#234F87", ElaThemeType::PrimaryPress}, // 强调按下
|
||||
{"#9AA6B6", ElaThemeType::BasicTextDisable}, // 禁用文字
|
||||
{"#F0F2F6", ElaThemeType::BasicDisable}, // 禁用底
|
||||
{"#8A93A3", ElaThemeType::BasicTextDisable}, // 禁用文字2
|
||||
{"#DCE0E7", ElaThemeType::BasicBorder}, // 禁用边框
|
||||
{"#A7B4C7", ElaThemeType::BasicBorderDeep}, // 滚动条柄悬停
|
||||
{"#E1E6EE", ElaThemeType::BasicBorder}, // 菜单分隔
|
||||
{"#DCE6F4", ElaThemeType::BasicHover}, // 菜单栏选中
|
||||
{"#E6EBF3", ElaThemeType::BasicDisable}, // 进度条底
|
||||
{"#EAF1FB", ElaThemeType::BasicHover}, // 工具按钮选中底
|
||||
{"#C0392B", ElaThemeType::StatusDanger}, // 危险
|
||||
// 内联 chrome(TopBar/PanelHeader)额外用到的令牌:
|
||||
{"#EEF1F5", ElaThemeType::BasicBorder}, // 菜单栏底边线
|
||||
{"#E6EAF1", ElaThemeType::BasicBorder}, // 面板表头底边线
|
||||
{"#EAEEF5", ElaThemeType::BasicBaseDeep}, // 面板徽标底
|
||||
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"},
|
||||
};
|
||||
|
||||
QColor roleColor(bool dark, ElaThemeType::ThemeColor role)
|
||||
QString darkOf(const QString& lightHex)
|
||||
{
|
||||
return eTheme->getThemeColor(dark ? ElaThemeType::Dark : ElaThemeType::Light, role);
|
||||
for (const auto& p : kDarkMap)
|
||||
if (lightHex.compare(QLatin1String(p.light), Qt::CaseInsensitive) == 0)
|
||||
return QString::fromLatin1(p.dark);
|
||||
return lightHex;
|
||||
}
|
||||
|
||||
// 把一段设计稿 QSS 按 ElaTheme 当前模式着色(浅色令牌→真实角色色)。供全局与内联样式共用。
|
||||
// 设计令牌(浅色 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& rm : kRoleMap)
|
||||
s.replace(QLatin1String(rm.token), roleColor(dark, rm.role).name());
|
||||
for (const auto& p : kDarkMap)
|
||||
s.replace(QString::fromLatin1(p.light), QString::fromLatin1(p.dark), Qt::CaseInsensitive);
|
||||
return s;
|
||||
}
|
||||
|
||||
|
|
@ -400,20 +434,20 @@ QString styleSheetForMode(bool dark)
|
|||
QPalette buildPalette(bool dark)
|
||||
{
|
||||
QPalette p;
|
||||
const QColor shell = roleColor(dark, ElaThemeType::WindowBase);
|
||||
const QColor panel = roleColor(dark, ElaThemeType::BasicBase);
|
||||
const QColor text = roleColor(dark, ElaThemeType::BasicText);
|
||||
const QColor muted = roleColor(dark, ElaThemeType::BasicTextNoFocus);
|
||||
const QColor accent = roleColor(dark, ElaThemeType::PrimaryNormal);
|
||||
const QColor border = roleColor(dark, ElaThemeType::BasicBorder);
|
||||
const QColor disabled = roleColor(dark, ElaThemeType::BasicTextDisable);
|
||||
const QColor shell = roleColor(dark, "#F4F6FA");
|
||||
const QColor panel = roleColor(dark, "#FFFFFF");
|
||||
const QColor text = roleColor(dark, "#1F2A3D");
|
||||
const QColor muted = roleColor(dark, "#5A6B85");
|
||||
const QColor accent = roleColor(dark, "#2D6CB5");
|
||||
const QColor border = roleColor(dark, "#D5DBE5");
|
||||
const QColor disabled = roleColor(dark, "#9AA6B6");
|
||||
|
||||
p.setColor(QPalette::Window, shell);
|
||||
p.setColor(QPalette::WindowText, text);
|
||||
p.setColor(QPalette::Base, panel);
|
||||
p.setColor(QPalette::AlternateBase, roleColor(dark, ElaThemeType::BasicAlternating));
|
||||
p.setColor(QPalette::AlternateBase, roleColor(dark, "#EDF1F7"));
|
||||
p.setColor(QPalette::Text, text);
|
||||
p.setColor(QPalette::Button, roleColor(dark, ElaThemeType::BasicBaseDeep));
|
||||
p.setColor(QPalette::Button, roleColor(dark, "#EDF1F7"));
|
||||
p.setColor(QPalette::ButtonText, text);
|
||||
p.setColor(QPalette::ToolTipBase, text);
|
||||
p.setColor(QPalette::ToolTipText, shell);
|
||||
|
|
@ -442,7 +476,7 @@ void applyThemeMode(QApplication& app, bool dark)
|
|||
|
||||
// 基础字体:微软雅黑 UI;基准字号取令牌 type::kBody(13px),与 QSS 同单位。
|
||||
QFont base(QStringLiteral("Microsoft YaHei UI"));
|
||||
base.setPixelSize(type::kBody);
|
||||
base.setPixelSize(scaledPx(type::kBody)); // 随界面字号缩放
|
||||
base.setStyleStrategy(QFont::PreferAntialias);
|
||||
app.setFont(base);
|
||||
|
||||
|
|
@ -455,31 +489,56 @@ void applyTheme(QApplication& app)
|
|||
applyThemeMode(app, false);
|
||||
}
|
||||
|
||||
void applyBrandAccent()
|
||||
{
|
||||
// 品牌蓝强调色。亮色用主色 #2D6CB5;暗色用同色相提亮版 #5E9BD6(深底上对比足够)。
|
||||
// Hover 略亮、Press 略深,三态成体系。设给 Ela 的 Primary,全 UI 选中/激活统一这一套。
|
||||
eTheme->setThemeColor(ElaThemeType::Light, ElaThemeType::PrimaryNormal, QColor(0x2D, 0x6C, 0xB5));
|
||||
eTheme->setThemeColor(ElaThemeType::Light, ElaThemeType::PrimaryHover, QColor(0x3E, 0x7B, 0xC0));
|
||||
eTheme->setThemeColor(ElaThemeType::Light, ElaThemeType::PrimaryPress, QColor(0x24, 0x5A, 0x9B));
|
||||
eTheme->setThemeColor(ElaThemeType::Dark, ElaThemeType::PrimaryNormal, QColor(0x5E, 0x9B, 0xD6));
|
||||
eTheme->setThemeColor(ElaThemeType::Dark, ElaThemeType::PrimaryHover, QColor(0x71, 0xA9, 0xDE));
|
||||
eTheme->setThemeColor(ElaThemeType::Dark, ElaThemeType::PrimaryPress, QColor(0x4E, 0x89, 0xC4));
|
||||
|
||||
// 选中底:Ela 默认是半透明灰、对比度弱。改成清晰的强调蓝实色(明=浅蓝、暗=中深蓝),
|
||||
// 让所有 Ela 列表/视图的选中行一眼可辨(与对象树 QSS 选中色 kTreeSel* 保持一致)。
|
||||
eTheme->setThemeColor(ElaThemeType::Light, ElaThemeType::BasicSelectedAlpha, QColor(0xC2, 0xD9, 0xF2));
|
||||
eTheme->setThemeColor(ElaThemeType::Light, ElaThemeType::BasicSelectedHoverAlpha, QColor(0xB1, 0xCD, 0xEF));
|
||||
eTheme->setThemeColor(ElaThemeType::Dark, ElaThemeType::BasicSelectedAlpha, QColor(0x33, 0x52, 0x7A));
|
||||
eTheme->setThemeColor(ElaThemeType::Dark, ElaThemeType::BasicSelectedHoverAlpha, QColor(0x3C, 0x5D, 0x87));
|
||||
}
|
||||
|
||||
// ── 设置:主题 / 字号 偏好 ──────────────────────────────────────────────
|
||||
// ── 主题管理器(替代 ElaTheme)+ 设置:主题 / 字号 偏好 ──────────────────
|
||||
namespace {
|
||||
constexpr int kBaseFontPx = 13; // 基准字号(与 eApp 默认一致)
|
||||
constexpr int kBaseFontPx = 13; // 基准字号
|
||||
int g_fontScale = 100; // 当前字号缩放百分比
|
||||
} // namespace
|
||||
|
||||
ThemeManager& ThemeManager::instance()
|
||||
{
|
||||
static ThemeManager inst;
|
||||
return inst;
|
||||
}
|
||||
|
||||
ThemeManager::ThemeManager(QObject* parent) : QObject(parent)
|
||||
{
|
||||
applyPersisted();
|
||||
// 跟随系统时,系统明暗变化即同步并发 changed。
|
||||
QObject::connect(qApp->styleHints(), &QStyleHints::colorSchemeChanged, this,
|
||||
[this](Qt::ColorScheme) {
|
||||
if (!follow_) return;
|
||||
const bool d = qApp->styleHints()->colorScheme() == Qt::ColorScheme::Dark;
|
||||
if (d != dark_) {
|
||||
dark_ = d;
|
||||
emit changed();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void ThemeManager::applyPersisted()
|
||||
{
|
||||
const QString m =
|
||||
QSettings().value(QStringLiteral("ui/themeMode"), QStringLiteral("system")).toString();
|
||||
if (m == QStringLiteral("light")) {
|
||||
follow_ = false;
|
||||
dark_ = false;
|
||||
} else if (m == QStringLiteral("dark")) {
|
||||
follow_ = false;
|
||||
dark_ = true;
|
||||
} else { // system
|
||||
follow_ = true;
|
||||
dark_ = qApp->styleHints()->colorScheme() == Qt::ColorScheme::Dark;
|
||||
}
|
||||
}
|
||||
|
||||
void ThemeManager::setMode(const QString& mode)
|
||||
{
|
||||
QSettings().setValue(QStringLiteral("ui/themeMode"), mode);
|
||||
applyPersisted();
|
||||
emit changed(); // 热切:全 UI 重着色
|
||||
}
|
||||
|
||||
QString themeModePreference()
|
||||
{
|
||||
return QSettings().value(QStringLiteral("ui/themeMode"), QStringLiteral("system")).toString();
|
||||
|
|
@ -487,22 +546,12 @@ QString themeModePreference()
|
|||
|
||||
void applyPersistedThemeMode()
|
||||
{
|
||||
const QString m = themeModePreference();
|
||||
if (m == QStringLiteral("light")) {
|
||||
eTheme->setIsFollowSystemTheme(false);
|
||||
eTheme->setThemeMode(ElaThemeType::Light);
|
||||
} else if (m == QStringLiteral("dark")) {
|
||||
eTheme->setIsFollowSystemTheme(false);
|
||||
eTheme->setThemeMode(ElaThemeType::Dark);
|
||||
} else { // system:跟随系统明暗
|
||||
eTheme->setIsFollowSystemTheme(true);
|
||||
}
|
||||
ThemeManager::instance().applyPersisted();
|
||||
}
|
||||
|
||||
void setThemeModePreference(const QString& mode)
|
||||
{
|
||||
QSettings().setValue(QStringLiteral("ui/themeMode"), mode);
|
||||
applyPersistedThemeMode(); // 主题可热切,立即应用
|
||||
ThemeManager::instance().setMode(mode); // 持久化 + 热切
|
||||
}
|
||||
|
||||
int fontScalePreference()
|
||||
|
|
@ -530,7 +579,7 @@ int scaledPx(int basePx)
|
|||
|
||||
bool isDarkTheme()
|
||||
{
|
||||
return eTheme->getThemeMode() == ElaThemeType::Dark;
|
||||
return ThemeManager::instance().isDark();
|
||||
}
|
||||
|
||||
QString themed(const QString& designQss)
|
||||
|
|
@ -540,7 +589,7 @@ QString themed(const QString& designQss)
|
|||
|
||||
void vtkBackground(double& r, double& g, double& b)
|
||||
{
|
||||
const QColor c = roleColor(isDarkTheme(), ElaThemeType::WindowBase);
|
||||
const QColor c = roleColor(isDarkTheme(), "#F4F6FA");
|
||||
r = c.redF();
|
||||
g = c.greenF();
|
||||
b = c.blueF();
|
||||
|
|
@ -550,10 +599,8 @@ void applyThemedStyleSheet(QWidget* w, const QString& designQss)
|
|||
{
|
||||
if (!w) return;
|
||||
w->setStyleSheet(themedQss(designQss, isDarkTheme()));
|
||||
QObject::connect(eTheme, &ElaTheme::themeModeChanged, w,
|
||||
[w, designQss](ElaThemeType::ThemeMode m) {
|
||||
w->setStyleSheet(themedQss(designQss, m == ElaThemeType::Dark));
|
||||
});
|
||||
QObject::connect(&ThemeManager::instance(), &ThemeManager::changed, w,
|
||||
[w, designQss]() { w->setStyleSheet(themedQss(designQss, isDarkTheme())); });
|
||||
}
|
||||
|
||||
} // namespace geopro::app
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@
|
|||
// 文字 #1F2A3D 次要文字 #5A6B85 边框 #D5DBE5 强边框 #C2CCDA
|
||||
// 危险 #C0392B
|
||||
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
|
||||
class QApplication;
|
||||
|
|
@ -16,6 +17,23 @@ class QWidget;
|
|||
|
||||
namespace geopro::app {
|
||||
|
||||
// 主题管理器(纯 Qt,替代 ElaTheme):持有当前明暗 + 是否跟随系统;切换发 changed() 信号,
|
||||
// 全 UI(全局 QSS 由 main 重应用、内联 chrome 由 applyThemedStyleSheet)据此热切重着色。
|
||||
class ThemeManager : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
static ThemeManager& instance();
|
||||
bool isDark() const { return dark_; }
|
||||
void setMode(const QString& mode); // "system"|"light"|"dark":持久化 + 应用 + 发 changed
|
||||
void applyPersisted(); // 按持久化偏好设置状态(系统模式则取系统明暗)
|
||||
signals:
|
||||
void changed();
|
||||
private:
|
||||
explicit ThemeManager(QObject* parent = nullptr);
|
||||
bool dark_ = false;
|
||||
bool follow_ = true;
|
||||
};
|
||||
|
||||
// ── 排版令牌(全项目唯一字号阶 + 字重角色)──────────────────────────
|
||||
// 各处 QSS 的 font-size / font-weight 一律引用这些值,不再散落硬编码 px。
|
||||
// 阶比 ~1.18(body→title→heading),刻意拉开层级——避免 11/12/13/14
|
||||
|
|
@ -90,10 +108,6 @@ void applyThemeMode(QApplication& app, bool dark);
|
|||
// 浅色主题快捷入口(= applyThemeMode(app,false))。经典壳启动调用一次。
|
||||
void applyTheme(QApplication& app);
|
||||
|
||||
// 统一强调色:把 ElaTheme 主色(Primary)设为本项目品牌蓝(亮/暗各一档),让所有 Ela 原生控件
|
||||
// (选中/激活/标题栏强调) 与本项目 QSS 共用同一套蓝,消除"多种蓝打架"。在 eApp->init() 后调一次。
|
||||
void applyBrandAccent();
|
||||
|
||||
// ── 设置:主题 / 界面字号 偏好(QSettings 持久化)────────────────────────
|
||||
// 启动时(eApp->init + applyBrandAccent 之后、弹登录窗之前)各调一次,使登录页与主页统一。
|
||||
void applyPersistedThemeMode(); // 应用持久化主题:跟随系统 / 浅色 / 深色 → ElaTheme
|
||||
|
|
|
|||
|
|
@ -17,11 +17,6 @@
|
|||
#include "Glyphs.hpp"
|
||||
#include "Theme.hpp"
|
||||
|
||||
#include <ElaDef.h>
|
||||
#include <ElaIconButton.h>
|
||||
#include <ElaMenu.h>
|
||||
#include <ElaMenuBar.h>
|
||||
#include <ElaToolButton.h>
|
||||
|
||||
namespace geopro::app {
|
||||
|
||||
|
|
@ -42,20 +37,23 @@ QFrame* makeDivider(QWidget* parent)
|
|||
return line;
|
||||
}
|
||||
|
||||
// 右侧图标按钮(Fluent ElaIconButton:自带图标字体 + 悬停 + 随主题着色)。
|
||||
// 用带固定宽高的构造(icon, 字号, 宽, 高, parent)——否则图标会被压扁变形。
|
||||
QWidget* makeIconButton(QWidget* parent, ElaIconType::IconName icon, const QString& tip)
|
||||
// 右侧图标按钮(QToolButton + 项目 glyph 图标,随主题着色;悬停底由 #iconBtn QSS 给)。
|
||||
QWidget* makeIconButton(QWidget* parent, Glyph icon, const QString& tip)
|
||||
{
|
||||
auto* btn = new ElaIconButton(icon, 18, 34, 34, parent);
|
||||
auto* btn = new QToolButton(parent);
|
||||
btn->setObjectName(QStringLiteral("iconBtn"));
|
||||
setThemedGlyph(btn, icon, kToolIcon);
|
||||
btn->setIconSize(QSize(kToolIcon, kToolIcon));
|
||||
btn->setToolTip(tip);
|
||||
btn->setCursor(Qt::PointingHandCursor);
|
||||
btn->setAutoRaise(true);
|
||||
return btn;
|
||||
}
|
||||
|
||||
// ── 四个菜单(结构对齐需求;叶子项当前为静态占位,后续接真实页面)──
|
||||
QMenu* buildViewMenu(QWidget* p)
|
||||
{
|
||||
auto* m = new ElaMenu(QStringLiteral("视图"), p);
|
||||
auto* m = new QMenu(QStringLiteral("视图"), p);
|
||||
m->addAction(QStringLiteral("分析视图"));
|
||||
m->addAction(QStringLiteral("大屏视图"));
|
||||
return m;
|
||||
|
|
@ -63,7 +61,7 @@ QMenu* buildViewMenu(QWidget* p)
|
|||
|
||||
QMenu* buildProjectMenu(QWidget* p)
|
||||
{
|
||||
auto* m = new ElaMenu(QStringLiteral("项目管理"), p);
|
||||
auto* m = new QMenu(QStringLiteral("项目管理"), p);
|
||||
m->addAction(QStringLiteral("数据视图"));
|
||||
auto* cfg = m->addMenu(QStringLiteral("项目配置"));
|
||||
cfg->addAction(QStringLiteral("基本信息"));
|
||||
|
|
@ -97,7 +95,7 @@ QMenu* buildProjectMenu(QWidget* p)
|
|||
|
||||
QMenu* buildToolsMenu(QWidget* p)
|
||||
{
|
||||
auto* m = new ElaMenu(QStringLiteral("业务工具"), p);
|
||||
auto* m = new QMenu(QStringLiteral("业务工具"), p);
|
||||
m->addAction(QStringLiteral("ERT 思维分析"));
|
||||
m->addAction(QStringLiteral("电法脚本与装置"));
|
||||
m->addAction(QStringLiteral("Geo 反演"));
|
||||
|
|
@ -107,7 +105,7 @@ QMenu* buildToolsMenu(QWidget* p)
|
|||
|
||||
QMenu* buildDeviceMenu(QWidget* p)
|
||||
{
|
||||
auto* m = new ElaMenu(QStringLiteral("设备"), p);
|
||||
auto* m = new QMenu(QStringLiteral("设备"), p);
|
||||
m->addAction(QStringLiteral("连接设备"));
|
||||
m->addAction(QStringLiteral("设备管理"));
|
||||
return m;
|
||||
|
|
@ -117,7 +115,7 @@ QMenu* buildDeviceMenu(QWidget* p)
|
|||
|
||||
QWidget* buildMenuBar(QWidget* parent)
|
||||
{
|
||||
auto* mb = new ElaMenuBar(parent);
|
||||
auto* mb = new QMenuBar(parent);
|
||||
mb->setObjectName(QStringLiteral("appMenuBar"));
|
||||
// ElaMenuBar 自绘 Fluent 外观并自动随 ElaTheme 明暗,不再写内联 QSS。
|
||||
mb->addMenu(buildViewMenu(mb));
|
||||
|
|
@ -142,6 +140,8 @@ TopBar::TopBar(QWidget* parent) : QWidget(parent) {
|
|||
"#wsSwitcher { color:#1F2A3D; border:none; border-radius:8px; padding:8px 12px;"
|
||||
" font-size:%6px; font-weight:%4; }"
|
||||
"#wsSwitcher:hover { background:#EEF3FB; }"
|
||||
"QToolButton#iconBtn { border:none; border-radius:8px; padding:8px; }"
|
||||
"QToolButton#iconBtn:hover { background:#EEF3FB; }"
|
||||
"#avatar { background:#2D6CB5; color:white; border-radius:17px; font-weight:%2;"
|
||||
" font-size:%1px; }"
|
||||
"#userName { color:#1F2A3D; font-size:%3px; font-weight:%4; }"
|
||||
|
|
@ -166,7 +166,7 @@ TopBar::TopBar(QWidget* parent) : QWidget(parent) {
|
|||
wsBtn_->setPopupMode(QToolButton::InstantPopup);
|
||||
wsBtn_->setCursor(Qt::PointingHandCursor);
|
||||
wsBtn_->setText(QStringLiteral("正在加载工作空间…"));
|
||||
wsBtn_->setMenu(new ElaMenu(wsBtn_));
|
||||
wsBtn_->setMenu(new QMenu(wsBtn_));
|
||||
lay->addWidget(wsBtn_);
|
||||
|
||||
lay->addSpacing(10);
|
||||
|
|
@ -182,14 +182,14 @@ TopBar::TopBar(QWidget* parent) : QWidget(parent) {
|
|||
projBtn_->setPopupMode(QToolButton::InstantPopup);
|
||||
projBtn_->setCursor(Qt::PointingHandCursor);
|
||||
projBtn_->setText(QStringLiteral("正在加载项目…"));
|
||||
projBtn_->setMenu(new ElaMenu(projBtn_));
|
||||
projBtn_->setMenu(new QMenu(projBtn_));
|
||||
lay->addWidget(projBtn_);
|
||||
|
||||
lay->addStretch();
|
||||
|
||||
lay->addWidget(makeIconButton(this, ElaIconType::CircleQuestion, QStringLiteral("帮助")));
|
||||
lay->addWidget(makeIconButton(this, ElaIconType::Bell, QStringLiteral("通知")));
|
||||
auto* gearBtn = makeIconButton(this, ElaIconType::Gear, QStringLiteral("设置"));
|
||||
lay->addWidget(makeIconButton(this, Glyph::Help, QStringLiteral("帮助")));
|
||||
lay->addWidget(makeIconButton(this, Glyph::Bell, QStringLiteral("通知")));
|
||||
auto* gearBtn = makeIconButton(this, Glyph::Gear, QStringLiteral("设置"));
|
||||
if (auto* gb = qobject_cast<QAbstractButton*>(gearBtn))
|
||||
QObject::connect(gb, &QAbstractButton::clicked, this, [this] { emit settingsRequested(); });
|
||||
lay->addWidget(gearBtn);
|
||||
|
|
@ -204,7 +204,7 @@ TopBar::TopBar(QWidget* parent) : QWidget(parent) {
|
|||
avatar->setFixedSize(34, 34);
|
||||
avatar->setCursor(Qt::PointingHandCursor);
|
||||
avatar->setPopupMode(QToolButton::InstantPopup);
|
||||
auto* userMenu = new ElaMenu(avatar);
|
||||
auto* userMenu = new QMenu(avatar);
|
||||
QObject::connect(userMenu->addAction(QStringLiteral("退出登录")), &QAction::triggered, this,
|
||||
[this] { emit logoutRequested(); });
|
||||
avatar->setMenu(userMenu);
|
||||
|
|
@ -225,7 +225,7 @@ TopBar::TopBar(QWidget* parent) : QWidget(parent) {
|
|||
}
|
||||
|
||||
void TopBar::setWorkspaces(const std::vector<data::Workspace>& list, const QString& currentId) {
|
||||
auto* menu = new ElaMenu(wsBtn_);
|
||||
auto* menu = new QMenu(wsBtn_);
|
||||
auto* header = menu->addAction(QStringLiteral("切换空间"));
|
||||
header->setEnabled(false);
|
||||
menu->addSeparator();
|
||||
|
|
@ -256,7 +256,7 @@ void TopBar::setWorkspaces(const std::vector<data::Workspace>& list, const QStri
|
|||
|
||||
void TopBar::setProjects(const std::vector<data::ProjectSummary>& list, const QString& currentId,
|
||||
bool hasMore) {
|
||||
auto* menu = new ElaMenu(projBtn_);
|
||||
auto* menu = new QMenu(projBtn_);
|
||||
auto* header = menu->addAction(QStringLiteral("切换项目"));
|
||||
header->setEnabled(false);
|
||||
menu->addSeparator();
|
||||
|
|
|
|||
|
|
@ -20,10 +20,6 @@
|
|||
#include "AuthService.hpp"
|
||||
#include "Theme.hpp"
|
||||
|
||||
#include <ElaCheckBox.h>
|
||||
#include <ElaLineEdit.h>
|
||||
#include <ElaPushButton.h>
|
||||
|
||||
namespace geopro::app {
|
||||
|
||||
namespace {
|
||||
|
|
@ -148,12 +144,12 @@ LoginWindow::LoginWindow(geopro::net::AuthService& auth, QWidget* parent)
|
|||
form->addSpacing(6);
|
||||
};
|
||||
|
||||
userEdit_ = new ElaLineEdit(body);
|
||||
userEdit_ = new QLineEdit(body);
|
||||
userEdit_->setPlaceholderText(QStringLiteral("请输入用户名"));
|
||||
userEdit_->setClearButtonEnabled(true);
|
||||
addField(QStringLiteral("用户名"), userEdit_);
|
||||
|
||||
pwdEdit_ = new ElaLineEdit(body);
|
||||
pwdEdit_ = new QLineEdit(body);
|
||||
pwdEdit_->setEchoMode(QLineEdit::Password);
|
||||
pwdEdit_->setPlaceholderText(QStringLiteral("请输入密码"));
|
||||
addField(QStringLiteral("密码"), pwdEdit_);
|
||||
|
|
@ -165,7 +161,7 @@ LoginWindow::LoginWindow(geopro::net::AuthService& auth, QWidget* parent)
|
|||
|
||||
auto* captchaRow = new QHBoxLayout();
|
||||
captchaRow->setSpacing(10);
|
||||
codeEdit_ = new ElaLineEdit(body);
|
||||
codeEdit_ = new QLineEdit(body);
|
||||
codeEdit_->setMinimumHeight(40);
|
||||
codeEdit_->setPlaceholderText(QStringLiteral("请输入验证码"));
|
||||
captchaLabel_ = new QLabel(body);
|
||||
|
|
@ -191,7 +187,7 @@ LoginWindow::LoginWindow(geopro::net::AuthService& auth, QWidget* parent)
|
|||
form->addLayout(refreshRow);
|
||||
|
||||
// 记住登录:勾选后成功登录将安全存储 token,30 天内免登录。默认不勾(更安全)。
|
||||
rememberChk_ = new ElaCheckBox(QStringLiteral("记住登录(30 天内免登录)"), body);
|
||||
rememberChk_ = new QCheckBox(QStringLiteral("记住登录(30 天内免登录)"), body);
|
||||
rememberChk_->setCursor(Qt::PointingHandCursor); // ElaCheckBox 自绘 Fluent + 自动明暗
|
||||
form->addWidget(rememberChk_);
|
||||
|
||||
|
|
@ -206,7 +202,7 @@ LoginWindow::LoginWindow(geopro::net::AuthService& auth, QWidget* parent)
|
|||
form->addStretch();
|
||||
|
||||
// 主操作:满宽强调主按钮(von Restorff:唯一高强调元素引导主流程)。
|
||||
loginBtn_ = new ElaPushButton(QStringLiteral("登 录"), body); // Fluent 主按钮(自动明暗)
|
||||
loginBtn_ = new QPushButton(QStringLiteral("登 录"), body); // Fluent 主按钮(自动明暗)
|
||||
loginBtn_->setMinimumHeight(44);
|
||||
loginBtn_->setCursor(Qt::PointingHandCursor);
|
||||
loginBtn_->setDefault(true);
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@
|
|||
#include <QEvent>
|
||||
#include <QFile>
|
||||
#include <QButtonGroup>
|
||||
#include <QCheckBox>
|
||||
#include <QFrame>
|
||||
#include <QHBoxLayout>
|
||||
#include <QGraphicsOpacityEffect>
|
||||
|
|
@ -64,12 +65,6 @@
|
|||
#include <DockManager.h>
|
||||
#include <DockWidget.h>
|
||||
|
||||
#include <ElaApplication.h>
|
||||
#include <ElaCheckBox.h>
|
||||
#include <ElaDef.h>
|
||||
#include <ElaTheme.h>
|
||||
#include <ElaToolButton.h>
|
||||
#include <ElaWindow.h>
|
||||
|
||||
#include "model/ColorScale.hpp"
|
||||
#include "model/Field.hpp"
|
||||
|
|
@ -305,8 +300,8 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
|
|||
"ads--CDockContainerWidget ads--CDockSplitter::handle:hover { background: #C7D2E0; }")));
|
||||
};
|
||||
applyDockSplitter();
|
||||
QObject::connect(eTheme, &ElaTheme::themeModeChanged, dockManager,
|
||||
[applyDockSplitter](ElaThemeType::ThemeMode) { applyDockSplitter(); });
|
||||
QObject::connect(&geopro::app::ThemeManager::instance(), &geopro::app::ThemeManager::changed,
|
||||
dockManager, [applyDockSplitter]() { applyDockSplitter(); });
|
||||
|
||||
// 面板包装:内容顶部加自绘表头(图标+标题+操作按钮),ADS 自带标题栏随后隐藏,
|
||||
// 从而让面板表头与原型完全一致。返回 [表头 + 内容] 容器供 setWidget。
|
||||
|
|
@ -382,13 +377,13 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
|
|||
"padding-bottom:3px;font-size:%2px;")
|
||||
.arg(geopro::app::type::kWeightSemibold)
|
||||
.arg(geopro::app::scaledPx(geopro::app::type::kTitle)));
|
||||
auto* chkCurtain = new ElaCheckBox(QStringLiteral("帘面(断面墙)"));
|
||||
auto* chkCurtain = new QCheckBox(QStringLiteral("帘面(断面墙)"));
|
||||
chkCurtain->setChecked(true);
|
||||
auto* chkVoxel = new ElaCheckBox(QStringLiteral("体素(dd_voxel)"));
|
||||
auto* chkVoxel = new QCheckBox(QStringLiteral("体素(dd_voxel)"));
|
||||
chkVoxel->setChecked(false);
|
||||
auto* chkTerrain = new ElaCheckBox(QStringLiteral("地形(DEM+影像)"));
|
||||
auto* chkTerrain = new QCheckBox(QStringLiteral("地形(DEM+影像)"));
|
||||
chkTerrain->setChecked(false);
|
||||
auto* chkSlice = new ElaCheckBox(QStringLiteral("切片(dd_slice)"));
|
||||
auto* chkSlice = new QCheckBox(QStringLiteral("切片(dd_slice)"));
|
||||
chkSlice->setChecked(false);
|
||||
if (!crs) { // PROJ 不可用 → 体素/切片/地形层(都需配准)禁用并提示
|
||||
const QString tip = QStringLiteral("PROJ 数据(proj.db)缺失,配准不可用");
|
||||
|
|
@ -557,8 +552,8 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
|
|||
.arg(selBg, selFg));
|
||||
};
|
||||
styleIt();
|
||||
QObject::connect(eTheme, &ElaTheme::themeModeChanged, lw,
|
||||
[styleIt](ElaThemeType::ThemeMode) { styleIt(); });
|
||||
QObject::connect(&geopro::app::ThemeManager::instance(), &geopro::app::ThemeManager::changed,
|
||||
lw, [styleIt]() { styleIt(); });
|
||||
};
|
||||
|
||||
// 左下 dock:数据真实显示栏(选中测线后列其采集批次=数据集;tab 数据/文件)。
|
||||
|
|
@ -884,7 +879,7 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
|
|||
|
||||
// VTK 背景随主题切换:直接重跑 rebuildCentral/rebuildDetail(走完整渲染路径、末尾必 Render,
|
||||
// 比手动 SetBackground+Render 稳;兼顾 syncSystemTheme 异步切暗的时序)。
|
||||
QObject::connect(eTheme, &ElaTheme::themeModeChanged, &window,
|
||||
QObject::connect(&geopro::app::ThemeManager::instance(), &geopro::app::ThemeManager::changed, &window,
|
||||
[rebuildCentral, rebuildDetail]() {
|
||||
rebuildCentral();
|
||||
rebuildDetail();
|
||||
|
|
@ -1063,16 +1058,11 @@ int main(int argc, char* argv[])
|
|||
QCoreApplication::setOrganizationName(QStringLiteral("Geomative"));
|
||||
QCoreApplication::setApplicationName(QStringLiteral("Geopro3"));
|
||||
|
||||
// ElaApplication:Fluent 主题/字体/动画基建。无条件初始化——登录窗与各面板已 Ela 化,
|
||||
// 两种壳都需要它(登录发生在选壳之前)。Ela 控件跟随 ElaTheme;标准控件仍由下面 QSS 接管。
|
||||
eApp->init();
|
||||
geopro::app::applyBrandAccent(); // 统一品牌强调色(Ela Primary),全 UI 选中/激活一套蓝
|
||||
geopro::app::applyPersistedThemeMode(); // 应用持久化主题(跟随系统/浅/深)——登录页与主页统一
|
||||
geopro::app::applyPersistedFontScale(); // 应用持久化字号缩放(登录页也随之缩放)
|
||||
|
||||
// 专业主题(Fusion + 调色板 + 全局样式表):标准控件外观,登录窗与工作台共用。
|
||||
// 跟随最终 ElaTheme 模式(持久化偏好已生效),使登录窗与标准控件明暗一致。
|
||||
geopro::app::applyThemeMode(app, eTheme->getThemeMode() == ElaThemeType::Dark);
|
||||
// 主题与字号:应用持久化偏好(跟随系统/浅/深 + 字号缩放),再套用 Fusion + 全局 QSS + 调色板。
|
||||
// 在弹登录窗之前完成 → 登录页与主页 明暗/字号 统一。
|
||||
geopro::app::applyPersistedThemeMode();
|
||||
geopro::app::applyPersistedFontScale();
|
||||
geopro::app::applyThemeMode(app, geopro::app::isDarkTheme());
|
||||
|
||||
// PROJ 数据(proj.db)定位:体素配准的 CrsTransform 需要。优先已设环境变量;
|
||||
// 否则按 exe 旁 / 构建目录候选设置。部署时须随包附带 proj 数据并设此变量。
|
||||
|
|
@ -1119,35 +1109,25 @@ int main(int argc, char* argv[])
|
|||
geopro::data::ApiProjectRepository projectRepo(api);
|
||||
geopro::controller::WorkbenchNavController nav(projectRepo);
|
||||
|
||||
// ── 外壳:Fluent ElaWindow(唯一路径)。ElaWindow 用 addPageNode 包裹一个承载工作台的内层
|
||||
// QMainWindow(buildWorkbench 依赖 QMainWindow 的 setCentralWidget/setMenuWidget/statusBar,
|
||||
// ElaWindow 自身将其设为私有,故用内层 QMainWindow 承接,零改 buildWorkbench)。
|
||||
// ── 外壳:标准 QMainWindow(原生标题栏)。buildWorkbench 直接用其
|
||||
// setCentralWidget/setMenuWidget/statusBar 承载工作台。
|
||||
const QString kTitle = QStringLiteral("Geopro 3.0 — 项目分析视图 (M1)");
|
||||
auto* ela = new ElaWindow;
|
||||
ela->setWindowTitle(kTitle);
|
||||
ela->resize(1280, 800);
|
||||
ela->setMinimumSize(1024, 680);
|
||||
ela->setIsNavigationBarEnable(false); // 纯中心内容,不显示左侧导航栏
|
||||
ela->setAppBarHeight(38); // 默认 45 偏大,收紧标题栏更接近原生
|
||||
auto* inner = new QMainWindow(ela); // 以 ela 为父,避免无父期调色板/DPI 抖动
|
||||
buildWorkbench(*inner, repo, projectRepo, nav);
|
||||
// 用 addPageNode 把工作台作为唯一页面放进中心页栈(填满到底边)。
|
||||
// 注意:不能用 setCentralCustomWidget——它把控件插到页栈容器“之上”,空页栈仍占底部,
|
||||
// 导致状态栏不贴底边(见 ElaCentralStackedWidget::setCustomWidget 的 insertWidget(0,...))。
|
||||
ela->addPageNode(kTitle, inner);
|
||||
auto* window = new QMainWindow;
|
||||
window->setWindowTitle(kTitle);
|
||||
window->resize(1280, 800);
|
||||
window->setMinimumSize(1024, 680);
|
||||
buildWorkbench(*window, repo, projectRepo, nav);
|
||||
|
||||
// 主题桥:ElaTheme 明/暗切换 → 同步全局 QSS+调色板(覆盖所有标准控件与 ADS)。
|
||||
QObject::connect(eTheme, &ElaTheme::themeModeChanged, ela, [&app](ElaThemeType::ThemeMode m) {
|
||||
geopro::app::applyThemeMode(app, m == ElaThemeType::Dark);
|
||||
// 主题桥:ThemeManager 明/暗切换 → 重应用全局 QSS+调色板(标准控件 + ADS;内联 chrome 经各自连接)。
|
||||
QObject::connect(&geopro::app::ThemeManager::instance(), &geopro::app::ThemeManager::changed,
|
||||
window, [&app]() { geopro::app::applyThemeMode(app, geopro::app::isDarkTheme()); });
|
||||
// 主题切换快捷键 Ctrl+Shift+T(持久化;设置→外观 亦可改)。
|
||||
auto* themeSc = new QShortcut(QKeySequence(QStringLiteral("Ctrl+Shift+T")), window);
|
||||
QObject::connect(themeSc, &QShortcut::activated, window, [] {
|
||||
geopro::app::setThemeModePreference(geopro::app::isDarkTheme() ? QStringLiteral("light")
|
||||
: QStringLiteral("dark"));
|
||||
});
|
||||
geopro::app::applyThemeMode(app, eTheme->getThemeMode() == ElaThemeType::Dark); // 初始对齐
|
||||
// 主题切换快捷键 Ctrl+Shift+T(标题栏亦有 Ela 自带的明暗切换键)。
|
||||
auto* themeSc = new QShortcut(QKeySequence(QStringLiteral("Ctrl+Shift+T")), ela);
|
||||
QObject::connect(themeSc, &QShortcut::activated, ela, [] {
|
||||
eTheme->setThemeMode(eTheme->getThemeMode() == ElaThemeType::Light ? ElaThemeType::Dark
|
||||
: ElaThemeType::Light);
|
||||
});
|
||||
ela->show();
|
||||
window->show();
|
||||
|
||||
nav.start(); // 进入工作台后拉真实 空间/项目/结构
|
||||
|
||||
|
|
|
|||
|
|
@ -6,9 +6,6 @@
|
|||
#include <QTreeWidgetItem>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include <ElaDef.h>
|
||||
#include <ElaTheme.h>
|
||||
|
||||
#include "Glyphs.hpp"
|
||||
#include "Theme.hpp"
|
||||
#include "dto/NavDto.hpp"
|
||||
|
|
@ -49,8 +46,7 @@ ObjectTreePanel::ObjectTreePanel(QWidget* parent) : QWidget(parent) {
|
|||
const bool dark = geopro::app::isDarkTheme();
|
||||
const QColor border = dark ? QColor(0x8A, 0x92, 0x9C) : QColor(0x8A, 0x93, 0xA3);
|
||||
const QColor boxBg = dark ? QColor(0x2B, 0x2D, 0x30) : QColor(0xFF, 0xFF, 0xFF);
|
||||
const QColor accent = eTheme->getThemeColor(
|
||||
dark ? ElaThemeType::Dark : ElaThemeType::Light, ElaThemeType::PrimaryNormal);
|
||||
const QColor accent = dark ? QColor(0x5E, 0x9B, 0xD6) : QColor(0x2D, 0x6C, 0xB5); // 强调明/暗
|
||||
const QString tag = dark ? QStringLiteral("d") : QStringLiteral("l");
|
||||
const QString off = geopro::app::writeCheckboxIcon(false, border, boxBg, Qt::white, tag);
|
||||
const QString on = geopro::app::writeCheckboxIcon(true, accent, accent, Qt::white, tag);
|
||||
|
|
@ -65,8 +61,8 @@ ObjectTreePanel::ObjectTreePanel(QWidget* parent) : QWidget(parent) {
|
|||
.arg(off, on, selBg, selFg));
|
||||
};
|
||||
applyCheckboxStyle();
|
||||
QObject::connect(eTheme, &ElaTheme::themeModeChanged, tree_,
|
||||
[applyCheckboxStyle](ElaThemeType::ThemeMode) { applyCheckboxStyle(); });
|
||||
QObject::connect(&ThemeManager::instance(), &ThemeManager::changed, tree_,
|
||||
[applyCheckboxStyle]() { applyCheckboxStyle(); });
|
||||
|
||||
lay->addWidget(tree_, 1);
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue