docs: ScriptView 对话模式按钮完整修复设计文档

This commit is contained in:
2026-06-29 22:24:12 +08:00
parent 6ae721eb89
commit 7fb90299d1
@@ -0,0 +1,286 @@
---
author: Peanut
created_at: 2026-06-29
purpose: 完整修复 ScriptView 对话模式的按钮缺失问题,补齐 spec 要求的版本/改写/续写/历史版本/删除功能
---
# ScriptView 对话模式按钮完整修复设计
## 背景
`bf6f279 feat: ScriptView 改造为对话中心查看/修改模式` 重构后,对话模式的按钮出现多处丢失:
1. **旧版按钮丢失**`MessageCard``isShortMessage=true`(短消息)分支没有按钮组,而旧版 assistant 消息后面有「复制/换个方向/不像我/继续生成/播放」按钮组(commit `0db434c` 添加)
2. **Spec 功能未实现**`2026-06-28-script-dialogue-design.md` 第 5.4 节要求 AI 剧本消息有「改写 / 续写 / 查看历史版本 / 删除版本」按钮,当前完全没实现
3. **根版本被过滤**`displayMessages``m.type !== 'script' || !m.parentMessageId` 过滤,把根版本 AI 剧本消息丢掉了
4. **版本标签缺失**AI 消息没有 `V1 / V2` 版本号标签和「(当前)」标记
## 设计
### 1. MessageCard.vue 重构
**新增 props**
- `messageType`: `'script' | 'chat' | 'system'`,默认 `'script'`
- `versionLabel`: 版本号显示(如 `V1 (当前)`),script 类型必填,其他类型不传
- `hasChildren`: 是否有子版本(布尔,用于控制「查看历史版本」按钮是否显示)
- `canDelete`: 是否可删除(布尔,当前生效版本不可删除)
- `canRewrite`: 是否可改写(布尔,默认 true)
- `canContinue`: 是否可续写(布尔,默认 true)
**渲染规则**
| messageType | 渲染方式 |
|---|---|
| `'script'` | **永远**走长文本 story-card 分支(不论 `isShortMessage`),因为剧本消息总是需要按钮组 |
| `'chat'``'system'` | 保持原逻辑(短消息气泡 / 长消息 story-card |
**按钮组**(按 messageType 切换):
```
script 消息按钮组:
┌─ 顶部操作(story-head-actions,不变)
│ · 收起/展开
│ · 复制
├─ 版本标签(新增,仅在 versionLabel 有值时显示)
│ · V1 (当前) / V2 / ...
├─ 功能按钮(新增,script 专属)
│ · 改写(emit 'rewrite'
│ · 续写(emit 'continue-script'
│ · 查看历史版本(emit 'view-versions'hasChildren=true 才显示)
│ · 删除版本(emit 'delete-version'canDelete=true 才显示)
└─ 通用操作(保留旧版)
· TTS 播放(emit 'play-tts'
chat 消息按钮组(isShortMessage=false 时):
┌─ 复制
├─ 收起/展开
└─ TTS 播放(emit 'play-tts'
system 消息(isShortMessage=true 默认走气泡):
└─ 不变
```
**新增 emits**
- `rewrite` — 改写按钮
- `continue-script` — 续写按钮(避免和旧 `continue` 语义冲突,但保留旧 `continue` 兼容)
- `view-versions` — 查看历史版本
- `delete-version` — 删除版本
### 2. ScriptView.vue 改动
**改动 2.1:显示根版本**
```js
// 旧
const displayMessages = computed(() => {
return messages.value.filter(m => m.type !== 'script' || !m.parentMessageId)
})
// 新:保留所有消息,根版本作为 chat 列表首条
const displayMessages = computed(() => messages.value)
```
**改动 2.2:传 messageType 给 MessageCard**
```vue
<MessageCard
v-if="message.sender === 'assistant' || message.type === 'script'"
message-type="script"
:version-label="formatVersionLabel(message)"
:has-children="messageHasChildren(message)"
:can-delete="message.id !== currentVersionMessageId"
:can-rewrite="true"
:can-continue="true"
:content="message.content"
:collapsed="isMessageCollapsed(message)"
:content-length="message.content.length"
:is-short-message="false"
:tts-icon="..."
:tts-text="..."
@toggle-collapse="toggleMessageCollapse(message)"
@copy="copyMessageContent(message)"
@rewrite="rewriteMessage(message)"
@continue-script="continueMessage(message)"
@view-versions="viewMessageVersions(message)"
@delete-version="removeVersion(message)"
@play-tts="playMessageTts(message)"
@change-direction="changeDirection"
@not-like-me="notLikeMe"
/>
```
**改动 2.3:版本标签生成函数**
```js
const formatVersionLabel = (message) => {
if (message.type !== 'script') return ''
const versionNum = message.versionNumber || 1
const isCurrent = message.id === currentVersionMessageId.value
return isCurrent ? `V${versionNum} (当前)` : `V${versionNum}`
}
const messageHasChildren = (message) => {
// 通过 versions 数组长度判断(已加载的)或 metadata 推断
return versions.value.some(v => v.parentMessageId === message.id)
|| (message.versionNumber && message.versionNumber > 1)
|| (message.childrenCount && message.childrenCount > 0)
}
```
**改动 2.4:查看历史版本**
新增 `viewMessageVersions(msg)` 函数:
1. 调用 `listMessageVersions(msg.id)`
2.`uni.showActionSheet` 列出所有版本(V1、V2...
3. 用户选择后:
- 如果选的是当前版本:`uni.showToast` 提示「当前已生效」
- 如果选的是其他版本:调用 `switchVersion({ scriptId, messageId: selectedId })` → 更新 `currentVersionMessageId``loadMessages()` 刷新
### 4. 历史剧本兼容层(高优先级)
### 现状
DB 中绝大多数历史剧本的 `conversation_id` 是前端早期自造的临时 ID`conv-<ts>-<rand>` 格式),**`t_conversation` 表中没有对应记录**。
```
329987360219471872 我的人生剧本 → YES (最新剧本,走新流程创建)
f82818b702d5ad4a... 高考 → NO
a3dadb7e85a6c75b... 我高考了 → NO
49ae0f432253aaff... 我中了100w → NO
... 其余 6 条 → NO
```
### 影响分析
| 场景 | 是否受影响 | 原因 |
|---|---|---|
| ScriptDetailView 阅读模式 | ✅ 正常 | 读 `t_epic_script.plotJson`,不依赖 conversation |
| ScriptView `viewMode='read'` 阅读模式 | ✅ 正常 | 通过 `currentScriptContent``currentVersionMessageId` 指向的 messagefallback 到 `plotJson` |
| ScriptView `viewMode='chat'` 聊天模式 | ❌ 异常 | `listByConversation` 返回空,对话历史丢失 |
| 改写 / 续写 / 版本切换 | ❌ 异常 | 需要有效的 conversation 和 message 才能操作 |
### 兼容策略:前端 fallback + 后端自动建表
**改动 A:后端自动建 conversation**
`EpicScriptServiceImpl.getScriptById`(或 `getListByCurrentUser` 返回前)检查:
-`script.conversation_id` 指向的 conversation 不存在 → 自动创建一条 `t_conversation` 记录,写入 `user_id``script_id``type='script'``status='active'`,并回填 `t_epic_script.conversation_id` 为新 ID
**改动 B:前端首次进入聊天模式时,基于 `plotJson` 初始化首条消息**
在 ScriptView.vue 的 `loadMessages` 中:
- 若 API 返回空列表 && `script.plotJson?.fullContent` 存在 → 自动调用 `/message/create` 创建一条 `type='script'` 的 AI 剧本消息(content 来自 plotJson
- 后续改写/续写/版本切换基于这条真实消息操作
**改动 CScriptView 阅读模式 fallback 强化**
当前 `currentScriptContent` 已在 message 不存在时 fallback 到 `plotJson`。需确保:
- `viewMode='read'` 下,版本标签在没有 versions 数据时隐藏(不显示"V0"之类)
- "进入对话修改"按钮在没有 conversation 时仍可点击,触发改动 B 的初始化流程
### 验收标准(历史剧本)
- [ ] 历史剧本在 ScriptDetailView 完整显示标题、summary、content
- [ ] 历史剧本在 ScriptView `viewMode='read'` 完整显示标题、章节内容(无版本标签时 UI 不异常)
- [ ] 历史剧本点击"进入对话修改" → 自动创建 conversation + 初始化首条 AI 消息 → 进入 chat 模式
- [ ] 历史剧本进入 chat 模式后,能看到完整对话历史(首条为用户心愿 + AI 剧本)
- [ ] 历史剧本的"改写/续写/查看历史版本"功能可正常使用
- [ ] 新创建的剧本所有功能不受影响(回归验证)
## 5. 实施顺序
| 顺序 | 任务 | 类型 |
|---|---|---|
| 1 | 后端 `getScriptById` 自动建 conversation | 数据兼容 |
| 2 | 前端 MessageCard 重构 + 按钮组 | 功能补齐 |
| 3 | 前端 ScriptView displayMessages 去掉根版本过滤 | Bug 修复 |
| 4 | 前端 loadMessages 空结果时自动初始化首条消息 | 数据兼容 |
| 5 | 前端 chat 模式传 messageType/versionLabel 等 props | 功能补齐 |
| 6 | 端到端验收(新剧本 + 历史剧本) | 验收 |
## 6. 影响范围
| 文件 | 改动量 |
|---|---|
| `server/.../EpicScriptServiceImpl.java` | 小(getScriptById 检查 + 自动建 conversation |
| `mini-program/src/components/MessageCard.vue` | 中(+props、+emits、+按钮组条件渲染) |
| `mini-program/src/pages/main/ScriptView.vue` | 中(displayMessages filter、MessageCard props、formatVersionLabel、viewMessageVersions、loadMessages 初始化) |
## 验收标准(代码层面)
- [ ] chat 模式下,根版本 AI 剧本消息显示为 chat 列表首条
- [ ] AI 剧本消息显示 V1 / V2 版本号 + (当前) 标记
- [ ] AI 剧本消息按钮组包含:收起/展开、复制、TTS 播放、改写、续写、查看历史版本、删除版本
- [ ] 用户消息(type=chat)保持气泡样式,有收起/展开按钮
- [ ] 点击「改写」弹出 modal 输入改写意图,提交后生成新版本
- [ ] 点击「续写」直接生成新版本
- [ ] 点击「查看历史版本」弹 ActionSheet 列出版本,切换后 UI 更新
- [ ] 当前生效版本不显示「删除版本」按钮
- [ ] 历史剧本进入 ScriptView chat 模式 → 自动创建 conversation + 初始化首条 AI 消息
- [ ] 浏览器 Console 无报错
- [ ] 不影响 read 模式(阅读模式保持现状)
## H5 端到端验收流程(强制)
**后端验证码**`AuthServiceImpl.java:80` 已硬编码 `DEFAULT_SMS_CODE = "123456"`,无需改动。
**测试账号**:手机号 `19928748688`,验证码 `123456`
### 步骤 1:启动本地 H5
```bash
python dev-services.py start mini-program
# 访问 http://localhost:5180
```
### 步骤 2:登录
1. 打开 `http://localhost:5180`
2. 输入手机号 `19928748688`
3. **必须点击"发送验证码"按钮**(触发 `/auth/sms-code` 接口,后端写入 Redis 并返回固定验证码)
4. 输入验证码 `123456`
5. 提交登录
### 步骤 3:新剧本完整流程
1. 首页输入心愿 → 生成第一个剧本
2. 查看剧本详情 → 标题、summary、content 完整显示
3. 点击"继续" → 进入 ScriptView chat 模式
4. **验证**
- 用户心愿气泡显示
- AI 剧本消息显示,带 `V1 (当前)` 版本标签
- AI 消息按钮组包含:收起/展开、复制、TTS 播放、**改写**、**续写**、**查看历史版本**、**删除版本**(当前版本不显示删除)
5. 点"改写" → 输入改写意图 → 生成新版本 V2
6. 点"查看历史版本" → ActionSheet 列出 V1/V2,点击切换
7. 点"续写" → 生成新内容
### 步骤 4:历史剧本完整流程
1. 点"历史"按钮 → 进入剧本库
2. 点任一**历史剧本**(非本次新生成的)→ 进入 ScriptDetailView
3. **验证**
- 标题、summary、content 完整显示(读 `plotJson.fullContent`
- 风格、篇幅、字数正确
4. 点"继续" → 进入 ScriptView
5. **验证**
- 自动创建 `t_conversation`DB 中新增记录)
- 自动初始化首条 AI 消息(基于 plotJson
- chat 模式看到完整对话历史(心愿 + AI 剧本)
- AI 消息带版本标签
- 改写/续写/查看历史版本 可正常操作
### 步骤 5Console 与 Network 检查
- DevTools Console 无报错
- Network 中所有 API 调用返回 2xx(无 5xx、无 401、无 403
- `listByConversation` 调用返回 200 且有数据
### 步骤 6:验收通过标准
- ✅ 新剧本 + 历史剧本所有功能正常
- ✅ 按钮组完整
- ✅ 版本标签正确
- ✅ Console 无报错
- ✅ 历史剧本 conversation 自动创建成功
**未通过的处理**:任一验收项失败 → 停止 → 定位问题 → 修复 → 重新验收 → 直到全部通过才算完成
## 风险与回退
- **MessageCard 重构风险**:新增 props 都带默认值,不会破坏非对话模式(生成页、结果页)的现有用法
- **向后兼容**:旧 `continue` emit 保留,新增 `continue-script` 作为 script 专属,避免语义冲突
- **回退方案**git revert 单个 commit 即可