docs(plan): frontend 深色主题 + 会话搜索/分组 + 聊天收紧
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,503 @@
|
|||||||
|
# Frontend 深色主题 + 会话搜索/分组 + 聊天收紧 Implementation Plan
|
||||||
|
|
||||||
|
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||||
|
|
||||||
|
**Goal:** 把 frontend 页面调整为:石墨黑 + 蓝紫 accent 的深色主题、会话侧栏顶部"搜索会话"输入框、按 `今天 / 昨天 / 更早` 分组且"当前会话所在分组自动展开、其他折叠"、聊天模块气泡节奏收紧对齐 chat.png。本次仅修改本地代码,不做 git 推送。
|
||||||
|
|
||||||
|
**Architecture:** 三个文件局部改动,不新增组件、不引入新依赖。`theme.scss` 替换 `:root` 变量值;`ConversationSidebar.vue` 在 header 与 scrollbar 之间插入搜索框,scrollbar 内部从一维列表改为分组结构,添加 `groupOf / parseTimestamp / sessionGroups / expandedGroups / syncExpandedWithCurrent` 等本地逻辑;`ChatWindow.vue` 仅调整气泡相关 SCSS。所有改动通过 `--fe-*` 变量串联,浅色主题不受影响。
|
||||||
|
|
||||||
|
**Tech Stack:** Vue 3.5 + Composition API + Element Plus 2.13 + Sass(sass-embedded 1.97)。测试使用 Vitest(需在 Task 1 中确认是否已配置)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## File Structure
|
||||||
|
|
||||||
|
| 文件 | 操作 | 职责 |
|
||||||
|
|---|---|---|
|
||||||
|
| `src/views/frontend/styles/theme.scss` | 改 | 仅替换 `:root` 下 `--fe-*` 值;其它块保持原样 |
|
||||||
|
| `src/views/frontend/components/ConversationSidebar.vue` | 改 | header 下新增搜索框;scrollbar 内分组渲染;新增本地状态/方法;新增分组标题样式 |
|
||||||
|
| `src/views/frontend/components/ChatWindow.vue` | 改 | 仅 `<style scoped>` 内调整 `padding / margin-bottom / border-radius` |
|
||||||
|
|
||||||
|
无新增文件;不新增组件抽象。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 1: 摸底测试栈与现有测试位置
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Inspect: `package.json`, `vitest.config.*`, `tests/`、`src/**/*.test.{js,ts}`
|
||||||
|
|
||||||
|
- [ ] **Step 1: 检查 package.json 是否声明了测试框架**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd "D:/数科智联/项目/agent-frontend-web"
|
||||||
|
grep -E "vitest|jest|@vue/test-utils" package.json
|
||||||
|
```
|
||||||
|
|
||||||
|
预期:很可能没有测试框架;如果 `npm ls vitest` 无输出,跳过 vitest 步骤(前端为视觉改动,以手动 dev 验证为主)。记录结论到 Task 1 末尾的"测试结论"。
|
||||||
|
|
||||||
|
- [ ] **Step 2: 若无测试框架,标注结论并跳过所有 TDD 步骤**
|
||||||
|
|
||||||
|
在执行 Task 2~4 时,把"Write the failing test / Run it to verify it fails"两步替换为"手动 dev 验证(启动 `npm run dev` 在浏览器查看)"。
|
||||||
|
|
||||||
|
测试结论:当前仓库未配置前端单元测试;本次采用手动 dev 验证(步骤在 Task 5 统一列出)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 2: 替换 `theme.scss` 深色调色板
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `src/views/frontend/styles/theme.scss:3-46`(`:root` 块)
|
||||||
|
|
||||||
|
- [ ] **Step 1: 替换 `:root` 变量值**
|
||||||
|
|
||||||
|
将 `src/views/frontend/styles/theme.scss` 的 `:root { ... }` 块整体替换为以下内容(变量名不变,仅改值):
|
||||||
|
|
||||||
|
```scss
|
||||||
|
:root {
|
||||||
|
// —— 基础色 ——
|
||||||
|
--fe-bg-base: #14171c;
|
||||||
|
--fe-bg-elevated: #1c1f24;
|
||||||
|
--fe-bg-overlay: #23272e;
|
||||||
|
--fe-bg-input: #2a2e35;
|
||||||
|
--fe-bg-hover: #1f2329;
|
||||||
|
|
||||||
|
// —— 边框 ——
|
||||||
|
--fe-border: #2c3138;
|
||||||
|
--fe-border-strong: #4a505a;
|
||||||
|
|
||||||
|
// —— 文本 ——
|
||||||
|
--fe-text-primary: #e4e6eb;
|
||||||
|
--fe-text-secondary: #9aa0a6;
|
||||||
|
--fe-text-muted: #6b7280;
|
||||||
|
--fe-text-inverse: #14171c;
|
||||||
|
|
||||||
|
// —— 品牌色 ——
|
||||||
|
--fe-accent: #6c8cff;
|
||||||
|
--fe-accent-hover: #8aa3ff;
|
||||||
|
--fe-accent-soft: rgba(108, 140, 255, 0.15);
|
||||||
|
--fe-danger: #f07178;
|
||||||
|
--fe-danger-soft: rgba(240, 113, 120, 0.1);
|
||||||
|
--fe-warning: #e0a050;
|
||||||
|
--fe-info: #6cc4d6;
|
||||||
|
--fe-info-soft: rgba(108, 196, 214, 0.1);
|
||||||
|
--fe-info-border: rgba(108, 196, 214, 0.4);
|
||||||
|
--fe-accent-border: rgba(108, 140, 255, 0.4);
|
||||||
|
--fe-danger-border: rgba(240, 113, 120, 0.4);
|
||||||
|
|
||||||
|
// —— 布局尺寸 ——
|
||||||
|
--fe-sidebar-width: 260px;
|
||||||
|
--fe-file-panel-width: 280px;
|
||||||
|
--fe-file-preview-width: 600px;
|
||||||
|
|
||||||
|
// —— 阴影 / 圆角 ——
|
||||||
|
--fe-radius-sm: 4px;
|
||||||
|
--fe-radius-md: 6px;
|
||||||
|
--fe-radius-lg: 12px;
|
||||||
|
--fe-shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.5);
|
||||||
|
--fe-shadow-md: 0 4px 12px rgba(0, 0, 0, 0.55);
|
||||||
|
--fe-shadow-lg: 0 4px 12px rgba(0, 0, 0, 0.65);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: 校验未触碰其它块**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd "D:/数科智联/项目/agent-frontend-web"
|
||||||
|
sed -n '47,90p' src/views/frontend/styles/theme.scss
|
||||||
|
```
|
||||||
|
|
||||||
|
预期:`body[data-fe-theme="light"]` 块保持 `body[data-fe-theme="light"] { --fe-bg-base: #ffffff; ... }` 原样输出。
|
||||||
|
|
||||||
|
- [ ] **Step 3: 校验 `.frontend-container` 与浅色 el-* 块未改**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd "D:/数科智联/项目/agent-frontend-web"
|
||||||
|
grep -n "frontend-container" src/views/frontend/styles/theme.scss
|
||||||
|
```
|
||||||
|
|
||||||
|
预期:仍有两处 `.frontend-container`(不含 light 子选择器与 light 修饰),行号与改前一致。
|
||||||
|
|
||||||
|
- [ ] **Step 4: 启动 dev,目测深色页面底色与 accent 是否变为石墨黑/蓝紫**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd "D:/数科智联/项目/agent-frontend-web"
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
打开 `/frontend` 路由,深色主题下容器底色变 `#14171c`、accent 高亮变 `#6c8cff`;切到浅色主题,颜色与之前完全一致。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 3: `ConversationSidebar.vue` —— 模板新增搜索框 + 分组渲染
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `src/views/frontend/components/ConversationSidebar.vue:10-59`(模板)、`:372-`(.conversation-item 圆角)
|
||||||
|
|
||||||
|
- [ ] **Step 1: 在 `sidebar-header` 之后插入搜索框**
|
||||||
|
|
||||||
|
在 `<div class="sidebar-header">...</div>` 之后、`<el-scrollbar class="sidebar-scrollbar">` 之前,插入:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="sidebar-search">
|
||||||
|
<el-input
|
||||||
|
v-model="searchKeyword"
|
||||||
|
placeholder="搜索会话"
|
||||||
|
:prefix-icon="Search"
|
||||||
|
clearable
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: 把 scrollbar 内的列表改成分组渲染**
|
||||||
|
|
||||||
|
将 `<div v-for="session in sessionList" ...>` 整段替换为:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<template v-for="group in sessionGroups" :key="group.key">
|
||||||
|
<template v-if="group.items.length > 0">
|
||||||
|
<div class="group-header" @click="toggleGroup(group.key)">
|
||||||
|
<span class="group-title">{{ group.label }}</span>
|
||||||
|
<el-icon class="group-toggle">
|
||||||
|
<ArrowDown v-if="expandedGroups.has(group.key)" />
|
||||||
|
<ArrowRight v-else />
|
||||||
|
</el-icon>
|
||||||
|
</div>
|
||||||
|
<div v-show="expandedGroups.has(group.key)" class="group-items">
|
||||||
|
<div
|
||||||
|
v-for="session in group.items"
|
||||||
|
:key="session.sessionId"
|
||||||
|
class="conversation-item"
|
||||||
|
:class="{ active: currentSession?.sessionId === session.sessionId, 'has-new-message': session.hasNewMessage }"
|
||||||
|
@click="selectSession(session)"
|
||||||
|
@contextmenu.prevent="handleRightClick($event, session)"
|
||||||
|
>
|
||||||
|
<div class="conv-dot"></div>
|
||||||
|
<div class="conv-info">
|
||||||
|
<span class="conv-name">{{ session.title || session.name }}</span>
|
||||||
|
<span class="conv-time">{{ formatTimeAgo(session.updateTime) }}</span>
|
||||||
|
</div>
|
||||||
|
<div v-if="session.hasNewMessage" class="new-message-dot"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
```
|
||||||
|
|
||||||
|
空列表提示 `<div v-if="!loading && sessionList.length === 0" class="empty-sessions">暂无会话</div>` 保持不变。
|
||||||
|
|
||||||
|
- [ ] **Step 3: 把 `.conversation-item` 的固定 8px 圆角改为变量**
|
||||||
|
|
||||||
|
在 `<style scoped lang="scss">` 中找到:
|
||||||
|
|
||||||
|
```scss
|
||||||
|
.conversation-item {
|
||||||
|
...
|
||||||
|
border-radius: 8px;
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
将 `border-radius: 8px;` 改为 `border-radius: var(--fe-radius-md);`。
|
||||||
|
|
||||||
|
- [ ] **Step 4: 在 `<style scoped lang="scss">` 内追加搜索框与分组样式**
|
||||||
|
|
||||||
|
紧跟在 `.sidebar-header { ... }` 块之后追加:
|
||||||
|
|
||||||
|
```scss
|
||||||
|
.sidebar-search {
|
||||||
|
padding: 8px 12px 0;
|
||||||
|
:deep(.el-input__wrapper) {
|
||||||
|
background: var(--fe-bg-elevated);
|
||||||
|
box-shadow: 0 0 0 1px var(--fe-border) inset;
|
||||||
|
border-radius: var(--fe-radius-md);
|
||||||
|
}
|
||||||
|
:deep(.el-input__inner) {
|
||||||
|
color: var(--fe-text-primary);
|
||||||
|
&::placeholder { color: var(--fe-text-muted); }
|
||||||
|
}
|
||||||
|
:deep(.el-input__prefix .el-icon) { color: var(--fe-text-muted); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.group-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 12px 16px 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
|
.group-title {
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--fe-accent);
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
}
|
||||||
|
.group-toggle {
|
||||||
|
color: var(--fe-text-secondary);
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
&:hover .group-toggle { color: var(--fe-text-primary); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.group-items { padding: 0 8px; }
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 5: dev 验证(任务3)**
|
||||||
|
|
||||||
|
启动 dev,进入 `/frontend`,目测:
|
||||||
|
- header 下方出现搜索框;
|
||||||
|
- 列表按 `今天 / 昨天 / 更早` 三段渲染;
|
||||||
|
- 分组标题为蓝紫色,左侧 ▾/▸ 图标。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 4: `ConversationSidebar.vue` —— 脚本新增分组逻辑
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `src/views/frontend/components/ConversationSidebar.vue:68-262`(script setup)
|
||||||
|
|
||||||
|
- [ ] **Step 1: 扩展 icon 导入**
|
||||||
|
|
||||||
|
将:
|
||||||
|
|
||||||
|
```js
|
||||||
|
import { DArrowLeft, DArrowRight, Refresh, Plus, Delete, Loading } from '@element-plus/icons-vue'
|
||||||
|
```
|
||||||
|
|
||||||
|
改为:
|
||||||
|
|
||||||
|
```js
|
||||||
|
import { DArrowLeft, DArrowRight, Refresh, Plus, Delete, Loading, Search, ArrowDown, ArrowRight } from '@element-plus/icons-vue'
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: 扩展 import,引入 computed**
|
||||||
|
|
||||||
|
将 `import { ref, onMounted, onUnmounted } from 'vue'` 改为 `import { ref, computed, onMounted, onUnmounted } from 'vue'`。
|
||||||
|
|
||||||
|
- [ ] **Step 3: 新增 state 与 helper,放在 `currentSession` 之后**
|
||||||
|
|
||||||
|
在 `const currentSession = ref(null)` 之后插入:
|
||||||
|
|
||||||
|
```js
|
||||||
|
// —— 搜索关键字(仅样式占位,本次不绑定行为) ——
|
||||||
|
const searchKeyword = ref('')
|
||||||
|
|
||||||
|
// —— 分组折叠状态:'today' | 'yesterday' | 'earlier' ——
|
||||||
|
const expandedGroups = ref(new Set())
|
||||||
|
|
||||||
|
function parseTimestamp(t) {
|
||||||
|
if (!t) return null
|
||||||
|
if (typeof t === 'number' && String(t).length === 13) return t
|
||||||
|
if (typeof t === 'number' || /^\d{14}$/.test(t)) {
|
||||||
|
const s = String(t)
|
||||||
|
return new Date(`${s.slice(0,4)}-${s.slice(4,6)}-${s.slice(6,8)}T${s.slice(8,10)}:${s.slice(10,12)}:${s.slice(12,14)}`).getTime()
|
||||||
|
}
|
||||||
|
if (typeof t === 'string' && t.includes(' ')) {
|
||||||
|
const iso = t.replace(' ', 'T').replace(/\.(\d+)?$/, '')
|
||||||
|
const ms = new Date(iso).getTime()
|
||||||
|
return Number.isNaN(ms) ? null : ms
|
||||||
|
}
|
||||||
|
const ms = new Date(t).getTime()
|
||||||
|
return Number.isNaN(ms) ? null : ms
|
||||||
|
}
|
||||||
|
|
||||||
|
function groupOf(session) {
|
||||||
|
const ts = parseTimestamp(session?.updateTime)
|
||||||
|
if (ts == null) {
|
||||||
|
console.warn('[ConversationSidebar] 无法解析会话时间', session)
|
||||||
|
return 'earlier'
|
||||||
|
}
|
||||||
|
const now = new Date()
|
||||||
|
const d = new Date(ts)
|
||||||
|
const sameDay = (a, b) =>
|
||||||
|
a.getFullYear() === b.getFullYear() &&
|
||||||
|
a.getMonth() === b.getMonth() &&
|
||||||
|
a.getDate() === b.getDate()
|
||||||
|
const yesterday = new Date(now); yesterday.setDate(now.getDate() - 1)
|
||||||
|
if (sameDay(d, now)) return 'today'
|
||||||
|
if (sameDay(d, yesterday)) return 'yesterday'
|
||||||
|
return 'earlier'
|
||||||
|
}
|
||||||
|
|
||||||
|
const sessionGroups = computed(() => {
|
||||||
|
const groups = { today: [], yesterday: [], earlier: [] }
|
||||||
|
for (const s of sessionList.value) {
|
||||||
|
groups[groupOf(s)].push(s)
|
||||||
|
}
|
||||||
|
return [
|
||||||
|
{ key: 'today', label: '今天', items: groups.today },
|
||||||
|
{ key: 'yesterday', label: '昨天', items: groups.yesterday },
|
||||||
|
{ key: 'earlier', label: '更早', items: groups.earlier }
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
function toggleGroup(key) {
|
||||||
|
const next = new Set(expandedGroups.value)
|
||||||
|
if (next.has(key)) next.delete(key)
|
||||||
|
else next.add(key)
|
||||||
|
expandedGroups.value = next
|
||||||
|
}
|
||||||
|
|
||||||
|
function syncExpandedWithCurrent() {
|
||||||
|
if (currentSession.value) {
|
||||||
|
expandedGroups.value = new Set([groupOf(currentSession.value)])
|
||||||
|
} else {
|
||||||
|
expandedGroups.value = new Set(['today'])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 4: 在 `selectSession` 末尾追加 `syncExpandedWithCurrent()`**
|
||||||
|
|
||||||
|
将:
|
||||||
|
|
||||||
|
```js
|
||||||
|
function selectSession(session) {
|
||||||
|
currentSession.value = session
|
||||||
|
emit('select', session)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
改为:
|
||||||
|
|
||||||
|
```js
|
||||||
|
function selectSession(session) {
|
||||||
|
currentSession.value = session
|
||||||
|
emit('select', session)
|
||||||
|
syncExpandedWithCurrent()
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 5: 在 `fetchSessions` 的 `finally` 中调用 `syncExpandedWithCurrent()`**
|
||||||
|
|
||||||
|
将 `fetchSessions` 函数体中 `finally` 块改为:
|
||||||
|
|
||||||
|
```js
|
||||||
|
} finally {
|
||||||
|
// 强制最小 loading 时长,避免接口返回过快导致 icon 一闪而过
|
||||||
|
const elapsed = Date.now() - start
|
||||||
|
const remain = 300 - elapsed
|
||||||
|
if (remain > 0) await new Promise(resolve => setTimeout(resolve, remain))
|
||||||
|
loading.value = false
|
||||||
|
syncExpandedWithCurrent()
|
||||||
|
emit('ready')
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 6: 在 `handleDeleteSession` 删除条目后追加 `syncExpandedWithCurrent()`**
|
||||||
|
|
||||||
|
找到:
|
||||||
|
|
||||||
|
```js
|
||||||
|
if (currentSession.value?.sessionId === session.sessionId) {
|
||||||
|
currentSession.value = null
|
||||||
|
emit('select', null)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
在它之后追加:
|
||||||
|
|
||||||
|
```js
|
||||||
|
syncExpandedWithCurrent()
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 7: dev 验证(任务4)**
|
||||||
|
|
||||||
|
启动 dev,进入 `/frontend`:
|
||||||
|
- 列表加载完成后默认 `今天` 展开、昨天 / 更早折叠;
|
||||||
|
- 点击任一会话 → 仅其所在分组展开;
|
||||||
|
- 点击分组标题 → 切换展开 / 折叠。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 5: `ChatWindow.vue` —— 气泡节奏收紧
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `src/views/frontend/components/ChatWindow.vue`(`<style scoped lang="scss">` 段)
|
||||||
|
|
||||||
|
- [ ] **Step 1: 收紧容器与消息组**
|
||||||
|
|
||||||
|
在 `<style scoped lang="scss">` 中定位 `.chat-window` 与 `.message-group`,将:
|
||||||
|
|
||||||
|
```scss
|
||||||
|
.chat-window {
|
||||||
|
...
|
||||||
|
padding: 16px 20px;
|
||||||
|
...
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-group {
|
||||||
|
margin-bottom: 12px;
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
分别改为:
|
||||||
|
|
||||||
|
```scss
|
||||||
|
.chat-window {
|
||||||
|
...
|
||||||
|
padding: 12px 16px;
|
||||||
|
...
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-group {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: 收紧气泡**
|
||||||
|
|
||||||
|
定位 `.message-bubble`(user / assistant 共享样式),将 `padding: 10px 14px;` 改为 `padding: 9px 13px;`;将 `border-radius: 12px;` 改为 `border-radius: 10px;`。
|
||||||
|
|
||||||
|
- [ ] **Step 3: dev 验证(任务5)**
|
||||||
|
|
||||||
|
启动 dev,进入 `/frontend`,选择一个会话发消息,目测:
|
||||||
|
- 用户 / 助手气泡圆角 10px;
|
||||||
|
- 气泡内边距 9 / 13px;
|
||||||
|
- 消息组间距 10px;
|
||||||
|
- 整体节奏与 `chat.png` 一致;
|
||||||
|
- 发送消息后流式追加仍自动滚到底。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 6: 端到端冒烟清单(合并验证)
|
||||||
|
|
||||||
|
**Files:** 无新增;仅执行 dev 自检。
|
||||||
|
|
||||||
|
- [ ] **Step 1: 深色主题**
|
||||||
|
|
||||||
|
进入 `/frontend` 默认深色:
|
||||||
|
- 容器底色 `#14171c`、卡片背景 `#1c1f24`;
|
||||||
|
- ChatHeader、ConversationSidebar、ChatWindow、InputArea 颜色一致;
|
||||||
|
- accent 高亮 `#6c8cff`(搜索框聚焦边框、分组标题、新建会话图标等)。
|
||||||
|
|
||||||
|
- [ ] **Step 2: 浅色主题未变**
|
||||||
|
|
||||||
|
切到浅色主题:底色 `#ffffff`、accent `#1890ff`;与改动前完全一致。
|
||||||
|
|
||||||
|
- [ ] **Step 3: 上下文 popover / 模型选择 / ElMessageBox**
|
||||||
|
|
||||||
|
点击 ChatHeader 切换模型、点击 InputArea 右下角上下文指示器、删除会话触发确认弹窗:新深色系跟随;浅色下保持原样。
|
||||||
|
|
||||||
|
- [ ] **Step 4: 侧栏分组 + 搜索框**
|
||||||
|
|
||||||
|
- 列表加载默认 `今天` 展开;
|
||||||
|
- 点击分组标题切换展开 / 折叠;
|
||||||
|
- 选中任一会话 → 仅其所在分组展开;
|
||||||
|
- 删除当前会话后折叠状态回退到 `今天`(或当前会话所在分组);
|
||||||
|
- 搜索框聚焦高亮、placeholder "搜索会话"、输入不改变列表;
|
||||||
|
- 右键菜单仍可重命名 / 删除会话。
|
||||||
|
|
||||||
|
- [ ] **Step 5: 聊天模块**
|
||||||
|
|
||||||
|
- 用户 / 助手气泡圆角 10px、内边距 9/13px;
|
||||||
|
- 消息组间距 10px;
|
||||||
|
- 流式追加自动滚到底;
|
||||||
|
- 上拉后向下箭头按钮仍可点击回到底部;
|
||||||
|
- 处理详情折叠面板(thinking / tool)展开 / 折叠正常。
|
||||||
|
|
||||||
|
- [ ] **Step 6: 不做 git 推送**
|
||||||
|
|
||||||
|
本次仅修改本地文件,不运行 `git push` / `git commit`(如果用户希望记录变更,可手动提交)。
|
||||||
Reference in New Issue
Block a user