fix:澄清卡片显示完整选项和中文答案

问题:详情页 chat-bubble 卡片视图显示「已回答:family」等英文 value,
而不是中文 label(如「家人」),且不展示所有可选选项。

修复:
- 新增 getAnswerLabel(msg) 函数,通过 card.options 查找 value 对应的中文 label
- rebuildResultMessages 增加 selectedValue 字段,与 answer 同时存原始 value
- 卡片已回答视图新增 card-options-list,渲染所有选项,已选加 .selected 样式并标 ✓
- submitClarification(实时模式)同步存 selectedValue,保持渲染逻辑一致

影响:
- ScriptView.vue(chat-bubble 流程视图)
- ScriptDetailView.vue(详情页只读视图)

数据基础未动,后端澄清问答 answers 仍存原始 value(语义化)。
全局约束:rpx 单位、代码注释中文、禁止 mock 数据。
This commit is contained in:
2026-07-26 16:49:44 +08:00
parent 50fb2404fd
commit 7d19fa5898
2 changed files with 186 additions and 11 deletions
@@ -24,7 +24,24 @@
<view v-else-if="msg.kind === 'card'" class="chat-bubble system">
<view class="card-answered">
<text v-if="msg.card?.question" class="card-question-text">{{ msg.card.question }}</text>
<text class="card-answer-text">已回答{{ msg.answer || '未回答' }}</text>
<!-- 用户回答中文 label通过 options value 映射回 label 显示 -->
<view class="card-answer-row">
<text class="card-answer-prefix">已回答</text>
<text class="card-answer-label">{{ getAnswerLabel(msg) }}</text>
</view>
<!-- 选项列表展示所有可选选项已选的高亮显示 -->
<view v-if="Array.isArray(msg.card?.options) && msg.card.options.length" class="card-options-list">
<view
v-for="opt in msg.card.options"
:key="opt.value"
class="card-option-item"
:class="{ selected: opt.value === (msg.selectedValue || msg.answer) }"
>
<text class="card-option-label">{{ opt.label || opt.value }}</text>
<text v-if="opt.description" class="card-option-desc">{{ opt.description }}</text>
<text v-if="opt.value === (msg.selectedValue || msg.answer)" class="card-option-mark"></text>
</view>
</view>
</view>
</view>
@@ -135,7 +152,9 @@ const rebuildResultMessages = (scriptData, messages) => {
} else if (m.type === 'clarification_answer') {
const lastCard = [...result].reverse().find(x => x.kind === 'card')
if (lastCard) {
lastCard.answer = m.content || '未回答'
// answer 存用户提交的 value,渲染时通过 card.options 映射成中文 label
lastCard.answer = m.content || ''
lastCard.selectedValue = m.content || ''
} else {
// 无对应 question,作为独立 user 消息
result.push({ id: m.id, role: 'user', kind: 'text', content: m.content || '' })
@@ -167,6 +186,20 @@ const rebuildResultMessages = (scriptData, messages) => {
return result
}
/**
* 把已回答卡片的 value(如 "family")映射为对应的中文 label(如 "家人"
* 若选项中找不到匹配的 value,则原样返回 answer(兜底)
* 若 msg.card 不存在则返回 "未回答"
*/
const getAnswerLabel = (msg) => {
if (!msg || !msg.card) return '未回答'
const raw = msg.selectedValue || msg.answer || ''
if (!raw) return '未回答'
const options = Array.isArray(msg.card.options) ? msg.card.options : []
const match = options.find(opt => opt && opt.value === raw)
return match?.label || raw
}
const continueCurrent = () => {
if (!script.value?.id) return
uni.setStorageSync('pending_open_script_chat', {
@@ -332,9 +365,62 @@ onUnmounted(() => {
color: rgba(255, 255, 255, 0.9);
font-weight: 500;
}
.card-answer-text {
.card-answer-row {
display: flex;
align-items: baseline;
gap: 8rpx;
margin-bottom: 4rpx;
}
.card-answer-prefix {
font-size: 26rpx;
color: rgba(193, 134, 255, 0.88);
color: rgba(255, 255, 255, 0.6);
}
.card-answer-label {
font-size: 28rpx;
color: rgba(193, 134, 255, 0.95);
font-weight: 600;
}
.card-options-list {
display: flex;
flex-direction: column;
gap: 10rpx;
margin-top: 8rpx;
}
.card-option-item {
position: relative;
display: flex;
flex-direction: column;
gap: 4rpx;
padding: 14rpx 18rpx;
border-radius: 14rpx;
background: rgba(255, 255, 255, 0.04);
border: 1rpx solid rgba(255, 255, 255, 0.08);
}
.card-option-item.selected {
background: rgba(168, 85, 247, 0.16);
border-color: rgba(168, 85, 247, 0.45);
}
.card-option-label {
font-size: 26rpx;
color: rgba(255, 255, 255, 0.92);
font-weight: 500;
}
.card-option-item.selected .card-option-label {
color: rgba(220, 195, 255, 1);
}
.card-option-desc {
font-size: 22rpx;
color: rgba(255, 255, 255, 0.55);
line-height: 1.5;
}
.card-option-mark {
position: absolute;
right: 14rpx;
top: 50%;
transform: translateY(-50%);
font-size: 30rpx;
color: rgba(220, 195, 255, 1);
font-weight: 700;
}
.outline-title {
font-size: 32rpx;
+96 -7
View File
@@ -130,7 +130,24 @@
/>
<view v-else class="card-answered">
<text v-if="msg.card?.question" class="card-question-text">{{ msg.card.question }}</text>
<text>已回答{{ msg.answer }}</text>
<!-- 用户回答中文 label通过 options value 映射回 label 显示 -->
<view class="card-answer-row">
<text class="card-answer-prefix">已回答</text>
<text class="card-answer-label">{{ getAnswerLabel(msg) }}</text>
</view>
<!-- 选项列表展示用户可选的所有选项已选的高亮显示 -->
<view v-if="Array.isArray(msg.card?.options) && msg.card.options.length" class="card-options-list">
<view
v-for="opt in msg.card.options"
:key="opt.value"
class="card-option-item"
:class="{ selected: opt.value === (msg.selectedValue || msg.answer) }"
>
<text class="card-option-label">{{ opt.label || opt.value }}</text>
<text v-if="opt.description" class="card-option-desc">{{ opt.description }}</text>
<text v-if="opt.value === (msg.selectedValue || msg.answer)" class="card-option-mark"></text>
</view>
</view>
</view>
</view>
@@ -1189,6 +1206,20 @@ const PENDING_SCRIPT_CHAT_KEY = 'pending_open_script_chat'
const createMessageId = () => `msg-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`
const createConversationId = () => `conv-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`
/**
* 把已回答卡片的 value(如 "family")映射为对应的中文 label(如 "家人"
* 若选项中找不到匹配的 value,则原样返回 answer(兜底)
* 若 msg.card 不存在则返回 "未回答"
*/
const getAnswerLabel = (msg) => {
if (!msg || !msg.card) return '未回答'
const raw = msg.selectedValue || msg.answer || ''
if (!raw) return '未回答'
const options = Array.isArray(msg.card.options) ? msg.card.options : []
const match = options.find(opt => opt && opt.value === raw)
return match?.label || raw
}
const getCurrentScriptId = () => String(currentResult.value?.id || '')
const getCurrentConversationId = () => String(currentConversationId.value || currentResult.value?.conversationId || '')
@@ -1475,10 +1506,12 @@ const openScriptChat = async (payload = {}) => {
confirmed: true
})
} else if (m.type === 'clarification_answer') {
// 把答案合并到上一条 card 消息answer 字段
// 把答案合并到上一条 card 消息answer 存用户原始 valueselectedValue 也存 value 用于渲染时映射 label
const lastCard = [...resultMessages.value].reverse().find(x => x.kind === 'card')
if (lastCard) {
lastCard.answer = m.content || '未回答'
const rawValue = m.content || ''
lastCard.answer = rawValue // 原始 value(用于查找 label
lastCard.selectedValue = rawValue // 选中的 value(渲染时通过 options 映射成 label
lastCard.submitted = true
} else {
// 无对应 question,作为独立 user 消息
@@ -1841,12 +1874,11 @@ const submitClarification = (answer) => {
if (answeringClarification.value) return
// 找最后一张未提交的卡片消息(mp-weixin 模板不支持内联箭头函数传 msg,改为自己查找)
const card = [...resultMessages.value].reverse().find(m => m.kind === 'card' && !m.submitted)
// 通过 value 查找选项的 label,用于前端显示;后端 payload 仍用 value(语义化)
const option = card?.card?.options?.find(opt => opt.value === answer)
const label = option?.label || answer
if (card) {
card.submitted = true
card.answer = label // 显示用 label(中文);答案已在卡片中显示,无需额外用户气泡
// answer 存用户提交的 value,渲染时通过 card.options 映射成中文 label(与历史回放逻辑一致)
card.answer = answer
card.selectedValue = answer
}
pendingNextResponse.value = true // 标记等待下一个 assistant 响应
answeringClarification.value = true
@@ -3775,6 +3807,63 @@ onUnmounted(() => {
font-size: 26rpx;
padding: 16rpx 0;
}
.card-answer-row {
display: flex;
align-items: baseline;
gap: 8rpx;
margin-bottom: 12rpx;
}
.card-answer-prefix {
font-size: 26rpx;
color: rgba(255, 255, 255, 0.6);
}
.card-answer-label {
font-size: 28rpx;
color: rgba(193, 134, 255, 0.95);
font-weight: 600;
}
.card-options-list {
display: flex;
flex-direction: column;
gap: 10rpx;
margin-top: 8rpx;
}
.card-option-item {
position: relative;
display: flex;
flex-direction: column;
gap: 4rpx;
padding: 14rpx 18rpx;
border-radius: 14rpx;
background: rgba(255, 255, 255, 0.04);
border: 1rpx solid rgba(255, 255, 255, 0.08);
}
.card-option-item.selected {
background: rgba(168, 85, 247, 0.16);
border-color: rgba(168, 85, 247, 0.45);
}
.card-option-label {
font-size: 26rpx;
color: rgba(255, 255, 255, 0.92);
font-weight: 500;
}
.card-option-item.selected .card-option-label {
color: rgba(220, 195, 255, 1);
}
.card-option-desc {
font-size: 22rpx;
color: rgba(255, 255, 255, 0.55);
line-height: 1.5;
}
.card-option-mark {
position: absolute;
right: 14rpx;
top: 50%;
transform: translateY(-50%);
font-size: 30rpx;
color: rgba(220, 195, 255, 1);
font-weight: 700;
}
.outline-title {
color: #ffffff;