16 KiB
author, created_at, purpose
| author | created_at | purpose |
|---|---|---|
| AI Assistant | 2026-07-28 | 恢复小程序小说生成页和详情页丢失的"复制、播放、折叠/展开"功能 |
小说消息操作按钮恢复设计
问题背景
小程序的小说生成页(ScriptView.vue)和详情页(ScriptDetailView.vue)在之前的重构过程中丢失了"复制、播放、折叠/展开"等操作按钮。用户要求完整恢复这些功能,且必须有真实实现,不能有任何 mock 或占位。
根因分析
重构历史追溯
通过 git 历史分析,发现功能丢失的关键节点:
-
commit
9b3006d(2026-06-27):添加消息卡片功能方法- 新增
copyMessageContent(message):复制消息内容 - 新增
playMessageTts(message):播放 TTS 音频 - 新增
toggleMessageCollapse(message):折叠/展开消息
- 新增
-
commit
0db434c(2026-06-27):在 result-chat-list 中添加功能按钮组- 在每条 assistant 消息下方添加 5 个按钮:复制、换个方向、不像我、继续生成、播放
- 使用
.message-actions样式类
-
commit
c2ed051:ScriptView 复用 MessageCard 组件- 将消息卡片提取为独立的
MessageCard.vue组件 - MessageCard 包含完整功能:折叠、复制、换个方向、不像我、续写、改写、查看历史、删除、播放
- 将消息卡片提取为独立的
-
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) collapsedMessageIdsref 保留(行 287)- 按钮组 UI 丢失
-
ScriptDetailView.vue:从未使用过 MessageCard,也从未有过这些功能
当前状态
ScriptView.vue(生成页):
- ✅ 所有相关函数已存在(copyMessageContent、playMessageTts、toggleMessageCollapse 等)
- ✅
collapsedMessageIdsref 已存在 - ❌ 模板中按钮组 UI 已丢失
- ❌ 相关样式可能已删除或无用
ScriptDetailView.vue(详情页):
- ❌ 完全没有相关函数
- ❌ 完全没有
collapsedMessageIdsref - ❌ 完全没有按钮组 UI
- ✅ 底部有全局播放按钮(
ttsPlayer.playSource)
设计方案
核心设计决策
| 维度 | 决策 |
|---|---|
| 功能范围 | 完整恢复所有按钮功能,但布局采用极简设计 |
| 页面范围 | 生成页(ScriptView)+ 详情页(ScriptDetailView) |
| 应用对象 | 仅 kind === 'novel' 的小说正文消息 |
| 按钮布局 | 每条 novel 消息下方:折叠/展开 + 复制 + 播放(3 个按钮) |
| 修订类按钮 | 放在底部操作区(已有),不在每条消息下重复 |
设计原则
- 极简设计:每条 novel 消息下方只放 3 个核心功能按钮(折叠/展开、复制、播放)
- 功能就近:内容操作类按钮(复制、播放、折叠)贴近消息,用户操作直观
- 修订集中:方向修订类按钮(换个方向、不像我、继续生成)集中在底部操作区,避免重复
- 样式统一:两个页面使用相同的按钮组样式,保持视觉一致性
- 代码复用:ScriptView 已有函数直接复用,ScriptDetailView 新增相同逻辑
功能实现细节
1. 折叠/展开
状态管理:
const collapsedMessageIds = ref({})
核心函数:
// 判断消息是否折叠
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. 复制
核心函数:
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. 播放
核心函数:
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(生成页)
已有资源:
- ✅
collapsedMessageIdsref(行 287) - ✅
isMessageCollapsed(message)函数(行 399) - ✅
getMessageDisplayContent(message)函数(行 402-407) - ✅
toggleMessageCollapse(message)函数(行 409-420) - ✅
copyMessageContent(message)函数(行 422-438) - ✅
playMessageTts(message)函数(行 440-447) - ✅
ttsPlayercomposable(行 320) - ✅
ttsActionTextcomputed(行 986-990) - ✅
ttsActionIconcomputed(行 992-994)
需要补充:
- ❌ 模板中按钮组 UI(在
kind === 'novel'的消息下方添加) - ❌ 按钮组样式(
.message-actions、.action-btn等)
ScriptDetailView(详情页)
需要新增:
- ❌
collapsedMessageIdsref - ❌
isMessageCollapsed(message)函数 - ❌
getMessageDisplayContent(message)函数 - ❌
toggleMessageCollapse(message)函数 - ❌
copyMessageContent(message)函数 - ❌
playMessageTts(message)函数 - ❌ 模板中按钮组 UI
- ❌ 按钮组样式
已有资源:
- ✅
ttsPlayercomposable(行 110) - ✅
detailTtsActionTextcomputed(行 118-122) - ✅
detailTtsIconcomputed(行 124-126)
实现步骤
步骤 1:ScriptView 恢复按钮组 UI
- 打开
mini-program/src/pages/main/ScriptView.vue - 定位到
kind === 'novel'的消息渲染部分(约行 180-187) - 在
<view v-else-if="msg.kind === 'novel'" class="chat-bubble system novel-bubble">内部:- 将
<Markdown :content="msg.content" />改为<Markdown :content="getMessageDisplayContent(msg)" /> - 在 Markdown 下方添加折叠/展开按钮
- 在折叠/展开按钮下方添加操作按钮组(复制、播放)
- 将
- 添加按钮组样式(
.message-actions、.action-btn等)
步骤 2:ScriptDetailView 新增函数
- 打开
mini-program/src/pages/main/ScriptDetailView.vue - 在
<script setup>中添加:const collapsedMessageIds = ref({})isMessageCollapsed(message)函数getMessageDisplayContent(message)函数toggleMessageCollapse(message)函数copyMessageContent(message)函数playMessageTts(message)函数
步骤 3:ScriptDetailView 添加按钮组 UI
- 定位到
kind === 'novel'的消息渲染部分(约行 74-76) - 在
<view v-else-if="msg.kind === 'novel'" class="chat-bubble system novel-bubble">内部:- 将
<Markdown :content="msg.content" />改为<Markdown :content="getMessageDisplayContent(msg)" /> - 在 Markdown 下方添加折叠/展开按钮
- 在折叠/展开按钮下方添加操作按钮组(复制、播放)
- 将
- 添加按钮组样式(与 ScriptView 保持一致)
步骤 4:H5 端到端验收
- 启动 mini-program H5:
python dev-services.py start mini-program(端口 5180) - 浏览器访问
http://localhost:5180/#/pages/main/index?tab=script - 生成页验收:
- 进入一个已有小说的生成页(或新建一个)
- 验证小说正文下方有 3 个按钮:折叠/展开、复制、播放
- 点击"折叠":内容截断显示前 200 字 + "展开全文"按钮
- 点击"展开":内容完整显示 + "收起全文"按钮
- 点击"复制":Toast 提示"已复制",剪贴板中有内容
- 点击"播放":TTS 开始播放,按钮变为"暂停"
- 浏览器 Console 检查:0 新增错误
- 详情页验收:
- 进入小说详情页(从列表页点击剧本)
- 验证小说正文下方有 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 播放状态同步:播放时按钮显示"暂停",暂停时按钮显示"播放"
影响范围
修改文件
-
mini-program/src/pages/main/ScriptView.vue- 模板:为
kind === 'novel'的消息添加按钮组 UI - 样式:添加
.message-actions、.action-btn等样式
- 模板:为
-
mini-program/src/pages/main/ScriptDetailView.vue- Script:新增 6 个函数/ref
- 模板:为
kind === 'novel'的消息添加按钮组 UI - 样式:添加
.message-actions、.action-btn等样式
不修改的部分
- ❌ 后端代码:不涉及
- ❌ 数据库:不涉及
- ❌ MessageCard.vue 组件:不修改(保持现状)
- ❌ 其他页面:不涉及
兼容性
- ✅ 老剧本(无 clarification/outline 数据):不影响,仍可正常显示
- ✅ 新剧本(有完整数据):新增按钮组功能
- ✅ 无小说正文的剧本:不显示按钮组(
kind === 'novel'的消息不存在)
风险与注意事项
-
TTS 播放的是整个小说:
ttsPlayer.playSource(scriptId)播放的是整个小说的音频,不是单条消息的音频。这是历史设计,保持一致。 -
折叠阈值:折叠时显示前 200 字符。这个阈值可以根据实际需求调整。
-
样式一致性:两个页面的按钮组样式必须保持一致,建议直接复制 ScriptView 的样式到 ScriptDetailView。
-
性能考虑:
collapsedMessageIds是响应式对象,每次切换折叠状态都会触发重新渲染。对于大量消息的场景,可能需要优化(当前场景下消息数量有限,不构成问题)。 -
埋点完整性:所有用户操作(折叠、复制、播放)都必须触发埋点,用于数据分析。
总结
本设计方案通过极简设计恢复小程序小说生成页和详情页丢失的"复制、播放、折叠/展开"功能。核心决策是:每条 novel 消息下方只放 3 个核心功能按钮,修订类按钮集中在底部操作区。实现上,ScriptView 复用已有函数,ScriptDetailView 新增相同逻辑。验收标准明确,影响范围可控。