--- 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. 切换 `` 上的 `.dark` class(深浅色) 2. 向 `` 写入 `--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 的 `
` 加 inline script:提前读取 localStorage 中持久化的 `darkMode`,在 CSS 加载前给 `` 加 `.dark` class,避免刷新闪白 ### 性能说明 - 切换操作仅执行 1 次 `setProperty` + 1 次 `classList.toggle`,无组件重渲染开销 - `color-mix()` 由浏览器一次性计算,无运行时 JS 计算成本;目标为现代浏览器(Vite 8 默认目标,Chrome 111+ 已支持) ## 架构设计 ```mermaid flowchart LR A[app store