222 lines
9.8 KiB
Markdown
222 lines
9.8 KiB
Markdown
---
|
||
name: 主题色切换与深浅色模式
|
||
overview: 为 fms-vue 增加主题色切换(预设色板点选主色)与深/浅色切换功能,采用纯手写 + CSS 变量驱动方案,参考 Soybean Admin 与 Vue Vben Admin 的 design tokens 设计,主题状态用 pinia 持久化。
|
||
design:
|
||
architecture:
|
||
framework: vue
|
||
styleKeywords:
|
||
- 现代简洁
|
||
- 中后台
|
||
- 克制专业
|
||
- 平滑过渡
|
||
fontSystem:
|
||
fontFamily: Noto Sans SC
|
||
heading:
|
||
size: 28px
|
||
weight: 600
|
||
subheading:
|
||
size: 18px
|
||
weight: 500
|
||
body:
|
||
size: 14px
|
||
weight: 400
|
||
colorSystem:
|
||
primary:
|
||
- "#1677ff"
|
||
- "#0958d9"
|
||
- "#003eb3"
|
||
background:
|
||
- "#f5f6f8"
|
||
- "#ffffff"
|
||
- "#0f1115"
|
||
- "#1a1d24"
|
||
text:
|
||
- "#1f2329"
|
||
- "#86909c"
|
||
- "#e6e8eb"
|
||
functional:
|
||
- "#16a34a"
|
||
- "#f59e0b"
|
||
- "#ef4444"
|
||
- "#e5e6eb"
|
||
todos:
|
||
- id: theme-tokens-foundation
|
||
content: 创建 theme/presets.js 预设色板、theme/tokens.css 设计变量(浅色/深色 + color-mix 派生)及 theme/index.js 应用函数
|
||
status: completed
|
||
- id: theme-store-init
|
||
content: 创建 stores/app.js(Pinia 持久化)并改造 main.js 与 index.html,完成初始化与防闪烁
|
||
status: completed
|
||
dependencies:
|
||
- theme-tokens-foundation
|
||
- id: theme-ui-components
|
||
content: 使用 [skill:ui-ux-pro-max] 设计并实现 ThemeToggle 深浅切换按钮与 ColorThemePicker 色板组件
|
||
status: completed
|
||
dependencies:
|
||
- theme-store-init
|
||
- id: theme-showcase
|
||
content: 为 button.vue 与 App.vue 补充基于 tokens 的样式并接入主题切换组件,验证联动效果
|
||
status: completed
|
||
dependencies:
|
||
- theme-ui-components
|
||
---
|
||
|
||
## 产品概述
|
||
|
||
为 fms-vue 增加一套轻量主题系统,支持「主色切换」与「深/浅色切换」。方案参考 Soybean Admin 与 Vue Vben Admin 的 token 化设计:CSS 变量作为唯一运行时真相,JS 仅负责集中配置与动态写变量。保持纯手写组件、不引入任何 UI 组件库。
|
||
|
||
## 核心功能
|
||
|
||
- **预设主色切换**:提供一组精选预设色板,点击色块一键切换品牌主色(primary),success / warning / destructive 保持固定
|
||
- **深/浅色切换**:手动切换明暗模式,状态持久化到 pinia(localStorage),刷新后保持用户选择
|
||
- **衍生色自动派生**:主色的 hover、active、前景色等通过 color-mix 自动派生,仅维护一个主色值,避免多处重复维护
|
||
- **变量驱动组件**:按钮、页面背景、卡片、文字、边框等全部通过 CSS 变量取色,切换主题即时生效
|
||
- **首屏防闪烁**:深色模式下刷新页面不出现浅色闪白
|
||
|
||
## 视觉要求
|
||
|
||
- 主题切换控件(深浅切换按钮 + 色板)置于页面顶部,精致、克制
|
||
- 色板以圆形色块网格展示,选中态有清晰描边标识
|
||
- 组件展示区能直观看到主色与深浅色的联动效果
|
||
|
||
## 技术选型
|
||
|
||
- 前端框架:Vue 3(已有)+ Vite(已有)
|
||
- 状态与持久化:Pinia + pinia-plugin-persistedstate(已在 package.json 中,无需新增依赖)
|
||
- 样式方案:原生 CSS 变量(design tokens)+ `color-mix()` 派生色,不引入 UI 库、Tailwind/UnoCSS/Less
|
||
- 无新增任何运行时依赖
|
||
|
||
## 实现方案
|
||
|
||
### 总体策略
|
||
|
||
借鉴 Soybean / Vben 的共同设计:**CSS 变量是唯一运行时真相**。主题切换本质上只做两件事:
|
||
|
||
1. 切换 `<html>` 上的 `.dark` class(深浅色)
|
||
2. 向 `<html>` 写入 `--primary` 变量(主色)
|
||
|
||
其余所有颜色(衍生色、中性色、固定语义色)都由 tokens.css 静态定义或派生,JS 不散落颜色值。
|
||
|
||
### 颜色单源设计
|
||
|
||
- **固定值全部进 tokens.css**:中性色(background/card/foreground/muted/border)与固定语义色(success/warning/destructive)在 `:root`(浅色)和 `.dark`(深色)两组中集中维护
|
||
- **动态值只有 `--primary`**:由 JS 从预设色板写入;`--primary-hover`、`--primary-active`、`--primary-foreground` 用 `color-mix()` 派生
|
||
- 深色模式不再重复维护主色:浅色下 hover 加深(mix black)、深色下 hover 提亮(mix white),在 `.dark` 中仅覆盖派生规则
|
||
|
||
### 模块职责
|
||
|
||
- `theme/presets.js`:预设主色色板(颜色值单源)
|
||
- `theme/tokens.css`:全部 CSS 变量(浅色 + 深色 + 主色派生规则)
|
||
- `theme/index.js`:纯函数 `applyPrimaryColor(color)`、`applyDarkMode(isDark)`
|
||
- `stores/app.js`:Pinia store,state 为 `darkMode` + `primaryColor`,watch 变化时调用 theme/index.js 同步到 DOM,`persist: true` 持久化
|
||
|
||
### 初始化与防闪烁
|
||
|
||
- main.js 注册 pinia + 持久化插件,mount 前实例化 app store(此时持久化状态已恢复并 watch 首次触发)
|
||
- index.html 的 `<head>` 加 inline script:提前读取 localStorage 中持久化的 `darkMode`,在 CSS 加载前给 `<html>` 加 `.dark` class,避免刷新闪白
|
||
|
||
### 性能说明
|
||
|
||
- 切换操作仅执行 1 次 `setProperty` + 1 次 `classList.toggle`,无组件重渲染开销
|
||
- `color-mix()` 由浏览器一次性计算,无运行时 JS 计算成本;目标为现代浏览器(Vite 8 默认目标,Chrome 111+ 已支持)
|
||
|
||
## 架构设计
|
||
|
||
```mermaid
|
||
flowchart LR
|
||
A[app store<br/>darkMode + primaryColor] -->|watch| B[theme/index.js]
|
||
B -->|classList.toggle dark| C[html.dark]
|
||
B -->|setProperty --primary| D[html 变量]
|
||
C --> E[tokens.css 深色覆盖]
|
||
D --> F[color-mix 派生 hover/active]
|
||
E --> G[组件样式 var 取值]
|
||
F --> G
|
||
H[index.html inline script] -->|提前恢复| C
|
||
```
|
||
|
||
## 目录结构
|
||
|
||
```
|
||
fms-vue/
|
||
├── index.html # [MODIFY] head 加 inline script 提前恢复 .dark,防刷新闪烁
|
||
└── src/
|
||
├── main.js # [MODIFY] 注册 pinia + 持久化插件,mount 前初始化 app store
|
||
├── styles/
|
||
│ └── index.css # [MODIFY] 顶部 @import '../theme/tokens.css',保留字体配置
|
||
├── theme/
|
||
│ ├── presets.js # [NEW] 预设主色色板(约 6-8 个精选主色,含默认蓝/青/绿/橙/紫/玫红)
|
||
│ ├── tokens.css # [NEW] 浅色 :root + 深色 .dark 全部 CSS 变量 + 主色 color-mix 派生规则
|
||
│ └── index.js # [NEW] applyPrimaryColor / applyDarkMode 纯函数,操作 documentElement
|
||
├── stores/
|
||
│ └── app.js # [NEW] Pinia app store:darkMode/primaryColor + setDark/toggleDark/setPrimary + persist
|
||
├── components/
|
||
│ └── ui/
|
||
│ ├── ThemeToggle.vue # [NEW] 深/浅色切换按钮(太阳/月亮图标 + 动画过渡)
|
||
│ ├── ColorThemePicker.vue # [NEW] 预设色板点选组件(圆形色块网格 + 选中描边)
|
||
│ └── button/
|
||
│ └── button.vue # [MODIFY] 补充基于 tokens 的按钮样式(<style scoped> 引用 var)
|
||
└── App.vue # [MODIFY] header 接入 ThemeToggle + ColorThemePicker,补充展示区样式
|
||
```
|
||
|
||
## 关键代码结构
|
||
|
||
### presets.js
|
||
|
||
`export const PRESETS = [{ name: '拂晓蓝', color: '#1677ff' }, ...]`,第一个为默认主色。
|
||
|
||
### stores/app.js(state 与 actions 契约)
|
||
|
||
- state:`{ darkMode: false, primaryColor: PRESETS[0].color }`
|
||
- actions:`setDark(v)`、`toggleDark()`、`setPrimary(color)`
|
||
- `persist: true`(key 默认即 store id `app`,与 index.html 读取的 key 一致)
|
||
|
||
### theme/index.js
|
||
|
||
- `applyPrimaryColor(color)`:`documentElement.style.setProperty('--primary', color)`
|
||
- `applyDarkMode(isDark)`:`documentElement.classList.toggle('dark', isDark)`
|
||
|
||
### tokens.css 变量骨架
|
||
|
||
- 中性色:`--background`、`--card`、`--foreground`、`--muted`、`--muted-foreground`、`--border`
|
||
- 固定语义色:`--success`、`--warning`、`--destructive`(浅/深各一组)
|
||
- 主色系列:`--primary`(JS 写入)、`--primary-foreground`、`--primary-hover`、`--primary-active`(color-mix 派生,`.dark` 下覆盖派生方向)
|
||
|
||
## 设计风格
|
||
|
||
采用现代简洁的中后台风格,参考 Soybean / Vben 的克制专业气质。整体轻量、留白充足,主题控件精致不喧宾夺主,切换过程有平滑过渡。
|
||
|
||
## 页面结构(App.vue 展示页)
|
||
|
||
### 顶部导航栏
|
||
|
||
- 左侧:品牌标识「FMS UI」+ 页面标题,使用较大的字重形成层次
|
||
- 右侧:主题控件区,依次为 ColorThemePicker 色板与 ThemeToggle 深浅切换按钮,间距适度
|
||
|
||
### 组件展示区
|
||
|
||
- 卡片式容器承载 Button 组件展示,卡片背景、边框、文字均随深浅色变化
|
||
- 主色按钮(default 类型)作为主题色最直观的反馈载体,切换色板时按钮颜色即时变化
|
||
|
||
## 控件设计
|
||
|
||
### ColorThemePicker 色板
|
||
|
||
- 圆形色块网格排列,每个色块展示预设主色,hover 轻微放大并显示阴影
|
||
- 当前选中的色块带外圈描边环 + 内部对勾标识,与浅/深背景均有足够对比度
|
||
|
||
### ThemeToggle 深浅切换
|
||
|
||
- 胶囊形或圆形按钮,内含太阳/月亮图标,点击在浅色与深色间切换
|
||
- 切换采用 icon 旋转/淡入淡出的微动画,状态持久化后刷新不丢失
|
||
|
||
## 响应式与可访问性
|
||
|
||
- 色板与切换按钮在窄屏下自动换行或收缩,保持可点击区域不小于 36px
|
||
- 选中态不仅靠颜色区分,还依赖描边与图标双重标识,兼顾色弱用户
|
||
|
||
## Agent Extensions
|
||
|
||
### Skill
|
||
|
||
- **ui-ux-pro-max**
|
||
- 用途:设计主题切换控件(ColorThemePicker 色板与 ThemeToggle 深浅切换按钮)的视觉方案,确定预设主色色板配色与选中态、hover 态、微动画细节
|
||
- 预期产出:一套与中后台风格匹配的控件视觉规范,包含色板颜色选择、控件尺寸/圆角/描边、深浅色下的对比度与可访问性建议 |