UI 重设计:Apple 高级浅色主题 + 浅深双模式
日期:2026-05-09
分支:feat/ui-redesign-apple-theme
状态:设计已定,实施进行中
1. 背景与目标
1.1 问题陈述
当前 UI 是 PySide6 默认皮肤 + 65+ 处零散 inline setStyleSheet(image_generator.py:65 处、task_queue.py:4 处),没有统一设计语言。具体表现:
- 全灰白默认 Qt 风格,缺现代感
- "生成图片"和"下载图片"主次按钮视觉权重一样
- GroupBox 边框过重(Win95 观感)
- "任务队列"sidebar 标题在视觉上飘到主区 tab bar 旁边
- 字号/间距/圆角不统一
- macOS 系统切换深色主题时整个应用观感断裂
1.2 目标
- 统一设计语言:Apple Human Interface Guidelines 浅色风格 + Apple Blue (#0071E3) 主色
- 跨平台一致:macOS 和 Windows 11 视觉一致(用户原话:"统一两端的设计语言")
-
支持系统深浅主题自动切换:跟随
QGuiApplication.styleHints().colorScheme() - 修复关键 layout 问题:按钮主次分级、sidebar 头部、卡片化 GroupBox
- 保持业务逻辑不变:纯视觉层重构,不动生成 / 队列 / 历史功能
1.3 非目标(YAGNI)
- 不引第三方 UI 库(qfluentwidgets、qt-material 等)
- 不重做信息架构(tabs、表单字段)
- 不增加新功能
- 不做主题色自定义(用户改色等高级功能)
2. 已确认的设计决策
| 决策点 | 结果 | 用户原话/理由 |
|---|---|---|
| 设计方向 | C · Apple 高级感浅色 | "可以统一 MacOS 与 Windows 的设计语言" |
| 主色调 | A · Apple Blue #0071E3
|
"跟我们的品牌元素的颜色契合" |
| 重写范围 | B · 外观 + 关键 layout 修复 | (选择推荐项) |
| 深色模式 | 完整双套主题 | "macos 会让软件跟随时间自由转换深浅主题,不做就巨丑巨难看" |
| 二级窗口 | 一并重做(登录 + 所有 dialog) | (选择推荐项) |
| 实现路径 | B · 设计令牌生成器(Python tokens → build_qss) | Python dict 比双 .qss 文件不容易飘,dark mode 90% 是规则反转 |
3. 设计令牌(Design Tokens)
所有数值在 theme.py 中以 Python dict 定义,单一真相源。
3.1 颜色 — 浅色(Light)
# 表面层级
bg_canvas #fbfbfd 主窗口背景(极浅冷灰)
bg_surface #ffffff 卡片 / dialog 背景
bg_elevated #ffffff 带投影的浮起容器
bg_subtle #f4f4f7 GroupBox / 输入框 hover 底色
# 文字层级
text_primary #1d1d1f 主文字
text_secondary #6e6e73 次要文字 / 标签
text_tertiary #86868b 占位符 / 禁用文字
text_on_accent #ffffff 主按钮上的白字
# 边框 / 分隔
border_default rgba(0,0,0,0.10) 默认边框(投影替代式)
border_strong rgba(0,0,0,0.15) 焦点 / 选中
divider rgba(0,0,0,0.06) 分隔线
# 主色
accent #0071e3 Apple Blue
accent_hover #0077ed 悬停(略亮)
accent_pressed #0062c4 按下
accent_subtle rgba(0,113,227,0.08) 主色淡背景(hover 卡片等)
# 状态色
success #34c759 成功 / 在线
warning #ff9500 警告
danger #ff3b30 危险 / 错误
3.2 颜色 — 深色(Dark)
bg_canvas #1a1a1c
bg_surface #2c2c2e
bg_elevated #3a3a3c
bg_subtle #242426
text_primary #ffffff
text_secondary #98989d
text_tertiary #6e6e73
text_on_accent #ffffff
border_default rgba(255,255,255,0.10)
border_strong rgba(255,255,255,0.18)
divider rgba(255,255,255,0.06)
accent #0a84ff (Dark mode 下 Apple Blue 略亮)
accent_hover #1d8cff
accent_pressed #006edc
accent_subtle rgba(10,132,255,0.16)
success #30d158
warning #ff9f0a
danger #ff453a
设计原则:浅深对照不是简单灰阶反转,而是参考 Apple SF Symbols / SwiftUI 的语义对照表。bg_canvas 比 bg_surface 低半级(浅色:surface 比 canvas 亮;深色:surface 比 canvas 亮)—— 卡片永远比底板"凸"。
3.3 间距与尺寸
# 圆角
radius_sm 4px 标签 / chip
radius_md 8px 输入框 / 普通按钮 / 卡片
radius_lg 12px 大卡片 / dialog
radius_pill 980px 胶囊主按钮(Apple HIG 标志性)
# 间距(4px grid)
space_1 4px
space_2 8px
space_3 12px
space_4 16px
space_5 20px
space_6 24px
space_8 32px
# 字号(pt - Qt 跨平台)
font_xs 10pt 提示 / 标签
font_sm 11pt 次要文字 / 按钮文字
font_base 12pt 正文(Windows)/ 13pt(macOS, 系统检测加 1)
font_lg 14pt 小标题
font_xl 17pt 主标题
# 控件高度
control_h_sm 28px 小按钮 / 紧凑输入框
control_h_md 32px 常规按钮 / 输入框(默认)
control_h_lg 40px 主操作按钮 / 大输入框
3.4 字体栈
font_family = "-apple-system, 'SF Pro Text', 'PingFang SC',
'Microsoft YaHei UI', 'Segoe UI Variable',
'Segoe UI', sans-serif"
策略:让操作系统选最合适的字体——macOS 走 SF Pro / 苹方,Windows 走 Segoe UI Variable / 微软雅黑。Qt 会自动 fallback。
4. 模块架构
4.1 theme.py(新文件,约 300 行)
# 公共 API
def apply_theme(app: QApplication) -> ThemeManager
"""应用主题。在 main() 创建 QApplication 后立即调用。返回 ThemeManager
实例(持有引用以保持信号连接)。"""
class ThemeManager(QObject):
theme_changed = Signal(str) # 'light' | 'dark'
def current_mode() -> str
def force_mode(mode: str | None) # None = 跟随系统
# 内部:监听 QGuiApplication.styleHints().colorSchemeChanged
# 令牌
TOKENS_LIGHT: dict[str, str]
TOKENS_DARK: dict[str, str]
def build_qss(mode: str) -> str
"""根据 mode(light/dark)生成完整 QSS 字符串。"""
4.2 集成点
main 入口 image_generator.py:4940:
app = QApplication(sys.argv)
from theme import apply_theme
theme_manager = apply_theme(app) # 必须在创建任何窗口前
preflight 入口 preflight.py:215, 218, 256, 258:同上
所有 widget:通过 setObjectName() 或 setProperty() 来命中 QSS 选择器,禁止再用 inline setStyleSheet(除少数语义性状态色,见 §6.2)。
4.3 数据流
系统切换深色 → Qt 触发 colorSchemeChanged
→ ThemeManager 接收信号
→ build_qss(new_mode)
→ app.setStyleSheet(new_qss)
→ 所有 widget 自动重绘(Qt 内置)
→ 发射 theme_changed 信号(业务代码可监听,目前不需要)
5. 组件样式映射
5.1 按钮分级
QPushButton 默认次按钮(白底蓝边/字)
QPushButton[variant="primary"] 主按钮(实色 accent 背景,白字,pill 圆角)
QPushButton[variant="ghost"] 幽灵按钮(透明背景,hover 出 accent_subtle)
QPushButton[variant="danger"] 危险按钮(红字描边)
应用清单:
-
生成图片→variant="primary"(pill 圆角 + accent 背景) -
下载图片/添加图片/粘贴图片→ 默认次按钮 -
删除/清空历史→variant="danger" -
收藏之类的图标按钮 →variant="ghost"
5.2 GroupBox → 卡片化
去掉边框 + Win95 风格标题,改为:
- 白色底(
bg_surface) - 极淡的投影(
box-shadow: 0 1px 2px rgba(0,0,0,0.04),Qt QSS 不直接支持 box-shadow,用border: 1px solid border_default模拟) - 标题不再镶嵌在边框上,改为顶部独立 label(uppercase 小字 +
text_secondary)
5.3 Tab 栏
QTabBar::tab 默认状态:透明背景 + text_secondary 文字
QTabBar::tab:selected 选中:accent 文字 + 底部 2px accent 横线
QTabBar::tab:hover 悬停:text_primary 文字
去掉默认 Qt tab 的"凸起带边框"外观,改为下划线式(更现代,Notion/VSCode/Chrome 都这风格)。
5.4 输入框 / 下拉
QLineEdit, QTextEdit, QComboBox
默认:bg_surface + 1px border_default + radius_md
焦点:1px border_strong + 1px accent 外发光(QSS 不支持 outline,用双层 border 实现)
禁用:bg_subtle + text_tertiary
5.5 状态色(保留 inline,但走 property)
# 之前
self.status_label.setStyleSheet("QLabel { color: #34C759; }")
# 之后
self.status_label.setProperty("status", "success")
self.status_label.style().polish(self.status_label) # 重绘
QSS 里:
QLabel[status="success"] { color: palette(success); }
QLabel[status="error"] { color: palette(danger); }
QLabel[status="muted"] { color: palette(text_secondary); }
5.6 任务队列 sidebar header 修复
当前 sidebar 顶部"任务队列"label 在视觉上飘到主区 tab bar 右侧。修复:sidebar 顶部加一个 32px 高的 header bar,使 sidebar 头部与主区 tab bar 在同一基线。
6. 迁移策略
6.1 全量删除清单
image_generator.py: 65 处 setStyleSheet
task_queue.py: 4 处 setStyleSheet
合计 69 处
按性质分类:
| 类型 | 数量估算 | 处理方式 |
|---|---|---|
| 全局 QSS 块(line 1331/1602/1644/1659/1676/2427) | 6 | 删除,迁移到 theme.py |
| 装饰色(按钮背景、容器边框、字体大小) | ~50 | 删除,依赖全局主题 |
| 状态色(红/绿/灰) | ~10 | 改 setProperty + style().polish() |
| 平台兼容 hack(Mac 输入可见性等) | ~3 | 保留并在 design doc 标注 |
6.2 保留的 inline setStyleSheet
只有以下场景允许 inline:
- 平台 hack(如 macOS 输入框可见性 fix,line 1285)
- 临时高亮反馈(鼠标悬停后 1 秒消失这种)
其他一律走 QSS。
6.3 OBJECT_NAME 命名约定
loginDialog 登录对话框
mainWindow 主窗口
generationTab 生成 tab
styleDesignerTab 款式设计 tab
historyTab 历史记录 tab
taskQueueSidebar 任务队列侧边栏
sidebarHeader 侧边栏头部 32px
previewArea 预览区
referenceImageDrop 参考图拖拽区
QSS 通过 #objectName 命中。
7. Layout 修复清单(Scope B 范围内)
| 问题 | 修复方案 |
|---|---|
| "任务队列"飘右上角 | sidebar 顶部加 32px header bar 与 tab bar 对齐 |
| 主次按钮不分 | "生成图片"加 variant="primary"
|
| GroupBox 边框过重 | 卡片化(白底 + 极淡边框 + 圆角) |
| 控件间距不一 | 统一改用 space_3 (12px) 间距 |
| 状态文字颜色 inline | 改 property |
| 登录页和主窗口风格断裂 | 全部走全局主题 |
不做的 layout 改动(避免范围蔓延):
- 不重排 tab 顺序
- 不动 70/30 主区与 sidebar 比例
- 不改字段顺序
- 不改"款式设计"tab 的 9 个 GroupBox 信息架构
8. 测试策略
8.1 自动化测试
项目目前没有 GUI 测试框架。不为本次重构新增测试(避免引入 pytest-qt 等依赖)。
保留现有 test_config_loading.py 跑通即可(QSS 重构不影响配置)。
8.2 手工验收清单
启动后按顺序检查:
- 登录页能正常显示和登录
- 主窗口三个 tab 切换正常
- "生成图片"按钮视觉权重明显高于"下载图片"
- GroupBox 看起来是"卡片"而不是"边框"
- 任务队列侧边栏头部不再飘到主区 tab 上
- 输入框有焦点蓝边
- 系统切换深色(Mac: 系统设置 → 外观 / Win11: 设置 → 个性化 → 颜色)→ 应用自动跟随
- QMessageBox 弹窗也跟主题(Qt 默认会,确认一下)
- 所有"删除"按钮有 danger 视觉
- 业务功能完全没动(生成、队列、历史)
8.3 打包验证
PyInstaller 打包跑一次,确保:
- exe 能启动到登录页
- 启动到主窗口
- 主题资源(如有)被打包进去
9. 风险与回退
9.1 已知风险
| 风险 | 缓解 |
|---|---|
| Qt QSS 不支持 box-shadow,圆角阴影效果有限 | 用双层 border 模拟,效果接近但不等同 web |
| StyleHint colorSchemeChanged 在某些 Linux DE 不发射 | 项目实际部署只在 macOS + Windows,无影响 |
| 删 inline setStyleSheet 时漏改某个 widget → 视觉断层 | 每次 commit 单独跑一次启动验证 |
| 全局 palette 改变后 QMessageBox 系统 dialog 可能不跟随 | 全局 setStyleSheet 会覆盖;如不行再单独处理 |
9.2 回退方案
每次 commit 颗粒度小,单点回退:
git revert <commit-sha>
最坏情况整分支扔掉:
git checkout master
git branch -D feat/ui-redesign-apple-theme
master 完全不动。
10. 实施步骤(参考 TaskList)
-
创建分支 -
写本设计文档 - 实现
theme.py - 主入口接入
- 迁移 ImageGeneratorWindow
- 迁移 LoginDialog
- 迁移 StyleDesignerTab + DraggableThumbnail
- 迁移 TaskQueueWidget
- 打包验证
- 推送 + 写 OVERNIGHT_SUMMARY.md
每步 1-2 个 commit,每步完成后启动跑一次冒烟(python image_generator.py 能进登录页即可)。
11. 给明天的你
醒来先看仓库根目录的 OVERNIGHT_SUMMARY.md,里面有完整进度、commit 列表、怎么运行、怎么验收、已知问题。
如果某处方向不对,直接 git checkout master 当无事发生。