17 KiB
小说消息操作按钮恢复实施计划
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: 恢复小程序小说生成页和详情页丢失的"复制、播放、折叠/展开"功能,每条 novel 消息下方添加 3 个操作按钮。
Architecture: 在 ScriptView.vue 和 ScriptDetailView.vue 中为 kind === 'novel' 的消息添加按钮组 UI(折叠/展开、复制、播放)。ScriptView 复用已有函数,ScriptDetailView 新增相同逻辑。按钮组样式统一,参考 MessageCard 的 result-actions 样式。
Tech Stack: Vue 3 Composition API / UniApp / Pinia / uni.setClipboardData / useTtsPlayer composable / H5 端到端验收
Global Constraints
- 所有注释必须使用中文
- 样式单位必须使用 rpx(小程序移动端)
- 禁止任何形式的 mock、兜底、默认值掩盖错误
- 每个按钮必须有真实实现,不能有任何占位或 mock 功能
- 修改后必须通过 H5 端到端验收(Console 0 新增错误)
- 部署使用
python deploy.py backend(后端)或热加载(前端) - 埋点必须完整触发:
script_message_collapse_toggle、script_message_copy_click、script_message_tts_click
Task 1: ScriptView 恢复按钮组 UI
Files:
- Modify:
mini-program/src/pages/main/ScriptView.vue:180-187(novel 消息渲染部分) - Modify:
mini-program/src/pages/main/ScriptView.vue:3500+(样式部分)
Interfaces:
- Consumes: 已有函数
getMessageDisplayContent(msg)、toggleMessageCollapse(msg)、copyMessageContent(msg)、playMessageTts(msg)、isMessageCollapsed(msg) - Produces: novel 消息下方显示 3 个按钮(折叠/展开、复制、播放),功能正常
背景
ScriptView.vue 中所有相关函数已存在(行 399-447),但模板中按钮组 UI 在 commit 2c778fa 中被删除。本任务恢复按钮组 UI。
步骤
- Step 1: 定位 novel 消息渲染部分
打开 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" />
</view>
- Step 2: 修改 Markdown 渲染,使用折叠内容
将 <Markdown :content="msg.content" /> 改为:
<Markdown :content="getMessageDisplayContent(msg)" />
这样折叠时只显示前 200 字符。
- Step 3: 添加折叠/展开按钮
在 <Markdown> 下方添加折叠/展开按钮:
<view v-if="msg.content && msg.content.length > 200" class="collapse-toggle-row" @click="toggleMessageCollapse(msg)">
<text class="collapse-toggle-text">{{ isMessageCollapsed(msg) ? '展开全文' : '收起全文' }}</text>
<view class="collapse-chevron" :class="{ down: isMessageCollapsed(msg) }">
<view></view>
<view></view>
</view>
</view>
- Step 4: 添加操作按钮组
在折叠/展开按钮下方添加操作按钮组(复制、播放):
<view class="message-actions">
<button class="action-btn" @click="copyMessageContent(msg)">
<text>📋 复制</text>
</button>
<button class="action-btn primary" @click="playMessageTts(msg)">
<text class="action-icon">{{ ttsPlayer.playing.value ? 'Ⅱ' : '▶' }}</text>
<text>{{ ttsPlayer.playing.value ? '暂停' : '播放' }}</text>
</button>
</view>
注意:只有 2 个按钮(复制、播放),折叠/展开按钮已在上方独立显示。
- Step 5: 添加按钮组样式
在 <style scoped> 部分(约行 3500+)添加以下样式:
/* 折叠/展开按钮行 */
.collapse-toggle-row {
display: flex;
align-items: center;
justify-content: center;
gap: 12rpx;
margin-top: 20rpx;
padding: 16rpx 0;
border-radius: 999rpx;
background: rgba(88, 28, 135, 0.12);
border: 1rpx solid rgba(192, 132, 252, 0.28);
color: rgba(246, 230, 255, 0.96);
font-size: 26rpx;
font-weight: 700;
}
.collapse-toggle-row:active {
transform: scale(0.98);
opacity: 0.92;
}
.collapse-toggle-text {
line-height: 1;
}
/* 折叠箭头图标 */
.collapse-chevron {
position: relative;
width: 20rpx;
height: 16rpx;
flex-shrink: 0;
transition: transform 0.18s ease;
}
.collapse-chevron.down {
transform: rotate(180deg);
}
.collapse-chevron view {
position: absolute;
top: 7rpx;
width: 12rpx;
height: 4rpx;
border-radius: 999rpx;
background: linear-gradient(90deg, #fff3b0, #ffd86b);
box-shadow: 0 0 12rpx rgba(255, 216, 107, 0.5);
}
.collapse-chevron view:first-child {
left: 0;
transform: rotate(-38deg);
transform-origin: right center;
}
.collapse-chevron view:last-child {
right: 0;
transform: rotate(38deg);
transform-origin: left center;
}
/* 操作按钮组 */
.message-actions {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 14rpx;
margin-top: 20rpx;
}
.action-btn {
height: 72rpx;
min-height: 72rpx;
padding: 0 8rpx;
border-radius: 28rpx;
display: flex;
align-items: center;
justify-content: center;
color: #e8ccff;
font-size: 26rpx;
font-weight: 700;
line-height: 1.15;
text-align: center;
white-space: normal;
box-sizing: border-box;
background: rgba(88, 28, 135, 0.18);
border: 1rpx solid rgba(192, 132, 252, 0.35);
}
.action-btn.primary {
color: #fff;
background: linear-gradient(145deg, #8c44f2, #5f1db8);
}
.action-icon {
margin-right: 8rpx;
font-size: 24rpx;
font-weight: 900;
line-height: 1;
}
.action-btn::after {
border: 0;
}
- Step 6: H5 验收
启动 H5 并验收:
python dev-services.py start mini-program
浏览器访问 http://localhost:5180/#/pages/main/index?tab=script,进入一个已有小说的生成页:
验收项:
-
✅ 小说正文下方有折叠/展开按钮(内容超过 200 字时显示)
-
✅ 点击"展开全文":内容完整显示,按钮变为"收起全文"
-
✅ 点击"收起全文":内容截断显示前 200 字,按钮变为"展开全文"
-
✅ 小说正文下方有 2 个操作按钮:复制、播放
-
✅ 点击"复制":Toast 提示"已复制",剪贴板中有内容
-
✅ 点击"播放":TTS 开始播放,按钮变为"暂停"
-
✅ 浏览器 Console 检查:0 新增错误
-
Step 7: Commit
git add mini-program/src/pages/main/ScriptView.vue
git commit -m "feat(mini-program): ScriptView 恢复小说消息操作按钮组
- 为 kind === 'novel' 的消息添加折叠/展开、复制、播放按钮
- 复用已有函数:getMessageDisplayContent、toggleMessageCollapse、copyMessageContent、playMessageTts
- 添加 .collapse-toggle-row、.message-actions、.action-btn 样式
- H5 验收通过,Console 0 新增错误"
验收标准
- ✅ 每条 novel 消息下方有折叠/展开按钮(内容超过 200 字时显示)
- ✅ 折叠/展开功能正常:折叠时显示前 200 字,展开时显示完整内容
- ✅ 每条 novel 消息下方有 2 个操作按钮:复制、播放
- ✅ 复制功能正常:点击后 Toast 提示"已复制"
- ✅ 播放功能正常:点击后 TTS 开始播放,按钮变为"暂停"
- ✅ Console 无新增错误
Task 2: ScriptDetailView 新增函数和按钮组 UI
Files:
- Modify:
mini-program/src/pages/main/ScriptDetailView.vue:96-311(script 部分) - Modify:
mini-program/src/pages/main/ScriptDetailView.vue:74-76(novel 消息渲染部分) - Modify:
mini-program/src/pages/main/ScriptDetailView.vue:313+(样式部分)
Interfaces:
- Consumes:
ttsPlayercomposable(已有)、scriptref(已有) - Produces: novel 消息下方显示 3 个按钮(折叠/展开、复制、播放),功能正常
背景
ScriptDetailView.vue 中完全没有相关函数和按钮组 UI。本任务新增所有函数 + 模板按钮组 + 样式。
步骤
- Step 1: 新增 collapsedMessageIds ref
打开 mini-program/src/pages/main/ScriptDetailView.vue,在 <script setup> 部分(约行 96-110)添加:
const collapsedMessageIds = ref({})
放在 const resultMessages = ref([]) 下方。
- Step 2: 新增 isMessageCollapsed 函数
在 collapsedMessageIds 下方添加:
/**
* 判断消息是否折叠
*/
const isMessageCollapsed = (message) => {
return Boolean(collapsedMessageIds.value[message.id])
}
- Step 3: 新增 getMessageDisplayContent 函数
在 isMessageCollapsed 下方添加:
/**
* 获取消息显示内容(折叠时截断)
*/
const getMessageDisplayContent = (message) => {
const content = String(message?.content || '')
if (!isMessageCollapsed(message)) return content
if (content.length <= 200) return content
return `${content.slice(0, 200)}...`
}
- Step 4: 新增 toggleMessageCollapse 函数
在 getMessageDisplayContent 下方添加:
/**
* 切换消息折叠状态
*/
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 })
}
- Step 5: 新增 copyMessageContent 函数
在 toggleMessageCollapse 下方添加:
/**
* 复制消息内容到剪贴板
*/
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 })
}
- Step 6: 新增 playMessageTts 函数
在 copyMessageContent 下方添加:
/**
* 播放消息 TTS 音频
*/
const playMessageTts = (message) => {
const scriptId = script.value?.id || ''
ttsPlayer.playSource(scriptId)
analytics.track('script_message_tts_click', {
message_id: message?.id || '',
script_id: scriptId
}, { eventType: 'script', pagePath })
}
- Step 7: 定位 novel 消息渲染部分
在模板中找到 kind === 'novel' 的消息渲染部分(约行 74-76):
<view v-else-if="msg.kind === 'novel'" class="chat-bubble system novel-bubble">
<Markdown :content="msg.content" />
</view>
- Step 8: 修改 Markdown 渲染,使用折叠内容
将 <Markdown :content="msg.content" /> 改为:
<Markdown :content="getMessageDisplayContent(msg)" />
- Step 9: 添加折叠/展开按钮
在 <Markdown> 下方添加折叠/展开按钮:
<view v-if="msg.content && msg.content.length > 200" class="collapse-toggle-row" @click="toggleMessageCollapse(msg)">
<text class="collapse-toggle-text">{{ isMessageCollapsed(msg) ? '展开全文' : '收起全文' }}</text>
<view class="collapse-chevron" :class="{ down: isMessageCollapsed(msg) }">
<view></view>
<view></view>
</view>
</view>
- Step 10: 添加操作按钮组
在折叠/展开按钮下方添加操作按钮组(复制、播放):
<view class="message-actions">
<button class="action-btn" @click="copyMessageContent(msg)">
<text>📋 复制</text>
</button>
<button class="action-btn primary" @click="playMessageTts(msg)">
<text class="action-icon">{{ ttsPlayer.playing.value ? 'Ⅱ' : '▶' }}</text>
<text>{{ ttsPlayer.playing.value ? '暂停' : '播放' }}</text>
</button>
</view>
- Step 11: 添加按钮组样式
在 <style scoped> 部分(约行 313+)添加以下样式(与 ScriptView 完全一致):
/* 折叠/展开按钮行 */
.collapse-toggle-row {
display: flex;
align-items: center;
justify-content: center;
gap: 12rpx;
margin-top: 20rpx;
padding: 16rpx 0;
border-radius: 999rpx;
background: rgba(88, 28, 135, 0.12);
border: 1rpx solid rgba(192, 132, 252, 0.28);
color: rgba(246, 230, 255, 0.96);
font-size: 26rpx;
font-weight: 700;
}
.collapse-toggle-row:active {
transform: scale(0.98);
opacity: 0.92;
}
.collapse-toggle-text {
line-height: 1;
}
/* 折叠箭头图标 */
.collapse-chevron {
position: relative;
width: 20rpx;
height: 16rpx;
flex-shrink: 0;
transition: transform 0.18s ease;
}
.collapse-chevron.down {
transform: rotate(180deg);
}
.collapse-chevron view {
position: absolute;
top: 7rpx;
width: 12rpx;
height: 4rpx;
border-radius: 999rpx;
background: linear-gradient(90deg, #fff3b0, #ffd86b);
box-shadow: 0 0 12rpx rgba(255, 216, 107, 0.5);
}
.collapse-chevron view:first-child {
left: 0;
transform: rotate(-38deg);
transform-origin: right center;
}
.collapse-chevron view:last-child {
right: 0;
transform: rotate(38deg);
transform-origin: left center;
}
/* 操作按钮组 */
.message-actions {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 14rpx;
margin-top: 20rpx;
}
.action-btn {
height: 72rpx;
min-height: 72rpx;
padding: 0 8rpx;
border-radius: 28rpx;
display: flex;
align-items: center;
justify-content: center;
color: #e8ccff;
font-size: 26rpx;
font-weight: 700;
line-height: 1.15;
text-align: center;
white-space: normal;
box-sizing: border-box;
background: rgba(88, 28, 135, 0.18);
border: 1rpx solid rgba(192, 132, 252, 0.35);
}
.action-btn.primary {
color: #fff;
background: linear-gradient(145deg, #8c44f2, #5f1db8);
}
.action-icon {
margin-right: 8rpx;
font-size: 24rpx;
font-weight: 900;
line-height: 1;
}
.action-btn::after {
border: 0;
}
- Step 12: H5 验收
H5 服务应已在运行(端口 5180),浏览器访问详情页:
从列表页点击一个已有小说的剧本,进入详情页。
验收项:
-
✅ 小说正文下方有折叠/展开按钮(内容超过 200 字时显示)
-
✅ 点击"展开全文":内容完整显示,按钮变为"收起全文"
-
✅ 点击"收起全文":内容截断显示前 200 字,按钮变为"展开全文"
-
✅ 小说正文下方有 2 个操作按钮:复制、播放
-
✅ 点击"复制":Toast 提示"已复制",剪贴板中有内容
-
✅ 点击"播放":TTS 开始播放,按钮变为"暂停"
-
✅ 浏览器 Console 检查:0 新增错误
-
Step 13: Commit
git add mini-program/src/pages/main/ScriptDetailView.vue
git commit -m "feat(mini-program): ScriptDetailView 新增小说消息操作按钮组
- 新增 collapsedMessageIds ref + 5 个函数(isMessageCollapsed、getMessageDisplayContent、toggleMessageCollapse、copyMessageContent、playMessageTts)
- 为 kind === 'novel' 的消息添加折叠/展开、复制、播放按钮
- 添加 .collapse-toggle-row、.message-actions、.action-btn 样式
- H5 验收通过,Console 0 新增错误"
验收标准
- ✅ 每条 novel 消息下方有折叠/展开按钮(内容超过 200 字时显示)
- ✅ 折叠/展开功能正常:折叠时显示前 200 字,展开时显示完整内容
- ✅ 每条 novel 消息下方有 2 个操作按钮:复制、播放
- ✅ 复制功能正常:点击后 Toast 提示"已复制"
- ✅ 播放功能正常:点击后 TTS 开始播放,按钮变为"暂停"
- ✅ Console 无新增错误
- ✅ 埋点正常触发:
script_message_collapse_toggle、script_message_copy_click、script_message_tts_click
整体验收
两个 Task 完成后,进行整体验收:
-
生成页验收:
- 访问
http://localhost:5180/#/pages/main/index?tab=script - 进入一个已有小说的生成页
- 验证折叠/展开、复制、播放功能
- Console 0 新增错误
- 访问
-
详情页验收:
- 从列表页点击一个已有小说的剧本,进入详情页
- 验证折叠/展开、复制、播放功能
- Console 0 新增错误
-
样式一致性:
- 两个页面的按钮组样式完全一致
- 按钮布局、颜色、圆角、间距一致
-
埋点验证:
- 浏览器 Console 中查看埋点日志
- 确认
script_message_collapse_toggle、script_message_copy_click、script_message_tts_click正常触发
风险与注意事项
-
TTS 播放的是整个小说:
ttsPlayer.playSource(scriptId)播放的是整个小说的音频,不是单条消息的音频。这是历史设计,保持一致。 -
折叠阈值:折叠时显示前 200 字符。这个阈值可以根据实际需求调整。
-
样式一致性:两个页面的按钮组样式必须保持一致,直接复制 ScriptView 的样式到 ScriptDetailView。
-
性能考虑:
collapsedMessageIds是响应式对象,每次切换折叠状态都会触发重新渲染。对于大量消息的场景,可能需要优化(当前场景下消息数量有限,不构成问题)。 -
热加载:前端修改后利用热加载自动生效,不需要重启。如果热加载不生效,使用
python dev-services.py restart mini-program。
提交历史
完成所有 Task 后,提交历史应为:
<hash> feat(mini-program): ScriptDetailView 新增小说消息操作按钮组
<hash> feat(mini-program): ScriptView 恢复小说消息操作按钮组