Files
agent-frontend-web/docs/superpowers/specs/2026-08-06-frontend-dark-theme-design.md
T
2026-08-07 13:56:29 +08:00

265 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Frontend 自定义暗色主题 — Design Spec
**日期:** 2026-08-06
**状态:** Approved
**参考:** 项目根目录 `image.png`(深色三栏 AI IDE 风格)
## 背景
`src/views/frontend/index.vue` 是核心 AI 对话页面,当前使用 Element Plus 默认浅色主题 + 局部样式,整体观感偏传统后台。用户要求基于 `image.png`(类 Cursor 的暗色三栏 IDE 风)对 frontend 自定义样式,以匹配产品"AI 智能体 IDE"的定位。
## 目标
将 frontend 页面(不含管理后台)改造为:
1. 暗色三栏布局:左(会话列表)/ 中(聊天 + 输入)/ 右(文件列表面板 + 预览面板)
2. 统一的深色主题 token,作用域隔离,不污染管理后台
3. 响应式:屏幕变窄时自动收起右侧文件面板,必要时隐藏侧边栏
4. 代码块使用 **Shiki** 渲染,支持与聊天消息中 Markdown 代码块 + 文件预览中的源码文件同款高亮
## 非目标
- **不**改动管理后台(`src/views/system/*``src/views/tool/*` 等)
- **不**新增功能(Git 工具、Progress 面板、终端、Workspace 切换等),仅样式
- **不**调整后端 API、不改 WebSocket 协议
- **不**引入新的 UI 组件库(继续使用 Element Plus + 少量自定义)
- **不**重构 ChatWindow 的消息结构 / 渲染逻辑(保留 markdown-it,仅替换代码块 highlight 插件)
## 现有约束
| 项目 | 现状 |
| --- | --- |
| 根容器类名 | `.frontend-container`(已存在于 `index.vue:861` |
| Markdown 渲染 | 已用 `markdown-it@14.3.0`ChatWindow |
| 代码高亮 | 暂未配置(默认无高亮或浏览器 monospace) |
| 布局 | flex 三栏:sidebar / main-content / file-panel-wrapper + file-preview-wrapper |
| 文件面板显隐 | 由 `filePanelVisible` ref 控制(小屏需保持这个行为) |
| Element Plus 版本 | 2.13.1(支持 `dark` 命名空间 + CSS 变量覆盖) |
## 方案概览
### Phase 1:主题基础设施(半天)
新增 `src/views/frontend/styles/theme.scss`,定义 SCSS 变量 + CSS 自定义属性,**作用域挂载在 `.frontend-container` 下**
```scss
.frontend-container {
// 基础色
--fe-bg-base: #1e1e1e;
--fe-bg-elevated: #252526;
--fe-bg-overlay: #2d2d30;
--fe-bg-input: #3c3c3c;
// 边框
--fe-border: #3c3c3c;
--fe-border-strong: #555;
// 文本
--fe-text-primary: #e6e6e6;
--fe-text-secondary: #a0a0a0;
--fe-text-muted: #6e6e6e;
--fe-text-inverse: #1e1e1e;
// 品牌色
--fe-accent: #4ec9b0;
--fe-accent-hover: #5fd9c0;
--fe-danger: #f48771;
--fe-warning: #dcdcaa;
--fe-info: #569cd6;
// 布局尺寸
--fe-sidebar-width: 260px;
--fe-file-panel-width: 280px;
--fe-file-preview-width: 600px;
// 阴影 / 圆角
--fe-radius-sm: 4px;
--fe-radius-md: 6px;
--fe-shadow-md: 0 4px 12px rgba(0, 0, 0, 0.4);
}
```
并 override Element Plus 暗色变量(仅在 `.frontend-container` 作用域内有效,避免影响其他页面):
```scss
.frontend-container {
--el-bg-color: var(--fe-bg-elevated);
--el-bg-color-page: var(--fe-bg-base);
--el-bg-color-overlay: var(--fe-bg-overlay);
--el-text-color-primary: var(--fe-text-primary);
--el-text-color-regular: var(--fe-text-secondary);
--el-text-color-secondary: var(--fe-text-muted);
--el-border-color: var(--fe-border);
--el-border-color-light: var(--fe-border);
--el-fill-color-blank: var(--fe-bg-elevated);
--el-color-primary: var(--fe-accent);
--el-color-primary-light-3: var(--fe-accent-hover);
--el-color-danger: var(--fe-danger);
--el-color-warning: var(--fe-warning);
--el-color-info: var(--fe-info);
}
```
`index.vue` 顶部 `@import './styles/theme.scss';` 引入一次。
### Phase 2:组件样式改造(1.5 天,逐组件,**不改 props/emit/函数体**
| 组件 | 主要变更 |
| --- | --- |
| `ConversationSidebar.vue` | 整体改为暗色 token;激活态用 `--fe-accent` 替代 `#409eff`;新消息小红点保留;右侧收缩按钮用暗色 hover |
| `UserArea.vue` | 头像区背景色改暗;昵称文字 `--fe-text-primary`;按钮 hover 用 `--fe-bg-overlay` |
| `ChatHeader.vue` | 顶部栏背景 `--fe-bg-elevated`,下边框 `--fe-border`;模型选择器 / 清空按钮使用暗色变量 |
| `ChatWindow.vue` | 消息气泡:用户右侧 `--fe-accent` 浅色背景;助手左侧 `--fe-bg-elevated`;代码块由 Shiki 输出;Markdown 文本颜色用 `--fe-text-primary` |
| `InputArea.vue` | 输入框背景 `--fe-bg-input`;按钮图标色改 `--fe-text-secondary`loading 状态颜色保留 |
| `FilePanel.vue` | 列表背景 `--fe-bg-elevated`,选中态 `--fe-bg-overlay` + `--fe-accent` 边框 |
| `UserInfoDialog.vue` | `el-dialog` 暗色(依赖 EP 变量覆盖);头像遮罩、按钮沿用暗色 token |
| `index.vue` 文件预览面板 | 顶部 header 用 `--fe-bg-elevated`;文本预览 `<pre>` 用 Shiki 输出背景 `--fe-bg-base` + `--fe-text-primary` |
**不动的部分:**
- 所有 `data` / `methods` / `computed` / 生命周期函数
- 模板的事件绑定、v-if/v-show 条件、ref 名称
- WebSocket 协议、API 调用、Pinia store
### Phase 3Shiki 集成(半天)
1. 安装:`yarn add shiki`(不锁版本,跟随 markdown-it 主版本策略)
2. 新增 `src/views/frontend/utils/highlighter.js`
```js
import { createHighlighter } from 'shiki'
import markdownIt from 'markdown-it'
const highlighterPromise = createHighlighter({
themes: ['github-dark'],
langs: ['javascript', 'typescript', 'vue', 'json', 'bash',
'python', 'java', 'go', 'rust', 'sql', 'yaml',
'html', 'css', 'scss', 'markdown', 'tsx', 'jsx']
})
export async function createMarkdownRenderer() {
const highlighter = await highlighterPromise
const md = markdownIt({ html: false, linkify: true, breaks: true })
md.options.highlight = (code, lang) => {
const langKey = highlighter.getLoadedLanguages().includes(lang) ? lang : 'text'
return highlighter.codeToHtml(code, { lang: langKey, theme: 'github-dark' })
}
return md
}
```
3. `ChatWindow.vue`:把当前 `new markdownIt(...)` 替换为 `const md = await createMarkdownRenderer()`;保留 `mermaid` / 链接点击等现有扩展(如果有),并保留对 `code` 标签外层包裹(语言标签)
4. `index.vue` 文件预览面板:`<pre>{{ previewContent }}</pre>` 改为 `<div v-html="renderedCode"></div>``renderedCode` 在 `previewContent` 变化时由同样的 highlighter 渲染;不识别扩展名时降级为纯文本
5. **异步初始化**:在 `ChatWindow` `setup` 顶层用 `await` 不可行(不能阻塞 setup),改用 `onMounted` 中预热 highlighter 缓存到一个共享 `ref`;首屏代码块用 fallback`<pre><code>{{ code }}</code></pre>`)展示 200ms 后由 watcher 替换
### Phase 4:响应式断点(半天)
在 `theme.scss` 中追加:
```scss
.frontend-container {
// 1200px 以下:隐藏右侧文件预览面板
@media (max-width: 1200px) {
.file-preview-wrapper { display: none; }
}
// 900px 以下:默认收起侧边栏
@media (max-width: 900px) {
.conversation-sidebar { width: 28px; }
.conversation-sidebar .sidebar-content { display: none; }
}
// 768px 以下:隐藏右侧文件列表面板(小屏仅保留聊天)
@media (max-width: 768px) {
.file-panel-wrapper { display: none; }
}
}
```
**保留现有 `filePanelVisible` 行为不变** —— 用户主动开关的状态在小屏下依然生效(只是视觉上多了一层媒体查询的隐藏,两者独立工作)。
## 组件契约
### `src/views/frontend/styles/theme.scss`
- 无 export,纯 SCSS
- `:root` 之外的所有变量挂在 `.frontend-container` 选择器下
### `src/views/frontend/utils/highlighter.js`
| 名称 | 类型 | 说明 |
| --- | --- | --- |
| `createMarkdownRenderer()` | `() => Promise<MarkdownIt>` | 异步创建带 Shiki 高亮的 markdown-it 实例 |
| `renderCode(code, lang?)` | `(code: string, lang?: string) => Promise<string>` | 文件预览专用,直接返回 HTML |
### ChatWindow.vue 改动
- 新增 `import { createMarkdownRenderer } from '@/views/frontend/utils/highlighter'`
- `md` 从同步创建改为异步预热 + `ref` 持有
- 模板中 `v-html` 渲染前判断 `mdReady.value`,未就绪时降级 `<pre>`
## 数据流
无新数据流。Shiki 的 highlighter 实例是模块级单例,ChatWindow 与文件预览共用。
## 错误处理
- Shiki 加载失败:`markdown-it` 的 `highlight` 函数降级为 `(c) => \`<pre><code>${escapeHtml(c)}</code></pre>\`,避免阻塞消息渲染
- 不支持的语言:fallback 到 `'text'` 语言(无高亮但保留配色)
- 文件预览读取失败:已有 `ElMessage.error`,不动
## 视觉规范
| 用途 | 值 |
| --- | --- |
| 主背景 | `#1e1e1e` |
| 卡片 / 面板 | `#252526` |
| 输入框 / 浮层 | `#3c3c3c` |
| 主色(accent | `#4ec9b0`(青绿,类似 VS Code Dark+ |
| 危险 | `#f48771` |
| 文本主 | `#e6e6e6` |
| 文本次 | `#a0a0a0` |
| 代码块主题 | `github-dark`(与整体色调一致) |
## 测试
### 手动测试清单
1. **主题覆盖**:访问 `/frontend/index`,确认背景变暗;切到管理后台 `/system/user`,确认**未受污染**
2. **三栏布局**:左 260px / 中 flex / 右 280px(文件列表面板)+ 600px(文件预览)
3. **响应式**:浏览器窗口拉到 1100px → 文件预览消失;拉到 850px → 侧边栏自动收起;拉到 750px → 文件列表面板消失
4. **Shiki 渲染**:发一条带 ```js const x = 1 ``` 的消息,确认代码块背景为 `github-dark` 主题色,语法高亮生效
5. **文件预览**:点击 markdown / json / py 文件,源码用同款暗色主题渲染
6. **原有功能**:新建会话、刷新会话、退出登录、上传头像、修改昵称、二次确认弹窗、WebSocket 收发消息、上下文压缩条、模型切换、token 计数 —— 均**无变化**
### 回归
- `yarn dev` 启动后控制台无 SCSS / Vue warn
- `yarn build:prod` 通过
- 任意管理后台页面(系统管理、监控、工具)打开,目视无暗色污染
## 风险
| 风险 | 缓解 |
| --- | --- |
| Element Plus 组件深色覆盖不完整(部分组件用 hardcoded 色) | Phase 2 组件级逐一覆盖;如 `el-dialog` 头背景、`el-message` 背景用 `!important` + 选择器权重提升 |
| Shiki bundle 体积大 | 仅打包 `github-dark` 主题 + 16 个常用语言;按需扩展 |
| 文件预览大量代码时高亮阻塞 | highlighter 异步;用 `requestIdleCallback``setTimeout(0)` 让出主线程 |
| 响应式断点与既有状态(如 `filePanelVisible`)冲突 | 断点仅控制 `display`,状态变量保留;大屏恢复时面板重新可见 |
## 后续可扩展(不在本次范围)
- 主题切换(亮 / 暗 / 系统跟随)
- Shiki 多主题切换
- 侧边栏拖拽调宽
- 文件预览面板内嵌 Monaco Editor
## 实施顺序
| 阶段 | 任务 | 依赖 |
| --- | --- | --- |
| 1.1 | 创建 `theme.scss` 基础 token + EP 变量覆盖 | 无 |
| 1.2 | `index.vue` 引入 `@import './styles/theme.scss'` | 1.1 |
| 1.3 | 验证 EP 按钮 / dialog / message 颜色变化 | 1.2 |
| 2.x | 逐组件改造样式(不改 JS | 1.3 |
| 3.1 | 安装 shiki + 新增 `utils/highlighter.js` | 无 |
| 3.2 | ChatWindow 集成 markdown-it + Shiki | 3.1 |
| 3.3 | 文件预览面板使用同一 highlighter | 3.2 |
| 4.1 | 三个媒体查询断点 | 1.3 |
| 4.2 | 响应式回归测试 | 4.1 + 全部组件完成 |