docs: 添加小说消息操作按钮恢复设计文档

This commit is contained in:
2026-07-28 19:32:28 +08:00
parent 9808e2eb93
commit 760c1445fe
@@ -0,0 +1,376 @@
---
author: AI Assistant
created_at: 2026-07-28
purpose: 恢复小程序小说生成页和详情页丢失的"复制、播放、折叠/展开"功能
---
# 小说消息操作按钮恢复设计
## 问题背景
小程序的小说生成页(ScriptView.vue)和详情页(ScriptDetailView.vue)在之前的重构过程中丢失了"复制、播放、折叠/展开"等操作按钮。用户要求完整恢复这些功能,且必须有真实实现,不能有任何 mock 或占位。
## 根因分析
### 重构历史追溯
通过 git 历史分析,发现功能丢失的关键节点:
1. **commit 9b3006d**(2026-06-27):添加消息卡片功能方法
- 新增 `copyMessageContent(message)`:复制消息内容
- 新增 `playMessageTts(message)`:播放 TTS 音频
- 新增 `toggleMessageCollapse(message)`:折叠/展开消息
2. **commit 0db434c**2026-06-27):在 result-chat-list 中添加功能按钮组
- 在每条 assistant 消息下方添加 5 个按钮:复制、换个方向、不像我、继续生成、播放
- 使用 `.message-actions` 样式类
3. **commit c2ed051**ScriptView 复用 MessageCard 组件
- 将消息卡片提取为独立的 `MessageCard.vue` 组件
- MessageCard 包含完整功能:折叠、复制、换个方向、不像我、续写、改写、查看历史、删除、播放
4. **commit 2c778fa**2026-07-01):**ScriptView 删除 read 模式 + 兼容旧模式,统一对话流入口**
- 删除了 102 行代码,包括所有使用 MessageCard 的调用
- 模板中改为裸的 `<view class="chat-bubble">` 渲染消息
- **MessageCard 导入保留但不再使用**(行 241)
- **JS 函数保留**`copyMessageContent`(行 422-438)、`playMessageTts`(行 440-447)、`toggleMessageCollapse`(行 409-420)、`getMessageDisplayContent`(行 402-407)、`isMessageCollapsed`(行 399
- **`collapsedMessageIds` ref 保留**(行 287
- **按钮组 UI 丢失**
5. **ScriptDetailView.vue**:从未使用过 MessageCard,也从未有过这些功能
### 当前状态
**ScriptView.vue(生成页)**
- ✅ 所有相关函数已存在(copyMessageContent、playMessageTts、toggleMessageCollapse 等)
-`collapsedMessageIds` ref 已存在
- ❌ 模板中按钮组 UI 已丢失
- ❌ 相关样式可能已删除或无用
**ScriptDetailView.vue(详情页)**
- ❌ 完全没有相关函数
- ❌ 完全没有 `collapsedMessageIds` ref
- ❌ 完全没有按钮组 UI
- ✅ 底部有全局播放按钮(`ttsPlayer.playSource`
## 设计方案
### 核心设计决策
| 维度 | 决策 |
|------|------|
| **功能范围** | 完整恢复所有按钮功能,但布局采用极简设计 |
| **页面范围** | 生成页(ScriptView+ 详情页(ScriptDetailView |
| **应用对象** | 仅 `kind === 'novel'` 的小说正文消息 |
| **按钮布局** | 每条 novel 消息下方:折叠/展开 + 复制 + 播放(3 个按钮)|
| **修订类按钮** | 放在底部操作区(已有),不在每条消息下重复 |
### 设计原则
1. **极简设计**:每条 novel 消息下方只放 3 个核心功能按钮(折叠/展开、复制、播放)
2. **功能就近**:内容操作类按钮(复制、播放、折叠)贴近消息,用户操作直观
3. **修订集中**:方向修订类按钮(换个方向、不像我、继续生成)集中在底部操作区,避免重复
4. **样式统一**:两个页面使用相同的按钮组样式,保持视觉一致性
5. **代码复用**ScriptView 已有函数直接复用,ScriptDetailView 新增相同逻辑
### 功能实现细节
#### 1. 折叠/展开
**状态管理**
```javascript
const collapsedMessageIds = ref({})
```
**核心函数**
```javascript
// 判断消息是否折叠
const isMessageCollapsed = (message) => {
return Boolean(collapsedMessageIds.value[message.id])
}
// 获取消息显示内容(折叠时截断)
const getMessageDisplayContent = (message) => {
const content = String(message?.content || '')
if (!isMessageCollapsed(message)) return content
if (content.length <= 200) return content
return `${content.slice(0, 200)}...`
}
// 切换折叠状态
const toggleMessageCollapse = (message) => {
collapsedMessageIds.value = {
...collapsedMessageIds.value,
[message.id]: !isMessageCollapsed(message)
}
analytics.track('script_message_collapse_toggle', {
message_id: message?.id || '',
collapsed: collapsedMessageIds.value[message.id]
}, { eventType: 'script', pagePath })
}
```
**UI 交互**
- 折叠时:显示前 200 字符 + "展开全文"按钮
- 展开时:显示完整内容 + "收起全文"按钮
- 折叠/展开按钮放在消息内容下方,按钮组上方
#### 2. 复制
**核心函数**
```javascript
const copyMessageContent = (message) => {
const content = String(message?.content || '')
if (!content.trim()) {
uni.showToast({ title: '暂无可复制内容', icon: 'none' })
return
}
uni.setClipboardData({
data: content,
success: () => {
uni.showToast({ title: '已复制', icon: 'success' })
}
})
analytics.track('script_message_copy_click', {
message_id: message?.id || '',
content_length: content.length
}, { eventType: 'script', pagePath })
}
```
**UI 交互**
- 点击"复制"按钮,调用 `uni.setClipboardData`
- 成功复制后显示 Toast 提示"已复制"
- 内容为空时显示"暂无可复制内容"
#### 3. 播放
**核心函数**
```javascript
const playMessageTts = (message) => {
const scriptId = currentResult.value?.id || '' // ScriptView
// 或 script.value?.id || '' // ScriptDetailView
ttsPlayer.playSource(scriptId)
analytics.track('script_message_tts_click', {
message_id: message?.id || '',
script_id: scriptId
}, { eventType: 'script', pagePath })
}
```
**UI 交互**
- 播放图标动态显示:`▶`(未播放)/ `Ⅱ`(播放中)
- 播放文本动态显示:`播放`(未播放)/ `暂停`(播放中)
- 点击按钮调用 `ttsPlayer.playSource(scriptId)` 播放整个小说的 TTS 音频
### UI 设计
**每条 novel 消息下方按钮组**
```
┌─────────────────────────────────────┐
│ [小说正文内容] │
│ (折叠时显示前 200 字...) │
│ │
│ [▼ 展开全文] ← 折叠/展开按钮 │
│ │
│ ┌──────────┬──────────┬──────────┐ │
│ │ 📋 复制 │ ▶ 播放 │ ⚙ 更多 │ │ ← 操作按钮组
│ └──────────┴──────────┴──────────┘ │
└─────────────────────────────────────┘
```
**实际实现(3 个按钮)**
```
┌─────────────────────────────────────┐
│ [小说正文内容] │
│ │
│ [▼ 展开全文] │
│ │
│ ┌──────────┬──────────┬──────────┐ │
│ │ 复制 │ ▶ 播放 │ 收起 │ │
│ └──────────┴──────────┴──────────┘ │
└─────────────────────────────────────┘
```
**按钮样式**
- 参考 MessageCard 的 `result-actions` 样式(grid 布局)
- 3 个按钮横向排列,等宽分布
- 播放按钮为 `primary` 样式(紫色渐变,突出显示)
- 折叠/展开按钮为独立行,放在按钮组上方
- 按钮高度:72rpx
- 按钮圆角:28rpx
- 按钮背景:`rgba(88, 28, 135, 0.18)`
- 按钮边框:`1rpx solid rgba(192, 132, 252, 0.35)`
### 页面差异化
| 页面 | 现有修订类按钮 | 需新增功能 |
|------|--------------|-----------|
| **生成页(ScriptView** | 底部输入区已有"换个方向"、"不像我"等按钮 | 每条 novel 消息下加 3 个按钮(复用已有函数) |
| **详情页(ScriptDetailView** | 底部已有"继续生成"按钮 | 每条 novel 消息下加 3 个按钮 + 新增所有函数 |
### 应用对象
**只在 `kind === 'novel'` 的 assistant 消息上显示按钮组**
-`kind === 'novel'`:小说正文,需要复制、播放、折叠/展开
-`kind === 'card'`:澄清卡片,有特殊交互(提交按钮),不需要这些按钮
-`kind === 'outline'`:大纲,结构化数据,不需要这些按钮
-`kind === 'text'`:用户消息,不需要这些按钮
## 数据流与代码复用
### ScriptView(生成页)
**已有资源**
-`collapsedMessageIds` ref(行 287
-`isMessageCollapsed(message)` 函数(行 399
-`getMessageDisplayContent(message)` 函数(行 402-407
-`toggleMessageCollapse(message)` 函数(行 409-420
-`copyMessageContent(message)` 函数(行 422-438
-`playMessageTts(message)` 函数(行 440-447
-`ttsPlayer` composable(行 320
-`ttsActionText` computed(行 986-990
-`ttsActionIcon` computed(行 992-994
**需要补充**
- ❌ 模板中按钮组 UI(在 `kind === 'novel'` 的消息下方添加)
- ❌ 按钮组样式(`.message-actions``.action-btn` 等)
### ScriptDetailView(详情页)
**需要新增**
-`collapsedMessageIds` ref
-`isMessageCollapsed(message)` 函数
-`getMessageDisplayContent(message)` 函数
-`toggleMessageCollapse(message)` 函数
-`copyMessageContent(message)` 函数
-`playMessageTts(message)` 函数
- ❌ 模板中按钮组 UI
- ❌ 按钮组样式
**已有资源**
-`ttsPlayer` composable(行 110
-`detailTtsActionText` computed(行 118-122
-`detailTtsIcon` computed(行 124-126
## 实现步骤
### 步骤 1ScriptView 恢复按钮组 UI
1. 打开 `mini-program/src/pages/main/ScriptView.vue`
2. 定位到 `kind === 'novel'` 的消息渲染部分(约行 180-187)
3.`<view v-else-if="msg.kind === 'novel'" class="chat-bubble system novel-bubble">` 内部:
-`<Markdown :content="msg.content" />` 改为 `<Markdown :content="getMessageDisplayContent(msg)" />`
- 在 Markdown 下方添加折叠/展开按钮
- 在折叠/展开按钮下方添加操作按钮组(复制、播放)
4. 添加按钮组样式(`.message-actions``.action-btn` 等)
### 步骤 2ScriptDetailView 新增函数
1. 打开 `mini-program/src/pages/main/ScriptDetailView.vue`
2.`<script setup>` 中添加:
- `const collapsedMessageIds = ref({})`
- `isMessageCollapsed(message)` 函数
- `getMessageDisplayContent(message)` 函数
- `toggleMessageCollapse(message)` 函数
- `copyMessageContent(message)` 函数
- `playMessageTts(message)` 函数
### 步骤 3ScriptDetailView 添加按钮组 UI
1. 定位到 `kind === 'novel'` 的消息渲染部分(约行 74-76
2.`<view v-else-if="msg.kind === 'novel'" class="chat-bubble system novel-bubble">` 内部:
-`<Markdown :content="msg.content" />` 改为 `<Markdown :content="getMessageDisplayContent(msg)" />`
- 在 Markdown 下方添加折叠/展开按钮
- 在折叠/展开按钮下方添加操作按钮组(复制、播放)
3. 添加按钮组样式(与 ScriptView 保持一致)
### 步骤 4H5 端到端验收
1. 启动 mini-program H5`python dev-services.py start mini-program`(端口 5180
2. 浏览器访问 `http://localhost:5180/#/pages/main/index?tab=script`
3. **生成页验收**
- 进入一个已有小说的生成页(或新建一个)
- 验证小说正文下方有 3 个按钮:折叠/展开、复制、播放
- 点击"折叠":内容截断显示前 200 字 + "展开全文"按钮
- 点击"展开":内容完整显示 + "收起全文"按钮
- 点击"复制"Toast 提示"已复制",剪贴板中有内容
- 点击"播放"TTS 开始播放,按钮变为"暂停"
- 浏览器 Console 检查:0 新增错误
4. **详情页验收**
- 进入小说详情页(从列表页点击剧本)
- 验证小说正文下方有 3 个按钮:折叠/展开、复制、播放
- 重复上述验收步骤
- 浏览器 Console 检查:0 新增错误
## 验收标准
### 功能验收
-**生成页(ScriptView**
- 每条 novel 消息下方有 3 个按钮:折叠/展开、复制、播放
- 折叠/展开功能正常:折叠时显示前 200 字,展开时显示完整内容
- 复制功能正常:点击后 Toast 提示"已复制",剪贴板中有内容
- 播放功能正常:点击后 TTS 开始播放,按钮变为"暂停"
- Console 无新增错误
-**详情页(ScriptDetailView**
- 每条 novel 消息下方有 3 个按钮:折叠/展开、复制、播放
- 折叠/展开功能正常
- 复制功能正常
- 播放功能正常
- Console 无新增错误
### 样式验收
- ✅ 按钮组横向排列,3 个按钮等宽分布
- ✅ 播放按钮为 primary 样式(紫色渐变)
- ✅ 折叠/展开按钮为独立行,在按钮组上方
- ✅ 按钮样式与 MessageCard 保持一致(圆角、背景、边框)
### 数据流验收
- ✅ 埋点正常触发:`script_message_collapse_toggle``script_message_copy_click``script_message_tts_click`
- ✅ TTS 播放状态同步:播放时按钮显示"暂停",暂停时按钮显示"播放"
## 影响范围
### 修改文件
1. `mini-program/src/pages/main/ScriptView.vue`
- 模板:为 `kind === 'novel'` 的消息添加按钮组 UI
- 样式:添加 `.message-actions``.action-btn` 等样式
2. `mini-program/src/pages/main/ScriptDetailView.vue`
- Script:新增 6 个函数/ref
- 模板:为 `kind === 'novel'` 的消息添加按钮组 UI
- 样式:添加 `.message-actions``.action-btn` 等样式
### 不修改的部分
- ❌ 后端代码:不涉及
- ❌ 数据库:不涉及
- ❌ MessageCard.vue 组件:不修改(保持现状)
- ❌ 其他页面:不涉及
### 兼容性
- ✅ 老剧本(无 clarification/outline 数据):不影响,仍可正常显示
- ✅ 新剧本(有完整数据):新增按钮组功能
- ✅ 无小说正文的剧本:不显示按钮组(`kind === 'novel'` 的消息不存在)
## 风险与注意事项
1. **TTS 播放的是整个小说**`ttsPlayer.playSource(scriptId)` 播放的是整个小说的音频,不是单条消息的音频。这是历史设计,保持一致。
2. **折叠阈值**:折叠时显示前 200 字符。这个阈值可以根据实际需求调整。
3. **样式一致性**:两个页面的按钮组样式必须保持一致,建议直接复制 ScriptView 的样式到 ScriptDetailView。
4. **性能考虑**`collapsedMessageIds` 是响应式对象,每次切换折叠状态都会触发重新渲染。对于大量消息的场景,可能需要优化(当前场景下消息数量有限,不构成问题)。
5. **埋点完整性**:所有用户操作(折叠、复制、播放)都必须触发埋点,用于数据分析。
## 总结
本设计方案通过极简设计恢复小程序小说生成页和详情页丢失的"复制、播放、折叠/展开"功能。核心决策是:每条 novel 消息下方只放 3 个核心功能按钮,修订类按钮集中在底部操作区。实现上,ScriptView 复用已有函数,ScriptDetailView 新增相同逻辑。验收标准明确,影响范围可控。