zhangwencui
7 天以前 7619c19a67c2ac824f803090bab753fc5ea14408
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
/** IM 内容类型枚举(对齐后端 ImContentTypeEnum) */
export const ImContentType = {
  // ========== 用户聊天消息(101-105 直接复用 OpenIM 段位编号) ==========
  TEXT: 101, // 文本(对应 OpenIM Text=101)
  IMAGE: 102, // 图片(对应 OpenIM Picture=102)
  VOICE: 103, // 语音(对应 OpenIM Sound=103)
  VIDEO: 104, // 视频(对应 OpenIM Video=104)
  FILE: 105, // 文件(对应 OpenIM File=105)
  MERGE: 107, // 合并转发(对应 OpenIM Merger=107;payload 内嵌完整快照)
  CARD: 108, // 名片(对应 OpenIM Card=108)
  FACE: 115, // 表情贴图(对应 OpenIM Face=115;Unicode emoji 仍走 TEXT)
  // ========== 频道消息扩展段(125+;OpenIM 122 之后未占用,作为频道 / 公众号类消息扩展) ==========
  MATERIAL: 125, // 频道素材(图文卡片:title + coverUrl + summary + url;详情拉 get-content)
  // ========== 信号类(2101 / 2200 直接复用 OpenIM 段位编号;2201 自有扩展) ==========
  RECALL: 2101, // 撤回(对应 OpenIM RevokeNotification=2101)
  RECEIPT: 2200, // 回执(对应 OpenIM HasReadReceipt=2200)
  READ: 2201, // 已读(多端同步,OpenIM 无对应;自有扩展)
  // ========== 实时通话信令(1601-1605 段位与 OpenIM 对齐;1610+ 自有扩展) ==========
  RTC_CALL: 1601, // 通话信令统一入口(对应 OpenIM SignalingNotification=1601)
  RTC_PARTICIPANT_CONNECTED: 1602, // 通话参与者加入(对应 OpenIM RoomParticipantsConnectedNotification=1602)
  RTC_PARTICIPANT_DISCONNECTED: 1603, // 通话参与者离开(对应 OpenIM RoomParticipantsDisconnectedNotification=1603)
  // 1604-1609 OpenIM 已用 / 留作扩展,本系统暂不使用
  RTC_CALL_START: 1610, // 通话开始(自有扩展,OpenIM 无;仅群聊;与 END 两段式配对)
  RTC_CALL_END: 1611, // 通话结束(自有扩展,OpenIM 无;私聊 / 群聊)
  // ========== 好友通知(1201-1210 直接复用 OpenIM 段位编号) ==========
  FRIEND_REQUEST_APPROVED: 1201, // 好友申请被同意
  FRIEND_REQUEST_REJECTED: 1202, // 好友申请被拒绝
  FRIEND_REQUEST_RECEIVED: 1203, // 收到新的好友申请
  FRIEND_ADD: 1204, // 新增好友(双方建立关系)
  FRIEND_DELETE: 1205, // 好友被删除
  // 1206 对应 OpenIM FriendRemarkSetNotification;本系统并入 FRIEND_UPDATE(1210) 统一推送,单一字段变更不再独立通道
  FRIEND_BLOCK: 1207, // 加入黑名单
  FRIEND_UNBLOCK: 1208, // 移出黑名单
  FRIEND_INFO_UPDATED: 1209, // 好友资料变更(昵称 / 头像)
  FRIEND_UPDATE: 1210, // 好友信息批量更新(silent / pinned)
  // ========== 群事件(1501-1520 直接复用 OpenIM 段位编号;1530+ 自有扩展段) ==========
  GROUP_CREATE: 1501, // 群创建
  GROUP_INFO_UPDATE: 1502, // 群信息变更(NAME / NOTICE 之外字段兜底)
  GROUP_REQUEST_RECEIVED: 1503, // 收到新的入群申请(私聊定向推送给群主 + 全部管理员)
  GROUP_MEMBER_QUIT: 1504, // 成员退群
  GROUP_REQUEST_APPROVED: 1505, // 入群申请被同意(私聊推给申请人 + 群主 + 全部管理员)
  GROUP_REQUEST_REJECTED: 1506, // 入群申请被拒绝(同上)
  GROUP_OWNER_TRANSFER: 1507, // 群主转让
  GROUP_MEMBER_KICK: 1508, // 成员被移出
  GROUP_MEMBER_INVITE: 1509, // 成员加入
  GROUP_MEMBER_ENTER: 1510, // 自由进群(FREE 模式或申请通过后;全员广播)
  GROUP_DISSOLVE: 1511, // 群解散
  GROUP_MEMBER_MUTED: 1512, // 单成员禁言
  GROUP_MEMBER_CANCEL_MUTED: 1513, // 单成员取消禁言
  GROUP_MUTED: 1514, // 全群禁言
  GROUP_CANCEL_MUTED: 1515, // 全群取消禁言
  GROUP_MEMBER_NICKNAME_UPDATE: 1516, // 成员昵称变更(窄化到 displayUserName)
  GROUP_ADMIN_ADD: 1517, // 添加管理员
  GROUP_ADMIN_REMOVE: 1518, // 撤销管理员
  GROUP_NOTICE_UPDATE: 1519, // 群公告变更
  GROUP_NAME_UPDATE: 1520, // 群名变更
  // ========== 自有扩展段(1530+,OpenIM 1500-1520 段位无对应物) ==========
  GROUP_MEMBER_SETTING_UPDATE: 1530, // 群成员个人设置变更:silent / groupRemark 个人多端同步
  GROUP_MESSAGE_PIN: 1531, // 群消息置顶(自有扩展,OpenIM 无)
  GROUP_MESSAGE_UNPIN: 1532, // 群消息取消置顶(自有扩展,OpenIM 无)
  GROUP_BANNED: 1533 // 群封禁变更(自有扩展,OpenIM 无)
} as const
 
