✨ feat(im): 优化【消息引用】的功能,来自第一波 code review
parent
1dfab43b8a
commit
cfeee7bbb7
|
|
@ -546,6 +546,15 @@ function clearReply() {
|
|||
draftStore.clearReply(conversation)
|
||||
}
|
||||
|
||||
/** 取走当前 reply 快照(抓一次清一次),媒体上传链路在动手前统一调它拿 quote */
|
||||
function consumeReply(): QuoteMessage | undefined {
|
||||
const quote = replyTarget.value
|
||||
if (quote) {
|
||||
clearReply()
|
||||
}
|
||||
return quote
|
||||
}
|
||||
|
||||
// ==================== 表情 ====================
|
||||
const emojiVisible = ref(false)
|
||||
/** 切换表情面板;打开时互斥关掉语音面板 */
|
||||
|
|
@ -767,8 +776,7 @@ function onKeydown(e: KeyboardEvent) {
|
|||
// ==================== 图片 / 文件上传 ====================
|
||||
/** 上传并发送 IMAGE 消息;quote 抓取后立即清 draft.reply 让顶部引用条同步消失 */
|
||||
async function uploadAndSendImage(file: File) {
|
||||
const replyQuote = replyTarget.value
|
||||
clearReply()
|
||||
const replyQuote = consumeReply()
|
||||
const form = new FormData()
|
||||
form.append('file', file)
|
||||
const url = ((await updateFile(form)) as { data?: string })?.data
|
||||
|
|
@ -781,8 +789,7 @@ async function uploadAndSendImage(file: File) {
|
|||
|
||||
/** 上传并发送 FILE 消息;附原始 name / size 让接收端展示文件名和体积 */
|
||||
async function uploadAndSendFile(file: File) {
|
||||
const replyQuote = replyTarget.value
|
||||
clearReply()
|
||||
const replyQuote = consumeReply()
|
||||
const form = new FormData()
|
||||
form.append('file', file)
|
||||
const url = ((await updateFile(form)) as { data?: string })?.data
|
||||
|
|
@ -825,8 +832,7 @@ function openVoice() {
|
|||
}
|
||||
/** VoiceRecorder 录完后回传 blob,包成 webm 文件上传,发送 VOICE 消息 */
|
||||
async function onVoiceSend(payload: { blob: Blob; duration: number }) {
|
||||
const replyQuote = replyTarget.value
|
||||
clearReply()
|
||||
const replyQuote = consumeReply()
|
||||
const file = new File([payload.blob], `voice-${Date.now()}.webm`, { type: payload.blob.type })
|
||||
const form = new FormData()
|
||||
form.append('file', file)
|
||||
|
|
@ -955,8 +961,7 @@ async function uploadAndSendVideo(file: File) {
|
|||
}
|
||||
const startKey = getConversationKey(startConversation)
|
||||
// 1.2 quote 抓取后立即清 draft.reply,与图片 / 文件 / 语音上传链路一致
|
||||
const replyQuote = replyTarget.value
|
||||
clearReply()
|
||||
const replyQuote = consumeReply()
|
||||
|
||||
// 2. 三路并行起跑(probe 与两条上传无依赖,封面上传等 probe 出 cover 后立即接力)
|
||||
// 2.1 视频本体上传:立即 catch 兜底为 url=undefined,由 step 3.2 拿不到 url 时放弃;同时让 promise 不再 floating
|
||||
|
|
|
|||
|
|
@ -535,7 +535,7 @@ const isAtMe = computed(() => {
|
|||
|
||||
/**
|
||||
* 右键菜单项:
|
||||
* - 回复:仅已落库(id≠0)且未撤回的消息可引用,引用块写入 draftStore.reply
|
||||
* - 回复:仅已落库(id≠0)且未撤回的消息可引用,引用块写入 draftStore.reply
|
||||
* - 删除:从本地消息列表移除(不动后端)
|
||||
* - 撤回:仅自己发送、已送达(有 id)的消息
|
||||
*
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
<template>
|
||||
<!--
|
||||
引用消息预览块,对齐微信 PC:浅灰底块 + 大 padding + 文本可换行(line-clamp 2 行)
|
||||
- clickable=true(气泡内): 点击触发 locate emit;撤回态禁用跳转
|
||||
- closable=true(输入条): 显示右上 × 圆形按钮,hover 时显示圆形底
|
||||
- 撤回降级:命中本地缓存且 type === RECALL 时显示「原消息已撤回」斜体灰字
|
||||
- 富预览:type 为 IMAGE / VIDEO 时直接从 quote.content 取缩略图,不依赖本地缓存
|
||||
引用消息预览块,对齐微信 PC:浅灰底块 + 大 padding + 文本可换行(line-clamp 2 行)
|
||||
- clickable=true(气泡内):点击触发 locate emit;撤回态禁用跳转
|
||||
- closable=true(输入条):显示右上 × 圆形按钮,hover 时显示圆形底
|
||||
- 撤回降级:命中本地缓存且 type === RECALL 时显示「原消息已撤回」斜体灰字
|
||||
- 富预览:type 为 IMAGE / VIDEO 时直接从 quote.content 取缩略图,不依赖本地缓存
|
||||
-->
|
||||
<div
|
||||
class="im-reply-preview flex gap-2 items-start min-w-0 px-3 py-2 rounded text-13px bg-[var(--el-fill-color-light)]"
|
||||
|
|
@ -101,25 +101,29 @@ const senderName = computed(() => {
|
|||
return getSenderDisplayName(props.quote.senderId, conversation.type, conversation.targetId)
|
||||
})
|
||||
|
||||
/** 摘要文案:已撤回降级,否则按 type 从 quote.content 派生(文本截断 / 非文本走类型 tag) */
|
||||
/** quote.content 解析一次缓存,让 snippetText / thumbnailUrl 复用,长会话每条引用气泡少一次 JSON.parse */
|
||||
type AnyQuotePayload = Partial<TextMessage & ImageMessage & FileMessage & AudioMessage & VideoMessage>
|
||||
const parsedPayload = computed(() => parseMessage<AnyQuotePayload>(props.quote.content))
|
||||
|
||||
/** 摘要文案:已撤回降级,否则按 type 从 quote.content 派生(文本截断 / 非文本走类型 tag) */
|
||||
const snippetText = computed(() => {
|
||||
if (isRecalled.value) {
|
||||
return '原消息已撤回'
|
||||
}
|
||||
const { type, content } = props.quote
|
||||
const { type } = props.quote
|
||||
if (type === ImMessageType.TEXT) {
|
||||
const text = parseMessage<TextMessage>(content)?.content ?? ''
|
||||
const text = parsedPayload.value?.content ?? ''
|
||||
return text.length <= MAX_TEXT_PREVIEW_LEN ? text : `${text.substring(0, MAX_TEXT_PREVIEW_LEN)}…`
|
||||
}
|
||||
if (type === ImMessageType.IMAGE) {
|
||||
return '[图片]'
|
||||
}
|
||||
if (type === ImMessageType.FILE) {
|
||||
const name = parseMessage<FileMessage>(content)?.name
|
||||
const name = parsedPayload.value?.name
|
||||
return name ? `[文件 ${name}]` : '[文件]'
|
||||
}
|
||||
if (type === ImMessageType.VOICE) {
|
||||
const duration = parseMessage<AudioMessage>(content)?.duration
|
||||
const duration = parsedPayload.value?.duration
|
||||
return duration ? `[语音 ${duration}″]` : '[语音]'
|
||||
}
|
||||
if (type === ImMessageType.VIDEO) {
|
||||
|
|
@ -128,18 +132,17 @@ const snippetText = computed(() => {
|
|||
return ''
|
||||
})
|
||||
|
||||
/** 缩略图 URL:仅图片 / 视频从 quote.content 直接取,不依赖本地缓存 */
|
||||
/** 缩略图 URL:仅图片 / 视频从 quote.content 直接取,不依赖本地缓存 */
|
||||
const thumbnailUrl = computed<string | undefined>(() => {
|
||||
if (isRecalled.value) {
|
||||
return undefined
|
||||
}
|
||||
const { type, content } = props.quote
|
||||
const { type } = props.quote
|
||||
if (type === ImMessageType.IMAGE) {
|
||||
const payload = parseMessage<ImageMessage>(content)
|
||||
return payload?.thumbnailUrl || payload?.url
|
||||
return parsedPayload.value?.thumbnailUrl || parsedPayload.value?.url
|
||||
}
|
||||
if (type === ImMessageType.VIDEO) {
|
||||
return parseMessage<VideoMessage>(content)?.coverUrl
|
||||
return parsedPayload.value?.coverUrl
|
||||
}
|
||||
return undefined
|
||||
})
|
||||
|
|
|
|||
Loading…
Reference in New Issue