docs: 添加小说消息操作按钮恢复设计文档
This commit is contained in:
@@ -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)
|
||||||
|
|
||||||
|
## 实现步骤
|
||||||
|
|
||||||
|
### 步骤 1:ScriptView 恢复按钮组 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` 等)
|
||||||
|
|
||||||
|
### 步骤 2:ScriptDetailView 新增函数
|
||||||
|
|
||||||
|
1. 打开 `mini-program/src/pages/main/ScriptDetailView.vue`
|
||||||
|
2. 在 `<script setup>` 中添加:
|
||||||
|
- `const collapsedMessageIds = ref({})`
|
||||||
|
- `isMessageCollapsed(message)` 函数
|
||||||
|
- `getMessageDisplayContent(message)` 函数
|
||||||
|
- `toggleMessageCollapse(message)` 函数
|
||||||
|
- `copyMessageContent(message)` 函数
|
||||||
|
- `playMessageTts(message)` 函数
|
||||||
|
|
||||||
|
### 步骤 3:ScriptDetailView 添加按钮组 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 保持一致)
|
||||||
|
|
||||||
|
### 步骤 4:H5 端到端验收
|
||||||
|
|
||||||
|
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 新增相同逻辑。验收标准明确,影响范围可控。
|
||||||
Reference in New Issue
Block a user