/** 判断是否「群广播事件」:[GROUP_CREATE, GROUP_BANNED] 段位都算,仅 GROUP_MEMBER_SETTING_UPDATE 是个人信号排除 */
export function isGroupNotification(type: number): boolean {
  return (
    type >= ImContentType.GROUP_CREATE &&
    type <= ImContentType.GROUP_BANNED &&
    type !== ImContentType.GROUP_MEMBER_SETTING_UPDATE
  )
}
 
/** 判断是否「好友通知事件」:1201-1210 段位 */
export function isFriendNotification(type: number): boolean {
  return type >= ImContentType.FRIEND_REQUEST_APPROVED && type <= ImContentType.FRIEND_UPDATE
}
 
/** 判断是否「加群申请通知事件」:1503/1505/1506 */
export function isGroupRequestNotification(type: number): boolean {
  return (
    type === ImContentType.GROUP_REQUEST_RECEIVED ||
    type === ImContentType.GROUP_REQUEST_APPROVED ||
    type === ImContentType.GROUP_REQUEST_REJECTED
  )
}
 
/** 判断是否「会话内的好友事件气泡」:FRIEND_ADD / FRIEND_DELETE 直接渲染成灰色提示,与群事件同处理 */
export function isFriendChatTip(type: number): boolean {
  return type === ImContentType.FRIEND_ADD || type === ImContentType.FRIEND_DELETE
}
 
/** 判断是否「会话内的通话事件气泡」:RTC_CALL_START / RTC_CALL_END 渲染成灰色提示 */
export function isRtcCallTip(type: number): boolean {
  return type === ImContentType.RTC_CALL_START || type === ImContentType.RTC_CALL_END
}
 
/**
 * IM 普通内容类型集合(normal vs event 二分;与后端 ImContentTypeEnum.normal 字段语义一致)
 *
 * 这个集合在多处被复用,新增类型前先确认所有副作用都符合预期:
 * 1. 后端发送入口校验(Im{Private,Group}MessageSendReqVO.isNormalType)—— 用户发送的内容类型必须 normal=true
 * 2. 前端接收侧未读 / 提示音(websocketStore)—— normal 消息计入会话未读数 + 触发声音
 * 3. 前端会话列表 lastType / @ 标签(ConversationItem)—— 只有 normal 才算「最后一条聊天消息」
 * 4. 前端群消息置顶菜单(message-item.vue 的 canPin)—— normal 才允许群主 / 管理员置顶
 *
 * 名片(CARD)/ 表情(FACE)都是「用户主动发的聊天消息」,1/2/3 都符合预期;4 同时放开 = 群主可置顶,语义合理
 */
