♻️ refactor(im):注释对齐 + patchMessage 复用 applyServerMessageUpdate

- recomputeConversationLast / videoCoverUrl / showSendingLoading 三处 JSDoc 跟实现对齐:
  原描述还停留在旧设计(lastSendTime 不重算 / 占位 coverUrl 用 blob / 「外层 loading 多余」),
  这轮一并改成当前事实,避免后续维护被误导
- patchMessage 删手写 revoke + Object.assign,改调 applyServerMessageUpdate,
  与 ackMessage / insertMessage(existingIndex) 共用一份服务端字段合并语义;
  「值未变早返回」保留在 patchMessage 顶部
- 抽 BLOB_URL_PREFIX 常量替代散落在 utils/message.ts 与 useMediaUploader.ts 的 3 处 'blob:' 字面量
im
YunaiV 2026-05-06 10:38:43 +08:00
parent 30d695d702
commit 1ac0650984
4 changed files with 13 additions and 13 deletions

View File

@ -7,6 +7,7 @@ import { useMuteOverlay } from './useMuteOverlay'
import { ImMessageStatus, ImMessageType } from '../../utils/constants'
import { getConversationKey } from '../../utils/conversation'
import {
BLOB_URL_PREFIX,
generateClientMessageId,
parseMessage,
serializeMessage,
@ -27,7 +28,7 @@ export type MediaPayload = ImageMessage | FileMessage | AudioMessage | VideoMess
*
* - voiceDuration VoiceRecorder AudioMessage.duration
* - videoProbe probeVideoFile VideoMessage
* - videoCoverUrl URLblobcommit URL blob
* - videoCoverUrl URL blob poster 退commit cover VideoMessage.coverUrl blob
*/
export interface MediaTypeContext {
voiceDuration?: number
@ -79,7 +80,7 @@ export const mediaTypeHandlers: Partial<Record<number, MediaTypeHandler>> = {
extractResendContext: (oldContent) => {
const old = parseMessage<VideoMessage>(oldContent)
// 旧 coverUrl 是 blob 说明上传期失败cover 没传成功),不复用;真实 URL 直接复用,省一次封面上传
const reuseCover = old?.coverUrl && !old.coverUrl.startsWith('blob:') ? old.coverUrl : undefined
const reuseCover = old?.coverUrl && !old.coverUrl.startsWith(BLOB_URL_PREFIX) ? old.coverUrl : undefined
return {
videoProbe: { duration: old?.duration, width: old?.width, height: old?.height },
videoCoverUrl: reuseCover

View File

@ -507,7 +507,7 @@ const uploadProgressText = computed(() => `${uploadProgress.value}%`)
/**
* 是否在气泡尾部显示发送中loading 转圈
*
* 图片 / 视频 / 文件气泡内嵌已有进度反馈遮罩 / 进度条外层 loading 多余则抑制
* 图片 / 视频 / 文件气泡内嵌已有进度反馈遮罩 / 进度条外层 loading 不再叠加
* 语音气泡只有麦克风 + 时长无内嵌进度必须保留外层 loading 让用户感知正在发送
*/
const showSendingLoading = computed(

View File

@ -67,8 +67,8 @@ function deriveLastSenderDisplayName(
/**
* conversation.messages last* /
*
* / loadConversations drop
* lastSendTime conversation removeMessage
* / loadConversations drop lastSendTime=0
* lastSendTime sendTime沿 conversation
*/
function recomputeConversationLast(conversation: Conversation): void {
const last = conversation.messages[conversation.messages.length - 1]
@ -627,11 +627,7 @@ export const useConversationStore = defineStore('imConversationStore', {
if (!changed) {
return
}
// 替换 content 时 revoke 旧 blob URL与 ackMessage 同语义
if (patch.content && patch.content !== message.content) {
revokeBlobUrlsInContent(message.content)
}
Object.assign(message, patch)
applyServerMessageUpdate(message, patch)
},
/**

View File

@ -105,12 +105,15 @@ export const parseMessage = <T>(content: string): T | null => {
/** 序列化消息 payload 为 content JSON 字符串;与 parseMessage 对称 */
export const serializeMessage = <T>(payload: T): string => JSON.stringify(payload)
/** `URL.createObjectURL(file)` 生成的 URL 前缀;占位 / revoke / 重传旧值识别共用 */
export const BLOB_URL_PREFIX = 'blob:'
/**
* payload blob URL /// url
*
* ImageMessage / VideoMessage / FileMessage / AudioMessage interface
* - url blob URLack URL
* - coverUrl url blobcover URL
* - coverUrlcommit URL blob poster 退
* - thumbnailUrl使 blob
*/
const MEDIA_BLOB_URL_FIELDS = ['url', 'coverUrl', 'thumbnailUrl'] as const
@ -122,7 +125,7 @@ const MEDIA_BLOB_URL_FIELDS = ['url', 'coverUrl', 'thumbnailUrl'] as const
* document blob URL IndexedDB blob URL document
*/
export const revokeBlobUrlsInContent = (content: string): void => {
if (!content || !content.includes('blob:')) {
if (!content || !content.includes(BLOB_URL_PREFIX)) {
return
}
const payload = parseMessage<Record<string, unknown>>(content)
@ -131,7 +134,7 @@ export const revokeBlobUrlsInContent = (content: string): void => {
}
for (const field of MEDIA_BLOB_URL_FIELDS) {
const value = payload[field]
if (typeof value === 'string' && value.startsWith('blob:')) {
if (typeof value === 'string' && value.startsWith(BLOB_URL_PREFIX)) {
URL.revokeObjectURL(value)
}
}