fix(im): 对齐群备注展示并修复 IM 消息管理字典

聊天端:
- 群 API 类型补充 groupRemark 和 silent
- 群列表同步时以接口返回的个人群设置为准,只保留成员缓存
- 会话名写入入口统一使用 getGroupDisplayName,避免群备注被原群名覆盖
- 聊天标题、转发、推荐名片、新建群入口同步群展示名逻辑
- 空群头像且成员未加载时异步预拉群成员,用于合成群头像
- 通讯录和合并消息详情补充滚动容器
- 消息历史日期选择改用 antd Calendar 卡片模式并修正样式

管理端:
- IM 字典常量统一为 im_content_type、im_message_status、im_message_receipt_status
- 私聊 / 群聊消息列表和详情页切换到统一内容类型、消息状态、回执状态字典
- 私聊消息 API 和详情页补充 receiptStatus
- 统计消息类型分布改用内容类型字典
pull/367/head
YunaiV 2026-06-18 08:59:19 -07:00
parent e61d0a5aa2
commit 5a4f8b4e2a
18 changed files with 93 additions and 46 deletions

View File

@ -19,6 +19,8 @@ export namespace ImGroupApi {
createTime?: string; // 创建时间 createTime?: string; // 创建时间
pinnedMessages?: ImGroupMessageApi.GroupMessageRespVO[]; // 群置顶消息列表(后端关联回填,仅当登录用户是群成员时非空) pinnedMessages?: ImGroupMessageApi.GroupMessageRespVO[]; // 群置顶消息列表(后端关联回填,仅当登录用户是群成员时非空)
joinStatus?: number; // 当前登录用户在该群的成员状态(参见 CommonStatusEnum0 在群 / 1 已退群);历史退群群仍返回,供展示离线消息的群名 / 头像 joinStatus?: number; // 当前登录用户在该群的成员状态(参见 CommonStatusEnum0 在群 / 1 已退群);历史退群群仍返回,供展示离线消息的群名 / 头像
groupRemark?: string; // 当前登录用户对该群的备注
silent?: boolean; // 当前登录用户是否免打扰
} }
/** 群消息置顶 / 取消置顶 Request VO */ /** 群消息置顶 / 取消置顶 Request VO */

View File

@ -14,6 +14,7 @@ export namespace ImManagerPrivateMessageApi {
type: number; type: number;
content: string; content: string;
status: number; status: number;
receiptStatus?: number;
sendTime: Date; sendTime: Date;
createTime: Date; createTime: Date;
} }

View File

