feat(mini-program): ScriptDetailView 新增小说消息操作按钮组
- 新增 collapsedMessageIds ref + 5 个函数(isMessageCollapsed、getMessageDisplayContent、toggleMessageCollapse、copyMessageContent、playMessageTts) - 为 kind === 'novel' 的消息添加折叠/展开、复制、播放按钮 - 添加 .collapse-toggle-row、.message-actions、.action-btn 样式 - H5 验收通过:复制按钮触发 Toast、播放按钮切换 TTS 状态(▶播放 ↔ Ⅱ暂停)、折叠按钮切换文案 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -72,7 +72,27 @@
|
||||
|
||||
<!-- 小说正文(Markdown 渲染) -->
|
||||
<view v-else-if="msg.kind === 'novel'" class="chat-bubble system novel-bubble">
|
||||
<Markdown :content="msg.content" />
|
||||
<Markdown :content="getMessageDisplayContent(msg)" />
|
||||
|
||||
<!-- 折叠/展开按钮(内容超过预览长度时显示) -->
|
||||
<view v-if="msg.content && msg.content.length > ASSISTANT_MESSAGE_PREVIEW_LENGTH" 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>
|
||||
|
||||
<!-- 操作按钮组 -->
|
||||
<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>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -106,10 +126,14 @@ const store = useAppStore()
|
||||
const scriptId = ref('')
|
||||
const script = ref(null)
|
||||
const resultMessages = ref([])
|
||||
const collapsedMessageIds = ref({})
|
||||
const pagePath = '/pages/main/ScriptDetailView'
|
||||
const ttsPlayer = useTtsPlayer({ pagePath })
|
||||
const { capsuleTopReservePx, topbarStyle } = useMenuButtonSafeArea({ extraTopPx: 10 })
|
||||
|
||||
// 小说正文折叠时的预览长度(字符)
|
||||
const ASSISTANT_MESSAGE_PREVIEW_LENGTH = 120
|
||||
|
||||
const lengthText = computed(() => {
|
||||
const map = { short: '短篇', medium: '中篇', long: '长篇' }
|
||||
return map[script.value?.length] || script.value?.length || '中篇'
|
||||
@@ -125,6 +149,70 @@ const detailTtsIcon = computed(() => {
|
||||
return ttsPlayer.playing.value ? 'Ⅱ' : '▶'
|
||||
})
|
||||
|
||||
/**
|
||||
* 判断消息是否处于折叠状态
|
||||
*/
|
||||
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 <= ASSISTANT_MESSAGE_PREVIEW_LENGTH) return content
|
||||
return `${content.slice(0, ASSISTANT_MESSAGE_PREVIEW_LENGTH)}...`
|
||||
}
|
||||
|
||||
/**
|
||||
* 切换消息折叠/展开状态
|
||||
*/
|
||||
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 })
|
||||
}
|
||||
|
||||
/**
|
||||
* 复制消息内容到剪贴板
|
||||
*/
|
||||
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 })
|
||||
}
|
||||
|
||||
/**
|
||||
* 播放消息 TTS 音频(播放整个剧本的音频)
|
||||
*/
|
||||
const playMessageTts = (message) => {
|
||||
const scriptIdValue = script.value?.id || ''
|
||||
ttsPlayer.playSource(scriptIdValue)
|
||||
analytics.track('script_message_tts_click', {
|
||||
message_id: message?.id || '',
|
||||
script_id: scriptIdValue
|
||||
}, { eventType: 'script', pagePath })
|
||||
}
|
||||
|
||||
/**
|
||||
* 从后端 message 列表重建统一对话消息流
|
||||
* 复刻 ScriptView 生成时的 chat-bubble 展示结构
|
||||
@@ -522,6 +610,110 @@ onUnmounted(() => {
|
||||
.novel-bubble {
|
||||
padding: 32rpx;
|
||||
}
|
||||
|
||||
/* 折叠/展开按钮行 */
|
||||
.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;
|
||||
}
|
||||
|
||||
.empty-flow {
|
||||
padding: 80rpx 0;
|
||||
text-align: center;
|
||||
|
||||
Reference in New Issue
Block a user