20260816220132

This commit is contained in:
oneao committed 2026-08-16 22:01:32 +08:00
1 parent b6f9a4c912
commit 72993ecdcd
179 files changed
+20050 -633

No files matched your search

@@ -0,0 +1,222 @@
---
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 态、微动画细节
- 预期产出:一套与中后台风格匹配的控件视觉规范,包含色板颜色选择、控件尺寸/圆角/描边、深浅色下的对比度与可访问性建议