// ====================================================================
|
// 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)
|
}
|