@ -14,7 +14,7 @@ import { ImContentType, ImConversationType, isGroupConversation } from '../../..
import { getConversationKey } from '../../../utils/conversation' import { getConversationKey } from '../../../utils/conversation'
import { buildDefaultGroupName } from '../../../utils/group' import { buildDefaultGroupName } from '../../../utils/group'
import { type CardTarget, serializeMessage } from '../../../utils/message' import { type CardTarget, serializeMessage } from '../../../utils/message'
import { isGroupQuit } from '../../../utils/user' import { getGroupDisplayName, isGroupQuit } from '../../../utils/user'
import { useMessageSender } from '../../composables/useMessageSender' import { useMessageSender } from '../../composables/useMessageSender'
import { FacePicker } from '../../pages/conversation/components/input' import { FacePicker } from '../../pages/conversation/components/input'
import { useConversationStore } from '../../store/conversationStore' import { useConversationStore } from '../../store/conversationStore'
@ -181,7 +181,7 @@ async function handleCreateGroupAndSend() {
const newConversation: Conversation = { const newConversation: Conversation = {
type: ImConversationType.GROUP, type: ImConversationType.GROUP,
targetId: group.id, targetId: group.id,
name: group.name || name, name: getGroupDisplayName(group) || name,
avatar: group.avatar || '', avatar: group.avatar || '',
unreadCount: 0, unreadCount: 0,
lastContent: '', lastContent: '',

View File

@ -26,7 +26,7 @@ import {
} from '../../utils/constants' } from '../../utils/constants'
import { generateClientMessageId, getPrivateMessagePeerId } from '../../utils/message' import { generateClientMessageId, getPrivateMessagePeerId } from '../../utils/message'
import { runMinIdPull } from '../../utils/pull' import { runMinIdPull } from '../../utils/pull'
import { getFriendDisplayName } from '../../utils/user' import { getFriendDisplayName, getGroupDisplayName } from '../../utils/user'
import { useConversationStore } from '../store/conversationStore' import { useConversationStore } from '../store/conversationStore'
import { useFriendStore } from '../store/friendStore' import { useFriendStore } from '../store/friendStore'
import { useGroupRequestStore } from '../store/groupRequestStore' import { useGroupRequestStore } from '../store/groupRequestStore'
@ -146,7 +146,7 @@ export const useMessagePuller = () => {
return { return {
type: ImConversationType.GROUP, type: ImConversationType.GROUP,
targetId: message.groupId, targetId: message.groupId,
name: group?.name || String(message.groupId), name: group ? getGroupDisplayName(group) : String(message.groupId),
avatar: group?.avatar || '', avatar: group?.avatar || '',
silent: group?.silent silent: group?.silent
} }

View File

@ -241,7 +241,8 @@ function onRemarkSaved(displayName: string) {
</div> </div>
<!-- 列表主体 FriendRequestList / GroupList / FriendList 三个子组件各自管理折叠 + 过滤本页只透传选中态 --> <!-- 列表主体 FriendRequestList / GroupList / FriendList 三个子组件各自管理折叠 + 过滤本页只透传选中态 -->
<div class="flex-1"> <!-- overflow-y-auto联系人多时本列表可滚动 ResizableAside 不提供滚动对齐 Vue3 el-scrollbar -->
<div class="flex-1 overflow-y-auto">
<FriendRequestList <FriendRequestList
:requests="friendRequests" :requests="friendRequests"
:active-id="selection?.type === 'request' ? selection.request.id : undefined" :active-id="selection?.type === 'request' ? selection.request.id : undefined"

View File

@ -11,7 +11,7 @@ import { useConversationStore } from '#/views/im/home/store/conversationStore'
import { useFriendStore } from '#/views/im/home/store/friendStore' import { useFriendStore } from '#/views/im/home/store/friendStore'
import { useGroupStore } from '#/views/im/home/store/groupStore' import { useGroupStore } from '#/views/im/home/store/groupStore'
import { ImConversationType } from '#/views/im/utils/constants' import { ImConversationType } from '#/views/im/utils/constants'
import { getFriendDisplayName } from '#/views/im/utils/user' import { getFriendDisplayName, getGroupDisplayName } from '#/views/im/utils/user'
import { GroupCreateDialog } from '../../../../components/group' import { GroupCreateDialog } from '../../../../components/group'
import { UserAvatar } from '../../../../components/user' import { UserAvatar } from '../../../../components/user'
@ -119,7 +119,7 @@ function handleGroupCreated(groupId: number) {
conversationStore.openConversation( conversationStore.openConversation(
groupId, groupId,
ImConversationType.GROUP, ImConversationType.GROUP,
group.name, getGroupDisplayName(group),
group.avatar || '', group.avatar || '',
{ silent: !!group.silent } { silent: !!group.silent }
) )

View File

@ -29,7 +29,7 @@ import {
removeQuotePayload, removeQuotePayload,
serializeMessage serializeMessage
} from '#/views/im/utils/message' } from '#/views/im/utils/message'
import { isGroupQuit } from '#/views/im/utils/user' import { getGroupDisplayName, isGroupQuit } from '#/views/im/utils/user'
import { FacePicker } from '../../input' import { FacePicker } from '../../input'
@ -263,7 +263,7 @@ async function handleCreateGroupAndSend() {
const newConversation: Conversation = { const newConversation: Conversation = {
type: ImConversationType.GROUP, type: ImConversationType.GROUP,
targetId: group.id, targetId: group.id,
name: group.name || name, name: getGroupDisplayName(group) || name,
avatar: group.avatar || '', avatar: group.avatar || '',
unreadCount: 0, unreadCount: 0,
lastContent: '', lastContent: '',

View File

@ -84,7 +84,8 @@ function handleClose() {
> >
以下是 {{ currentPayload.messages.length }} 条消息 以下是 {{ currentPayload.messages.length }} 条消息
</div> </div>
<div class="flex-1"> <!-- overflow-y-auto合并消息多时在定高弹窗内可滚动对齐 Vue3 el-scrollbar -->
<div class="flex-1 overflow-y-auto">
<div <div
v-for="(item, idx) in currentPayload.messages" v-for="(item, idx) in currentPayload.messages"
:key="idx" :key="idx"

View File

@ -582,7 +582,11 @@ function locateMessage(messageId: number) {
<div class="px-2 pt-1 pb-2 text-13px font-medium text-[var(--ant-color-text)]"> <div class="px-2 pt-1 pb-2 text-13px font-medium text-[var(--ant-color-text)]">
选择发送日期 选择发送日期
</div> </div>
<Calendar v-model:value="datePickerValue" class="im-message-history__calendar" /> <Calendar
v-model:value="datePickerValue"
:fullscreen="false"
class="im-message-history__calendar"
/>
<div class="flex gap-2 justify-end px-2 pt-2"> <div class="flex gap-2 justify-end px-2 pt-2">
<Button size="small" @click="datePopoverVisible = false">取消</Button> <Button size="small" @click="datePopoverVisible = false">取消</Button>
<Button size="small" type="primary" @click="onDateConfirm"></Button> <Button size="small" type="primary" @click="onDateConfirm"></Button>
@ -785,18 +789,19 @@ function locateMessage(messageId: number) {
border-radius: 1px; border-radius: 1px;
} }
/* :deep 穿透 el-calendar 子组件 DOM默认偏大压一压让它能塞进 320 popover */ /* :deep 穿透 antd Calendarfullscreen=false 卡片模式)子 DOM压一压塞进 320 popover */
.im-message-history__calendar :deep(.el-calendar) { .im-message-history__calendar :deep(.ant-picker-calendar-header) {
40px: 36px;
}
.im-message-history__calendar :deep(.el-calendar__header) {
padding: 4px 8px; padding: 4px 8px;
} }
.im-message-history__calendar :deep(.el-calendar-table) { .im-message-history__calendar :deep(.ant-picker-content) {
font-size: 12px; font-size: 12px;
} }
.im-message-history__calendar :deep(.el-calendar-day) { .im-message-history__calendar :deep(.ant-picker-cell) {
height: 36px; padding: 1px 0;
padding: 4px; }
.im-message-history__calendar :deep(.ant-picker-cell .ant-picker-calendar-date) {
height: 28px;
margin: 0 2px;
padding: 2px 4px 0;
} }
</style> </style>

View File

@ -13,7 +13,7 @@ import { getCurrentUserId } from '#/views/im/utils/auth'
import { ImConversationType, ImRtcCallMediaType, ImRtcCallStatus } from '#/views/im/utils/constants' import { ImConversationType, ImRtcCallMediaType, ImRtcCallStatus } from '#/views/im/utils/constants'
import { getClientConversationId } from '#/views/im/utils/db' import { getClientConversationId } from '#/views/im/utils/db'
import { resolveCallEndReasonText } from '#/views/im/utils/message' import { resolveCallEndReasonText } from '#/views/im/utils/message'
import { getMemberDisplayName, isGroupQuit } from '#/views/im/utils/user' import { getGroupDisplayName, getMemberDisplayName, isGroupQuit } from '#/views/im/utils/user'
import { GroupMuteMemberDialog } from '../../../../components/group' import { GroupMuteMemberDialog } from '../../../../components/group'
import { import {
@ -185,10 +185,11 @@ const groupInfo = computed<
} }
const group = groupStore.getGroup(conversation.targetId) const group = groupStore.getGroup(conversation.targetId)
const selfMember = group?.members?.find((member) => member.userId === getCurrentUserId()) const selfMember = group?.members?.find((member) => member.userId === getCurrentUserId())
const showGroupName = group ? getGroupDisplayName(group) : conversation.name
return { return {
id: conversation.targetId, id: conversation.targetId,
name: group?.name || conversation.name, name: group?.name || conversation.name,
showGroupName: group?.name || conversation.name, showGroupName,
showImage: group?.avatar || conversation.avatar, showImage: group?.avatar || conversation.avatar,
notice: group?.notice, notice: group?.notice,
remarkNickName: selfMember?.displayUserName, remarkNickName: selfMember?.displayUserName,

View File

@ -10,6 +10,7 @@ import { Button, Dropdown, Input, Menu } from 'ant-design-vue'
import { ImConversationType } from '../../../utils/constants' import { ImConversationType } from '../../../utils/constants'
import { filterConversationsByKeyword, getConversationKey } from '../../../utils/conversation' import { filterConversationsByKeyword, getConversationKey } from '../../../utils/conversation'
import { StorageKeys } from '../../../utils/db' import { StorageKeys } from '../../../utils/db'
import { getGroupDisplayName } from '../../../utils/user'
import { ResizableAside } from '../../components' import { ResizableAside } from '../../components'
import { FriendAddDialog } from '../../components/friend' import { FriendAddDialog } from '../../components/friend'
import { GroupCreateDialog } from '../../components/group' import { GroupCreateDialog } from '../../components/group'
@ -124,7 +125,7 @@ function handleGroupCreated(groupId: number) {
conversationStore.openConversation( conversationStore.openConversation(
groupId, groupId,
ImConversationType.GROUP, ImConversationType.GROUP,
group.name, getGroupDisplayName(group),
group.avatar || '', group.avatar || '',
{ silent: !!group.silent } { silent: !!group.silent }
) )

View File

@ -223,7 +223,7 @@ export const useGroupStore = defineStore('imGroupStore', {
return return
} }
const fresh = (list || []).map((group) => convertGroup(group)) const fresh = (list || []).map((group) => convertGroup(group))
// 合并而非全量替换:silent / groupRemark / 成员缓存这些字段不在 ImGroupApi.GroupRespVO 里,得从旧 group 保留 // 合并而非全量替换:成员缓存只在成员列表接口维护,群个人设置以群列表接口为准
const groupMap = new Map(this.groups.map((group) => [group.id, group])) const groupMap = new Map(this.groups.map((group) => [group.id, group]))
this.groups = fresh.map((group) => { this.groups = fresh.map((group) => {
const existing = groupMap.get(group.id) const existing = groupMap.get(group.id)
@ -234,8 +234,6 @@ export const useGroupStore = defineStore('imGroupStore', {
...group, ...group,
members: existing.members, members: existing.members,
memberCount: existing.memberCount ?? group.memberCount, memberCount: existing.memberCount ?? group.memberCount,
silent: existing.silent ?? group.silent,
groupRemark: existing.groupRemark,
membersLoaded: existing.membersLoaded, membersLoaded: existing.membersLoaded,
membersExpired: existing.membersExpired membersExpired: existing.membersExpired
} }
@ -250,6 +248,24 @@ export const useGroupStore = defineStore('imGroupStore', {
}) })
} }
this.saveGroupList() this.saveGroupList()
this.preloadMembersForEmptyAvatarGroups()
},
/** 预加载空群头像的成员列表,供 GroupAvatar 异步合成群头像 */
preloadMembersForEmptyAvatarGroups() {
for (const group of this.groups) {
if (
group.avatar ||
group.joinStatus === CommonStatusEnum.DISABLE ||
(group.membersLoaded && !group.membersExpired && group.members?.length)
) {
continue
}
const force = !!group.membersLoaded && !group.membersExpired && !group.members?.length
this.fetchGroupMemberList(group.id, force).catch((error) => {
console.warn('[IM groupStore] 预加载群头像成员失败', { groupId: group.id }, error)
})
}
}, },
/** 失效全部群成员缓存 */ /** 失效全部群成员缓存 */
@ -908,7 +924,9 @@ function convertGroup(group: ImGroupApi.GroupRespVO): Group {
mutedAll: group.mutedAll, mutedAll: group.mutedAll,
banned: group.banned, banned: group.banned,
joinApproval: group.joinApproval, joinApproval: group.joinApproval,
joinStatus: group.joinStatus joinStatus: group.joinStatus,
groupRemark: group.groupRemark,
silent: group.silent
} }
} }

View File

@ -45,7 +45,7 @@ import {
playAudioTip, playAudioTip,
resolveCallEndReasonText resolveCallEndReasonText
} from '../../utils/message' } from '../../utils/message'
import { getFriendDisplayName } from '../../utils/user' import { getFriendDisplayName, getGroupDisplayName } from '../../utils/user'
import { useConversationStore } from './conversationStore' import { useConversationStore } from './conversationStore'
import { type FriendNotificationPayload, useFriendStore } from './friendStore' import { type FriendNotificationPayload, useFriendStore } from './friendStore'
import { useGroupRequestStore } from './groupRequestStore' import { useGroupRequestStore } from './groupRequestStore'
@ -781,7 +781,7 @@ export const useImWebSocketStore = defineStore('imWebSocketStore', {
{ {
type: ImConversationType.GROUP, type: ImConversationType.GROUP,
targetId: websocketMessage.groupId, targetId: websocketMessage.groupId,
name: group?.name || String(websocketMessage.groupId), name: group ? getGroupDisplayName(group) : String(websocketMessage.groupId),
avatar: group?.avatar || '', avatar: group?.avatar || '',
silent: group?.silent silent: group?.silent
}, },

View File

@ -35,7 +35,7 @@ export function usePrivateGridFormSchema(): VbenFormSchema[] {
component: 'Select', component: 'Select',
componentProps: { componentProps: {
allowClear: true, allowClear: true,
options: getDictOptions(DICT_TYPE.IM_MESSAGE_TYPE, 'number'), options: getDictOptions(DICT_TYPE.IM_CONTENT_TYPE, 'number'),
placeholder: '请选择内容类型', placeholder: '请选择内容类型',
}, },
}, },
@ -84,7 +84,7 @@ export function usePrivateGridColumns(showReadColumns: boolean): VxeTableGridOpt
width: 100, width: 100,
cellRender: { cellRender: {
name: 'CellDict', name: 'CellDict',
props: { type: DICT_TYPE.IM_MESSAGE_TYPE }, props: { type: DICT_TYPE.IM_CONTENT_TYPE },
}, },
}, },
{ {
@ -101,7 +101,17 @@ export function usePrivateGridColumns(showReadColumns: boolean): VxeTableGridOpt
width: 100, width: 100,
cellRender: { cellRender: {
name: 'CellDict', name: 'CellDict',
props: { type: DICT_TYPE.IM_PRIVATE_MESSAGE_STATUS }, props: { type: DICT_TYPE.IM_MESSAGE_STATUS },
},
},
{
field: 'receiptStatus',
title: '回执',
width: 110,
cellRender: {
name: 'CellDict',
// 回执状态(私聊 / 群聊共用 im_message_receipt_status与源端「回执」列对齐
props: { type: DICT_TYPE.IM_MESSAGE_RECEIPT_STATUS },
}, },
}, },
] ]
@ -143,7 +153,7 @@ export function useGroupGridFormSchema(): VbenFormSchema[] {
component: 'Select', component: 'Select',
componentProps: { componentProps: {
allowClear: true, allowClear: true,
options: getDictOptions(DICT_TYPE.IM_MESSAGE_TYPE, 'number'), options: getDictOptions(DICT_TYPE.IM_CONTENT_TYPE, 'number'),
placeholder: '请选择内容类型', placeholder: '请选择内容类型',
}, },
}, },
@ -192,7 +202,7 @@ export function useGroupGridColumns(showReadColumns: boolean): VxeTableGridOptio
width: 100, width: 100,
cellRender: { cellRender: {
name: 'CellDict', name: 'CellDict',
props: { type: DICT_TYPE.IM_MESSAGE_TYPE }, props: { type: DICT_TYPE.IM_CONTENT_TYPE },
}, },
}, },
{ {
@ -214,7 +224,7 @@ export function useGroupGridColumns(showReadColumns: boolean): VxeTableGridOptio
width: 100, width: 100,
cellRender: { cellRender: {
name: 'CellDict', name: 'CellDict',
props: { type: DICT_TYPE.IM_GROUP_MESSAGE_STATUS }, props: { type: DICT_TYPE.IM_MESSAGE_STATUS },
}, },
}, },
{ {
@ -223,7 +233,7 @@ export function useGroupGridColumns(showReadColumns: boolean): VxeTableGridOptio
width: 110, width: 110,
cellRender: { cellRender: {
name: 'CellDict', name: 'CellDict',
props: { type: DICT_TYPE.IM_GROUP_MESSAGE_RECEIPT_STATUS }, props: { type: DICT_TYPE.IM_MESSAGE_RECEIPT_STATUS },
}, },
}, },
] ]