const ImContentTypeNormals = new Set<number>([
  ImContentType.CARD,
  ImContentType.FACE,
  ImContentType.FILE,
  ImContentType.IMAGE,
  ImContentType.MATERIAL, // 频道素材计入未读数 + 进会话列表
  ImContentType.MERGE,
  ImContentType.TEXT,
  ImContentType.VIDEO,
  ImContentType.VOICE
])
 
/** 判断是否"普通消息" */
export function isNormalMessage(type: number): boolean {
  return ImContentTypeNormals.has(type)
}
 
/** IM 媒体内容类型集合:发送依赖本地 File 上传,刷新后 _localFile 丢失即不可恢复 */
const ImContentTypeMedia = new Set<number>([
  ImContentType.FILE,
  ImContentType.IMAGE,
  ImContentType.VIDEO,
  ImContentType.VOICE
])
 
/** 判断是否「媒体消息」:图片 / 文件 / 语音 / 视频 */
export function isMediaMessageType(type: number): boolean {
  return ImContentTypeMedia.has(type)
}
 
/**
 * IM 消息状态枚举(对齐后端 ImMessageStatusEnum,前端扩展 SENDING + FAILED)
 *
 * 生命周期:FAILED(-2) → SENDING(-1) → NORMAL(0) → RECALL(2)
 */
export const ImMessageStatus = {
  FAILED: -2, // 发送失败(前端独有)
  SENDING: -1, // 发送中(前端独有)
  NORMAL: 0, // 正常
  RECALL: 2 // 已撤回
} as const
 
/** IM 会话类型枚举 */
export const ImConversationType = {
  NONE: 0, // 无会话
  PRIVATE: 1, // 私聊
  GROUP: 2, // 群聊
  CHANNEL: 3 // 频道 / 公众号
} as const
 
/** ImConversationType 取值(用于消息 payload 字段类型收窄) */
export type ImConversationTypeValue = (typeof ImConversationType)[keyof typeof ImConversationType]
 
/** 是否私聊会话;同时收窄类型 */
export function isPrivateConversation(type: number | undefined): boolean {
  return type === ImConversationType.PRIVATE
}
 
/** 是否群聊会话;同时收窄类型 */
export function isGroupConversation(type: number | undefined): boolean {
  return type === ImConversationType.GROUP
}
 
/** 是否频道会话;同时收窄类型 */
export function isChannelConversation(type: number | undefined): boolean {
  return type === ImConversationType.CHANNEL
}
 
/** IM 通话媒体类型(对齐后端 ImRtcCallMediaTypeEnum) */
export const ImRtcCallMediaType = {
  VOICE: 1,
  VIDEO: 2
} as const
 
/** IM 通话状态(对齐后端 ImRtcCallStatusEnum) */
export const ImRtcCallStatus = {
  CREATED: 10, // 创建:私聊等被叫接听;群聊发起人已进房等其他人加入
  RUNNING: 20, // 进行中:第一个非发起人接通后进入
  ENDED: 30 // 已结束
} as const
 
/** IM 通话结束原因(对齐后端 ImRtcCallEndReasonEnum) */
export const ImRtcCallEndReason = {
  HANGUP: 1, // 接通后任一方主动挂断
  REJECT: 2, // 被叫接通前点拒接
  CANCEL: 3, // 主叫接通前主动取消
  NO_ANSWER: 4, // 振铃超时未接通
  BUSY: 5, // 私聊呼叫时对方正忙
  ERROR: 9 // 网络中断 / 设备失败
} as const
 
/** ImRtcCallEndReason 取值类型 */
export type ImRtcCallEndReasonValue = (typeof ImRtcCallEndReason)[keyof typeof ImRtcCallEndReason]
 
