Files
WeChatDataAnalysis/frontend/components/EditedMessagePreview.vue
T
2977094657 16a13af18a feat(chat): 支持位置消息与小程序卡片解析展示
- 新增位置消息解析,补充经纬度、地点名和地址字段
- 修复小程序分享 type 识别,避免嵌套 type 干扰
- 聊天页新增位置卡片展示,并补充小程序卡片样式
- 导出、搜索和会话预览同步支持位置消息
- 补充位置导出与小程序解析测试
2026-03-06 21:19:15 +08:00

139 lines
4.8 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div v-if="!message" class="flex items-center justify-center py-4">
<span class="text-sm text-gray-400">无数据</span>
</div>
<!-- 完全复用聊天页消息结构外层 flex + 头像 + 气泡 -->
<div v-else class="flex items-center" :class="message.isSent ? 'justify-end' : 'justify-start'">
<div class="flex items-start" :class="message.isSent ? 'flex-row-reverse' : ''">
<!-- 头像与聊天页完全一致 -->
<div class="relative">
<div
class="w-[calc(42px/var(--dpr,1))] h-[calc(42px/var(--dpr,1))] rounded-md overflow-hidden bg-gray-300 flex-shrink-0"
:class="message.isSent ? 'ml-3' : 'mr-3'"
>
<div v-if="resolvedAvatar" class="w-full h-full">
<img
:src="resolvedAvatar"
alt="avatar"
class="w-full h-full object-cover"
referrerpolicy="no-referrer"
/>
</div>
<div
v-else
class="w-full h-full flex items-center justify-center text-white text-xs font-bold"
:style="{ backgroundColor: message.avatarColor || (message.isSent ? '#4B5563' : '#6B7280') }"
>
{{ avatarLetter }}
</div>
</div>
</div>
<!-- 消息内容气泡与聊天页完全一致 -->
<div
class="flex flex-col relative group"
:class="message.isSent ? 'items-end' : 'items-start'"
>
<!-- 群聊发送者名可选 -->
<div
v-if="senderName && !message.isSent"
class="text-[11px] text-gray-500 mb-1"
:class="message.isSent ? 'text-right' : 'text-left'"
>
{{ senderName }}
</div>
<!-- 时间悬浮 tooltip -->
<div
v-if="message.fullTime"
class="absolute -top-6 z-10 rounded bg-black/70 text-white text-[10px] px-2 py-1 opacity-0 group-hover:opacity-100 transition-opacity pointer-events-none whitespace-nowrap"
:class="message.isSent ? 'right-0' : 'left-0'"
>
{{ message.fullTime }}
</div>
<!-- 表情 -->
<div v-if="renderType === 'emoji' && message.emojiUrl">
<img :src="normalizeMaybeUrl(message.emojiUrl)" alt="emoji" class="w-24 h-24 object-contain" />
</div>
<!-- 图片 -->
<div v-else-if="renderType === 'image' && message.imageUrl" class="max-w-sm">
<div class="msg-radius overflow-hidden">
<img :src="normalizeMaybeUrl(message.imageUrl)" alt="图片" class="max-w-[240px] max-h-[240px] object-cover" />
</div>
</div>
<!-- 视频 -->
<div
v-else-if="renderType === 'video'"
class="px-3 py-2 text-sm max-w-sm relative msg-bubble whitespace-pre-wrap break-words leading-relaxed"
:class="message.isSent ? 'bg-[#95EC69] text-black bubble-tail-r' : 'bg-white text-gray-800 bubble-tail-l'"
>
[视频]
</div>
<!-- 语音 -->
<div
v-else-if="renderType === 'voice'"
class="px-3 py-2 text-sm max-w-sm relative msg-bubble whitespace-pre-wrap break-words leading-relaxed"
:class="message.isSent ? 'bg-[#95EC69] text-black bubble-tail-r' : 'bg-white text-gray-800 bubble-tail-l'"
>
[语音]
</div>
<div v-else-if="renderType === 'location'" class="max-w-sm">
<ChatLocationCard :message="message" />
</div>
<!-- 默认文本消息 -->
<div
v-else
class="px-3 py-2 text-sm max-w-sm relative msg-bubble whitespace-pre-wrap break-words leading-relaxed"
:class="message.isSent ? 'bg-[#95EC69] text-black bubble-tail-r' : 'bg-white text-gray-800 bubble-tail-l'"
>
{{ message.content || '' }}
</div>
</div>
</div>
</div>
</template>
<script setup>
const props = defineProps({
message: { type: Object, default: null },
})
const apiBase = useApiBase()
const normalizeMaybeUrl = (u) => {
const raw = String(u || '').trim()
if (!raw) return ''
if (/^https?:\/\//i.test(raw) || /^blob:/i.test(raw) || /^data:/i.test(raw)) return raw
if (/^\/api\//i.test(raw)) return `${apiBase}${raw.slice(4)}`
return raw
}
const renderType = computed(() => String(props.message?.renderType || '').trim())
const resolvedAvatar = computed(() => {
const m = props.message
if (!m) return ''
return normalizeMaybeUrl(m.avatar || m.senderAvatar || '')
})
const avatarLetter = computed(() => {
const m = props.message
if (!m) return '?'
const name = m.senderDisplayName || m.senderUsername || m.sender || ''
return name.charAt(0) || '?'
})
const senderName = computed(() => {
const m = props.message
if (!m) return ''
return m.senderDisplayName || m.senderUsername || ''
})
</script>