feat(im): 重构群通知相关,对齐 openim 的消息编号(继续优化代码)

im
YunaiV 2026-05-03 09:22:53 +08:00
parent 43372c05ad
commit ffb69063b9
8 changed files with 169 additions and 143 deletions

View File

@ -626,10 +626,11 @@ async function handleRemoveComplete(members: GroupMemberFlag[]) {
// ---------- ---------- // ---------- ----------
/** 当前管理员的 userId 列表,作为 Selector 默认勾选 */ /** 当前管理员的 userId 列表,作为 Selector 默认勾选;过滤已退群成员,避免 maxSize 名额被隐藏成员占用导致无法新增管理员 */
const adminCheckedIds = computed(() => const adminCheckedIds = computed(() =>
props.members props.members
.filter((member) => member.role === ImGroupMemberRole.ADMIN) .filter((member) => member.role === ImGroupMemberRole.ADMIN
&& member.status !== CommonStatusEnum.DISABLE)
.map((member) => member.userId) .map((member) => member.userId)
) )

View File

@ -152,7 +152,7 @@
{{ resolveTipText(message.content) }} {{ resolveTipText(message.content) }}
</div> </div>
<!-- 群广播事件文案1501-1520 / 1530 TIP_TEXT 同样的居中灰色样式 --> <!-- 群广播事件文案 TIP_TEXT 同样的居中灰色样式 -->
<div <div
v-else-if="isGroupNotification(message.type)" 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)]" class="px-4 py-3 text-12px text-center italic text-[var(--el-text-color-secondary)] border-b border-[var(--el-border-color-lighter)]"

View File

