d717f7dd by 柴进

docs: UI 重设计方案 — Apple 浅深双主题

- 设计令牌为单一真相源(theme.py 中 Python dict)
- Apple Blue 主色统一 macOS/Windows
- 监听 styleHints colorSchemeChanged 实现自动跟随系统
- 范围:外观 + 关键 layout 修复(按钮分级、卡片化、sidebar 头部)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 5f2a43f7
......@@ -27,3 +27,10 @@ images/
# OS
.DS_Store
Thumbs.db
# Brainstorming / superpowers session artifacts
.superpowers/
# Local config / screenshots
config_no_db.json
ScreenShot_*.png
......
# 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 目标
1. **统一设计语言**:Apple Human Interface Guidelines 浅色风格 + Apple Blue (#0071E3) 主色
2. **跨平台一致**:macOS 和 Windows 11 视觉一致(用户原话:"统一两端的设计语言")
3. **支持系统深浅主题自动切换**:跟随 `QGuiApplication.styleHints().colorScheme()`
4. **修复关键 layout 问题**:按钮主次分级、sidebar 头部、卡片化 GroupBox
5. **保持业务逻辑不变**:纯视觉层重构,不动生成 / 队列 / 历史功能
### 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 行)
```python
# 公共 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`
```python
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)
```python
# 之前
self.status_label.setStyleSheet("QLabel { color: #34C759; }")
# 之后
self.status_label.setProperty("status", "success")
self.status_label.style().polish(self.status_label) # 重绘
```
QSS 里:
```css
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:
1. 平台 hack(如 macOS 输入框可见性 fix,line 1285)
2. 临时高亮反馈(鼠标悬停后 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 颗粒度小,单点回退:
```bash
git revert <commit-sha>
```
最坏情况整分支扔掉:
```bash
git checkout master
git branch -D feat/ui-redesign-apple-theme
```
master 完全不动。
---
## 10. 实施步骤(参考 TaskList)
1. ✅ 创建分支
2. ✅ 写本设计文档
3. 实现 `theme.py`
4. 主入口接入
5. 迁移 ImageGeneratorWindow
6. 迁移 LoginDialog
7. 迁移 StyleDesignerTab + DraggableThumbnail
8. 迁移 TaskQueueWidget
9. 打包验证
10. 推送 + 写 OVERNIGHT_SUMMARY.md
每步 1-2 个 commit,每步完成后启动跑一次冒烟(`python image_generator.py` 能进登录页即可)。
---
## 11. 给明天的你
醒来先看仓库根目录的 `OVERNIGHT_SUMMARY.md`,里面有完整进度、commit 列表、怎么运行、怎么验收、已知问题。
如果某处方向不对,直接 `git checkout master` 当无事发生。