✨ feat(im): 初始化表情包 v0.2:第三把 review
parent
2f513f7b8f
commit
a98e32554c
|
|
@ -148,7 +148,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 留言(单行):右侧表情按钮触发 EmojiPicker,所选 emoji 直接拼接到输入末尾 -->
|
<!-- 留言(单行):右侧表情按钮触发 FacePicker(emoji-only),所选 emoji 直接拼接到输入末尾 -->
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
<el-input v-model="leaveMessage" :maxlength="100" placeholder="给朋友留言">
|
<el-input v-model="leaveMessage" :maxlength="100" placeholder="给朋友留言">
|
||||||
<template #suffix>
|
<template #suffix>
|
||||||
|
|
@ -161,10 +161,11 @@
|
||||||
</template>
|
</template>
|
||||||
</el-input>
|
</el-input>
|
||||||
<!-- bottom-full 让 picker 下沿贴 input 顶部,向上弹出;right-0 对齐 input 右侧表情按钮 -->
|
<!-- bottom-full 让 picker 下沿贴 input 顶部,向上弹出;right-0 对齐 input 右侧表情按钮 -->
|
||||||
<EmojiPicker
|
<FacePicker
|
||||||
v-model:visible="emojiVisible"
|
v-model:visible="emojiVisible"
|
||||||
|
mode="emoji-only"
|
||||||
class="bottom-full right-0 mb-2"
|
class="bottom-full right-0 mb-2"
|
||||||
@select="handleEmojiSelect"
|
@select-emoji="handleEmojiSelect"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -192,7 +193,7 @@ import Icon from '@/components/Icon/src/Icon.vue'
|
||||||
import { useMessage } from '@/hooks/web/useMessage'
|
import { useMessage } from '@/hooks/web/useMessage'
|
||||||
|
|
||||||
import UserAvatar from './UserAvatar.vue'
|
import UserAvatar from './UserAvatar.vue'
|
||||||
import EmojiPicker from '../../pages/conversation/components/input/EmojiPicker.vue'
|
import FacePicker from '../../pages/conversation/components/input/FacePicker.vue'
|
||||||
import { useConversationStore } from '../../store/conversationStore'
|
import { useConversationStore } from '../../store/conversationStore'
|
||||||
import { useMessageSender } from '../../composables/useMessageSender'
|
import { useMessageSender } from '../../composables/useMessageSender'
|
||||||
import { ImConversationType, ImMessageType } from '../../../utils/constants'
|
import { ImConversationType, ImMessageType } from '../../../utils/constants'
|
||||||
|
|
@ -243,7 +244,7 @@ function resetForm() {
|
||||||
emojiVisible.value = false
|
emojiVisible.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 选中 emoji:直接拼到留言末尾;EmojiPicker 自身 emit('update:visible', false) 关闭面板 */
|
/** 选中 emoji:直接拼到留言末尾;FacePicker 自身 emit('update:visible', false) 关闭面板 */
|
||||||
function handleEmojiSelect(emoji: string) {
|
function handleEmojiSelect(emoji: string) {
|
||||||
leaveMessage.value = `${leaveMessage.value}${emoji}`
|
leaveMessage.value = `${leaveMessage.value}${emoji}`
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,7 @@
|
||||||
<el-scrollbar v-if="isFullMode" v-show="activeTab === FACE_TAB.MINE" height="300px">
|
<el-scrollbar v-if="isFullMode" v-show="activeTab === FACE_TAB.MINE" height="300px">
|
||||||
<div class="grid grid-cols-5 gap-2 p-3">
|
<div class="grid grid-cols-5 gap-2 p-3">
|
||||||
<!-- 上传入口固定放第一格,对齐微信 -->
|
<!-- 上传入口固定放第一格,对齐微信 -->
|
||||||
|
<!-- TODO @AI:这里的界面,有点丑,你看看:/Users/yunai/Downloads/iShot_2026-05-06_21.07.24.png -->
|
||||||
<button
|
<button
|
||||||
class="aspect-square flex items-center justify-center rounded-md border border-[var(--el-border-color-lighter)] bg-[var(--el-fill-color-lighter)] text-2xl text-[var(--el-text-color-placeholder)] cursor-pointer transition-colors hover:bg-[var(--el-fill-color)]"
|
class="aspect-square flex items-center justify-center rounded-md border border-[var(--el-border-color-lighter)] bg-[var(--el-fill-color-lighter)] text-2xl text-[var(--el-text-color-placeholder)] cursor-pointer transition-colors hover:bg-[var(--el-fill-color)]"
|
||||||
type="button"
|
type="button"
|
||||||
|
|
@ -187,6 +188,7 @@ import type { ImFacePackUserItemVO, ImFaceUserItemVO } from '@/api/im/face'
|
||||||
defineOptions({ name: 'ImFacePicker' })
|
defineOptions({ name: 'ImFacePicker' })
|
||||||
|
|
||||||
/** 面板模式 */
|
/** 面板模式 */
|
||||||
|
// TODO @AI:直接就叫 emoji,不用带 only
|
||||||
type FacePickerMode = 'full' | 'emoji-only'
|
type FacePickerMode = 'full' | 'emoji-only'
|
||||||
|
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
|
|
@ -349,7 +351,9 @@ onUnmounted(() => {
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
color: var(--el-text-color-regular);
|
color: var(--el-text-color-regular);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: background-color 0.15s, color 0.15s;
|
transition:
|
||||||
|
background-color 0.15s,
|
||||||
|
color 0.15s;
|
||||||
}
|
}
|
||||||
.im-face-tab:hover {
|
.im-face-tab:hover {
|
||||||
background-color: var(--el-fill-color);
|
background-color: var(--el-fill-color);
|
||||||
|
|
|
||||||
|
|
@ -261,6 +261,15 @@
|
||||||
<span>个人名片:{{ cardOf(message)?.nickname || '' }}</span>
|
<span>个人名片:{{ cardOf(message)?.nickname || '' }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 表情贴图:直接渲染图片,对照微信观感 -->
|
||||||
|
<img
|
||||||
|
v-else-if="message.type === ImMessageType.FACE && faceOf(message)?.url"
|
||||||
|
:src="faceOf(message)?.url"
|
||||||
|
:alt="faceOf(message)?.name || '表情'"
|
||||||
|
class="block max-w-[120px] max-h-[120px] object-contain"
|
||||||
|
draggable="false"
|
||||||
|
/>
|
||||||
|
|
||||||
<!-- 撤回 -->
|
<!-- 撤回 -->
|
||||||
<div
|
<div
|
||||||
v-else-if="message.type === ImMessageType.RECALL"
|
v-else-if="message.type === ImMessageType.RECALL"
|
||||||
|
|
@ -324,7 +333,7 @@ import {
|
||||||
resolveFriendNotificationText,
|
resolveFriendNotificationText,
|
||||||
resolveGroupNotificationText
|
resolveGroupNotificationText
|
||||||
} from '@/views/im/utils/user'
|
} from '@/views/im/utils/user'
|
||||||
import { buildRecallTip } from '@/views/im/utils/conversation'
|
import { buildFacePreviewText, buildRecallTip } from '@/views/im/utils/conversation'
|
||||||
import { useMessagePuller } from '@/views/im/home/composables/useMessagePuller'
|
import { useMessagePuller } from '@/views/im/home/composables/useMessagePuller'
|
||||||
import {
|
import {
|
||||||
ImConversationType,
|
ImConversationType,
|
||||||
|
|
@ -339,7 +348,8 @@ import {
|
||||||
type ImageMessage,
|
type ImageMessage,
|
||||||
type FileMessage,
|
type FileMessage,
|
||||||
type AudioMessage,
|
type AudioMessage,
|
||||||
type CardMessage
|
type CardMessage,
|
||||||
|
type FaceMessage
|
||||||
} from '@/views/im/utils/message'
|
} from '@/views/im/utils/message'
|
||||||
import type { Message } from '@/views/im/home/types'
|
import type { Message } from '@/views/im/home/types'
|
||||||
import UserAvatar from '../../../../components/user/UserAvatar.vue'
|
import UserAvatar from '../../../../components/user/UserAvatar.vue'
|
||||||
|
|
@ -679,6 +689,9 @@ function audioOf(message: Message): AudioMessage | null {
|
||||||
function cardOf(message: Message): CardMessage | null {
|
function cardOf(message: Message): CardMessage | null {
|
||||||
return parseMessage<CardMessage>(message.content)
|
return parseMessage<CardMessage>(message.content)
|
||||||
}
|
}
|
||||||
|
function faceOf(message: Message): FaceMessage | null {
|
||||||
|
return parseMessage<FaceMessage>(message.content)
|
||||||
|
}
|
||||||
|
|
||||||
/** 关键字命中文本:文本类返回原文、文件返回文件名(利于按文件名搜)、其他返回占位词 */
|
/** 关键字命中文本:文本类返回原文、文件返回文件名(利于按文件名搜)、其他返回占位词 */
|
||||||
function textSnippetOf(message: Message): string {
|
function textSnippetOf(message: Message): string {
|
||||||
|
|
@ -698,6 +711,8 @@ function textSnippetOf(message: Message): string {
|
||||||
return '[视频]'
|
return '[视频]'
|
||||||
case ImMessageType.CARD:
|
case ImMessageType.CARD:
|
||||||
return `[个人名片] ${parseMessage<CardMessage>(message.content)?.nickname ?? ''}`
|
return `[个人名片] ${parseMessage<CardMessage>(message.content)?.nickname ?? ''}`
|
||||||
|
case ImMessageType.FACE:
|
||||||
|
return buildFacePreviewText(faceOf(message))
|
||||||
case ImMessageType.RECALL:
|
case ImMessageType.RECALL:
|
||||||
return recallTipOf(message)
|
return recallTipOf(message)
|
||||||
default:
|
default:
|
||||||
|
|
|
||||||
|
|
@ -157,6 +157,21 @@
|
||||||
>
|
>
|
||||||
[视频消息]
|
[视频消息]
|
||||||
</div>
|
</div>
|
||||||
|
<!-- 表情贴图:裸 <img>,不套气泡(对齐微信观感:贴图本体就是装饰,再叠气泡显累赘) -->
|
||||||
|
<div
|
||||||
|
v-else-if="isFace && facePayload"
|
||||||
|
class="inline-block"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
:src="facePayload.url"
|
||||||
|
:alt="facePayload.name || '表情'"
|
||||||
|
:title="facePayload.name || ''"
|
||||||
|
:width="facePayload.width"
|
||||||
|
:height="facePayload.height"
|
||||||
|
class="block max-w-[160px] max-h-[160px] object-contain"
|
||||||
|
draggable="false"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<!-- 名片消息:头像 + 昵称 + 「个人名片」标签;点击气泡弹被推荐用户的名片浮层
|
<!-- 名片消息:头像 + 昵称 + 「个人名片」标签;点击气泡弹被推荐用户的名片浮层
|
||||||
参照微信观感:自己 / 对方都是浅灰白卡片不染绿,头像圆角块,底部分隔条灰字「个人名片」 -->
|
参照微信观感:自己 / 对方都是浅灰白卡片不染绿,头像圆角块,底部分隔条灰字「个人名片」 -->
|
||||||
<div
|
<div
|
||||||
|
|
@ -275,6 +290,7 @@ import { pinGroupMessage as apiPinGroupMessage, cancelMuteMember } from '@/api/i
|
||||||
import { removeGroupMember } from '@/api/im/group/member'
|
import { removeGroupMember } from '@/api/im/group/member'
|
||||||
import {
|
import {
|
||||||
buildQuoteFromMessage,
|
buildQuoteFromMessage,
|
||||||
|
extractAddableFace,
|
||||||
getQuoteFromMessage,
|
getQuoteFromMessage,
|
||||||
parseMessage,
|
parseMessage,
|
||||||
getFileIconInfo,
|
getFileIconInfo,
|
||||||
|
|
@ -283,9 +299,10 @@ import {
|
||||||
type FileMessage,
|
type FileMessage,
|
||||||
type AudioMessage,
|
type AudioMessage,
|
||||||
type VideoMessage,
|
type VideoMessage,
|
||||||
type CardMessage
|
type CardMessage,
|
||||||
|
type FaceMessage
|
||||||
} from '@/views/im/utils/message'
|
} from '@/views/im/utils/message'
|
||||||
import { buildRecallTip } from '../../../../../utils/conversation'
|
import { buildRecallTip } from '@/views/im/utils/conversation'
|
||||||
import { formatSeconds } from '@/utils/formatTime'
|
import { formatSeconds } from '@/utils/formatTime'
|
||||||
import { formatFileSize } from '@/utils/file'
|
import { formatFileSize } from '@/utils/file'
|
||||||
import { useUserStore } from '@/store/modules/user'
|
import { useUserStore } from '@/store/modules/user'
|
||||||
|
|
@ -293,6 +310,7 @@ import { useConversationStore } from '../../../../store/conversationStore'
|
||||||
import { useGroupStore } from '../../../../store/groupStore'
|
import { useGroupStore } from '../../../../store/groupStore'
|
||||||
import { useFriendStore } from '../../../../store/friendStore'
|
import { useFriendStore } from '../../../../store/friendStore'
|
||||||
import { useDraftStore } from '../../../../store/draftStore'
|
import { useDraftStore } from '../../../../store/draftStore'
|
||||||
|
import { useFaceStore } from '../../../../store/faceStore'
|
||||||
import {
|
import {
|
||||||
getMemberDisplayName,
|
getMemberDisplayName,
|
||||||
getSenderDisplayName,
|
getSenderDisplayName,
|
||||||
|
|
@ -334,6 +352,7 @@ const conversationStore = useConversationStore()
|
||||||
const groupStore = useGroupStore()
|
const groupStore = useGroupStore()
|
||||||
const friendStore = useFriendStore()
|
const friendStore = useFriendStore()
|
||||||
const draftStore = useDraftStore()
|
const draftStore = useDraftStore()
|
||||||
|
const faceStore = useFaceStore()
|
||||||
const uiStore = useImUiStore()
|
const uiStore = useImUiStore()
|
||||||
const { recall, sendRaw } = useMessageSender()
|
const { recall, sendRaw } = useMessageSender()
|
||||||
const { uploadAndSendMedia } = useMediaUploader()
|
const { uploadAndSendMedia } = useMediaUploader()
|
||||||
|
|
@ -361,6 +380,7 @@ const isFile = computed(() => props.message.type === ImMessageType.FILE)
|
||||||
const isVoice = computed(() => props.message.type === ImMessageType.VOICE)
|
const isVoice = computed(() => props.message.type === ImMessageType.VOICE)
|
||||||
const isVideo = computed(() => props.message.type === ImMessageType.VIDEO)
|
const isVideo = computed(() => props.message.type === ImMessageType.VIDEO)
|
||||||
const isCard = computed(() => props.message.type === ImMessageType.CARD)
|
const isCard = computed(() => props.message.type === ImMessageType.CARD)
|
||||||
|
const isFace = computed(() => props.message.type === ImMessageType.FACE)
|
||||||
|
|
||||||
// ==================== 气泡配色 helper ====================
|
// ==================== 气泡配色 helper ====================
|
||||||
// self / other 两侧 ::before 三角靠 message-bubble--self/other 类承载;本 helper 把 4 处 selfSend ternary
|
// self / other 两侧 ::before 三角靠 message-bubble--self/other 类承载;本 helper 把 4 处 selfSend ternary
|
||||||
|
|
@ -477,6 +497,9 @@ const videoPayload = computed(() =>
|
||||||
const cardPayload = computed(() =>
|
const cardPayload = computed(() =>
|
||||||
isCard.value ? parseMessage<CardMessage>(props.message.content) : null
|
isCard.value ? parseMessage<CardMessage>(props.message.content) : null
|
||||||
)
|
)
|
||||||
|
const facePayload = computed(() =>
|
||||||
|
isFace.value ? parseMessage<FaceMessage>(props.message.content) : null
|
||||||
|
)
|
||||||
|
|
||||||
/** 名片点击:弹被推荐用户的 UserInfoCard;陌生人名片走「名片」加好友来源 */
|
/** 名片点击:弹被推荐用户的 UserInfoCard;陌生人名片走「名片」加好友来源 */
|
||||||
function handleCardClick(e: MouseEvent) {
|
function handleCardClick(e: MouseEvent) {
|
||||||
|
|
@ -666,6 +689,7 @@ const isAtMe = computed(() => {
|
||||||
const MENU_KEYS = {
|
const MENU_KEYS = {
|
||||||
REPLY: 'REPLY',
|
REPLY: 'REPLY',
|
||||||
PIN: 'PIN',
|
PIN: 'PIN',
|
||||||
|
ADD_TO_FACE: 'ADD_TO_FACE',
|
||||||
MUTE: 'MUTE',
|
MUTE: 'MUTE',
|
||||||
UNMUTE: 'UNMUTE',
|
UNMUTE: 'UNMUTE',
|
||||||
KICK: 'KICK',
|
KICK: 'KICK',
|
||||||
|
|
@ -714,6 +738,14 @@ async function handleContextMenu(e: MouseEvent) {
|
||||||
icon: 'ant-design:pushpin-outlined'
|
icon: 'ant-design:pushpin-outlined'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
// 「添加到表情」:FACE / IMAGE 消息 + 已落库 + 未撤回;写入个人表情包,对照微信「添加到表情」
|
||||||
|
if (canAddToFace.value) {
|
||||||
|
items.push({
|
||||||
|
key: MENU_KEYS.ADD_TO_FACE,
|
||||||
|
name: '添加到表情',
|
||||||
|
icon: 'ant-design:smile-outlined'
|
||||||
|
})
|
||||||
|
}
|
||||||
// 「禁言 / 解禁 / 移除」:群聊 + 非自己消息 + 我是群主或管理员
|
// 「禁言 / 解禁 / 移除」:群聊 + 非自己消息 + 我是群主或管理员
|
||||||
if (currentGroup.value && !props.message.selfSend && canManageSender.value) {
|
if (currentGroup.value && !props.message.selfSend && canManageSender.value) {
|
||||||
const senderMember = currentGroup.value.members?.find(
|
const senderMember = currentGroup.value.members?.find(
|
||||||
|
|
@ -775,6 +807,8 @@ async function handleContextMenu(e: MouseEvent) {
|
||||||
handleReply()
|
handleReply()
|
||||||
} else if (item.key === MENU_KEYS.PIN) {
|
} else if (item.key === MENU_KEYS.PIN) {
|
||||||
await handlePin()
|
await handlePin()
|
||||||
|
} else if (item.key === MENU_KEYS.ADD_TO_FACE) {
|
||||||
|
await handleAddToFace()
|
||||||
} else if (item.key === MENU_KEYS.MUTE) {
|
} else if (item.key === MENU_KEYS.MUTE) {
|
||||||
handleMute()
|
handleMute()
|
||||||
} else if (item.key === MENU_KEYS.UNMUTE) {
|
} else if (item.key === MENU_KEYS.UNMUTE) {
|
||||||
|
|
@ -789,6 +823,30 @@ async function handleContextMenu(e: MouseEvent) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 是否可「添加到表情」:FACE / IMAGE 消息 + 已落库 + 未撤回(GIF / 静图都允许) */
|
||||||
|
const canAddToFace = computed(() => {
|
||||||
|
if (isRecall.value || !props.message.id) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return extractAddableFace(props.message) !== null
|
||||||
|
})
|
||||||
|
|
||||||
|
/** 添加到个人表情:从 message 抽 url + 尺寸 + name 写入个人表情库;幂等失败时返回 false 走 toast 兜底 */
|
||||||
|
async function handleAddToFace() {
|
||||||
|
const payload = extractAddableFace(props.message)
|
||||||
|
if (!payload) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// TODO @AI:改成 data;更符合预期
|
||||||
|
const ok = await faceStore.addFaceUserItem({
|
||||||
|
...payload,
|
||||||
|
sourceMessageId: props.message.id
|
||||||
|
})
|
||||||
|
if (ok) {
|
||||||
|
successMessage('已添加到表情')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** 当前激活会话对应的群(私聊场景为 undefined) */
|
/** 当前激活会话对应的群(私聊场景为 undefined) */
|
||||||
const currentGroup = computed(() => {
|
const currentGroup = computed(() => {
|
||||||
const conversation = conversationStore.activeConversation
|
const conversation = conversationStore.activeConversation
|
||||||
|
|
|
||||||
|
|
@ -65,7 +65,15 @@
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- 图片 / 视频缩略图 -->
|
<!-- 表情贴图:缩略图 + name(无 name 仅显示 [表情]) -->
|
||||||
|
<template v-else-if="isFace">
|
||||||
|
<span class="flex-shrink-0">[表情]</span>
|
||||||
|
<span v-if="parsedPayload?.name" class="im-reply-preview__text min-w-0">
|
||||||
|
{{ parsedPayload.name }}
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 图片 / 视频 / 表情贴图缩略图 -->
|
||||||
<img
|
<img
|
||||||
v-if="thumbnailUrl"
|
v-if="thumbnailUrl"
|
||||||
:src="thumbnailUrl"
|
:src="thumbnailUrl"
|
||||||
|
|
@ -99,6 +107,7 @@ import {
|
||||||
getFileIconInfo,
|
getFileIconInfo,
|
||||||
type AudioMessage,
|
type AudioMessage,
|
||||||
type CardMessage,
|
type CardMessage,
|
||||||
|
type FaceMessage,
|
||||||
type FileMessage,
|
type FileMessage,
|
||||||
type ImageMessage,
|
type ImageMessage,
|
||||||
type TextMessage,
|
type TextMessage,
|
||||||
|
|
@ -158,7 +167,7 @@ const senderName = computed(() => {
|
||||||
|
|
||||||
/** quote.content 解析一次缓存,让多个 computed 复用,长会话每条引用气泡少一次 JSON.parse */
|
/** quote.content 解析一次缓存,让多个 computed 复用,长会话每条引用气泡少一次 JSON.parse */
|
||||||
type AnyQuotePayload = Partial<
|
type AnyQuotePayload = Partial<
|
||||||
TextMessage & ImageMessage & FileMessage & AudioMessage & VideoMessage & CardMessage
|
TextMessage & ImageMessage & FileMessage & AudioMessage & VideoMessage & CardMessage & FaceMessage
|
||||||
>
|
>
|
||||||
const parsedPayload = computed(() => parseMessage<AnyQuotePayload>(props.quote.content))
|
const parsedPayload = computed(() => parseMessage<AnyQuotePayload>(props.quote.content))
|
||||||
|
|
||||||
|
|
@ -166,6 +175,7 @@ const isText = computed(() => props.quote.type === ImMessageType.TEXT)
|
||||||
const isFile = computed(() => props.quote.type === ImMessageType.FILE)
|
const isFile = computed(() => props.quote.type === ImMessageType.FILE)
|
||||||
const isVoice = computed(() => props.quote.type === ImMessageType.VOICE)
|
const isVoice = computed(() => props.quote.type === ImMessageType.VOICE)
|
||||||
const isCard = computed(() => props.quote.type === ImMessageType.CARD)
|
const isCard = computed(() => props.quote.type === ImMessageType.CARD)
|
||||||
|
const isFace = computed(() => props.quote.type === ImMessageType.FACE)
|
||||||
|
|
||||||
/** 文本超过 MAX_TEXT_PREVIEW_LEN 截断,长内容不撑爆引用块 */
|
/** 文本超过 MAX_TEXT_PREVIEW_LEN 截断,长内容不撑爆引用块 */
|
||||||
const textPreview = computed(() => {
|
const textPreview = computed(() => {
|
||||||
|
|
@ -178,7 +188,7 @@ const textPreview = computed(() => {
|
||||||
/** 文件 icon:按扩展名挑色,跟主气泡渲染同源 */
|
/** 文件 icon:按扩展名挑色,跟主气泡渲染同源 */
|
||||||
const fileIcon = computed(() => getFileIconInfo(parsedPayload.value?.name))
|
const fileIcon = computed(() => getFileIconInfo(parsedPayload.value?.name))
|
||||||
|
|
||||||
/** 缩略图 URL:仅图片 / 视频从 quote.content 直接取,不依赖本地缓存 */
|
/** 缩略图 URL:图片 / 视频 / 表情贴图从 quote.content 直接取,不依赖本地缓存 */
|
||||||
const thumbnailUrl = computed<string | undefined>(() => {
|
const thumbnailUrl = computed<string | undefined>(() => {
|
||||||
if (isRecalled.value) {
|
if (isRecalled.value) {
|
||||||
return undefined
|
return undefined
|
||||||
|
|
@ -190,6 +200,9 @@ const thumbnailUrl = computed<string | undefined>(() => {
|
||||||
if (type === ImMessageType.VIDEO) {
|
if (type === ImMessageType.VIDEO) {
|
||||||
return parsedPayload.value?.coverUrl
|
return parsedPayload.value?.coverUrl
|
||||||
}
|
}
|
||||||
|
if (type === ImMessageType.FACE) {
|
||||||
|
return parsedPayload.value?.url
|
||||||
|
}
|
||||||
return undefined
|
return undefined
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue