f7dba6ba60
- 提取 MessageCard.vue 独立组件 - 按 100 字阈值自动切换短消息/完整卡片模式 - story-card 和 result-chat-list 复用同一组件
4.9 KiB
4.9 KiB
author, created_at, purpose
| author | created_at | purpose |
|---|---|---|
| claude | 2026-06-27 | 提取 MessageCard 组件,统一所有 AI 消息卡片样式,按内容长度阈值(100 字)决定是否显示完整功能按钮 |
MessageCard 组件统一设计
背景
在上一轮实现中,result-chat-list 中的 assistant 消息使用了 .chat-bubble.system 气泡样式,与 story-card 的卡片样式不一致。用户要求:
- 所有 AI 回复消息卡片的样式、功能、布局、按钮等,都与第一次生成剧本的
story-card完全一致 - 内容 < 100 字的 assistant 消息:只显示消息内容,无功能按钮
- 内容 ≥ 100 字的 assistant 消息:必须与
story-card完全一致
目标
- 提取独立的
MessageCard.vue组件 - story-card 和 result-chat-list 都复用该组件
- 按 100 字阈值自动切换显示模式
方案选择
采用 方案 B:提取独立 MessageCard.vue 组件
- 代码复用,维护性好
- 改动范围:新建 1 个组件 + 修改 ScriptView.vue
详细设计
1. 组件接口
文件:mini-program/src/components/MessageCard.vue
Props:
| Prop | 类型 | 必填 | 说明 |
|---|---|---|---|
content |
String | 是 | 消息/故事正文内容 |
title |
String | 否 | 卡片标题,默认 '我的人生剧本' |
tags |
Array | 否 | 标签列表 |
collapsed |
Boolean | 是 | 当前折叠状态 |
contentLength |
Number | 是 | 内容字数(用于阈值判断) |
isShortMessage |
Boolean | 是 | contentLength < 100 时为 true |
ttsIcon |
String | 否 | TTS 按钮图标(▶ 或 Ⅱ) |
ttsText |
String | 否 | TTS 按钮文字(播放 或 暂停) |
Events:
| 事件 | 说明 |
|---|---|
toggle-collapse |
展开/收起 |
copy |
复制内容 |
change-direction |
换个方向 |
not-like-me |
不像我 |
continue |
继续生成 |
play-tts |
播放 TTS |
2. 组件内部结构
短消息(isShortMessage = true):
<view class="chat-bubble system">
<text>{{ content }}</text>
</view>
只显示纯文本气泡,无标题、无标签、无按钮。
长消息(isShortMessage = false):
完整复用现有 story-card 的 HTML 结构:
- 标题(
.story-title)+ 标签(.tag-row) - 头部操作区:展开/收起按钮 + 复制按钮
- 正文:展开时用 scroll-view,收起时用 text
- 底部:展开全文/收起全文按钮
- 功能按钮组(2 列网格):换个方向、不像我、继续生成、播放
3. ScriptView.vue 中的使用
替换现有 story-card:
<MessageCard
:content="displayedResultContent"
:title="currentResult?.title"
:tags="resultTags"
:collapsed="storyCollapsed"
:content-length="displayedResultContent.length"
:is-short-message="false"
:tts-icon="ttsActionIcon"
:tts-text="ttsActionText"
@toggle-collapse="toggleStoryCollapse"
@copy="copyResultContent"
@change-direction="changeDirection"
@not-like-me="notLikeMe"
@continue="continueInChat"
@play-tts="trackTtsClick"
/>
替换 result-chat-list 中的 assistant 消息:
<view v-for="message in resultMessages" :key="message.id">
<MessageCard
v-if="isAssistantMessage(message)"
:content="message.content"
:collapsed="isMessageCollapsed(message)"
:content-length="message.content.length"
:is-short-message="message.content.length < 100"
:tts-icon="ttsPlayer.playing.value ? 'Ⅱ' : '▶'"
:tts-text="ttsPlayer.playing.value ? '暂停' : '播放'"
@toggle-collapse="toggleMessageCollapse(message)"
@copy="copyMessageContent(message)"
@change-direction="changeDirection"
@not-like-me="notLikeMe"
@continue="continueInChat"
@play-tts="playMessageTts(message)"
/>
<view v-else class="chat-bubble user">
<text>{{ message.content }}</text>
<text class="bubble-time">{{ message.time }}</text>
</view>
</view>
4. 样式迁移
将以下 CSS 类从 ScriptView.vue 迁移到 MessageCard.vue 的 <style scoped> 中:
.story-card、.story-card.collapsed.story-head、.story-head-actions、.story-title-wrap.story-title、.tag-row、.tag.collapse-icon、.copy-card-btn、.collapse-row.collapse-chevron、.story-body、.story-body-scroll.result-actions、.action-btn
ScriptView.vue 中删除这些已迁移的样式。
5. 边界情况
| 场景 | 行为 |
|---|---|
| content 刚好 100 字 | isShortMessage = false,显示完整卡片 |
| content 为空 | 显示空卡片(与 story-card 现有行为一致) |
| pending 中的消息 | 不使用 MessageCard,保持 thinking-dots 渲染 |
| user 消息 | 不使用 MessageCard,保持 .chat-bubble.user 气泡 |
修改范围
| 文件 | 操作 |
|---|---|
mini-program/src/components/MessageCard.vue |
新建 |
mini-program/src/pages/main/ScriptView.vue |
修改(模板 + 脚本 + 样式) |