@ -18,7 +18,7 @@
{{ tipText }} {{ tipText }}
</div> </div>
<!-- 群广播事件1501-1520 / 1530 TIP_TEXT 同样的居中灰色样式文案按 type 拼装 --> <!-- 群广播事件 TIP_TEXT 同样的居中灰色样式文案按 type 拼装 -->
<div <div
v-else-if="isGroupNotificationMessage" v-else-if="isGroupNotificationMessage"
class="flex items-center justify-center px-4 py-2 text-12px text-[var(--el-text-color-secondary)]" class="flex items-center justify-center px-4 py-2 text-12px text-[var(--el-text-color-secondary)]"
@ -363,7 +363,7 @@ const textContent = computed(() => parseMessage<TextMessage>(props.message.conte
/** TIP_TEXT 文案:与 conversationStore.resolveLastContent / MessageHistory.renderContent 共用 helper避免兼容性逻辑分裂 */ /** TIP_TEXT 文案:与 conversationStore.resolveLastContent / MessageHistory.renderContent 共用 helper避免兼容性逻辑分裂 */
const tipText = computed(() => resolveTipText(props.message.content)) const tipText = computed(() => resolveTipText(props.message.content))
/** 群广播事件1501-1520 / 1530 */ /** 群广播事件 */
const isGroupNotificationMessage = computed(() => isGroupNotification(props.message.type)) const isGroupNotificationMessage = computed(() => isGroupNotification(props.message.type))
const groupNotificationText = computed(() => resolveGroupNotificationText(props.message)) const groupNotificationText = computed(() => resolveGroupNotificationText(props.message))
const imagePayload = computed(() => const imagePayload = computed(() =>

View File

@ -21,7 +21,7 @@ import {
setQuietly, setQuietly,
StorageKeys StorageKeys
} from '../../utils/storage' } from '../../utils/storage'
import { getGroupDisplayName } from '../../utils/user' import { getGroupDisplayName, type GroupNotificationPayload } from '../../utils/user'
import type { Group, GroupMember } from '../types' import type { Group, GroupMember } from '../types'
/** /**
@ -38,9 +38,16 @@ const pendingMemberKey = (userId: number, groupId: number) => `${userId}:${group
* fetch fetch me muted * fetch fetch me muted
*/ */
const pendingSingleMemberFetches = new Map<string, Promise<GroupMember | null>>() const pendingSingleMemberFetches = new Map<string, Promise<GroupMember | null>>()
const pendingSingleMemberKey = (userId: number, groupId: number, memberUserId: number) => const pendingSingleMemberKey = (userId: number, groupId: number, memberUserId: number) =>
`${userId}:${groupId}:${memberUserId}` `${userId}:${groupId}:${memberUserId}`
/** 判断当前用户是否在 payload.memberUserIds 里GROUP_CREATE / INVITE / KICK 自判用) */
function isSelfInPayloadMembers(payload: GroupNotificationPayload): boolean {
const selfUserId = getCurrentUserId()
return !!selfUserId && (payload.memberUserIds || []).includes(selfUserId)
}
/** /**
* IM Store * IM Store
* *
@ -458,12 +465,16 @@ export const useGroupStore = defineStore('imGroupStore', {
this.saveGroupMembers(groupId) this.saveGroupMembers(groupId)
}, },
/** 局部更新群字段name / notice / avatar 等);未命中本地缓存时静默忽略,等 fetchGroups 兜底 */ /** 局部更新群字段name / notice / avatar 等);未命中本地缓存时静默忽略,等 fetchGroups 兜底;新值跟旧值都相同时跳过响应式 + IDB 写 */
updateGroupFields(groupId: number, fields: Partial<Group>) { updateGroupFields(groupId: number, fields: Partial<Group>) {
const group = this.getGroup(groupId) const group = this.getGroup(groupId)
if (!group) { if (!group) {
return return
} }
const changed = (Object.keys(fields) as (keyof Group)[]).some((k) => group[k] !== fields[k])
if (!changed) {
return
}
Object.assign(group, fields) Object.assign(group, fields)
const conversationStore = useConversationStore() const conversationStore = useConversationStore()
conversationStore.updateConversation(ImConversationType.GROUP, groupId, { conversationStore.updateConversation(ImConversationType.GROUP, groupId, {
@ -475,7 +486,7 @@ export const useGroupStore = defineStore('imGroupStore', {
}, },
/** /**
* GROUP_* 广 type mutation * GROUP_* 广 type action
* *
* WebSocket + useMessagePuller 线 pull conversationStore.insertMessage * WebSocket + useMessagePuller 线 pull conversationStore.insertMessage
* store fetchGroups * store fetchGroups
@ -484,84 +495,44 @@ export const useGroupStore = defineStore('imGroupStore', {
if (!groupId) { if (!groupId) {
return return
} }
let payload: Record<string, any> = {} let payload: GroupNotificationPayload = {}
try { try {
payload = content ? JSON.parse(content) : {} payload = content ? JSON.parse(content) : {}
} catch (error) { } catch (error) {
console.warn('[IM groupStore] applyGroupNotification 解析 content 失败', { groupId, type, content }, error) console.warn(
'[IM groupStore] applyGroupNotification 解析 content 失败',
{ groupId, type, contentLength: content?.length ?? 0 },
error
)
return return
} }
switch (type) { switch (type) {
case ImMessageType.GROUP_CREATE: { case ImMessageType.GROUP_CREATE:
// 创建群广播:创建者多端同步 + 初始成员 bootstrappayload.memberUserIds 含自己 → 拉群详情 / 成员 this.applyGroupCreateNotification(groupId, payload)
const selfUserId = getCurrentUserId()
const memberIds: number[] = payload.memberUserIds || []
if (selfUserId && memberIds.includes(selfUserId)) {
this.fetchGroupInfo(groupId).catch(() => undefined)
this.fetchGroupMembers(groupId, true).catch(() => undefined)
}
break break
}
case ImMessageType.GROUP_NAME_UPDATE: case ImMessageType.GROUP_NAME_UPDATE:
if (payload.newName) { this.applyGroupNameUpdateNotification(groupId, payload)
this.updateGroupFields(groupId, { name: payload.newName })
}
break break
case ImMessageType.GROUP_NOTICE_UPDATE: case ImMessageType.GROUP_NOTICE_UPDATE:
this.updateGroupFields(groupId, { notice: payload.notice ?? '' }) this.applyGroupNoticeUpdateNotification(groupId, payload)
break break
case ImMessageType.GROUP_INFO_UPDATE: { case ImMessageType.GROUP_INFO_UPDATE:
// 兜底 NAME / NOTICE 之外的群字段变更avatar 等);按非 null 字段累积更新 this.applyGroupInfoUpdateNotification(groupId, payload)
const fields: Partial<Group> = {}
if (payload.avatar) {
fields.avatar = payload.avatar
}
if (Object.keys(fields).length > 0) {
this.updateGroupFields(groupId, fields)
}
break break
}
case ImMessageType.GROUP_DISSOLVE: case ImMessageType.GROUP_DISSOLVE:
// 群解散:所有成员(含群主)收到广播 → 本端自行清群
this.removeGroup(groupId) this.removeGroup(groupId)
break break
case ImMessageType.GROUP_MEMBER_INVITE: { case ImMessageType.GROUP_MEMBER_INVITE:
// 被邀请者:本端 group 还没就位,先 fetchGroupInfo bootstrap再拉成员 this.applyGroupMemberInviteNotification(groupId, payload)
// 已在群成员:只刷成员列表(新成员 nickname / avatar 不在 payload必须 fetch
const selfUserId = getCurrentUserId()
const memberIds: number[] = payload.memberUserIds || []
const selfInvited = !!selfUserId && memberIds.includes(selfUserId)
if (selfInvited && !this.getGroup(groupId)) {
this.fetchGroupInfo(groupId).catch(() => undefined)
}
this.fetchGroupMembers(groupId, true).catch(() => undefined)
break break
} case ImMessageType.GROUP_MEMBER_QUIT:
case ImMessageType.GROUP_MEMBER_QUIT: { this.applyGroupMemberQuitNotification(groupId, payload)
// 退群者本人多端同步:清群;其他成员:从本地成员列表移除
const selfUserId = getCurrentUserId()
if (selfUserId && payload.operatorUserId === selfUserId) {
this.removeGroup(groupId)
} else if (payload.operatorUserId) {
this.removeMembersLocal(groupId, [payload.operatorUserId])
}
break break
} case ImMessageType.GROUP_MEMBER_KICK:
case ImMessageType.GROUP_MEMBER_KICK: { this.applyGroupMemberKickNotification(groupId, payload)
// 被踢者本人:清群;其他成员:从本地成员列表移除
const selfUserId = getCurrentUserId()
const memberIds: number[] = payload.memberUserIds || []
if (selfUserId && memberIds.includes(selfUserId)) {
this.removeGroup(groupId)
} else if (memberIds.length) {
this.removeMembersLocal(groupId, memberIds)
}
break break
}
case ImMessageType.GROUP_MEMBER_NICKNAME_UPDATE: case ImMessageType.GROUP_MEMBER_NICKNAME_UPDATE:
if (payload.operatorUserId) { this.applyGroupMemberNicknameUpdateNotification(groupId, payload)
this.updateMemberDisplayUserName(groupId, payload.operatorUserId, payload.displayUserName ?? '')
}
break break
case ImMessageType.GROUP_ADMIN_ADD: case ImMessageType.GROUP_ADMIN_ADD:
this.updateMembersRole(groupId, payload.memberUserIds || [], ImGroupMemberRole.ADMIN) this.updateMembersRole(groupId, payload.memberUserIds || [], ImGroupMemberRole.ADMIN)
@ -570,13 +541,94 @@ export const useGroupStore = defineStore('imGroupStore', {
this.updateMembersRole(groupId, payload.memberUserIds || [], ImGroupMemberRole.NORMAL) this.updateMembersRole(groupId, payload.memberUserIds || [], ImGroupMemberRole.NORMAL)
break break
case ImMessageType.GROUP_OWNER_TRANSFER: case ImMessageType.GROUP_OWNER_TRANSFER:
if (payload.operatorUserId && payload.newOwnerUserId) { this.applyGroupOwnerTransferNotification(groupId, payload)
this.transferOwner(groupId, payload.operatorUserId, payload.newOwnerUserId)
}
break break
} }
}, },
/** 创建群广播:创建者多端同步 + 初始成员 bootstrappayload.memberUserIds 含自己 → 拉群详情 / 成员;本端发起者已经 upsert 过本群,跳过避免双拉 */
applyGroupCreateNotification(groupId: number, payload: GroupNotificationPayload) {
if (!isSelfInPayloadMembers(payload)) {
return
}
const selfUserId = getCurrentUserId()
const selfIsOperator = !!selfUserId && payload.operatorUserId === selfUserId
if (selfIsOperator && this.getGroup(groupId)) {
return
}
this.fetchGroupInfo(groupId).catch(() => undefined)
this.fetchGroupMembers(groupId, true).catch(() => undefined)
},
/** 群名变更:按 newName 局部更新本地群名 */
applyGroupNameUpdateNotification(groupId: number, payload: GroupNotificationPayload) {
if (payload.newName) {
this.updateGroupFields(groupId, { name: payload.newName })
}
},
/** 群公告变更:按 newNotice 局部更新(允许空串作为「清空公告」) */
applyGroupNoticeUpdateNotification(groupId: number, payload: GroupNotificationPayload) {
this.updateGroupFields(groupId, { notice: payload.newNotice ?? '' })
},
/** 群信息变更NAME / NOTICE 之外字段,当前承载头像变更) */
applyGroupInfoUpdateNotification(groupId: number, payload: GroupNotificationPayload) {
const fields: Partial<Group> = {}
if (payload.newAvatar) {
fields.avatar = payload.newAvatar
}
if (Object.keys(fields).length > 0) {
this.updateGroupFields(groupId, fields)
}
},
/** 成员加入:被邀请者本端 group 未就位先 fetchGroupInfo bootstrap所有人都刷成员列表新成员 nickname / avatar 不在 payload */
applyGroupMemberInviteNotification(groupId: number, payload: GroupNotificationPayload) {
if (isSelfInPayloadMembers(payload) && !this.getGroup(groupId)) {
this.fetchGroupInfo(groupId).catch(() => undefined)
}
this.fetchGroupMembers(groupId, true).catch(() => undefined)
},
/** 成员退群:退群者本人多端同步走 removeGroup其他成员从本地列表移除 quitter */
applyGroupMemberQuitNotification(groupId: number, payload: GroupNotificationPayload) {
const selfUserId = getCurrentUserId()
if (selfUserId && payload.operatorUserId === selfUserId) {
this.removeGroup(groupId)
} else if (payload.operatorUserId) {
this.removeMembersLocal(groupId, [payload.operatorUserId])
}
},
/** 成员被移出:被踢者本人 removeGroup其他成员从本地列表移除被踢者 */
applyGroupMemberKickNotification(groupId: number, payload: GroupNotificationPayload) {
const memberIds = payload.memberUserIds || []
if (isSelfInPayloadMembers(payload)) {
this.removeGroup(groupId)
} else if (memberIds.length) {
this.removeMembersLocal(groupId, memberIds)
}
},
/** 成员昵称变更:按 operatorUserId 局部更新对应 member.displayUserName */
applyGroupMemberNicknameUpdateNotification(groupId: number, payload: GroupNotificationPayload) {
if (payload.operatorUserId) {
this.updateMemberDisplayUserName(
groupId,
payload.operatorUserId,
payload.displayUserName ?? ''
)
}
},
/** 群主转让:旧群主 → NORMAL新群主 → OWNER */
applyGroupOwnerTransferNotification(groupId: number, payload: GroupNotificationPayload) {
if (payload.operatorUserId && payload.newOwnerUserId) {
this.transferOwner(groupId, payload.operatorUserId, payload.newOwnerUserId)
}
},
/** 切账号时仅清 in-memoryIDB 按 userId 分桶天然隔离,回切秒开 */ /** 切账号时仅清 in-memoryIDB 按 userId 分桶天然隔离,回切秒开 */
clear() { clear() {
this.groups = [] this.groups = []

View File

@ -15,7 +15,8 @@ import type {
WebSocketFrame, WebSocketFrame,
ImPrivateMessageDTO, ImPrivateMessageDTO,
ImGroupMessageDTO, ImGroupMessageDTO,
Message Message,
Group
} from '../types' } from '../types'
/** /**
@ -73,7 +74,8 @@ const convertGroupMessage = (
* - TEXT / IMAGE / FILE / VOICE / VIDEO / TIP_TEXT + / * - TEXT / IMAGE / FILE / VOICE / VIDEO / TIP_TEXT + /
* - / READ / RECEIPT * - / READ / RECEIPT
* - FRIEND_ADD / DELETE / UPDATE friendStore + * - FRIEND_ADD / DELETE / UPDATE friendStore +
* - 1530 GROUP_MEMBER_SETTING_UPDATE groupStore + 广1501-1520 OpenIM handleGroupMessage + applyGroupNotification DISSOLVE/QUIT/KICK * - GROUP_MEMBER_SETTING_UPDATE groupStore +
* - 广GROUP_* handleGroupMessage + applyGroupNotification DISSOLVE / QUIT / KICK
*/ */
export const useImWebSocketStore = defineStore('imWebSocketStore', { export const useImWebSocketStore = defineStore('imWebSocketStore', {
state: () => ({ state: () => ({
@ -518,7 +520,7 @@ export const useImWebSocketStore = defineStore('imWebSocketStore', {
if (!group) { if (!group) {
return return
} }
const fields: Partial<typeof group> = {} const fields: Partial<Group> = {}
if (payload.muted != null) { if (payload.muted != null) {
fields.muted = payload.muted fields.muted = payload.muted
} }

View File

@ -76,7 +76,7 @@
[回执] [回执]
</span> </span>
<!-- 群广播事件1501-1520 / 1530拼装中文 tip 文案operator senderNicknamemember / newOwner 退化为 用户(id) --> <!-- 群广播事件拼装中文 tip 文案operator senderNicknamemember / newOwner 退化为 用户(id) -->
<span <span
v-else-if="isGroupNotificationType" v-else-if="isGroupNotificationType"
class="text-12px text-[var(--el-text-color-secondary)]" class="text-12px text-[var(--el-text-color-secondary)]"
@ -103,7 +103,6 @@ import {
type VideoMessage type VideoMessage
} from '@/views/im/utils/message' } from '@/views/im/utils/message'
import { resolveGroupNotificationText } from '@/views/im/utils/user' import { resolveGroupNotificationText } from '@/views/im/utils/user'
import type { Message } from '@/views/im/home/types'
defineOptions({ name: 'ImMessageContentPreview' }) defineOptions({ name: 'ImMessageContentPreview' })
@ -198,30 +197,15 @@ const fallbackText = computed(() => {
return raw return raw
}) })
/** 是否群广播事件1501-1520 / 1530 */ /** 是否群广播事件 */
const isGroupNotificationType = computed(() => isGroupNotification(props.type ?? -1)) const isGroupNotificationType = computed(() => isGroupNotification(props.type ?? -1))
/** 群广播事件 operatorUserId用于把 senderNickname 仅覆盖到 operator 这一个 id 上 */ /** 群广播事件文案:复用 utils/user.resolveGroupNotificationTextadmin 端 operator 用 senderNickname 直接覆盖,其它 id 退化为 用户(id) */
const groupOperatorUserId = computed<number | undefined>(() => {
try {
return JSON.parse(props.content || '{}')?.operatorUserId
} catch {
return undefined
}
})
/** 群广播事件文案:复用 utils/user.resolveGroupNotificationTextadmin 端 resolveName 用 senderNickname仅 operator+ 用户(id) 兜底 */
const groupNotificationText = computed(() => const groupNotificationText = computed(() =>
resolveGroupNotificationText( resolveGroupNotificationText(
{ { type: props.type, content: props.content },
type: props.type as number, (id) => `用户(${id})`,
content: props.content || '', props.senderNickname
targetId: 0
} as Pick<Message, 'type' | 'content' | 'targetId'>,
(id) =>
id === groupOperatorUserId.value && props.senderNickname
? props.senderNickname
: `用户(${id})`
) )
) )
</script> </script>

View File

@ -13,51 +13,33 @@ export const ImMessageType = {
FRIEND_ADD: 100, // 好友添加 FRIEND_ADD: 100, // 好友添加
FRIEND_DELETE: 101, // 好友删除 FRIEND_DELETE: 101, // 好友删除
FRIEND_UPDATE: 102, // 好友更新(客户端收到后自行拉取) FRIEND_UPDATE: 102, // 好友更新(客户端收到后自行拉取)
// 群事件1501-1520 直接复用 OpenIM 段位编号1530+ 我们独有扩展persistent=true 广播 + persistent=false 个人信号) // 群事件1501-1520 复用 OpenIM 段位编号1530+ 自有扩展段)
// 1500 mirror OpenIM GroupNotificationBegin marker不使用
GROUP_CREATE: 1501, // 群创建 GROUP_CREATE: 1501, // 群创建
GROUP_INFO_UPDATE: 1502, // 群信息变更NAME / NOTICE 之外字段兜底 GROUP_INFO_UPDATE: 1502, // 群信息变更NAME / NOTICE 之外字段兜底)
// 1503 GROUP_JOIN_APPLICATION TODO 未实现:入群申请 // 1503 GROUP_JOIN_APPLICATION TODO 未实现:入群申请
GROUP_MEMBER_QUIT: 1504, // 成员退群 GROUP_MEMBER_QUIT: 1504, // 成员退群
// 1505 GROUP_APPLICATION_ACCEPTED TODO 未实现 // 1505 GROUP_APPLICATION_ACCEPTED TODO 未实现:入群申请通过
// 1506 GROUP_APPLICATION_REJECTED TODO 未实现 // 1506 GROUP_APPLICATION_REJECTED TODO 未实现:入群申请拒绝
GROUP_OWNER_TRANSFER: 1507, // 群主转让 GROUP_OWNER_TRANSFER: 1507, // 群主转让
GROUP_MEMBER_KICK: 1508, // 成员被移出 GROUP_MEMBER_KICK: 1508, // 成员被移出
GROUP_MEMBER_INVITE: 1509, // 成员加入 GROUP_MEMBER_INVITE: 1509, // 成员加入
// 1510 GROUP_MEMBER_ENTER TODO 未实现:自由进群 // 1510 GROUP_MEMBER_ENTER TODO 未实现:自由进群
GROUP_DISSOLVE: 1511, // 群解散 GROUP_DISSOLVE: 1511, // 群解散
// 1512 GROUP_MEMBER_MUTED TODO 未实现:单成员禁言 // 1512 GROUP_MEMBER_MUTED TODO 未实现:单成员禁言
// 1513 GROUP_MEMBER_CANCEL_MUTED TODO 未实现 // 1513 GROUP_MEMBER_CANCEL_MUTED TODO 未实现:单成员取消禁言
// 1514 GROUP_MUTED TODO 未实现:全群禁言 // 1514 GROUP_MUTED TODO 未实现:全群禁言
// 1515 GROUP_CANCEL_MUTED TODO 未实现 // 1515 GROUP_CANCEL_MUTED TODO 未实现:全群取消禁言
GROUP_MEMBER_NICKNAME_UPDATE: 1516, // 成员昵称变更(窄化到 displayUserName GROUP_MEMBER_NICKNAME_UPDATE: 1516, // 成员昵称变更(窄化到 displayUserName
GROUP_ADMIN_ADD: 1517, // 添加管理员 GROUP_ADMIN_ADD: 1517, // 添加管理员
GROUP_ADMIN_REMOVE: 1518, // 撤销管理员 GROUP_ADMIN_REMOVE: 1518, // 撤销管理员
GROUP_NOTICE_UPDATE: 1519, // 群公告变更 GROUP_NOTICE_UPDATE: 1519, // 群公告变更
GROUP_NAME_UPDATE: 1520, // 群名变更 GROUP_NAME_UPDATE: 1520, // 群名变更
// 1530+ 我们独有扩展段 GROUP_MEMBER_SETTING_UPDATE: 1530 // 群成员个人设置变更muted / groupRemark 个人多端同步
GROUP_MEMBER_SETTING_UPDATE: 1530 // 群成员个人设置变更muted / groupRemark 多端同步(个人)
} as const } as const
/** 群广播事件 type 集合1501-1520 OpenIM 段位(除 1530 个人设置同步),前端按 type 分发到 applyGroupNotification */ /** 判断是否「群广播事件」:[GROUP_CREATE, GROUP_MEMBER_SETTING_UPDATE) 段位都算GROUP_MEMBER_SETTING_UPDATE 是个人信号不算 */
const ImGroupNotificationTypes: number[] = [
ImMessageType.GROUP_CREATE,
ImMessageType.GROUP_NAME_UPDATE,
ImMessageType.GROUP_NOTICE_UPDATE,
ImMessageType.GROUP_INFO_UPDATE,
ImMessageType.GROUP_DISSOLVE,
ImMessageType.GROUP_MEMBER_INVITE,
ImMessageType.GROUP_MEMBER_QUIT,
ImMessageType.GROUP_MEMBER_KICK,
ImMessageType.GROUP_MEMBER_NICKNAME_UPDATE,
ImMessageType.GROUP_ADMIN_ADD,
ImMessageType.GROUP_ADMIN_REMOVE,
ImMessageType.GROUP_OWNER_TRANSFER
]
/** 判断是否「群广播事件」 */
export function isGroupNotification(type: number): boolean { export function isGroupNotification(type: number): boolean {
return ImGroupNotificationTypes.includes(type) return type >= ImMessageType.GROUP_CREATE && type < ImMessageType.GROUP_MEMBER_SETTING_UPDATE
} }
/** IM 普通消息类型集合(聊天气泡中显示,并作为会话最后一条摘要) */ /** IM 普通消息类型集合(聊天气泡中显示,并作为会话最后一条摘要) */

View File

@ -14,16 +14,14 @@ import { ImConversationType, ImMessageType } from './constants'
import { getCurrentUserId } from './storage' import { getCurrentUserId } from './storage'
import { useFriendStore } from '../home/store/friendStore' import { useFriendStore } from '../home/store/friendStore'
import { useGroupStore } from '../home/store/groupStore' import { useGroupStore } from '../home/store/groupStore'
import type { Friend, Group, Message } from '../home/types' import type { Friend, Group } from '../home/types'
/** /**
* > * >
* *
* displayName DISABLE * displayName DISABLE
*/ */
export function getFriendDisplayName( export function getFriendDisplayName(friend: Pick<Friend, 'nickname' | 'displayName'>): string {
friend: Pick<Friend, 'nickname' | 'displayName'>
): string {
return friend.displayName || friend.nickname return friend.displayName || friend.nickname
} }
@ -151,35 +149,42 @@ export function getSenderRealNickname(
} }
/** /**
* 广GROUP_* 1501-1520 / 1530 * 广GROUP_*
* *
* message.type content payload getSenderDisplayName / / * message.type content payload getSenderDisplayName / /
* store resolveName id senderNickname + (id) * store resolveName id senderNickname + (id)
* home MessageItem.vue / ConversationItem.vue / MessageHistory.vue admin MessageContentPreview.vue * 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
}
export function resolveGroupNotificationText( export function resolveGroupNotificationText(
message: Pick<Message, 'type' | 'content' | 'targetId'>, message: { type?: number; content?: string; targetId?: number },
resolveName?: (userId: number) => string resolveName?: (userId: number) => string,
operatorNameOverride?: string
): string { ): string {
const groupId = message.targetId let payload: GroupNotificationPayload = {}
let payload: {
operatorUserId?: number
memberUserIds?: number[]
newOwnerUserId?: number
oldName?: string
newName?: string
notice?: string
avatar?: string
displayUserName?: string
} = {}
try { try {
payload = JSON.parse(message.content || '{}') payload = JSON.parse(message.content || '{}')
} catch { } catch {
return '' return ''
} }
const resolve = const resolve =
resolveName || ((id: number) => getSenderDisplayName(id, ImConversationType.GROUP, groupId)) resolveName ||
const operatorName = payload.operatorUserId ? resolve(payload.operatorUserId) : '' ((id: number) => getSenderDisplayName(id, ImConversationType.GROUP, message.targetId ?? 0))
const operatorName = payload.operatorUserId
? (operatorNameOverride ?? resolve(payload.operatorUserId))
: ''
const memberNames = (payload.memberUserIds || []).map(resolve).join('、') const memberNames = (payload.memberUserIds || []).map(resolve).join('、')
const newOwnerName = payload.newOwnerUserId ? resolve(payload.newOwnerUserId) : '' const newOwnerName = payload.newOwnerUserId ? resolve(payload.newOwnerUserId) : ''
switch (message.type) { switch (message.type) {
@ -191,7 +196,7 @@ export function resolveGroupNotificationText(
return `${operatorName} 更新了群公告` return `${operatorName} 更新了群公告`
case ImMessageType.GROUP_INFO_UPDATE: case ImMessageType.GROUP_INFO_UPDATE:
// 兜底事件:按非 null 字段优先匹配特化文案,全部为空时降级为 "更新了群信息" 通用文案 // 兜底事件:按非 null 字段优先匹配特化文案,全部为空时降级为 "更新了群信息" 通用文案
if (payload.avatar) { if (payload.newAvatar) {
return `${operatorName} 更换了群头像` return `${operatorName} 更换了群头像`
} }
return `${operatorName} 更新了群信息` return `${operatorName} 更新了群信息`