Files
happy-life-star/docs/superpowers/specs/2026-07-28-novel-message-actions-restore-design.md
T

16 KiB
Raw Blame History

author, created_at, purpose
author created_at purpose
AI Assistant 2026-07-28 恢复小程序小说生成页和详情页丢失的"复制、播放、折叠/展开"功能

小说消息操作按钮恢复设计

问题背景

小程序的小说生成页(ScriptView.vue)和详情页(ScriptDetailView.vue)在之前的重构过程中丢失了"复制、播放、折叠/展开"等操作按钮。用户要求完整恢复这些功能,且必须有真实实现,不能有任何 mock 或占位。

根因分析

重构历史追溯

通过 git 历史分析,发现功能丢失的关键节点:

  1. commit 9b3006d(2026-06-27):添加消息卡片功能方法

    • 新增 copyMessageContent(message):复制消息内容
    • 新增 playMessageTts(message):播放 TTS 音频
    • 新增 toggleMessageCollapse(message):折叠/展开消息
  2. commit 0db434c2026-06-27):在 result-chat-list 中添加功能按钮组

    • 在每条 assistant 消息下方添加 5 个按钮:复制、换个方向、不像我、继续生成、播放
    • 使用 .message-actions 样式类
  3. commit c2ed051ScriptView 复用 MessageCard 组件

    • 将消息卡片提取为独立的 MessageCard.vue 组件
    • MessageCard 包含完整功能:折叠、复制、换个方向、不像我、续写、改写、查看历史、删除、播放
  4. commit 2c778fa2026-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. 折叠/展开

状态管理

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(生成页)

已有资源

  • 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 H5python 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_togglescript_message_copy_clickscript_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 新增相同逻辑。验收标准明确,影响范围可控。