docs:新增 AI 消息卡片功能按钮统一设计文档

- 修复 story-card 展开失效 bug
- 为每条 AI 消息添加完整功能按钮组
- 采用方案 A:在现有单文件组件内修复 + 扩展
This commit is contained in:
2026-06-27 20:40:37 +08:00
parent 2198781c68
commit e014450aa3
@@ -0,0 +1,125 @@
---
author: claude
created_at: 2026-06-27
purpose: 统一小程序实现心愿页面中所有 AI 消息卡片的功能按钮,并修复 story-card 展开失效 bug
---
# AI 消息卡片功能按钮统一设计
## 背景
在小程序「实现心愿」页面(`mini-program/src/pages/main/ScriptView.vue`)中:
1. **第一个 AI 回复卡片**`story-card`)拥有完整的功能按钮:展开/收起、复制、换个方向、不像我、继续生成、播放。
2. **后续 AI 消息**`result-chat-list`)只有基础的展开/收起,缺少复制、换个方向、不像我、继续生成、播放。
3. **story-card 展开失效**:当用户触发过重新生成(`resultHasScriptReplies``true`)后,`<template v-if="!resultHasScriptReplies">` 会隐藏整个故事正文和底部展开按钮,导致点击展开无效果。
## 目标
- 每条 AI 回复消息卡片都拥有完整的功能按钮(展开/收起、复制、换个方向、不像我、继续生成、播放)
- 修复 story-card 展开失效的 bug
- 所有按钮功能正常可用
## 方案选择
采用 **方案 A:在现有单文件组件内修复 + 扩展**
- 不提取独立组件,保持现有文件结构
- 改动最小、风险低、不引入新文件
- 后续如有更复杂的差异化需求再升级
## 详细设计
### 1. 修复 story-card 展开失效
**根因**`ScriptView.vue` 第 192 行的条件包裹
```vue
<template v-if="!resultHasScriptReplies">
<scroll-view v-if="storyCollapsed" ...>
<text class="story-body">{{ displayedResultContent }}</text>
</scroll-view>
<text v-else class="story-body">{{ displayedResultContent }}</text>
<view class="collapse-row" @click="toggleStoryCollapse">...</view>
</template>
```
`resultHasScriptReplies``true`(有 `kind: 'script'` 的消息)时,整个故事正文和底部展开按钮被隐藏。
**修复**:去掉 `<template v-if="!resultHasScriptReplies">` 条件包裹,让故事正文始终由 `storyCollapsed` 控制展开/收起。
**副作用**story-card 始终显示完整故事正文,与 result-chat-list 中的 script 消息内容重复。这是预期行为——两个卡片独立操作各自内容。
### 2. 给每条 assistant 消息加功能按钮组
**布局**:在每条 assistant 消息的内容 + 展开/收起按钮之后,追加一行网格按钮组,2 列布局(与 story-card 的 `.result-actions` 一致)。
**按钮清单**(按顺序):
| 按钮 | 作用 | 实现方式 |
|---|---|---|
| 复制 | 复制该消息正文到剪贴板 | `uni.setClipboardData` |
| 换个方向 | 进入「换方向」修订确认 | 复用现有 `changeDirection()` |
| 不像我 | 进入「不像我」修订确认 | 复用现有 `notLikeMe()` |
| 继续生成 | 收起故事 + 聚焦输入框 | 复用现有 `continueInChat()` |
| 播放 | 播放 TTS 音频 | 复用 `ttsPlayer.playSource`,传 `currentResult.id` |
**按钮条件**
- 展开/收起、复制:所有 assistant 消息都有
- 换个方向、不像我、继续生成、播放:所有 assistant 消息都有(本质上是全局操作,作用在 `currentResult` 上)
- 正在 `pending` 的 assistant 消息不显示按钮组(保持现有逻辑,`isAssistantMessage` 已排除 `pending`
### 3. 新增/修改的事件处理器
1. **`copyMessageContent(message)`**(新增):
- 复制 `message.content` 到剪贴板
- 空内容时 `showToast` 提示「暂无可复制内容」
- 埋点 `script_message_copy_click`
2. **`playMessageTts(message)`**(新增):
- 调用 `ttsPlayer.playSource(currentResult.value?.id || '')`
- 所有消息都播放同一个故事的 TTS(TTS 基于 `currentResult` 生成)
- 埋点 `script_message_tts_click`
3. **`toggleMessageCollapse(message)`**(修改):
- 补充埋点 `script_message_collapse_toggle`(与 `toggleStoryCollapse` 对齐)
### 4. 样式
为消息卡片内的按钮组新建 `.message-actions` 样式,复用 `.result-actions``.action-btn` 的视觉风格:
```css
.message-actions {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 14rpx;
margin-top: 16rpx;
}
```
按钮直接复用 `.action-btn` 类名,确保视觉与主卡片一致。展开/收起按钮(`.message-toggle`)保留现有金色调样式,与功能按钮组做视觉区分。
### 5. 边界情况
| 场景 | 行为 |
|---|---|
| 空内容的 assistant 消息 | 复制按钮 `showToast` 提示「暂无可复制内容」;其他按钮正常可用 |
| pending 中的消息 | 不显示按钮组(现有逻辑) |
| user 消息 | 不显示按钮组 |
| 无 `currentResult` 时的播放 | 按钮仍可点击,`playSource` 会走内部兜底(传空 id |
## 修改范围
仅修改一个文件:`mini-program/src/pages/main/ScriptView.vue`
### 模板修改
1. 去掉第 192 行 `<template v-if="!resultHasScriptReplies">` 条件包裹
2.`result-chat-list` 的 assistant 消息中添加功能按钮组
### 脚本修改
1. 新增 `copyMessageContent(message)` 方法
2. 新增 `playMessageTts(message)` 方法
3. 修改 `toggleMessageCollapse(message)` 补充埋点
### 样式修改
1. 新增 `.message-actions` 样式