View File

@ -48,14 +48,14 @@ defineExpose({ open });
{{ formatUserLabel(detail.senderNickname, detail.senderId) }} {{ formatUserLabel(detail.senderNickname, detail.senderId) }}
</DescriptionsItem> </DescriptionsItem>
<DescriptionsItem label="类型"> <DescriptionsItem label="类型">
<DictTag :type="DICT_TYPE.IM_MESSAGE_TYPE" :value="detail.type" /> <DictTag :type="DICT_TYPE.IM_CONTENT_TYPE" :value="detail.type" />
</DescriptionsItem> </DescriptionsItem>
<DescriptionsItem label="状态"> <DescriptionsItem label="状态">
<DictTag :type="DICT_TYPE.IM_GROUP_MESSAGE_STATUS" :value="detail.status" /> <DictTag :type="DICT_TYPE.IM_MESSAGE_STATUS" :value="detail.status" />
</DescriptionsItem> </DescriptionsItem>
<DescriptionsItem v-if="MESSAGE_GROUP_READ_ENABLED" label="回执" :span="2"> <DescriptionsItem v-if="MESSAGE_GROUP_READ_ENABLED" label="回执" :span="2">
<DictTag <DictTag
:type="DICT_TYPE.IM_GROUP_MESSAGE_RECEIPT_STATUS" :type="DICT_TYPE.IM_MESSAGE_RECEIPT_STATUS"
:value="detail.receiptStatus" :value="detail.receiptStatus"
/> />
</DescriptionsItem> </DescriptionsItem>

