✨ feat(im): 增加好友申请的逻辑(v1)
parent
bf79e07d5c
commit
f86cd30af4
|
|
@ -7,6 +7,9 @@ export interface ImFriendRespVO {
|
||||||
muted?: boolean // 是否免打扰
|
muted?: boolean // 是否免打扰
|
||||||
displayName?: string // 好友展示备注(仅自己可见)
|
displayName?: string // 好友展示备注(仅自己可见)
|
||||||
displayNamePinyin?: string // 备注的拼音(小写无空格,前端按首字母分桶 / 拼音搜索)
|
displayNamePinyin?: string // 备注的拼音(小写无空格,前端按首字母分桶 / 拼音搜索)
|
||||||
|
addSource?: number // 添加来源;参见 ImFriendAddSourceEnum
|
||||||
|
pinned?: boolean // 是否置顶联系人
|
||||||
|
blocked?: boolean // 是否拉黑
|
||||||
status?: number // 好友状态(0=正常,1=已删除)
|
status?: number // 好友状态(0=正常,1=已删除)
|
||||||
addTime?: string // 添加好友时间
|
addTime?: string // 添加好友时间
|
||||||
deleteTime?: string // 删除好友时间
|
deleteTime?: string // 删除好友时间
|
||||||
|
|
@ -21,6 +24,7 @@ export interface ImFriendUpdateReqVO {
|
||||||
friendUserId: number // 好友的用户编号
|
friendUserId: number // 好友的用户编号
|
||||||
muted?: boolean // 是否免打扰
|
muted?: boolean // 是否免打扰
|
||||||
displayName?: string // 好友展示备注
|
displayName?: string // 好友展示备注
|
||||||
|
pinned?: boolean // 是否置顶联系人
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获得当前登录用户的好友列表
|
// 获得当前登录用户的好友列表
|
||||||
|
|
@ -33,17 +37,13 @@ export const getFriend = (friendUserId: number | string) => {
|
||||||
return request.get<ImFriendRespVO>({ url: '/im/friend/get', params: { friendUserId } })
|
return request.get<ImFriendRespVO>({ url: '/im/friend/get', params: { friendUserId } })
|
||||||
}
|
}
|
||||||
|
|
||||||
// 添加好友(双向建立关系)
|
// 删除好友(单向软删除)
|
||||||
export const addFriend = (friendUserId: number | string) => {
|
|
||||||
return request.post<boolean>({ url: '/im/friend/add', params: { friendUserId } })
|
|
||||||
}
|
|
||||||
|
|
||||||
// 删除好友(双向软删除)
|
|
||||||
export const deleteFriend = (friendUserId: number | string) => {
|
export const deleteFriend = (friendUserId: number | string) => {
|
||||||
return request.delete<boolean>({ url: '/im/friend/delete', params: { friendUserId } })
|
return request.delete<boolean>({ url: '/im/friend/delete', params: { friendUserId } })
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新好友信息
|
// 更新好友信息(备注 / 免打扰 / 联系人置顶)
|
||||||
export const updateFriend = (data: ImFriendUpdateReqVO) => {
|
export const updateFriend = (data: ImFriendUpdateReqVO) => {
|
||||||
return request.put<boolean>({ url: '/im/friend/update', data })
|
return request.put<boolean>({ url: '/im/friend/update', data })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,51 @@
|
||||||
|
import request from '@/config/axios'
|
||||||
|
|
||||||
|
// TODO DONE @AI:路径迁移到 api/im/friend/request/index.ts,与 api/im/group/member 这种嵌套结构对齐
|
||||||
|
// IM 好友申请 Response VO
|
||||||
|
export interface ImFriendRequestRespVO {
|
||||||
|
id: number // 申请编号
|
||||||
|
fromUserId: number // 发起方用户编号
|
||||||
|
toUserId: number // 接收方用户编号
|
||||||
|
handleResult: number // 处理结果;0=未处理;1=同意;2=拒绝
|
||||||
|
applyContent?: string // 申请理由
|
||||||
|
handleContent?: string // 处理理由(接收方拒绝时可选填)
|
||||||
|
addSource?: number // 添加来源;参见 ImFriendAddSourceEnum
|
||||||
|
handleTime?: string // 处理时间
|
||||||
|
createTime: string // 申请创建时间
|
||||||
|
// 聚合字段(自 AdminUser)
|
||||||
|
fromNickname?: string // 发起方昵称
|
||||||
|
fromAvatar?: string // 发起方头像
|
||||||
|
toNickname?: string // 接收方昵称
|
||||||
|
toAvatar?: string // 接收方头像
|
||||||
|
}
|
||||||
|
|
||||||
|
// IM 好友申请发起 Request VO
|
||||||
|
export interface ImFriendRequestApplyReqVO {
|
||||||
|
toUserId: number // 接收方用户编号
|
||||||
|
applyContent?: string // 申请理由
|
||||||
|
displayName?: string // 对接收方的备注(仅自己可见)
|
||||||
|
addSource?: number // 添加来源
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发起好友申请
|
||||||
|
export const applyFriendRequest = (data: ImFriendRequestApplyReqVO) => {
|
||||||
|
return request.post<number | null>({ url: '/im/friend-request/apply', data })
|
||||||
|
}
|
||||||
|
|
||||||
|
// 同意好友申请
|
||||||
|
export const agreeFriendRequest = (id: number | string) => {
|
||||||
|
return request.put<boolean>({ url: '/im/friend-request/agree', params: { id } })
|
||||||
|
}
|
||||||
|
|
||||||
|
// 拒绝好友申请
|
||||||
|
export const refuseFriendRequest = (id: number | string, handleContent?: string) => {
|
||||||
|
return request.put<boolean>({
|
||||||
|
url: '/im/friend-request/refuse',
|
||||||
|
params: { id, handleContent }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询「我相关」的好友申请列表(含我发起的、别人加我的)
|
||||||
|
export const getMyFriendRequestList = () => {
|
||||||
|
return request.get<ImFriendRequestRespVO[]>({ url: '/im/friend-request/list' })
|
||||||
|
}
|
||||||
|
|
@ -5,30 +5,61 @@ import { CommonStatusEnum } from '@/utils/constants'
|
||||||
import {
|
import {
|
||||||
getMyFriendList as apiGetMyFriendList,
|
getMyFriendList as apiGetMyFriendList,
|
||||||
getFriend as apiGetFriend,
|
getFriend as apiGetFriend,
|
||||||
addFriend as apiAddFriend,
|
|
||||||
deleteFriend as apiDeleteFriend,
|
deleteFriend as apiDeleteFriend,
|
||||||
updateFriend as apiUpdateFriend,
|
updateFriend as apiUpdateFriend,
|
||||||
type ImFriendRespVO
|
type ImFriendRespVO
|
||||||
} from '@/api/im/friend'
|
} from '@/api/im/friend'
|
||||||
|
import {
|
||||||
|
applyFriendRequest as apiApplyFriendRequest,
|
||||||
|
agreeFriendRequest as apiAgreeFriendRequest,
|
||||||
|
refuseFriendRequest as apiRefuseFriendRequest,
|
||||||
|
getMyFriendRequestList as apiGetMyFriendRequestList,
|
||||||
|
type ImFriendRequestApplyReqVO,
|
||||||
|
type ImFriendRequestRespVO
|
||||||
|
} from '@/api/im/friend/request'
|
||||||
import { useConversationStore } from './conversationStore'
|
import { useConversationStore } from './conversationStore'
|
||||||
import { ImConversationType } from '../../utils/constants'
|
import { ImConversationType } from '../../utils/constants'
|
||||||
import { getCurrentUserId, imStorage, setQuietly, StorageKeys } from '../../utils/storage'
|
import { getCurrentUserId, imStorage, setQuietly, StorageKeys } from '../../utils/storage'
|
||||||
import { getFriendDisplayName } from '../../utils/user'
|
import { getFriendDisplayName } from '../../utils/user'
|
||||||
import type { Friend } from '../types'
|
import type { Friend, FriendRequest } from '../types'
|
||||||
|
|
||||||
|
/** 好友申请处理结果(对齐后端 ImFriendRequestHandleResultEnum) */
|
||||||
|
const FriendRequestHandleResult = {
|
||||||
|
UNHANDLED: 0,
|
||||||
|
AGREED: 1,
|
||||||
|
REFUSED: 2
|
||||||
|
} as const
|
||||||
|
|
||||||
|
/** 好友通知 payload(对齐后端 BaseFriendNotification + 子类裁减后的字段) */
|
||||||
|
export interface FriendNotificationPayload {
|
||||||
|
operatorUserId: number
|
||||||
|
friendUserId: number
|
||||||
|
// FRIEND_APPLICATION 系列:申请记录的核心字段(避免 payload 携带完整 DO)
|
||||||
|
requestId?: number
|
||||||
|
applyContent?: string
|
||||||
|
handleContent?: string
|
||||||
|
addSource?: number
|
||||||
|
// FRIEND_UPDATE:单边属性变更
|
||||||
|
displayName?: string
|
||||||
|
muted?: boolean
|
||||||
|
pinned?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* IM 好友 Store
|
* IM 好友 Store
|
||||||
*
|
*
|
||||||
* 负责:
|
* 负责:
|
||||||
* - 拉取 / 缓存当前登录用户的好友列表
|
* - 拉取 / 缓存当前登录用户的好友列表 + 申请列表
|
||||||
* - 加好友 / 删好友(走后端 API + 本地乐观同步)
|
* - 申请-审批流程(apply / agree / refuse)+ 备注 / 免打扰 / 联系人置顶 / 拉黑
|
||||||
* - 被 ConversationItem / FriendPage / MessageInput 等多处消费
|
* - 接收 WebSocket 1201-1210 段位通知,按事件分发到 friendStore 内部各 dispatcher
|
||||||
*/
|
*/
|
||||||
export const useFriendStore = defineStore('imFriendStore', {
|
export const useFriendStore = defineStore('imFriendStore', {
|
||||||
state: () => ({
|
state: () => ({
|
||||||
friends: [] as Friend[],
|
friends: [] as Friend[],
|
||||||
// 仅 fetchFriends 成功后置位;loadFriends(IDB)不置位,否则后台 SWR 刷新会被缓存命中跳过
|
// 仅 fetchFriends 成功后置位;loadFriends(IDB)不置位,否则后台 SWR 刷新会被缓存命中跳过
|
||||||
loaded: false
|
loaded: false,
|
||||||
|
/** 我相关的好友申请列表(含我发起的 + 别人加我的;后端按 id 倒序,前端不再分页) */
|
||||||
|
friendRequests: [] as FriendRequest[]
|
||||||
}),
|
}),
|
||||||
|
|
||||||
getters: {
|
getters: {
|
||||||
|
|
@ -48,17 +79,26 @@ export const useFriendStore = defineStore('imFriendStore', {
|
||||||
const entry = this.getFriend(friendUserId)
|
const entry = this.getFriend(friendUserId)
|
||||||
return !!entry && entry.status !== CommonStatusEnum.DISABLE
|
return !!entry && entry.status !== CommonStatusEnum.DISABLE
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
/** 我的黑名单(blocked=true 且 ENABLE) */
|
||||||
|
getBlockedFriends: (state): Friend[] => {
|
||||||
|
return state.friends.filter(
|
||||||
|
(f) => f.status !== CommonStatusEnum.DISABLE && f.blocked === true
|
||||||
|
)
|
||||||
|
},
|
||||||
|
/** 未处理申请数(接收方=我)—— 实时派生,「新的朋友」红点用 */
|
||||||
|
getUnhandledRequestCount: (state): number => {
|
||||||
|
const me = Number(getCurrentUserId() || 0)
|
||||||
|
return state.friendRequests.filter(
|
||||||
|
(r) => r.handleResult === FriendRequestHandleResult.UNHANDLED && r.toUserId === me
|
||||||
|
).length
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
// ==================== 本地缓存 ====================
|
// ==================== 本地缓存 ====================
|
||||||
|
|
||||||
/**
|
/** 从 IDB 恢复好友列表 */
|
||||||
* 从 IDB 恢复好友列表
|
|
||||||
*
|
|
||||||
* @return 返回是否命中缓存
|
|
||||||
*/
|
|
||||||
async loadFriends(): Promise<boolean> {
|
async loadFriends(): Promise<boolean> {
|
||||||
const userId = getCurrentUserId()
|
const userId = getCurrentUserId()
|
||||||
if (!userId) {
|
if (!userId) {
|
||||||
|
|
@ -121,30 +161,96 @@ export const useFriendStore = defineStore('imFriendStore', {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/** 添加好友:后端双向建立关系后,本地占位插入(服务端返回后可 fetchFriends 刷新) */
|
// ==================== 申请-审批 ====================
|
||||||
async addFriend(friendUserId: number, preview?: Partial<Friend>) {
|
|
||||||
await apiAddFriend(friendUserId)
|
/** 发起好友申请:成功后等待对方同意(不直接落地为好友) */
|
||||||
if (preview) {
|
async applyFriend(reqVO: ImFriendRequestApplyReqVO): Promise<number | null> {
|
||||||
this.upsertFriend({
|
return await apiApplyFriendRequest(reqVO)
|
||||||
friendUserId,
|
},
|
||||||
nickname: preview.nickname || String(friendUserId),
|
|
||||||
avatar: preview.avatar,
|
/** 同意一条好友申请;后端会双向落库 + 推 FRIEND_ADD,本端等通知到达再 upsertFriend */
|
||||||
status: CommonStatusEnum.ENABLE
|
async agreeFriendRequest(requestId: number) {
|
||||||
})
|
await apiAgreeFriendRequest(requestId)
|
||||||
|
const request = this.findFriendRequest(requestId)
|
||||||
|
if (request) {
|
||||||
|
request.handleResult = FriendRequestHandleResult.AGREED
|
||||||
|
request.handleTime = Date.now()
|
||||||
} else {
|
} else {
|
||||||
await this.loadFriendInfo(friendUserId)
|
// 列表过期场景兜底重拉
|
||||||
|
await this.fetchFriendRequests()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/** 删除好友(软删,保留记录但置 DISABLE;同时级联清理本地私聊会话) */
|
/** 拒绝一条好友申请 */
|
||||||
|
async refuseFriendRequest(requestId: number, handleContent?: string) {
|
||||||
|
await apiRefuseFriendRequest(requestId, handleContent)
|
||||||
|
const request = this.findFriendRequest(requestId)
|
||||||
|
if (request) {
|
||||||
|
request.handleResult = FriendRequestHandleResult.REFUSED
|
||||||
|
request.handleContent = handleContent
|
||||||
|
request.handleTime = Date.now()
|
||||||
|
} else {
|
||||||
|
await this.fetchFriendRequests()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/** 拉取「我相关」的好友申请列表(页面打开时 / 收到 FRIEND_APPLICATION 时刷新) */
|
||||||
|
async fetchFriendRequests() {
|
||||||
|
const list = await apiGetMyFriendRequestList()
|
||||||
|
this.friendRequests = (list || []).map(convertFriendRequest)
|
||||||
|
},
|
||||||
|
|
||||||
|
/** 按 id 查申请记录 */
|
||||||
|
findFriendRequest(requestId: number): FriendRequest | undefined {
|
||||||
|
return this.friendRequests.find((r) => r.id === requestId)
|
||||||
|
},
|
||||||
|
|
||||||
|
// ==================== 好友关系操作 ====================
|
||||||
|
|
||||||
|
/** 删除好友(单向软删,本端置 DISABLE;级联清理本地私聊会话) */
|
||||||
async deleteFriend(friendUserId: number) {
|
async deleteFriend(friendUserId: number) {
|
||||||
await apiDeleteFriend(friendUserId)
|
await apiDeleteFriend(friendUserId)
|
||||||
this.removeFriend(friendUserId)
|
this.removeFriend(friendUserId)
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/** 切换免打扰 */
|
||||||
|
async setMuted(friendUserId: number, muted: boolean) {
|
||||||
|
await apiUpdateFriend({ friendUserId, muted })
|
||||||
|
const friend = this.getFriend(friendUserId)
|
||||||
|
if (friend) {
|
||||||
|
friend.muted = muted
|
||||||
|
this.saveFriends()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/** 切换联系人置顶 */
|
||||||
|
async setPinned(friendUserId: number, pinned: boolean) {
|
||||||
|
await apiUpdateFriend({ friendUserId, pinned })
|
||||||
|
const friend = this.getFriend(friendUserId)
|
||||||
|
if (friend) {
|
||||||
|
friend.pinned = pinned
|
||||||
|
this.saveFriends()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/** 修改好友展示备注(仅自己可见) */
|
||||||
|
async setDisplayName(friendUserId: number, displayName: string) {
|
||||||
|
const value = displayName.trim()
|
||||||
|
// 后端 displayName 语义:null/undefined = 不改,"" = 清空,所以这里直接传 value(可能是空串)
|
||||||
|
await apiUpdateFriend({ friendUserId, displayName: value })
|
||||||
|
const friend = this.getFriend(friendUserId)
|
||||||
|
if (friend) {
|
||||||
|
friend.displayName = value
|
||||||
|
const conversationStore = useConversationStore()
|
||||||
|
conversationStore.updateConversation(ImConversationType.PRIVATE, friendUserId, {
|
||||||
|
name: getFriendDisplayName(friend)
|
||||||
|
})
|
||||||
|
this.saveFriends()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
/** 本地合并 / 新增某个好友(WebSocket 事件 & 手动刷新都用) */
|
/** 本地合并 / 新增某个好友(WebSocket 事件 & 手动刷新都用) */
|
||||||
upsertFriend(friend: Friend) {
|
upsertFriend(friend: Friend) {
|
||||||
// 按 friendUserId 查已有记录下标:>=0 命中则覆盖合并,<0 则追加
|
|
||||||
const index = this.friends.findIndex((f) => f.friendUserId === friend.friendUserId)
|
const index = this.friends.findIndex((f) => f.friendUserId === friend.friendUserId)
|
||||||
if (index >= 0) {
|
if (index >= 0) {
|
||||||
this.friends[index] = {
|
this.friends[index] = {
|
||||||
|
|
@ -158,7 +264,6 @@ export const useFriendStore = defineStore('imFriendStore', {
|
||||||
status: friend.status ?? CommonStatusEnum.ENABLE
|
status: friend.status ?? CommonStatusEnum.ENABLE
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
// 同步对应私聊会话的展示
|
|
||||||
const conversationStore = useConversationStore()
|
const conversationStore = useConversationStore()
|
||||||
const merged = this.getFriend(friend.friendUserId)
|
const merged = this.getFriend(friend.friendUserId)
|
||||||
conversationStore.updateConversation(ImConversationType.PRIVATE, friend.friendUserId, {
|
conversationStore.updateConversation(ImConversationType.PRIVATE, friend.friendUserId, {
|
||||||
|
|
@ -169,13 +274,13 @@ export const useFriendStore = defineStore('imFriendStore', {
|
||||||
this.saveFriends()
|
this.saveFriends()
|
||||||
},
|
},
|
||||||
|
|
||||||
/** 本地标记删除(WebSocket FRIEND_DEL 事件触发;同时级联清私聊会话) */
|
/** 本地标记删除(WebSocket FRIEND_DELETE 事件触发;同时级联清私聊会话) */
|
||||||
removeFriend(friendUserId: number) {
|
removeFriend(friendUserId: number) {
|
||||||
// 软删:保留记录但置为 DISABLE,避免后续误判"陌生人"
|
|
||||||
const friend = this.getFriend(friendUserId)
|
const friend = this.getFriend(friendUserId)
|
||||||
if (friend) {
|
if (friend) {
|
||||||
friend.status = CommonStatusEnum.DISABLE
|
friend.status = CommonStatusEnum.DISABLE
|
||||||
friend.deleteTime = Date.now()
|
friend.deleteTime = Date.now()
|
||||||
|
friend.blocked = false
|
||||||
}
|
}
|
||||||
// 级联清理:把对应的私聊会话也软删,避免会话列表里留着已删好友
|
// 级联清理:把对应的私聊会话也软删,避免会话列表里留着已删好友
|
||||||
const conversationStore = useConversationStore()
|
const conversationStore = useConversationStore()
|
||||||
|
|
@ -183,39 +288,105 @@ export const useFriendStore = defineStore('imFriendStore', {
|
||||||
this.saveFriends()
|
this.saveFriends()
|
||||||
},
|
},
|
||||||
|
|
||||||
/** 切换免打扰 */
|
// ==================== WebSocket 事件 dispatcher(1201-1210 段) ====================
|
||||||
async setMuted(friendUserId: number, muted: boolean) {
|
|
||||||
await apiUpdateFriend({ friendUserId, muted })
|
/** FRIEND_APPLICATION(1203):收到新申请;payload 已裁减为核心字段,本地拉一次列表补齐 fromUser 等聚合字段 */
|
||||||
const friend = this.getFriend(friendUserId)
|
applyFriendRequestNotification(_payload: FriendNotificationPayload) {
|
||||||
|
this.fetchFriendRequests().catch(() => undefined)
|
||||||
|
},
|
||||||
|
|
||||||
|
/** FRIEND_REQUEST_APPROVED(1201):我的申请被同意;按 requestId 更新状态(FRIEND_ADD 会另外推) */
|
||||||
|
applyFriendRequestApprovedNotification(payload: FriendNotificationPayload) {
|
||||||
|
const request = payload.requestId ? this.findFriendRequest(payload.requestId) : undefined
|
||||||
|
if (request) {
|
||||||
|
request.handleResult = FriendRequestHandleResult.AGREED
|
||||||
|
request.handleTime = Date.now()
|
||||||
|
} else {
|
||||||
|
this.fetchFriendRequests().catch(() => undefined)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/** FRIEND_REQUEST_REJECTED(1202):我的申请被拒绝;按 requestId 更新状态 */
|
||||||
|
applyFriendRequestRejectedNotification(payload: FriendNotificationPayload) {
|
||||||
|
const request = payload.requestId ? this.findFriendRequest(payload.requestId) : undefined
|
||||||
|
if (request) {
|
||||||
|
request.handleResult = FriendRequestHandleResult.REFUSED
|
||||||
|
request.handleContent = payload.handleContent
|
||||||
|
request.handleTime = Date.now()
|
||||||
|
} else {
|
||||||
|
this.fetchFriendRequests().catch(() => undefined)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/** FRIEND_ADD(1204):新增好友;本端拉取好友详情并入库 */
|
||||||
|
applyFriendAddNotification(payload: FriendNotificationPayload) {
|
||||||
|
if (payload.friendUserId) {
|
||||||
|
this.loadFriendInfo(payload.friendUserId).catch(() => undefined)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/** FRIEND_DELETE(1205):好友被删除;本端清理 + 级联会话 */
|
||||||
|
applyFriendDeleteNotification(payload: FriendNotificationPayload) {
|
||||||
|
if (payload.friendUserId) {
|
||||||
|
this.removeFriend(payload.friendUserId)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/** FRIEND_BLOCK(1207):拉黑;多端同步 */
|
||||||
|
applyFriendBlockNotification(payload: FriendNotificationPayload) {
|
||||||
|
const friend = this.getFriend(payload.friendUserId)
|
||||||
if (friend) {
|
if (friend) {
|
||||||
friend.muted = muted
|
friend.blocked = true
|
||||||
|
this.saveFriends()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/** FRIEND_UNBLOCK(1208):移出黑名单;多端同步 */
|
||||||
|
applyFriendUnblockNotification(payload: FriendNotificationPayload) {
|
||||||
|
const friend = this.getFriend(payload.friendUserId)
|
||||||
|
if (friend) {
|
||||||
|
friend.blocked = false
|
||||||
this.saveFriends()
|
this.saveFriends()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 修改好友展示备注(仅自己可见)
|
* FRIEND_INFO_UPDATED(1209):好友资料变更(昵称 / 头像);重拉详情
|
||||||
*
|
* TODO @AI:后端暂未实现 1209 推送;待 system 模块改昵称 / 头像时回调触发,本 dispatcher 已就绪
|
||||||
* 走后端 /im/friend/update 接口;保存成功后再同步本地 friend + 会话列表 name,失败直接抛给上层让 UI 决定回滚 / 提示
|
|
||||||
*/
|
*/
|
||||||
async setDisplayName(friendUserId: number, displayName: string) {
|
applyFriendInfoUpdatedNotification(payload: FriendNotificationPayload) {
|
||||||
const value = displayName.trim()
|
if (payload.friendUserId) {
|
||||||
// 后端的 displayName 语义:null/undefined = 不改,"" = 清空,所以这里直接传 value(可能是空串)
|
this.loadFriendInfo(payload.friendUserId).catch(() => undefined)
|
||||||
await apiUpdateFriend({ friendUserId, displayName: value })
|
|
||||||
const friend = this.getFriend(friendUserId)
|
|
||||||
if (friend) {
|
|
||||||
friend.displayName = value
|
|
||||||
const conversationStore = useConversationStore()
|
|
||||||
conversationStore.updateConversation(ImConversationType.PRIVATE, friendUserId, {
|
|
||||||
name: getFriendDisplayName(friend)
|
|
||||||
})
|
|
||||||
this.saveFriends()
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/** FRIEND_UPDATE(1210):批量更新(备注 / 免打扰 / 联系人置顶);多端同步 */
|
||||||
|
applyFriendUpdateNotification(payload: FriendNotificationPayload) {
|
||||||
|
const friend = this.getFriend(payload.friendUserId)
|
||||||
|
if (!friend) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (payload.displayName != null) {
|
||||||
|
friend.displayName = payload.displayName
|
||||||
|
}
|
||||||
|
if (payload.muted != null) {
|
||||||
|
friend.muted = payload.muted
|
||||||
|
}
|
||||||
|
if (payload.pinned != null) {
|
||||||
|
friend.pinned = payload.pinned
|
||||||
|
}
|
||||||
|
const conversationStore = useConversationStore()
|
||||||
|
conversationStore.updateConversation(ImConversationType.PRIVATE, payload.friendUserId, {
|
||||||
|
name: getFriendDisplayName(friend),
|
||||||
|
muted: friend.muted
|
||||||
|
})
|
||||||
|
this.saveFriends()
|
||||||
|
},
|
||||||
|
|
||||||
/** 切账号时仅清 in-memory,IDB 按 userId 分桶天然隔离,回切秒开 */
|
/** 切账号时仅清 in-memory,IDB 按 userId 分桶天然隔离,回切秒开 */
|
||||||
clear() {
|
clear() {
|
||||||
this.friends = []
|
this.friends = []
|
||||||
|
this.friendRequests = []
|
||||||
this.loaded = false
|
this.loaded = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -231,16 +402,36 @@ function convertFriend(vo: ImFriendRespVO): Friend {
|
||||||
muted: !!vo.muted,
|
muted: !!vo.muted,
|
||||||
displayName: vo.displayName || '',
|
displayName: vo.displayName || '',
|
||||||
displayNamePinyin: vo.displayNamePinyin,
|
displayNamePinyin: vo.displayNamePinyin,
|
||||||
|
addSource: vo.addSource,
|
||||||
|
pinned: !!vo.pinned,
|
||||||
|
blocked: !!vo.blocked,
|
||||||
status: vo.status,
|
status: vo.status,
|
||||||
addTime: vo.addTime ? new Date(vo.addTime).getTime() : undefined,
|
addTime: vo.addTime ? new Date(vo.addTime).getTime() : undefined,
|
||||||
deleteTime: vo.deleteTime ? new Date(vo.deleteTime).getTime() : undefined
|
deleteTime: vo.deleteTime ? new Date(vo.deleteTime).getTime() : undefined
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function convertFriendRequest(vo: ImFriendRequestRespVO): FriendRequest {
|
||||||
|
return {
|
||||||
|
id: vo.id,
|
||||||
|
fromUserId: vo.fromUserId,
|
||||||
|
toUserId: vo.toUserId,
|
||||||
|
handleResult: vo.handleResult,
|
||||||
|
applyContent: vo.applyContent,
|
||||||
|
handleContent: vo.handleContent,
|
||||||
|
addSource: vo.addSource,
|
||||||
|
handleTime: vo.handleTime ? new Date(vo.handleTime).getTime() : undefined,
|
||||||
|
createTime: vo.createTime ? new Date(vo.createTime).getTime() : 0,
|
||||||
|
fromNickname: vo.fromNickname,
|
||||||
|
fromAvatar: vo.fromAvatar,
|
||||||
|
toNickname: vo.toNickname,
|
||||||
|
toAvatar: vo.toAvatar
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export const useFriendStoreWithOut = () => useFriendStore(store)
|
export const useFriendStoreWithOut = () => useFriendStore(store)
|
||||||
|
|
||||||
// dev: 让 Pinia 的 actions / state 改动支持 HMR,避免每次改 store 都得硬刷
|
// dev: 让 Pinia 的 actions / state 改动支持 HMR,避免每次改 store 都得硬刷
|
||||||
// 否则 Vite 把新模块推下来后,老 store 实例的 action 闭包仍指向旧函数体
|
|
||||||
if (import.meta.hot) {
|
if (import.meta.hot) {
|
||||||
import.meta.hot.accept(acceptHMRUpdate(useFriendStore, import.meta.hot))
|
import.meta.hot.accept(acceptHMRUpdate(useFriendStore, import.meta.hot))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,10 +3,15 @@ import { store } from '@/store'
|
||||||
import { getRefreshToken } from '@/utils/auth'
|
import { getRefreshToken } from '@/utils/auth'
|
||||||
import { useUserStore } from '@/store/modules/user'
|
import { useUserStore } from '@/store/modules/user'
|
||||||
|
|
||||||
import { ImWebSocketMessageType, ImMessageType, ImConversationType } from '../../utils/constants'
|
import {
|
||||||
|
ImWebSocketMessageType,
|
||||||
|
ImMessageType,
|
||||||
|
ImConversationType,
|
||||||
|
isFriendNotification
|
||||||
|
} from '../../utils/constants'
|
||||||
import { playAudioTip } from '../../utils/message'
|
import { playAudioTip } from '../../utils/message'
|
||||||
import { useConversationStore } from './conversationStore'
|
import { useConversationStore } from './conversationStore'
|
||||||
import { useFriendStore } from './friendStore'
|
import { useFriendStore, type FriendNotificationPayload } from './friendStore'
|
||||||
import { getFriendDisplayName } from '../../utils/user'
|
import { getFriendDisplayName } from '../../utils/user'
|
||||||
import { useGroupStore } from './groupStore'
|
import { useGroupStore } from './groupStore'
|
||||||
import { readPrivateMessages as apiReadPrivateMessages } from '@/api/im/message/private'
|
import { readPrivateMessages as apiReadPrivateMessages } from '@/api/im/message/private'
|
||||||
|
|
@ -202,30 +207,30 @@ export const useImWebSocketStore = defineStore('imWebSocketStore', {
|
||||||
// ==================== 普通消息 ====================
|
// ==================== 普通消息 ====================
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 私聊统一帧分发:按 payload.type(ImMessageType)分到已读 / 回执 / 好友变更 / 普通消息
|
* 私聊统一帧分发:按 payload.type(ImMessageType)分到已读 / 回执 / 好友通知 / 普通消息
|
||||||
*
|
*
|
||||||
* 对应后端 ImPrivateMessageDTO 的 ofRead / ofReceipt / ofFriendAdd / ofFriendDelete / ofFriendUpdate / ofSend
|
* 对应后端 ImPrivateMessageDTO 的 ofRead / ofReceipt / ofFriendNotification / ofSend
|
||||||
*/
|
*/
|
||||||
dispatchPrivateFrame(websocketMessage: ImPrivateMessageDTO) {
|
dispatchPrivateFrame(websocketMessage: ImPrivateMessageDTO) {
|
||||||
switch (websocketMessage.type) {
|
try {
|
||||||
case ImMessageType.READ:
|
switch (websocketMessage.type) {
|
||||||
this.handlePrivateRead(websocketMessage)
|
case ImMessageType.READ:
|
||||||
break
|
this.handlePrivateRead(websocketMessage)
|
||||||
case ImMessageType.RECEIPT:
|
break
|
||||||
this.handlePrivateReceipt(websocketMessage)
|
case ImMessageType.RECEIPT:
|
||||||
break
|
this.handlePrivateReceipt(websocketMessage)
|
||||||
case ImMessageType.FRIEND_ADD:
|
break
|
||||||
this.handleFriendAdd(websocketMessage)
|
default:
|
||||||
break
|
if (isFriendNotification(websocketMessage.type)) {
|
||||||
case ImMessageType.FRIEND_DELETE:
|
this.handleFriendNotification(websocketMessage)
|
||||||
this.handleFriendDelete(websocketMessage)
|
} else {
|
||||||
break
|
// TEXT / IMAGE / FILE / VOICE / VIDEO / TIP_TEXT 等普通消息
|
||||||
case ImMessageType.FRIEND_UPDATE:
|
this.handlePrivateMessage(websocketMessage)
|
||||||
this.handleFriendUpdate(websocketMessage)
|
}
|
||||||
break
|
}
|
||||||
default:
|
} catch (e) {
|
||||||
// TEXT / IMAGE / FILE / VOICE / VIDEO / TIP_TEXT 等普通消息
|
// 单条帧的处理异常不应阻断后续帧;打印完整 websocketMessage 便于排查
|
||||||
this.handlePrivateMessage(websocketMessage)
|
console.warn('[IM WS] dispatchPrivateFrame 处理失败', websocketMessage, e)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -235,19 +240,24 @@ export const useImWebSocketStore = defineStore('imWebSocketStore', {
|
||||||
* 1530 GROUP_MEMBER_SETTING_UPDATE 是个人信号;其它(普通消息 + 1501-1520 OpenIM 段位群广播事件)走 handleGroupMessage 入库 + 触发 applyGroupNotification 旁路
|
* 1530 GROUP_MEMBER_SETTING_UPDATE 是个人信号;其它(普通消息 + 1501-1520 OpenIM 段位群广播事件)走 handleGroupMessage 入库 + 触发 applyGroupNotification 旁路
|
||||||
*/
|
*/
|
||||||
dispatchGroupFrame(websocketMessage: ImGroupMessageDTO) {
|
dispatchGroupFrame(websocketMessage: ImGroupMessageDTO) {
|
||||||
switch (websocketMessage.type) {
|
try {
|
||||||
case ImMessageType.READ:
|
switch (websocketMessage.type) {
|
||||||
this.handleGroupRead(websocketMessage)
|
case ImMessageType.READ:
|
||||||
break
|
this.handleGroupRead(websocketMessage)
|
||||||
case ImMessageType.RECEIPT:
|
break
|
||||||
this.handleGroupReceipt(websocketMessage)
|
case ImMessageType.RECEIPT:
|
||||||
break
|
this.handleGroupReceipt(websocketMessage)
|
||||||
case ImMessageType.GROUP_MEMBER_SETTING_UPDATE:
|
break
|
||||||
this.handleGroupMemberSettingUpdate(websocketMessage)
|
case ImMessageType.GROUP_MEMBER_SETTING_UPDATE:
|
||||||
break
|
this.handleGroupMemberSettingUpdate(websocketMessage)
|
||||||
default:
|
break
|
||||||
// TEXT / IMAGE / FILE / VOICE / VIDEO / TIP_TEXT + GROUP_* 群广播事件
|
default:
|
||||||
this.handleGroupMessage(websocketMessage)
|
// TEXT / IMAGE / FILE / VOICE / VIDEO / TIP_TEXT + GROUP_* 群广播事件
|
||||||
|
this.handleGroupMessage(websocketMessage)
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// 单条帧的处理异常不应阻断后续帧;打印完整 websocketMessage 便于排查
|
||||||
|
console.warn('[IM WS] dispatchGroupFrame 处理失败', websocketMessage, e)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -461,43 +471,49 @@ export const useImWebSocketStore = defineStore('imWebSocketStore', {
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
// ==================== 好友关系事件(承载于私聊通道,按 inner type 分流) ====================
|
// ==================== 好友通知(1201-1210 段位,承载于私聊通道) ====================
|
||||||
|
|
||||||
/** FRIEND_ADD:后端推送给好友双方;本端拉取好友详情并入库,级联刷新私聊会话展示 */
|
/**
|
||||||
handleFriendAdd(websocketMessage: ImPrivateMessageDTO) {
|
* 好友通知统一入口:解析 content 里的 payload,按 type 分发到 friendStore 内部 dispatcher
|
||||||
|
*
|
||||||
|
* 对应后端 ImPrivateMessageDTO.ofFriendNotification 系列;payload 实际类型见
|
||||||
|
* BaseFriendNotification 子类(FriendRequestNotification / FriendAddNotification 等)
|
||||||
|
*/
|
||||||
|
handleFriendNotification(websocketMessage: ImPrivateMessageDTO) {
|
||||||
|
// content 解析失败由外层 dispatchPrivateFrame 的 try-catch 兜底(含 websocketMessage 打印),不重复 catch
|
||||||
|
const payload = JSON.parse(websocketMessage.content || '{}') as FriendNotificationPayload
|
||||||
const friendStore = useFriendStore()
|
const friendStore = useFriendStore()
|
||||||
// 后端 DTO 里只带 senderId/receiverId;收到这条时,对端 = 非自己的那一方
|
switch (websocketMessage.type) {
|
||||||
const userStore = useUserStore()
|
case ImMessageType.FRIEND_APPLICATION:
|
||||||
const selfId = Number(userStore.getUser?.id) || 0
|
friendStore.applyFriendRequestNotification(payload)
|
||||||
const friendUserId =
|
break
|
||||||
websocketMessage.senderId === selfId
|
case ImMessageType.FRIEND_REQUEST_APPROVED:
|
||||||
? websocketMessage.receiverId
|
friendStore.applyFriendRequestApprovedNotification(payload)
|
||||||
: websocketMessage.senderId
|
break
|
||||||
friendStore.loadFriendInfo(friendUserId).catch(() => undefined)
|
case ImMessageType.FRIEND_REQUEST_REJECTED:
|
||||||
},
|
friendStore.applyFriendRequestRejectedNotification(payload)
|
||||||
|
break
|
||||||
/** FRIEND_DELETE:本端标记好友已删 + 级联清理私聊会话 */
|
case ImMessageType.FRIEND_ADD:
|
||||||
handleFriendDelete(websocketMessage: ImPrivateMessageDTO) {
|
friendStore.applyFriendAddNotification(payload)
|
||||||
const friendStore = useFriendStore()
|
break
|
||||||
const userStore = useUserStore()
|
case ImMessageType.FRIEND_DELETE:
|
||||||
const selfId = Number(userStore.getUser?.id) || 0
|
friendStore.applyFriendDeleteNotification(payload)
|
||||||
const friendUserId =
|
break
|
||||||
websocketMessage.senderId === selfId
|
case ImMessageType.FRIEND_BLOCK:
|
||||||
? websocketMessage.receiverId
|
friendStore.applyFriendBlockNotification(payload)
|
||||||
: websocketMessage.senderId
|
break
|
||||||
friendStore.removeFriend(friendUserId)
|
case ImMessageType.FRIEND_UNBLOCK:
|
||||||
},
|
friendStore.applyFriendUnblockNotification(payload)
|
||||||
|
break
|
||||||
/** FRIEND_UPDATE:多端同步好友属性变更(当前主要是免打扰);重新拉取好友详情即可 */
|
case ImMessageType.FRIEND_INFO_UPDATED:
|
||||||
handleFriendUpdate(websocketMessage: ImPrivateMessageDTO) {
|
friendStore.applyFriendInfoUpdatedNotification(payload)
|
||||||
const friendStore = useFriendStore()
|
break
|
||||||
const userStore = useUserStore()
|
case ImMessageType.FRIEND_UPDATE:
|
||||||
const selfId = Number(userStore.getUser?.id) || 0
|
friendStore.applyFriendUpdateNotification(payload)
|
||||||
const friendUserId =
|
break
|
||||||
websocketMessage.senderId === selfId
|
default:
|
||||||
? websocketMessage.receiverId
|
console.debug('[IM WS] 未识别好友通知', websocketMessage)
|
||||||
: websocketMessage.senderId
|
}
|
||||||
friendStore.loadFriendInfo(friendUserId).catch(() => undefined)
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// ==================== 群关系事件(承载于群聊通道,按 inner type 分流) ====================
|
// ==================== 群关系事件(承载于群聊通道,按 inner type 分流) ====================
|
||||||
|
|
@ -508,13 +524,10 @@ export const useImWebSocketStore = defineStore('imWebSocketStore', {
|
||||||
* payload 携带变更字段,按非 null 字段直接局部更新;省一次 fetchGroupMembers 接口
|
* payload 携带变更字段,按非 null 字段直接局部更新;省一次 fetchGroupMembers 接口
|
||||||
*/
|
*/
|
||||||
handleGroupMemberSettingUpdate(websocketMessage: ImGroupMessageDTO) {
|
handleGroupMemberSettingUpdate(websocketMessage: ImGroupMessageDTO) {
|
||||||
let payload: { muted?: boolean; groupRemark?: string } = {}
|
// content 解析失败由外层 dispatchGroupFrame 的 try-catch 兜底(含 websocketMessage 打印),不重复 catch
|
||||||
try {
|
const payload: { muted?: boolean; groupRemark?: string } = JSON.parse(
|
||||||
payload = JSON.parse(websocketMessage.content || '{}')
|
websocketMessage.content || '{}'
|
||||||
} catch (error) {
|
)
|
||||||
console.warn('[IM WS] handleGroupMemberSettingUpdate 解析 content 失败', error)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const groupStore = useGroupStore()
|
const groupStore = useGroupStore()
|
||||||
const group = groupStore.getGroup(websocketMessage.groupId)
|
const group = groupStore.getGroup(websocketMessage.groupId)
|
||||||
if (!group) {
|
if (!group) {
|
||||||
|
|
|
||||||
|
|
@ -153,10 +153,35 @@ export interface Friend {
|
||||||
displayName?: string // 好友展示备注:仅自己可见的别名(单字段不歧义,不带 Friend 前缀)
|
displayName?: string // 好友展示备注:仅自己可见的别名(单字段不歧义,不带 Friend 前缀)
|
||||||
displayNamePinyin?: string // 备注的拼音(后端用 Pinyin4j 算好回填,小写无空格)
|
displayNamePinyin?: string // 备注的拼音(后端用 Pinyin4j 算好回填,小写无空格)
|
||||||
status?: number // 好友状态,对齐 CommonStatusEnum(DISABLE = 已删除,软删保留记录)
|
status?: number // 好友状态,对齐 CommonStatusEnum(DISABLE = 已删除,软删保留记录)
|
||||||
|
addSource?: number // 添加来源;参见 ImFriendAddSourceEnum
|
||||||
|
pinned?: boolean // 是否置顶联系人
|
||||||
|
blocked?: boolean // 是否拉黑(仅自己可见,单边屏蔽对方私聊消息)
|
||||||
addTime?: number // 添加好友时间(毫秒时间戳;后端为 LocalDateTime 字符串,在 convertFriend 转换)
|
addTime?: number // 添加好友时间(毫秒时间戳;后端为 LocalDateTime 字符串,在 convertFriend 转换)
|
||||||
deleteTime?: number // 删除好友时间(毫秒时间戳;后端为 LocalDateTime 字符串,在 convertFriend 转换)
|
deleteTime?: number // 删除好友时间(毫秒时间戳;后端为 LocalDateTime 字符串,在 convertFriend 转换)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 好友申请记录(前端内部结构,对齐后端 ImFriendRequestRespVO)
|
||||||
|
*/
|
||||||
|
export interface FriendRequest {
|
||||||
|
// ========== 后端字段(对齐 ImFriendRequestRespVO) ==========
|
||||||
|
id: number // 申请编号
|
||||||
|
fromUserId: number // 发起方用户编号
|
||||||
|
toUserId: number // 接收方用户编号
|
||||||
|
handleResult: number // 处理结果:0=未处理;1=同意;2=拒绝
|
||||||
|
applyContent?: string // 申请理由(发起方填写)
|
||||||
|
handleContent?: string // 处理理由(接收方拒绝时可选填)
|
||||||
|
addSource?: number // 添加来源;参见 ImFriendAddSourceEnum
|
||||||
|
handleTime?: number // 处理时间(毫秒时间戳)
|
||||||
|
createTime: number // 申请创建时间(毫秒时间戳)
|
||||||
|
|
||||||
|
// ========== 聚合字段(自 AdminUser,仅展示用) ==========
|
||||||
|
fromNickname?: string // 发起方昵称
|
||||||
|
fromAvatar?: string // 发起方头像
|
||||||
|
toNickname?: string // 接收方昵称
|
||||||
|
toAvatar?: string // 接收方头像
|
||||||
|
}
|
||||||
|
|
||||||
// ==================== 用户名片 ====================
|
// ==================== 用户名片 ====================
|
||||||
|
|
||||||
// 用户精简信息(对齐后端 UserSimpleRespVO,名片 / 头像 hover 等场景共用)
|
// 用户精简信息(对齐后端 UserSimpleRespVO,名片 / 头像 hover 等场景共用)
|
||||||
|
|
|
||||||
|
|
@ -10,9 +10,17 @@ export const ImMessageType = {
|
||||||
RECEIPT: 12, // 回执
|
RECEIPT: 12, // 回执
|
||||||
TIP_TIME: 20, // 时间分隔线(前端本地生成,不发送到后端)
|
TIP_TIME: 20, // 时间分隔线(前端本地生成,不发送到后端)
|
||||||
TIP_TEXT: 21, // 提示文本(撤回提示等)
|
TIP_TEXT: 21, // 提示文本(撤回提示等)
|
||||||
FRIEND_ADD: 100, // 好友添加
|
// 好友通知(1201-1210 复用 OpenIM 段位编号)
|
||||||
FRIEND_DELETE: 101, // 好友删除
|
FRIEND_REQUEST_APPROVED: 1201, // 好友申请被同意
|
||||||
FRIEND_UPDATE: 102, // 好友更新(客户端收到后自行拉取)
|
FRIEND_REQUEST_REJECTED: 1202, // 好友申请被拒绝
|
||||||
|
FRIEND_APPLICATION: 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, // 好友信息批量更新(muted / pinned)
|
||||||
// 群事件(1501-1520 复用 OpenIM 段位编号;1530+ 自有扩展段)
|
// 群事件(1501-1520 复用 OpenIM 段位编号;1530+ 自有扩展段)
|
||||||
GROUP_CREATE: 1501, // 群创建
|
GROUP_CREATE: 1501, // 群创建
|
||||||
GROUP_INFO_UPDATE: 1502, // 群信息变更(NAME / NOTICE 之外字段兜底)
|
GROUP_INFO_UPDATE: 1502, // 群信息变更(NAME / NOTICE 之外字段兜底)
|
||||||
|
|
@ -48,6 +56,11 @@ export function isGroupNotification(type: number): boolean {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 判断是否「好友通知事件」:1201-1210 段位 */
|
||||||
|
export function isFriendNotification(type: number): boolean {
|
||||||
|
return type >= ImMessageType.FRIEND_REQUEST_APPROVED && type <= ImMessageType.FRIEND_UPDATE
|
||||||
|
}
|
||||||
|
|
||||||
/** IM 普通消息类型集合(聊天气泡中显示,并作为会话最后一条摘要) */
|
/** IM 普通消息类型集合(聊天气泡中显示,并作为会话最后一条摘要) */
|
||||||
const ImMessageTypeNormals: number[] = [
|
const ImMessageTypeNormals: number[] = [
|
||||||
ImMessageType.TEXT,
|
ImMessageType.TEXT,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue