feat(im): 优化消息的 format 相关的逻辑,从 user 抽到 message 工具类里,更加统一

im
YunaiV 2026-05-13 23:27:02 +08:00
parent 841d2cb763
commit a170ae37ab
8 changed files with 417 additions and 208 deletions

View File

@ -154,7 +154,7 @@
v-else-if="isGroupNotification(message.type)"
class="px-4 py-3 text-12px text-center italic text-[var(--el-text-color-secondary)] border-b border-[var(--el-border-color-lighter)]"
>
<TipSegments :segments="resolveGroupNotificationSegments(message)" />
<TipSegments :segments="resolveGroupNotificationSegments(message, resolveGroupMemberName(message))" />
</div>
<!-- 普通消息行 -->
@ -252,11 +252,13 @@ import { IM_MERGE_DETAIL_DIALOG_KEY } from './forward/keys'
import {
getMemberDisplayName,
getSenderDisplayName,
getSenderRealNickname,
getSenderRealNickname
} from '@/views/im/utils/user'
import {
resolveFriendNotificationSegments,
resolveFriendNotificationText,
resolveGroupNotificationSegments
} from '@/views/im/utils/user'
} from '@/views/im/utils/message'
import {
buildFacePreviewText,
buildRecallTip,
@ -322,6 +324,12 @@ function senderDisplayNameOf(message: Message): string {
)
}
/** 群广播事件 segments 的成员名解析器;按当前会话 targetId 走 getSenderDisplayName */
function resolveGroupMemberName(message: Message): (userId: number) => string {
return (id: number) =>
getSenderDisplayName(id, ImConversationType.GROUP, message.targetId ?? 0)
}
/** 单条消息的发送人真实昵称:给 UserAvatar 色卡 / alt 用,永远是 nickname 不掺备注 */
function senderRealNicknameOf(message: Message): string {
return getSenderRealNickname(

View File

@ -23,6 +23,40 @@
<TipSegments :segments="groupNotificationSegments" />
</div>
<!-- 群通话事件RTC_CALL_START / RTC_CALL_END居中灰色 tip 两段式 -->
<div
v-else-if="isRtcCallTipMessage"
class="flex items-center justify-center px-4 py-2 text-12px text-[var(--el-text-color-secondary)]"
>
<TipSegments :segments="rtcCallTipSegments" />
</div>
<!-- 私聊通话结束RTC_CALL_END仿微信准气泡 selfSend 左右分布电话图标 + 文案 -->
<div v-else-if="isRtcCallPrivateBubbleMessage" class="flex gap-2 items-start px-4 py-2">
<div
class="flex flex-1 min-w-0 gap-2 items-start"
:class="{ 'flex-row-reverse': message.selfSend }"
>
<UserAvatar
:id="message.selfSend ? userStore.getUser?.id : message.senderId"
:name="senderRealNickname"
:url="message.selfSend ? userStore.getUser?.avatar : senderAvatar"
:size="36"
/>
<div
class="flex gap-2 items-center px-3.5 py-2 text-sm rounded-lg"
:class="
message.selfSend
? 'text-black bg-[#95ec69]'
: 'text-[var(--el-text-color-primary)] bg-[var(--el-fill-color-light)]'
"
>
<Icon icon="ant-design:phone-outlined" :size="16" class="rotate-[135deg] flex-shrink-0" />
<span class="whitespace-nowrap">{{ rtcCallPrivateBubbleText }}</span>
</div>
</div>
</div>
<!-- 撤回消息整行灰色 tipsender 名段可点击 -->
<div
v-else-if="isRecall"
@ -166,7 +200,8 @@ import {
isFriendChatTip,
isGroupNotification,
isMediaMessageType,
isNormalMessage
isNormalMessage,
isRtcCallTip
} from '@/views/im/utils/constants'
import {
MESSAGE_TIME_TIP_GAP_MS,
@ -197,10 +232,15 @@ import {
getMemberDisplayName,
getMentionCandidates,
getSenderDisplayName,
getSenderRealNickname,
resolveFriendNotificationSegments,
resolveGroupNotificationSegments
getSenderRealNickname
} from '@/views/im/utils/user'
import {
resolveFriendNotificationSegments,
resolveGroupNotificationSegments,
resolveRtcCallTipSegments,
resolveRtcCallPrivateBubbleText,
parseRtcCallPayload
} from '@/views/im/utils/message'
import { useImUiStore } from '../../../../store/uiStore'
import { useMessageSender } from '../../../../composables/useMessageSender'
import { mediaTypeHandlers, useMediaUploader } from '../../../../composables/useMediaUploader'
@ -294,7 +334,39 @@ const friendChatTipSegments = computed(() => resolveFriendNotificationSegments(p
const isGroupNotificationMessage = computed(() => isGroupNotification(props.message.type))
/** 群广播事件 segments */
const groupNotificationSegments = computed(() => resolveGroupNotificationSegments(props.message))
const groupNotificationSegments = computed(() =>
resolveGroupNotificationSegments(props.message, (id: number) =>
getSenderDisplayName(id, ImConversationType.GROUP, props.message.targetId ?? 0)
)
)
/** 私聊 RTC_CALL_END 走「准气泡」(左右分布 + 电话图标 + 文案);非私聊场景为 null */
const rtcCallEndPrivatePayload = computed(() => {
if (props.message.type !== ImMessageType.RTC_CALL_END) {
return null
}
const payload = parseRtcCallPayload(props.message.content)
return payload?.conversationType === ImConversationType.PRIVATE ? payload : null
})
/** 是否私聊通话气泡 */
const isRtcCallPrivateBubbleMessage = computed(() => rtcCallEndPrivatePayload.value !== null)
/** 私聊通话气泡文案(按 operatorUserId 是否当前用户区分;对齐微信两端不同视角) */
const rtcCallPrivateBubbleText = computed(() =>
resolveRtcCallPrivateBubbleText(rtcCallEndPrivatePayload.value)
)
/** 是否会话内通话事件居中 tip仅群聊场景START 总是群聊END 私聊走气泡分支,群聊走 tip */
const isRtcCallTipMessage = computed(() => {
if (!isRtcCallTip(props.message.type)) {
return false
}
return !isRtcCallPrivateBubbleMessage.value
})
/** 通话事件 segments仅群聊 tip 用 */
const rtcCallTipSegments = computed(() => resolveRtcCallTipSegments(props.message))
// ==================== / payload ====================

View File

@ -23,7 +23,8 @@ import {
setQuietly,
StorageKeys
} from '../../utils/storage'
import { getGroupDisplayName, type GroupNotificationPayload } from '../../utils/user'
import { getGroupDisplayName } from '../../utils/user'
import { type GroupNotificationPayload } from '../../utils/message'
import type { Group, GroupMember, Message } from '../types'
/**

View File

@ -134,6 +134,8 @@ import CardLineLabel from '@/views/im/home/components/card/CardLineLabel.vue'
import {
parseMessage,
getFileIconInfo,
resolveFriendNotificationText,
resolveGroupNotificationText,
type ImageMessage,
type FileMessage,
type AudioMessage,
@ -143,10 +145,6 @@ import {
type FaceMessage,
type MergeMessage
} from '@/views/im/utils/message'
import {
resolveFriendNotificationText,
resolveGroupNotificationText
} from '@/views/im/utils/user'
import { buildFacePreviewText, summarizeMessageContent } from '@/views/im/utils/conversation'
defineOptions({ name: 'ImMessageContentPreview' })
@ -242,7 +240,7 @@ const friendChatTipText = computed(() => resolveFriendNotificationText({ type: p
/** 是否群广播事件 */
const isGroupNotificationType = computed(() => isGroupNotification(props.type ?? -1))
/** 群广播事件文案:复用 utils/user.resolveGroupNotificationTextadmin 端 operator 用 senderNickname 直接覆盖,其它 id 退化为 用户(id) */
/** 群广播事件文案:admin 端 operator 用 senderNickname 直接覆盖,其它 id 退化为 用户(id) */
const groupNotificationText = computed(() =>
resolveGroupNotificationText(
{ type: props.type, content: props.content },

View File

@ -6,7 +6,7 @@
// 2. fallbackName 由调用方传入典型来源Conversation.lastSenderDisplayName 快照),透传到 getSenderDisplayName 内部,算不出真名时兜底
// ====================================================================
import { ImMessageType, isFriendChatTip, isGroupNotification } from './constants'
import { ImConversationType, ImMessageType, isFriendChatTip, isGroupNotification } from './constants'
import {
getCardLabelInfo,
parseMessage,
@ -19,11 +19,8 @@ import {
type TextMessage,
type TipSegment
} from './message'
import {
getSenderDisplayName,
resolveFriendNotificationText,
resolveGroupNotificationText
} from './user'
import { getSenderDisplayName } from './user'
import { resolveFriendNotificationText, resolveGroupNotificationText } from './message'
import type { Message } from '../home/types'
/** 会话主键:`type-targetId` 拼成稳定字符串,给 v-for :key、active 比对、map key 等场景共用 */
@ -151,7 +148,9 @@ export function resolveConversationLastContent(
return resolveFriendNotificationText(message)
}
if (isGroupNotification(message.type)) {
return resolveGroupNotificationText(message)
return resolveGroupNotificationText(message, (id) =>
getSenderDisplayName(id, ImConversationType.GROUP, message.targetId ?? 0)
)
}
return summarizeMessageContent(message)
}

View File

@ -1,7 +1,13 @@
import { generateUUID } from '@/utils'
import { useUserStore } from '@/store/modules/user'
import { ImConversationType, ImMessageType, type ImConversationTypeValue } from './constants'
import {
ImCallEndReason,
ImConversationType,
ImMessageType,
type ImConversationTypeValue
} from './constants'
import { getCurrentUserId } from './storage'
import { formatCallDuration } from './time'
import { useFriendStore } from '../home/store/friendStore'
import { useGroupStore } from '../home/store/groupStore'
import type { Conversation, Message, User, GroupLite } from '../home/types'
@ -644,3 +650,303 @@ export const formatJson = (content?: string): string => {
return content
}
}
// ==================== 群广播事件 payload + 文案 ====================
// 群广播事件 payload对齐后端 GroupNotificationMessage 子类聚合字段
export type GroupNotificationPayload = {
operatorUserId?: number
memberUserIds?: number[]
newOwnerUserId?: number
oldName?: string
newName?: string
oldNotice?: string
newNotice?: string
oldAvatar?: string
newAvatar?: string
displayUserName?: string
messageId?: number
// 禁言事件
mutedUserId?: number
muteEndTime?: string
// 全群封禁
banned?: boolean
// 自由进群事件
entrantUserId?: number
addSource?: number
// PIN 事件携带的完整被置顶消息对象
message?: {
id: number
clientMessageId?: string
senderId: number
groupId: number
type: number
content: string
status: number
sendTime: string
atUserIds?: number[]
receiverUserIds?: number[]
receiptStatus?: number
readCount?: number
}
}
/**
* 广 segments
*
* resolveName getSenderDisplayName
* operatorNameOverride operator mention userId payload.operatorUserId
*/
export function resolveGroupNotificationSegments(
message: { type?: number; content?: string; targetId?: number },
resolveName: (userId: number) => string,
operatorNameOverride?: string
): TipSegment[] {
let payload: GroupNotificationPayload = {}
try {
payload = JSON.parse(message.content || '{}')
} catch {
return []
}
// ENTER 主语是 entrant 而非 operator独立处理其它 case 都以 operatorUserId 为主语
if (message.type === ImMessageType.GROUP_MEMBER_ENTER) {
const entrantId = payload.entrantUserId ?? payload.operatorUserId
return entrantId
? [tipMention(entrantId, resolveName(entrantId)), tipText(' 加入了群聊')]
: []
}
if (!payload.operatorUserId) {
return []
}
const operatorSegment = tipMention(
payload.operatorUserId,
operatorNameOverride ?? resolveName(payload.operatorUserId)
)
const memberSegments = joinMentionSegments(payload.memberUserIds || [], '、', resolveName)
switch (message.type) {
case ImMessageType.GROUP_CREATE:
return [operatorSegment, tipText(' 创建了群聊')]
case ImMessageType.GROUP_NAME_UPDATE:
return [operatorSegment, tipText(` 将群名修改为 "${payload.newName ?? ''}"`)]
case ImMessageType.GROUP_NOTICE_UPDATE:
return [operatorSegment, tipText(' 更新了群公告')]
case ImMessageType.GROUP_INFO_UPDATE:
return payload.newAvatar
? [operatorSegment, tipText(' 更换了群头像')]
: [operatorSegment, tipText(' 更新了群信息')]
case ImMessageType.GROUP_DISSOLVE:
return [operatorSegment, tipText(' 解散了群聊')]
case ImMessageType.GROUP_MEMBER_INVITE:
return [operatorSegment, tipText(' 邀请 '), ...memberSegments, tipText(' 加入群聊')]
case ImMessageType.GROUP_MEMBER_QUIT:
return [operatorSegment, tipText(' 退出了群聊')]
case ImMessageType.GROUP_MEMBER_KICK:
return [operatorSegment, tipText(' 移出了 '), ...memberSegments]
case ImMessageType.GROUP_MEMBER_NICKNAME_UPDATE:
return [operatorSegment, tipText(` 修改群昵称为 "${payload.displayUserName ?? ''}"`)]
case ImMessageType.GROUP_ADMIN_ADD:
return [operatorSegment, tipText(' 将 '), ...memberSegments, tipText(' 设为管理员')]
case ImMessageType.GROUP_ADMIN_REMOVE:
return [operatorSegment, tipText(' 撤销了 '), ...memberSegments, tipText(' 的管理员身份')]
case ImMessageType.GROUP_OWNER_TRANSFER:
return payload.newOwnerUserId
? [
operatorSegment,
tipText(' 已将群主转让给 '),
tipMention(payload.newOwnerUserId, resolveName(payload.newOwnerUserId))
]
: []
case ImMessageType.GROUP_MESSAGE_PIN:
return [operatorSegment, tipText(' 置顶了一条消息')]
case ImMessageType.GROUP_MESSAGE_UNPIN:
return [operatorSegment, tipText(' 取消了一条置顶消息')]
case ImMessageType.GROUP_MEMBER_MUTED:
return payload.mutedUserId
? [
operatorSegment,
tipText(' 将 '),
tipMention(payload.mutedUserId, resolveName(payload.mutedUserId)),
tipText(' 禁言')
]
: []
case ImMessageType.GROUP_MEMBER_CANCEL_MUTED:
return payload.mutedUserId
? [
operatorSegment,
tipText(' 解除了 '),
tipMention(payload.mutedUserId, resolveName(payload.mutedUserId)),
tipText(' 的禁言')
]
: []
case ImMessageType.GROUP_MUTED:
return [operatorSegment, tipText(' 开启了全群禁言')]
case ImMessageType.GROUP_CANCEL_MUTED:
return [operatorSegment, tipText(' 关闭了全群禁言')]
case ImMessageType.GROUP_BANNED:
return [operatorSegment, tipText(payload.banned ? ' 封禁了该群' : ' 解封了该群')]
default:
return []
}
}
/** 群广播事件中文文案 */
export function resolveGroupNotificationText(
message: { type?: number; content?: string; targetId?: number },
resolveName: (userId: number) => string,
operatorNameOverride?: string
): string {
return segmentsToText(
resolveGroupNotificationSegments(message, resolveName, operatorNameOverride)
)
}
// ==================== 好友事件 ====================
/** 会话内好友事件 segments */
export function resolveFriendNotificationSegments(message: { type?: number }): TipSegment[] {
switch (message.type) {
case ImMessageType.FRIEND_ADD:
return [tipText('你们已经是好友了,开始聊天吧')]
case ImMessageType.FRIEND_DELETE:
return [tipText('你已删除好友')]
default:
return []
}
}
/** 会话内好友事件文案FRIEND_ADD / FRIEND_DELETE 渲染成灰色提示气泡,文案固定不依赖 payload */
export function resolveFriendNotificationText(message: { type?: number }): string {
return segmentsToText(resolveFriendNotificationSegments(message))
}
// ==================== RTC 通话事件 ====================
// RTC_CALL_START payload仅群聊用于聊天 tip 文案「{inviter} 发起了{voice/video}通话」
export type RtcCallStartPayload = {
room?: string
conversationType?: number
mediaType?: number
inviterUserId?: number
inviterNickname?: string
inviterAvatar?: string
}
// RTC_CALL_END payload私聊准气泡 + 群聊「通话已结束 [时长 X]」共用
export type RtcCallEndPayload = {
room?: string
conversationType?: number
mediaType?: number
endReason?: number
durationSeconds?: number
operatorUserId?: number
operatorNickname?: string
operatorAvatar?: string
}
/** 解析 RTC_CALL_START / RTC_CALL_END 消息 content解析失败返回 null */
export function parseRtcCallPayload(
content?: string
): (RtcCallStartPayload & RtcCallEndPayload) | null {
return content ? parseMessage<RtcCallStartPayload & RtcCallEndPayload>(content) : null
}
/** 媒体类型文案TODO 字典化 */
function callMediaText(mediaType: number | undefined): string {
return mediaType === 2 ? '视频' : '语音'
}
/**
* segmentsRTC_CALL_START / RTC_CALL_END
* <p>
* START{inviter} {voice/video}+ END{voice/video} [ X]
* <p>
* {@link resolveRtcCallPrivateBubbleText}
*/
export function resolveRtcCallTipSegments(message: {
type?: number
content?: string
selfSend?: boolean
}): TipSegment[] {
const payload = parseRtcCallPayload(message.content)
if (!payload) {
return []
}
const media = callMediaText(payload.mediaType)
if (message.type === ImMessageType.RTC_CALL_START) {
const inviter = payload.inviterNickname?.trim() || `用户 ${payload.inviterUserId ?? ''}`
return [tipText(`${inviter} 发起了${media}通话`)]
}
if (message.type === ImMessageType.RTC_CALL_END) {
if (payload.durationSeconds && payload.durationSeconds > 0) {
return [tipText(`${media}通话已结束(时长 ${formatCallDuration(payload.durationSeconds)}`)]
}
return [tipText(`${media}通话已结束`)]
}
return []
}
/**
* RTC_CALL_END operatorUserId
* <p>
*
* HANGUP duration > 0 N
* HANGUP duration 0
* CANCEL /
* REJECT /
* BUSY 线/ 线
* ERROR [N]duration > 0
*/
export function resolveRtcCallPrivateBubbleText(payload: RtcCallEndPayload | null): string {
if (!payload) {
return '通话已结束'
}
const duration = payload.durationSeconds ?? 0
const hasDuration = duration > 0
const isOperator = payload.operatorUserId === getCurrentUserId()
switch (payload.endReason) {
case ImCallEndReason.HANGUP:
return hasDuration ? `通话时长 ${formatCallDuration(duration)}` : '通话中断'
case ImCallEndReason.CANCEL:
return isOperator ? '已取消' : '对方已取消'
case ImCallEndReason.REJECT:
return isOperator ? '已拒绝' : '对方已拒绝'
case ImCallEndReason.BUSY:
return isOperator ? '忙线未接听' : '对方忙线中'
case ImCallEndReason.ERROR:
return hasDuration ? `通话中断 ${formatCallDuration(duration)}` : '通话中断'
default:
return hasDuration ? `通话时长 ${formatCallDuration(duration)}` : '通话已结束'
}
}
/** 会话内通话事件文案 */
export function resolveRtcCallTipText(message: {
type?: number
content?: string
selfSend?: boolean
}): string {
return segmentsToText(resolveRtcCallTipSegments(message))
}
/**
* RTC_CALL_END toast / console
* <p>
* operator + WS push {@link resolveRtcCallPrivateBubbleText}
*/
export function resolveCallEndReasonText(reason: number | undefined): string {
switch (reason) {
case ImCallEndReason.REJECT:
return '对方已拒绝'
case ImCallEndReason.CANCEL:
return '对方已取消'
case ImCallEndReason.BUSY:
return '对方忙线中'
case ImCallEndReason.HANGUP:
return '通话已结束'
case ImCallEndReason.ERROR:
return '通话异常'
default:
return '通话已断开'
}
}

View File

@ -88,3 +88,13 @@ export function formatMergeItemTime(timestamp: number): string {
}
return dayjs(timestamp).format('MM-DD HH:mm')
}
/** RTC 通话时长(秒)→ "00:06" / "1:23:45" */
export function formatCallDuration(seconds: number | undefined): string {
const total = Math.max(0, Math.floor(seconds || 0))
const h = Math.floor(total / 3600)
const m = Math.floor((total % 3600) / 60)
const s = total % 60
const pad = (n: number) => String(n).padStart(2, '0')
return h > 0 ? `${h}:${pad(m)}:${pad(s)}` : `${pad(m)}:${pad(s)}`
}

View File

@ -16,19 +16,11 @@ import { SystemUserSexEnum } from '@/utils/constants'
import {
ImConversationType,
ImFriendAddSource,
ImMessageType,
IM_AT_ALL_NICKNAME,
IM_AT_ALL_USER_ID
} from './constants'
import { getCurrentUserId } from './storage'
import {
joinMentionSegments,
segmentsToText,
tipMention,
tipText,
type MentionCandidate,
type TipSegment
} from './message'
import { type MentionCandidate } from './message'
import { useConversationStore } from '../home/store/conversationStore'
import { useFriendStore } from '../home/store/friendStore'
import { useGroupStore } from '../home/store/groupStore'
@ -301,183 +293,6 @@ export function openMentionUserInfoCardAtEvent(
)
}
/**
* 广GROUP_*
*
* message.type content payload getSenderDisplayName / /
* store resolveName id senderNickname + (id)
* home MessageItem.vue / ConversationItem.vue / MessageHistory.vue admin MessageContentPreview.vue
*/
export type GroupNotificationPayload = {
operatorUserId?: number
memberUserIds?: number[]
newOwnerUserId?: number
oldName?: string
newName?: string
oldNotice?: string
newNotice?: string
oldAvatar?: string
newAvatar?: string
displayUserName?: string
messageId?: number
mutedUserId?: number // 禁言目标用户
muteEndTime?: string // 禁言到期时间
banned?: boolean // 封禁状态
entrantUserId?: number // 自由进群事件:进群者用户编号
addSource?: number // 自由进群事件:来源(搜索 / 二维码 / 分享链接)
/** PIN 事件携带的完整被置顶消息对象 */
message?: {
id: number
clientMessageId?: string
senderId: number
groupId: number
type: number
content: string
status: number
sendTime: string
atUserIds?: number[]
receiverUserIds?: number[]
receiptStatus?: number
readCount?: number
}
}
/**
* 广 segments
*
* resolveName getSenderDisplayName resolver
* operatorNameOverride operator mention userId payload.operatorUserId
*/
export function resolveGroupNotificationSegments(
message: { type?: number; content?: string; targetId?: number },
resolveName?: (userId: number) => string,
operatorNameOverride?: string
): TipSegment[] {
let payload: GroupNotificationPayload = {}
try {
payload = JSON.parse(message.content || '{}')
} catch {
return []
}
const resolve =
resolveName ||
((id: number) => getSenderDisplayName(id, ImConversationType.GROUP, message.targetId ?? 0))
// ENTER 主语是 entrant 而非 operator独立处理其它 case 都以 operatorUserId 为主语
if (message.type === ImMessageType.GROUP_MEMBER_ENTER) {
const entrantId = payload.entrantUserId ?? payload.operatorUserId
return entrantId ? [tipMention(entrantId, resolve(entrantId)), tipText(' 加入了群聊')] : []
}
if (!payload.operatorUserId) {
return []
}
const operatorSegment = tipMention(
payload.operatorUserId,
operatorNameOverride ?? resolve(payload.operatorUserId)
)
const memberSegments = joinMentionSegments(payload.memberUserIds || [], '、', resolve)
switch (message.type) {
case ImMessageType.GROUP_CREATE:
return [operatorSegment, tipText(' 创建了群聊')]
case ImMessageType.GROUP_NAME_UPDATE:
return [operatorSegment, tipText(` 将群名修改为 "${payload.newName ?? ''}"`)]
case ImMessageType.GROUP_NOTICE_UPDATE:
return [operatorSegment, tipText(' 更新了群公告')]
case ImMessageType.GROUP_INFO_UPDATE:
return payload.newAvatar
? [operatorSegment, tipText(' 更换了群头像')]
: [operatorSegment, tipText(' 更新了群信息')]
case ImMessageType.GROUP_DISSOLVE:
return [operatorSegment, tipText(' 解散了群聊')]
case ImMessageType.GROUP_MEMBER_INVITE:
return [operatorSegment, tipText(' 邀请 '), ...memberSegments, tipText(' 加入群聊')]
case ImMessageType.GROUP_MEMBER_QUIT:
return [operatorSegment, tipText(' 退出了群聊')]
case ImMessageType.GROUP_MEMBER_KICK:
return [operatorSegment, tipText(' 移出了 '), ...memberSegments]
case ImMessageType.GROUP_MEMBER_NICKNAME_UPDATE:
return [operatorSegment, tipText(` 修改群昵称为 "${payload.displayUserName ?? ''}"`)]
case ImMessageType.GROUP_ADMIN_ADD:
return [operatorSegment, tipText(' 将 '), ...memberSegments, tipText(' 设为管理员')]
case ImMessageType.GROUP_ADMIN_REMOVE:
return [
operatorSegment,
tipText(' 撤销了 '),
...memberSegments,
tipText(' 的管理员身份')
]
case ImMessageType.GROUP_OWNER_TRANSFER:
return payload.newOwnerUserId
? [
operatorSegment,
tipText(' 已将群主转让给 '),
tipMention(payload.newOwnerUserId, resolve(payload.newOwnerUserId))
]
: []
case ImMessageType.GROUP_MESSAGE_PIN:
return [operatorSegment, tipText(' 置顶了一条消息')]
case ImMessageType.GROUP_MESSAGE_UNPIN:
return [operatorSegment, tipText(' 取消了一条置顶消息')]
case ImMessageType.GROUP_MEMBER_MUTED:
return payload.mutedUserId
? [
operatorSegment,
tipText(' 将 '),
tipMention(payload.mutedUserId, resolve(payload.mutedUserId)),
tipText(' 禁言')
]
: []
case ImMessageType.GROUP_MEMBER_CANCEL_MUTED:
return payload.mutedUserId
? [
operatorSegment,
tipText(' 解除了 '),
tipMention(payload.mutedUserId, resolve(payload.mutedUserId)),
tipText(' 的禁言')
]
: []
case ImMessageType.GROUP_MUTED:
return [operatorSegment, tipText(' 开启了全群禁言')]
case ImMessageType.GROUP_CANCEL_MUTED:
return [operatorSegment, tipText(' 关闭了全群禁言')]
case ImMessageType.GROUP_BANNED:
return [operatorSegment, tipText(payload.banned ? ' 封禁了该群' : ' 解封了该群')]
default:
return []
}
}
/** 群广播事件中文文案 */
export function resolveGroupNotificationText(
message: { type?: number; content?: string; targetId?: number },
resolveName?: (userId: number) => string,
operatorNameOverride?: string
): string {
return segmentsToText(
resolveGroupNotificationSegments(message, resolveName, operatorNameOverride)
)
}
/** 会话内好友事件 segments */
export function resolveFriendNotificationSegments(message: {
type?: number
}): TipSegment[] {
switch (message.type) {
case ImMessageType.FRIEND_ADD:
return [tipText('你们已经是好友了,开始聊天吧')]
case ImMessageType.FRIEND_DELETE:
return [tipText('你已删除好友')]
default:
return []
}
}
/** 会话内好友事件文案FRIEND_ADD / FRIEND_DELETE 渲染成灰色提示气泡,文案固定不依赖 payload */
export function resolveFriendNotificationText(message: { type?: number }): string {
return segmentsToText(resolveFriendNotificationSegments(message))
}
/** 性别图标UNKNOWN / null / undefined 一律不展示,对齐微信留白 */
export function getGenderIcon(sex?: number): string {
if (sex === SystemUserSexEnum.MALE) {