gaoluyang
昨天 b64a0deae5b5d33f9e20671a68936b27f0b9b00b
src/views/im/utils/conversation.ts
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,177 @@
// ====================================================================
// IM ä¼šè¯ / æ’¤å›žå±•示 utility
// ====================================================================
// åŸºäºŽ conversation ä¸Šä¸‹æ–‡ + sender ä¿¡æ¯å®žæ—¶ç®—"展示文案"。
// 1. å‘送人名一律由 utils/user.getSenderDisplayName å®žæ—¶ç®—,本文件只负责拼"撤回 tip / æ‘˜è¦"等文案。
// 2. fallbackName ç”±è°ƒç”¨æ–¹ä¼ å…¥ï¼ˆå…¸åž‹æ¥æºï¼šConversation.lastSenderDisplayName å¿«ç…§ï¼‰ï¼Œé€ä¼ åˆ° getSenderDisplayName å†…部,算不出真名时兜底
// ====================================================================
import type { Message } from '../home/types'
import { ImContentType, ImConversationType, isFriendChatTip, isGroupNotification, isRtcCallTip } from './constants'
import {
  type CardMessage,
  type FaceMessage,
  type FileMessage,
  getCardLabelInfo,
  type MaterialMessage,
  parseMessage,
  segmentsToText,
  type TextMessage,
  tipMention,
  type TipSegment,
  tipText
} from './message'
import { resolveFriendNotificationText, resolveGroupNotificationText, resolveRtcCallLastContent } from './message'
import { getSenderDisplayName } from './user'
/** ä¼šè¯ä¸»é”®ï¼š`type-targetId` æ‹¼æˆç¨³å®šå­—符串,给 v-for :key、active æ¯”对、map key ç­‰åœºæ™¯å…±ç”¨ */
export function getConversationKey(conversation: { targetId: number; type: number; }): string {
  return `${conversation.type}-${conversation.targetId}`
}
/** æŒ‰æ˜µç§°æ¨¡ç³Šè¿‡æ»¤ä¼šè¯åˆ—表:空 keyword åŽŸæ ·è¿”å›žï¼Œå‘½ä¸­èµ° toLowerCase ä¸åŒºåˆ†å¤§å°å†™ */
export function filterConversationsByKeyword<T extends { name?: string }>(
  list: T[],
  keyword: string
): T[] {
  const trimmed = keyword.trim().toLowerCase()
  if (!trimmed) {
    return list
  }
  return list.filter((c) => (c.name || '').toLowerCase().includes(trimmed))
}
/**
 * è¡¨æƒ…消息的统一文本预览:摘要 / åŽ†å²æœç´¢ / å¼•用块 / ç®¡ç†åŽå°é¢„览共用一份
 * æœ‰ name æ—¶èµ° `[表情] name`(系统包),无 name æ—¶èµ° `[表情]`(个人表情通常无 name)
 */
export function buildFacePreviewText(facePayload: null | undefined | { name?: string }): string {
  return facePayload?.name ? `[表情] ${facePayload.name}` : '[表情]'
}
/**
 * æ’¤å›žæç¤º segments
 *
 * senderId ç¼ºå¤±æ—¶ä¸æŒ‚ mention æ®µï¼Œé¿å…ç‚¹å‡ºé”™å·ï¼›ç®—不出真名降级为「对方」纯文本
 */
export function buildRecallTipSegments(
  senderId: number,
  selfSend: boolean,
  conversationType: number,
  conversationTargetId: number,
  fallbackName?: string
): TipSegment[] {
  if (selfSend) {
    return [tipText('你撤回了一条消息')]
  }
  const senderDisplayName = getSenderDisplayName(
    senderId,
    conversationType,
    conversationTargetId,
    fallbackName
  )
  if (!senderId) {
    return [tipText(`${senderDisplayName || '对方'} æ’¤å›žäº†ä¸€æ¡æ¶ˆæ¯`)]
  }
  return [
    tipMention(senderId, senderDisplayName || '对方'),
    tipText(' æ’¤å›žäº†ä¸€æ¡æ¶ˆæ¯')
  ]
}
/** æ’¤å›žæç¤ºæ–‡æ¡ˆï¼šè‡ªå·±æ’¤å›žå›ºå®šæ–‡æ¡ˆï¼Œå¯¹æ–¹æ’¤å›žå¸¦ sender åï¼ˆå®žæ—¶ç®— + fallbackName å…œåº•) */
export function buildRecallTip(
  senderId: number,
  selfSend: boolean,
  conversationType: number,
  conversationTargetId: number,
  fallbackName?: string
): string {
  return segmentsToText(
    buildRecallTipSegments(senderId, selfSend, conversationType, conversationTargetId, fallbackName)
  )
}
/**
 * å•条消息的纯文本摘要;不依赖 conversation ä¸Šä¸‹æ–‡
 *
 * - withFileName=true æ—¶æ–‡ä»¶æ¶ˆæ¯é™„带文件名(合并转发详情用)
 * - ä¸å¤„理 RECALL / ç¾¤å¹¿æ’­ / å¥½å‹é€šçŸ¥ï¼Œç”±è°ƒç”¨æ–¹åŒ…一层
 */
export function summarizeMessageContent(
  message: Pick<Message, 'content' | 'type'>,
  opts?: { withFileName?: boolean }
): string {
  switch (message.type) {
    case ImContentType.CARD: {
      return `[${getCardLabelInfo(parseMessage<CardMessage>(message.content)).label}]`
    }
    case ImContentType.FACE: {
      return buildFacePreviewText(parseMessage<FaceMessage>(message.content))
    }
    case ImContentType.FILE: {
      if (opts?.withFileName) {
        const file = parseMessage<FileMessage>(message.content)
        return file?.name ? `[文件] ${file.name}` : '[文件]'
      }
      return '[文件]'
    }
    case ImContentType.IMAGE: {
      return '[图片]'
    }
    case ImContentType.MATERIAL: {
      const material = parseMessage<MaterialMessage>(message.content)
      return material?.title ? `[频道] ${material.title}` : '[频道]'
    }
    case ImContentType.MERGE: {
      return '[聊天记录]'
    }
    case ImContentType.RTC_CALL_END:
    case ImContentType.RTC_CALL_START: {
      return '[语音通话]'
    }
    case ImContentType.TEXT: {
      return parseMessage<TextMessage>(message.content)?.content ?? ''
    }
    case ImContentType.VIDEO: {
      return '[视频]'
    }
    case ImContentType.VOICE: {
      return '[语音]'
    }
    default: {
      return ''
    }
  }
}
/** ä¼šè¯åˆ—表最后一条摘要:RECALL èµ° buildRecallTip + fallbackName;其它按内容类型派生 */
export function resolveConversationLastContent(
  message: Message,
  conversationType: number,
  conversationTargetId: number,
  fallbackName?: string
): string {
  if (message.type === ImContentType.RECALL) {
    return buildRecallTip(
      message.senderId,
      message.selfSend,
      conversationType,
      conversationTargetId,
      fallbackName
    )
  }
  if (isFriendChatTip(message.type)) {
    return resolveFriendNotificationText(message)
  }
  if (isGroupNotification(message.type)) {
    return resolveGroupNotificationText(message, (id) =>
      getSenderDisplayName(id, ImConversationType.GROUP, message.targetId ?? 0)
    )
  }
  if (isRtcCallTip(message.type)) {
    return resolveRtcCallLastContent(message, conversationType)
  }
  return summarizeMessageContent(message)
}