/** IM 通话参与者状态(对齐后端 ImRtcParticipantStatusEnum);同时作为 RTC_CALL 信令 status 字段取值 */
export const ImRtcParticipantStatus = {
  INVITING: 10, // 来电邀请
  JOINED: 20, // 接听 / 已加入
  REJECTED: 30, // 拒接
  NO_ANSWER: 40, // 主叫取消,被邀请方未应答
  LEFT: 50 // 挂断离开
} as const
 
/** ImRtcParticipantStatus 取值类型 */
export type ImRtcParticipantStatusValue =
  (typeof ImRtcParticipantStatus)[keyof typeof ImRtcParticipantStatus]
 
/**
 * IM 通话 UI 阶段;前端独有,用于驱动 inviting / incoming / running 三种弹窗切换;
 * 跟后端 ImRtcCallStatus 不是 1:1 映射,stage 多了「自己是主叫还是被叫」的角色维度
 */
export const ImRtcCallStage = {
  IDLE: 'idle', // 空闲;后端无对应(本端无活跃通话)
  INVITING: 'inviting', // 主叫等待对方接受;对应后端 ImRtcCallStatus.CREATED(自己是主叫)
  INCOMING: 'incoming', // 被叫来电响铃;对应后端 ImRtcCallStatus.CREATED(自己是被叫)
  RUNNING: 'running' // 通话中;对应后端 ImRtcCallStatus.RUNNING
} as const
 
/** ImRtcCallStage 取值类型 */
export type ImRtcCallStageValue = (typeof ImRtcCallStage)[keyof typeof ImRtcCallStage]
 
/** IM WebSocket 外层帧类型 */
export const ImWebSocketMessageType = {
  NOTIFICATION: 'im-notification' // IM 通知
} as const
 
/** IM 消息回执状态枚举(对齐后端 ImMessageReceiptStatusEnum) */
export const ImMessageReceiptStatus = {
  NO_RECEIPT: 0, // 不需要回执
  PENDING: 1, // 待完成
  DONE: 2 // 已完成
} as const
 
/** 群成员角色(对齐后端 ImGroupMemberRoleEnum) */
export const ImGroupMemberRole = {
  OWNER: 1, // 群主
  ADMIN: 2, // 管理员
  NORMAL: 3 // 普通成员
} as const
 
/** 加群来源(对齐后端 ImGroupAddSourceEnum) */
export const ImGroupAddSource = {
  SEARCH: 1, // 搜索
  INVITE: 2, // 邀请
  QR_CODE: 3, // 扫码
  SHARE_LINK: 4 // 分享链接
} as const
 
/** 加群申请处理结果(对齐后端 ImGroupRequestHandleResultEnum) */
export const ImGroupRequestHandleResult = {
  UNHANDLED: 0, // 未处理
  AGREED: 1, // 同意
  REFUSED: 2 // 拒绝
} as const
 
/** 好友添加来源(对齐后端 ImFriendAddSourceEnum) */
export const ImFriendAddSource = {
  SEARCH: 1, // 搜索
  GROUP: 2, // 群聊
  QR_CODE: 3, // 扫码
  CARD: 4 // 名片
} as const
 
/** 好友申请处理结果(对齐后端 ImFriendRequestHandleResultEnum) */
export const ImFriendRequestHandleResult = {
  UNHANDLED: 0, // 未处理
  AGREED: 1, // 同意
  REFUSED: 2 // 拒绝
} as const
 
/**
 * @全体成员 的特殊 userId 标识:atUserIds 中包含 -1 表示 @ 全体成员
 *
 * 与后端约定:群消息 atUserIds 数组里出现 -1 时,所有成员都收到提醒
 * MentionPicker 渲染虚拟项 + conversationStore.applyAt 判定 atAll 都靠这个值
 */
export const IM_AT_ALL_USER_ID = -1
 
/** @全体成员 的展示名(对齐微信 PC) */
export const IM_AT_ALL_NICKNAME = '所有人'
 
/** 转发模式:SINGLE 逐条原样转 / MERGE 打包成 MergeMessage */
export const ImForwardMode = {
  SINGLE: 'single',
  MERGE: 'merge'
} as const
 
/** ImForwardMode 取值类型 */
export type ImForwardModeValue = (typeof ImForwardMode)[keyof typeof ImForwardMode]