View File

@ -13,6 +13,7 @@ import {
formatJsonText, formatJsonText,
formatUserLabel, formatUserLabel,
} from '#/views/im/manager/utils/format'; } from '#/views/im/manager/utils/format';
import { MESSAGE_PRIVATE_READ_ENABLED } from '#/views/im/utils/config';
import { MessageContentPreview } from '../..'; import { MessageContentPreview } from '../..';
@ -44,10 +45,17 @@ defineExpose({ open });
{{ formatUserLabel(detail.receiverNickname, detail.receiverId) }} {{ formatUserLabel(detail.receiverNickname, detail.receiverId) }}
</DescriptionsItem> </DescriptionsItem>
<DescriptionsItem label="类型"> <DescriptionsItem label="类型">
<DictTag :type="DICT_TYPE.IM_MESSAGE_TYPE" :value="detail.type" /> <DictTag :type="DICT_TYPE.IM_CONTENT_TYPE" :value="detail.type" />
</DescriptionsItem> </DescriptionsItem>
<DescriptionsItem label="状态"> <DescriptionsItem label="状态">
<DictTag :type="DICT_TYPE.IM_PRIVATE_MESSAGE_STATUS" :value="detail.status" /> <DictTag :type="DICT_TYPE.IM_MESSAGE_STATUS" :value="detail.status" />
</DescriptionsItem>
<DescriptionsItem v-if="MESSAGE_PRIVATE_READ_ENABLED" label="回执">
<!-- 回执状态私聊 / 群聊共用 im_message_receipt_status与源端私聊详情回执对齐 -->
<DictTag
:type="DICT_TYPE.IM_MESSAGE_RECEIPT_STATUS"
:value="detail.receiptStatus"
/>
</DescriptionsItem> </DescriptionsItem>
<DescriptionsItem label="发送时间" :span="2"> <DescriptionsItem label="发送时间" :span="2">
{{ formatDateTimeText(detail.sendTime) }} {{ formatDateTimeText(detail.sendTime) }}

