diff --git a/src/views/im/home/pages/conversation/components/message/MessageHistory.vue b/src/views/im/home/pages/conversation/components/message/MessageHistory.vue
index f149263e4..6260b60e7 100644
--- a/src/views/im/home/pages/conversation/components/message/MessageHistory.vue
+++ b/src/views/im/home/pages/conversation/components/message/MessageHistory.vue
@@ -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)]"
>
-
+
@@ -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(
diff --git a/src/views/im/home/pages/conversation/components/message/MessageItem.vue b/src/views/im/home/pages/conversation/components/message/MessageItem.vue
index 24a88f7f5..3d7b02419 100644
--- a/src/views/im/home/pages/conversation/components/message/MessageItem.vue
+++ b/src/views/im/home/pages/conversation/components/message/MessageItem.vue
@@ -23,6 +23,40 @@
+
+
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 ====================
diff --git a/src/views/im/home/store/groupStore.ts b/src/views/im/home/store/groupStore.ts
index af6e8cffa..594709149 100644
--- a/src/views/im/home/store/groupStore.ts
+++ b/src/views/im/home/store/groupStore.ts
@@ -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'
/**
diff --git a/src/views/im/manager/message/MessageContentPreview.vue b/src/views/im/manager/message/MessageContentPreview.vue
index 50a95c41d..527d0e3c4 100644
--- a/src/views/im/manager/message/MessageContentPreview.vue
+++ b/src/views/im/manager/message/MessageContentPreview.vue
@@ -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.resolveGroupNotificationText;admin 端 operator 用 senderNickname 直接覆盖,其它 id 退化为 用户(id) */
+/** 群广播事件文案:admin 端 operator 用 senderNickname 直接覆盖,其它 id 退化为 用户(id) */
const groupNotificationText = computed(() =>
resolveGroupNotificationText(
{ type: props.type, content: props.content },
diff --git a/src/views/im/utils/conversation.ts b/src/views/im/utils/conversation.ts
index 572db6b5a..652d2e1a0 100644
--- a/src/views/im/utils/conversation.ts
+++ b/src/views/im/utils/conversation.ts
@@ -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)
}
diff --git a/src/views/im/utils/message.ts b/src/views/im/utils/message.ts
index 75236c84c..0b9f3a043 100644
--- a/src/views/im/utils/message.ts
+++ b/src/views/im/utils/message.ts
@@ -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
(content) : null
+}
+
+/** 媒体类型文案;TODO 字典化 */
+function callMediaText(mediaType: number | undefined): string {
+ return mediaType === 2 ? '视频' : '语音'
+}
+
+/**
+ * 会话内通话事件 segments(RTC_CALL_START / RTC_CALL_END)
+ *
+ * 群聊两段式:START「{inviter} 发起了{voice/video}通话」+ END「{voice/video}通话已结束 [时长 X]」
+ *
+ * 私聊气泡走 {@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 是不是自己渲染两端不同文案(对齐微信)
+ *
+ * 文案规则:
+ * 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 兜底用
+ *
+ * 缺 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 '通话已断开'
+ }
+}
diff --git a/src/views/im/utils/time.ts b/src/views/im/utils/time.ts
index 3848f81a3..a84bdac21 100644
--- a/src/views/im/utils/time.ts
+++ b/src/views/im/utils/time.ts
@@ -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)}`
+}
diff --git a/src/views/im/utils/user.ts b/src/views/im/utils/user.ts
index 5d76335a6..0260798ba 100644
--- a/src/views/im/utils/user.ts
+++ b/src/views/im/utils/user.ts
@@ -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) {