--- 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 的调用 - 模板中改为裸的 `` 渲染消息 - **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. 在 `` 内部: - 将 `` 改为 `` - 在 Markdown 下方添加折叠/展开按钮 - 在折叠/展开按钮下方添加操作按钮组(复制、播放) 4. 添加按钮组样式(`.message-actions`、`.action-btn` 等) ### 步骤 2:ScriptDetailView 新增函数 1. 打开 `mini-program/src/pages/main/ScriptDetailView.vue` 2. 在 `