5 个 Stage 分阶段推进: - Stage 1: 109 处 inline hex/rgba 颜色替换为 var(--color-*) - Stage 2: :root 拆 [data-theme="dark/light"] 两套, 浅色色板抄 Linear/Vercel - Stage 3: themeStore + 切换按钮 + localStorage 持久化 - Stage 4: 浅色色板调试 + 半透明色反相 / ECharts 重渲染 / AuroraCanvas 处理 - Stage 5: vitest + Playwright 本地无头浏览器深/浅截图回归 预期 AI 连续工时 7-9 小时, 人工 3.5-5 天。 关键发现: 项目未实际使用 Arco (CLAUDE.md 写错), 颜色高度集中 (Top 4 文件占 75%, Top 10 颜色占 60%+ 已有对应 var)。 待用户启动 /loop 后 AI 自主完成, 完成时输出截图 + 完成报告。 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
12 KiB
亮色主题切换(dark / light theme toggle)
状态:待开发 创建日期:2026-04-28 优先级:P3(影视工具默认深色更专业,浅色主要服务非影视用户 / 白天强光环境 / 投资人 demo)
需求背景
当前界面是深色固定主题(参考 Linear / Vercel 风格)。部分用户在强光环境 / 白天工作 / 习惯亮色的场景下希望能切换。
不走传统"产品+设计师"流程,直接由 AI 从代码层做改造。
现状分析(关键发现)
✅ 基础设施已就位
web/src/index.css:root集中定义了 35 个 CSS variable(--color-bg-page、--color-text-primary等)- 全项目 525 处 引用了
var(--color-*),集中可控 - 未使用 Arco Design(CLAUDE.md 写错了,实际
grep @arco-design = 0)→ 不需要适配第三方 UI 库主题,少了一大块工作量
⚠️ 109 处 inline 颜色散落在 .tsx
热点文件(占 75%):
| 文件 | inline 颜色数 |
|---|---|
pages/DashboardPage.tsx |
31 |
pages/TeamDashboardPage.tsx |
21 |
components/RecordDetailModal.tsx |
17 |
components/ReferenceList.tsx |
12 |
| 其他 | 28 |
颜色种类高度收敛(前 10 高频占 60%+):
17次 #8b8ea8 ← 已有 var: --color-text-secondary
10次 #888 ← 同上语义
6次 rgba(255,255,255,0.06) ← 已有 var: --color-bg-card / --color-bg-input-bar
5次 rgba(255,255,255,0.08) ← 已有 var: --color-bg-hover
5次 #f1f0ff ← 已有 var: --color-text-primary
6次 #6c63ff ← 已有 var: --color-primary
→ 大部分是机械性 grep+replace,不是创造性设计。
浅色主题色板设计
直接抄 Linear / Vercel 的浅色方案(这俩就是当前深色主题模仿的对象,他们都有官方浅色版)。
核心规则
| 类别 | 深色(现状) | 浅色(新增) | 说明 |
|---|---|---|---|
| 页面背景 | #07070f |
#fafafa |
主背景,深色近黑/浅色近白 |
| 卡片背景 | rgba(255,255,255,0.06) |
rgba(0,0,0,0.04) |
半透明色反相:白半透明 → 黑半透明 |
| Hover | rgba(255,255,255,0.08) |
rgba(0,0,0,0.06) |
同上 |
| 输入框背景 | rgba(255,255,255,0.06) |
#ffffff |
浅色下输入框直接给纯白更醒目 |
| 主文字 | #f1f0ff(亮紫白) |
#171823(深紫黑) |
反相,保留紫调一致性 |
| 次文字 | #8b8ea8 |
#6b6e85 |
同上 |
| 边框 | rgba(255,255,255,0.10) |
rgba(0,0,0,0.08) |
半透明反相 |
| 主色(按钮) | #6c63ff |
#5048cc |
浅色背景下加深 18%,对比度足 |
| 强调色 | #00b8e6 |
#0099cc |
同上原则 |
| 成功 | #00b894 |
#00a37e |
浅色下加深 |
| 危险 | #e74c3c |
#d63a2a |
同上 |
| 警告 | #f39c12 |
#d4860a |
同上 |
| Modal 阴影 | rgba(0,0,0,0.6) |
rgba(0,0,0,0.15) |
浅色下阴影减弱 |
切换机制
- 用
<html data-theme="dark">/<html data-theme="light">切换 - CSS 选择器
[data-theme="dark"] :root { ... }/[data-theme="light"] :root { ... }各定义一套 - 默认
data-theme="dark"(保留现有体验) - 用户切换后 localStorage 持久化,下次访问保持
改造步骤(分阶段,每阶段一次提交,不做大 PR)
Stage 1 — inline 颜色全部替换为 var(不破坏现有体验)
目标:所有 style={{ color: '#xxx' }} / background: 'rgba(...)' 改成 style={{ color: 'var(--color-xxx)' }},深色继续工作不变。
具体改动:
- DashboardPage.tsx — 31 处
- 大部分是图表 axis / legend / tooltip 颜色,对照现有 var 替换
- ECharts 配置里的
axisLine.lineStyle.color/tooltip.backgroundColor等改用getComputedStyle(document.documentElement).getPropertyValue('--color-xxx')动态读取(支持主题切换实时刷新图表)
- TeamDashboardPage.tsx — 21 处(类似 DashboardPage)
- RecordDetailModal.tsx — 17 处(弹窗各 section 标题 / 边框 / 背景)
- ReferenceList.tsx — 12 处
- VideoGenerationPage / VideoDetailModal / ProfilePage / AuroraCanvas — 共 ~28 处
新增的 var(覆盖现有 :root 没有的颜色):
--color-modal-overlay(替代各处rgba(0,0,0,0.6)/rgba(0,0,0,0.7))--color-text-tertiary(替代各处#888/#555)--color-bg-modal(替代各处#1a1a2e/#111118)--color-shadow(替代各处rgba(0,0,0,0.4)之类)
验收:
git diff看 109 处全部style={{ ... '#xxx' ... }}变var(--color-xxx)- 浏览器跑起来,肉眼对比改前改后无变化(因为 var 值还是原来的深色值)
预估:1-1.5 天(人)/ 2-3 小时(AI 连续 grep+sed+verify)
Stage 2 — :root 拆 dark / light 两套
目标:CSS 层面准备好两套值,但不切换(默认 dark,等 Stage 3 切按钮)。
改动:web/src/index.css
[data-theme="dark"] {
--color-bg-page: #07070f;
--color-text-primary: #f1f0ff;
/* ... 35 个 var 全部 */
}
[data-theme="light"] {
--color-bg-page: #fafafa;
--color-text-primary: #171823;
/* ... 同样 35 个 var 的浅色版本 */
}
<html data-theme="dark"> 默认值在 index.html 写死,等 Stage 3 才动。
验收:
- 手动改
index.html的data-theme="light"看一眼整页效果 - 不要求完美,对比强烈一眼能看出"啊它确实是浅色了"就行
- 列出"看着丑"的地方,进 Stage 4 修
预估:0.5 天(人)/ 1 小时(AI)
Stage 3 — themeStore + 切换按钮 + 持久化
改动:
-
新建
web/src/store/theme.ts(Zustand 风格,保持和其他 store 一致):import { create } from 'zustand'; type Theme = 'dark' | 'light'; interface ThemeState { theme: Theme; toggleTheme: () => void; } const STORAGE_KEY = 'airdrama-theme'; const initialTheme: Theme = (localStorage.getItem(STORAGE_KEY) as Theme) || 'dark'; document.documentElement.dataset.theme = initialTheme; export const useThemeStore = create<ThemeState>((set, get) => ({ theme: initialTheme, toggleTheme: () => { const next = get().theme === 'dark' ? 'light' : 'dark'; document.documentElement.dataset.theme = next; localStorage.setItem(STORAGE_KEY, next); set({ theme: next }); }, })); -
顶部加切换按钮(建议放
Sidebar.tsx底部 / 用户头像旁,月亮/太阳 SVG 图标) -
ECharts 等动态依赖 CSS var 的图表,订阅 theme 变化重新 render(用
useThemeStore((s) => s.theme)作为 key 触发重渲染)
验收:
- 点击按钮深↔浅切换流畅
- 刷新页面保持上次选择
- 登录页 / 错误页等所有路由都生效
预估:0.5 天(人)/ 1 小时(AI)
Stage 4 — 浅色色板调试 / 边角料修复
切完之后一定会发现:
- 某个按钮文字不可见(对比度不足)
- 某个磁玻璃 backdrop 太透看不清
- 某个图表的 grid line 浅色下消失
- ECharts tooltip 颜色没跟着切
做法:每页跑一遍,列 bug 表,逐个调整 [data-theme="light"] 块里的具体值。
预估:1-1.5 天(人)/ 2-3 小时(AI 配合用户截图反馈)
Stage 5 — 回归 vitest + 手测
- vitest 跑一遍(不会因为颜色变化挂,主要看依赖 DOM 结构的 snapshot test 没崩)
- 每个页面深 / 浅各走一遍:登录页 / 生成页 / 个人中心 / 7 个 admin 页 / 视频详情弹窗 / 任务详情弹窗 / 公告弹窗 / Toast
预估:0.5-1 天(人)/ 1 小时(AI 跑测试 + 用户走手测)
关键技术点(容易踩坑)
1. 半透明色反相不能简单替换
rgba(255,255,255, 0.06) 不是变成 rgba(0,0,0, 0.06),透明度也要调整。白半透明在深色背景下肉眼看是"浅色卡片";黑半透明在浅色背景下看是"深色卡片",但人眼对深色对比的敏感度不同。
经验值:浅色透明度通常比深色 -20%~-40%。比如深色 0.06 对应浅色 0.04。
2. ECharts 等图表的颜色需要 JS 同步切换
CSS variable 改了,但 ECharts 已经渲染的图表不会自动重新读 var。两种方案:
- 方案 A:图表内部颜色用
getComputedStyle(document.documentElement).getPropertyValue('--color-xxx').trim(),且组件内useEffect(theme => render)触发重渲染。 - 方案 B:所有图表配置传入颜色直接读 themeStore 的 theme 值,动态返回不同 hex。
推荐 A(保持单一颜色源)。
3. AuroraCanvas 极光动效
登录页 AuroraCanvas.tsx 是 canvas 画的极光渐变,硬编码紫色调。
- 暗色:紫蓝极光好看
- 浅色:极光放在白底上会刺眼
方案:浅色模式下整个 AuroraCanvas 直接 display: none,背景换成 #fafafa 纯净白,反而更"高级"。
4. 玻璃效果 backdrop-filter
不少地方用 backdrop-filter: blur(24px) + 白半透明做磨砂玻璃。浅色下 backdrop-filter 仍然有效,但底层颜色要换成黑半透明(rgba(0,0,0,0.04))。
5. 主色对比度(WCAG AA)
#6c63ff 紫色按钮:
- 深色背景 + 白字:对比度 ~7.1(AAA 级别)✓
- 浅色背景 + 白字:对比度 ~4.4(接近 AA 边界,但按钮上的小字可能不够)
- 解决:浅色模式
--color-primary: #5048cc(加深 18%),按钮上白字对比度 ~6.8(AAA)
验证清单
切换前后两种主题下都要看:
- 登录页(含 AuroraCanvas 切换)
- 生成页(卡片 / 输入框 / @ 标签 / 滚动条)
- 提示词标签(缩略图 / 文字)
- 任务卡片各种状态(生成中 / 完成 / 失败)
- 个人中心(消费图表)
- 公告弹窗(HTML 渲染)
- Toast / 各种 Modal / Dropdown
- 7 个 admin 页(Dashboard / Users / Records / Settings / Security / Logs / Assets)
- 团管 4 个页(TeamDashboard / TeamMembers / TeamRecords / TeamAssets)
- 火山 EP / 任务详情 / 录像弹窗
工作量预估
| Stage | 描述 | 人工时 | AI 工时 |
|---|---|---|---|
| 1 | inline 颜色 → var | 1-1.5 天 | 2-3 小时 |
| 2 | dark/light 两套 var | 0.5 天 | 1 小时 |
| 3 | themeStore + 切换按钮 | 0.5 天 | 1 小时 |
| 4 | 浅色色板调试 | 1-1.5 天 | 2-3 小时 |
| 5 | 回归测试 | 0.5-1 天 | 1 小时 |
| 总计 | 3.5-5 天 | 7-9 小时(AI 连续) |
不做的
- 跟随系统主题(
prefers-color-scheme: light):以后再加,初版手动切换就够 - 多套主题(如 sepia 米色 / 高对比度无障碍模式):用户没要求
- 管理后台和用户端独立主题:保持一致更简单
- 每个团队管理员可定制配色:复杂度爆炸,不做
风险点
- 改
:root默认 hex 写法 → 改成[data-theme]选择器后,原来组件的 var 引用都还能正确解析(CSS 优先级要确认。[data-theme="dark"]选择器优先级 0,1,0;:root优先级 0,0,1。前者会胜出 ✓) - AuroraCanvas 在浅色下隐藏 的产品决策需要用户确认(也可以保留,但调淡)
- ECharts 重渲染开销:每次切主题所有图表 re-render 一遍,仪表盘 6 个图表加起来 ~200ms 卡顿可接受
- localStorage 在隐身模式 / 用户禁用时:fallback 到 dark,不报错
参考资料
- Linear 浅色主题色板:https://linear.app(直接 toggle 看)
- Vercel 浅色主题:https://vercel.com(同上)
- WCAG 对比度计算:https://webaim.org/resources/contrastchecker/
prefers-color-schemeMDN: https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme
Critical Files
修改:
web/src/index.css—:root拆[data-theme="dark/light]两套web/src/store/theme.ts— 新建web/src/pages/DashboardPage.tsx— 31 处颜色替换web/src/pages/TeamDashboardPage.tsx— 21 处web/src/components/RecordDetailModal.tsx— 17 处web/src/components/ReferenceList.tsx— 12 处web/src/components/Sidebar.tsx— 加切换按钮(位置待定)- 其余 ~28 处 inline 颜色散落的组件
不动:
- 后端代码(纯前端改造)
- DB schema
- 现有路由 / API