Files
happy-life-star/docs/superpowers/specs/2026-06-27-message-card-component-design.md
T
peanut f7dba6ba60 docs:新增 MessageCard 组件统一设计文档
- 提取 MessageCard.vue 独立组件
- 按 100 字阈值自动切换短消息/完整卡片模式
- story-card 和 result-chat-list 复用同一组件
2026-06-27 23:25:26 +08:00

4.9 KiB
Raw Blame History

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 的卡片样式不一致。用户要求:

  1. 所有 AI 回复消息卡片的样式、功能、布局、按钮等,都与第一次生成剧本的 story-card 完全一致
  2. 内容 < 100 字的 assistant 消息:只显示消息内容,无功能按钮
  3. 内容 ≥ 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 修改(模板 + 脚本 + 样式)