View File

@ -56,7 +56,7 @@ async function loadData() {
const data = await getMessageTypeDistribution(); const data = await getMessageTypeDistribution();
const items = data.map((item) => ({ const items = data.map((item) => ({
name: name:
getDictObj(DICT_TYPE.IM_MESSAGE_TYPE, String(item.type))?.label || getDictObj(DICT_TYPE.IM_CONTENT_TYPE, String(item.type))?.label ||
`未知(${item.type})`, `未知(${item.type})`,
value: item.value, value: item.value,
})); }));

View File

@ -178,17 +178,16 @@ const IOT_DICT = {
/** ========== IM - 即时通讯模块 ========== */ /** ========== IM - 即时通讯模块 ========== */
const IM_DICT = { const IM_DICT = {
IM_CHANNEL_MATERIAL_TYPE: 'im_channel_material_type', // IM 频道素材类型 IM_CHANNEL_MATERIAL_TYPE: 'im_channel_material_type', // IM 频道素材类型
IM_CONTENT_TYPE: 'im_content_type', // IM 消息内容类型
IM_FRIEND_ADD_SOURCE: 'im_friend_add_source', // IM 好友添加来源 IM_FRIEND_ADD_SOURCE: 'im_friend_add_source', // IM 好友添加来源
IM_FRIEND_REQUEST_HANDLE_RESULT: 'im_friend_request_handle_result', // IM 好友申请处理结果 IM_FRIEND_REQUEST_HANDLE_RESULT: 'im_friend_request_handle_result', // IM 好友申请处理结果
IM_FRIEND_STATUS: 'im_friend_status', // IM 好友状态 IM_FRIEND_STATUS: 'im_friend_status', // IM 好友状态
IM_GROUP_ADD_SOURCE: 'im_group_add_source', // IM 加群来源 IM_GROUP_ADD_SOURCE: 'im_group_add_source', // IM 加群来源
IM_GROUP_MEMBER_ROLE: 'im_group_member_role', // IM 群成员角色 IM_GROUP_MEMBER_ROLE: 'im_group_member_role', // IM 群成员角色
IM_GROUP_MESSAGE_RECEIPT_STATUS: 'im_group_message_receipt_status', // IM 群消息回执状态
IM_GROUP_MESSAGE_STATUS: 'im_group_message_status', // IM 群聊消息状态
IM_GROUP_REQUEST_HANDLE_RESULT: 'im_group_request_handle_result', // IM 加群申请处理结果 IM_GROUP_REQUEST_HANDLE_RESULT: 'im_group_request_handle_result', // IM 加群申请处理结果
IM_GROUP_STATUS: 'im_group_status', // IM 群状态 IM_GROUP_STATUS: 'im_group_status', // IM 群状态
IM_MESSAGE_TYPE: 'im_message_type', // IM 消息类型 IM_MESSAGE_RECEIPT_STATUS: 'im_message_receipt_status', // IM 消息回执状态(私聊 / 群聊共用)
IM_PRIVATE_MESSAGE_STATUS: 'im_private_message_status', // IM 私聊消息状态 IM_MESSAGE_STATUS: 'im_message_status', // IM 消息状态(私聊 / 群聊共用)
IM_RTC_CALL_CONVERSATION_TYPE: 'im_rtc_call_conversation_type', // IM 通话会话类型 IM_RTC_CALL_CONVERSATION_TYPE: 'im_rtc_call_conversation_type', // IM 通话会话类型
IM_RTC_CALL_END_REASON: 'im_rtc_call_end_reason', // IM 通话结束原因 IM_RTC_CALL_END_REASON: 'im_rtc_call_end_reason', // IM 通话结束原因
IM_RTC_CALL_MEDIA_TYPE: 'im_rtc_call_media_type', // IM 通话媒体类型 IM_RTC_CALL_MEDIA_TYPE: 'im_rtc_call_media_type', // IM 通话媒体类型