fix(im):批量修复群管理、RTC 和消息链路问题
- 修复群管理行锁、管理员角色更新、群主转让、置顶消息并发问题 - 修复好友申请 maxId 游标、重复申请排序、通知类型校验和消息内容结构校验 - 修复消息统计口径、RTC token 鉴权、离会通知、前端拉取取消和媒体重试 - 优化表情批量删除、WebSocket 推送注释、群 READ 字段和相关单测 - 更新 bug_todo、bug_done 和 bug_rejected,剩余 9 个待修im
parent
f3807e30d5
commit
a4dfb717aa
|
|
@ -44,11 +44,11 @@ export const refuseFriendRequest = (id: number | string, handleContent?: string)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 查询「我相关」的好友申请列表(游标分页:传 lastRequestId 加载更多)
|
// 查询「我相关」的好友申请列表(游标分页:传 maxId 加载更多)
|
||||||
export const getMyFriendRequestList = (limit: number, lastRequestId?: number) => {
|
export const getMyFriendRequestList = (limit: number, maxId?: number) => {
|
||||||
const params: Record<string, number> = { limit }
|
const params: Record<string, number> = { limit }
|
||||||
if (lastRequestId != null) {
|
if (maxId != null) {
|
||||||
params.lastRequestId = lastRequestId
|
params.maxId = maxId
|
||||||
}
|
}
|
||||||
return request.get<ImFriendRequestRespVO[]>({
|
return request.get<ImFriendRequestRespVO[]>({
|
||||||
url: '/im/friend-request/list',
|
url: '/im/friend-request/list',
|
||||||
|
|
|
||||||
|
|
@ -12,10 +12,11 @@ export interface ImChannelMessageRespVO {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 拉取当前用户应收的频道消息(离线增量);按 minId 游标分页
|
// 拉取当前用户应收的频道消息(离线增量);按 minId 游标分页
|
||||||
export const pullChannelMessages = (params: { minId: number; size?: number }) => {
|
export const pullChannelMessages = (params: { minId: number; size?: number }, signal?: AbortSignal) => {
|
||||||
return request.get<ImChannelMessageRespVO[]>({
|
return request.get<ImChannelMessageRespVO[]>({
|
||||||
url: '/im/channel/message/pull',
|
url: '/im/channel/message/pull',
|
||||||
params
|
params,
|
||||||
|
signal
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -39,8 +39,11 @@ export const sendGroupMessage = (data: ImGroupMessageSendReqVO) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 拉取群聊消息(增量)
|
// 拉取群聊消息(增量)
|
||||||
export const pullGroupMessages = (params: { minId: number | string; size: number }) => {
|
export const pullGroupMessages = (
|
||||||
return request.get<ImGroupMessageRespVO[]>({ url: '/im/message/group/pull', params })
|
params: { minId: number | string; size: number },
|
||||||
|
signal?: AbortSignal
|
||||||
|
) => {
|
||||||
|
return request.get<ImGroupMessageRespVO[]>({ url: '/im/message/group/pull', params, signal })
|
||||||
}
|
}
|
||||||
|
|
||||||
// 查询群聊历史消息
|
// 查询群聊历史消息
|
||||||
|
|
|
||||||
|
|
@ -33,8 +33,11 @@ export const sendPrivateMessage = (data: ImPrivateMessageSendReqVO) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 拉取私聊消息(增量)
|
// 拉取私聊消息(增量)
|
||||||
export const pullPrivateMessages = (params: { minId: number | string; size: number }) => {
|
export const pullPrivateMessages = (
|
||||||
return request.get<ImPrivateMessageRespVO[]>({ url: '/im/message/private/pull', params })
|
params: { minId: number | string; size: number },
|
||||||
|
signal?: AbortSignal
|
||||||
|
) => {
|
||||||
|
return request.get<ImPrivateMessageRespVO[]>({ url: '/im/message/private/pull', params, signal })
|
||||||
}
|
}
|
||||||
|
|
||||||
// 查询私聊历史消息
|
// 查询私聊历史消息
|
||||||
|
|
@ -51,10 +54,11 @@ export const readPrivateMessages = (receiverId: number | string, messageId: numb
|
||||||
}
|
}
|
||||||
|
|
||||||
// 查询对方已读到我发的最大消息 id(多端 / 离线后用于补齐已读状态)
|
// 查询对方已读到我发的最大消息 id(多端 / 离线后用于补齐已读状态)
|
||||||
export const getPrivateMaxReadMessageId = (peerId: number | string) => {
|
export const getPrivateMaxReadMessageId = (peerId: number | string, signal?: AbortSignal) => {
|
||||||
return request.get<number | null>({
|
return request.get<number | null>({
|
||||||
url: '/im/message/private/max-read-message-id',
|
url: '/im/message/private/max-read-message-id',
|
||||||
params: { peerId }
|
params: { peerId },
|
||||||
|
signal
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -108,6 +108,8 @@ export interface UploadAndSendMediaOptions {
|
||||||
quote?: QuoteMessage
|
quote?: QuoteMessage
|
||||||
/** 锁定起始会话,上传期间会话切走则放弃发送 */
|
/** 锁定起始会话,上传期间会话切走则放弃发送 */
|
||||||
conversation: Conversation
|
conversation: Conversation
|
||||||
|
/** 重试已有占位消息时复用的客户端消息编号 */
|
||||||
|
existingClientMessageId?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -169,10 +171,20 @@ export const useMediaUploader = () => {
|
||||||
type: number
|
type: number
|
||||||
conversation: Conversation
|
conversation: Conversation
|
||||||
buildContent: (blobUrl: string) => string
|
buildContent: (blobUrl: string) => string
|
||||||
|
existingClientMessageId?: string
|
||||||
}): { clientMessageId: string; blobUrl: string } => {
|
}): { clientMessageId: string; blobUrl: string } => {
|
||||||
const { conversation } = opts
|
const { conversation } = opts
|
||||||
const blobUrl = URL.createObjectURL(opts.file)
|
const blobUrl = URL.createObjectURL(opts.file)
|
||||||
const clientMessageId = generateClientMessageId()
|
const clientMessageId = opts.existingClientMessageId || generateClientMessageId()
|
||||||
|
if (opts.existingClientMessageId) {
|
||||||
|
conversationStore.patchMessage(conversation.type, conversation.targetId, clientMessageId, {
|
||||||
|
content: opts.buildContent(blobUrl),
|
||||||
|
status: ImMessageStatus.SENDING,
|
||||||
|
uploadProgress: 0,
|
||||||
|
_localFile: opts.file
|
||||||
|
})
|
||||||
|
return { clientMessageId, blobUrl }
|
||||||
|
}
|
||||||
const placeholder: Message = {
|
const placeholder: Message = {
|
||||||
id: 0,
|
id: 0,
|
||||||
clientMessageId,
|
clientMessageId,
|
||||||
|
|
@ -332,7 +344,8 @@ export const useMediaUploader = () => {
|
||||||
file: opts.file,
|
file: opts.file,
|
||||||
type: opts.type,
|
type: opts.type,
|
||||||
conversation,
|
conversation,
|
||||||
buildContent
|
buildContent,
|
||||||
|
existingClientMessageId: opts.existingClientMessageId
|
||||||
})
|
})
|
||||||
|
|
||||||
// 2. 上传:进度回调 patch uploadProgress;失败保留 _localFile 供重试
|
// 2. 上传:进度回调 patch uploadProgress;失败保留 _localFile 供重试
|
||||||
|
|
|
||||||
|
|
@ -52,6 +52,12 @@ export const useMessagePuller = () => {
|
||||||
const groupStore = useGroupStore()
|
const groupStore = useGroupStore()
|
||||||
const currentUserId = getCurrentUserId()
|
const currentUserId = getCurrentUserId()
|
||||||
|
|
||||||
|
/** 判断请求是否被主动取消 */
|
||||||
|
const isAbortError = (e: unknown): boolean => {
|
||||||
|
const error = e as { name?: string; code?: string; message?: string }
|
||||||
|
return error?.name === 'CanceledError' || error?.code === 'ERR_CANCELED' || error?.message === 'canceled'
|
||||||
|
}
|
||||||
|
|
||||||
/** 私聊会话归属:自己发的算"发给 receiverId 的会话",否则算"发送方的会话";curry currentUserId 进闭包减少 3 处调用方的样板 */
|
/** 私聊会话归属:自己发的算"发给 receiverId 的会话",否则算"发送方的会话";curry currentUserId 进闭包减少 3 处调用方的样板 */
|
||||||
const getPrivatePeerId = (message: ImPrivateMessageRespVO) =>
|
const getPrivatePeerId = (message: ImPrivateMessageRespVO) =>
|
||||||
getPrivateMessagePeerId(message, currentUserId)
|
getPrivateMessagePeerId(message, currentUserId)
|
||||||
|
|
@ -147,25 +153,26 @@ export const useMessagePuller = () => {
|
||||||
conversationType: number,
|
conversationType: number,
|
||||||
startMinId: number,
|
startMinId: number,
|
||||||
startEpoch: number,
|
startEpoch: number,
|
||||||
startUserId: number
|
startUserId: number,
|
||||||
|
signal: AbortSignal
|
||||||
) => {
|
) => {
|
||||||
// 私聊 / 群聊 / 频道各自一套接口;按 conversationType 在循环内分支调度
|
// 私聊 / 群聊 / 频道各自一套接口;按 conversationType 在循环内分支调度
|
||||||
let minId = startMinId || 0
|
let minId = startMinId || 0
|
||||||
const isPrivate = conversationType === ImConversationType.PRIVATE
|
const isPrivate = conversationType === ImConversationType.PRIVATE
|
||||||
const isChannel = conversationType === ImConversationType.CHANNEL
|
const isChannel = conversationType === ImConversationType.CHANNEL
|
||||||
const size = isPrivate ? MESSAGE_PRIVATE_PULL_SIZE : MESSAGE_GROUP_PULL_SIZE
|
const size = isPrivate ? MESSAGE_PRIVATE_PULL_SIZE : MESSAGE_GROUP_PULL_SIZE
|
||||||
const isStillValid = () => pullEpoch === startEpoch && getCurrentUserId() === startUserId
|
const isStillValid = () => !signal.aborted && pullEpoch === startEpoch && getCurrentUserId() === startUserId
|
||||||
while (true) {
|
while (true) {
|
||||||
if (!isStillValid()) {
|
if (!isStillValid()) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let list: any[] | undefined
|
let list: any[] | undefined
|
||||||
if (isPrivate) {
|
if (isPrivate) {
|
||||||
list = await apiPullPrivateMessages({ minId, size })
|
list = await apiPullPrivateMessages({ minId, size }, signal)
|
||||||
} else if (isChannel) {
|
} else if (isChannel) {
|
||||||
list = await apiPullChannelMessages({ minId, size })
|
list = await apiPullChannelMessages({ minId, size }, signal)
|
||||||
} else {
|
} else {
|
||||||
list = await apiPullGroupMessages({ minId, size })
|
list = await apiPullGroupMessages({ minId, size }, signal)
|
||||||
}
|
}
|
||||||
// 接口返回期间发生 cancel / 切账号:丢弃本批不入库,也不再翻页
|
// 接口返回期间发生 cancel / 切账号:丢弃本批不入库,也不再翻页
|
||||||
if (!isStillValid()) {
|
if (!isStillValid()) {
|
||||||
|
|
@ -246,6 +253,7 @@ export const useMessagePuller = () => {
|
||||||
|
|
||||||
/** 同一时刻只允许一次 pull:Index.vue 的手动调用与重连 watch 触发可能并发,共用同一个 promise 即可去重 */
|
/** 同一时刻只允许一次 pull:Index.vue 的手动调用与重连 watch 触发可能并发,共用同一个 promise 即可去重 */
|
||||||
let pullPromise: Promise<void> | null = null
|
let pullPromise: Promise<void> | null = null
|
||||||
|
let pullAbortController: AbortController | null = null
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 首次 pull 是否已完成。仅在置 true 后,isConnected watch 才会触发 pull。
|
* 首次 pull 是否已完成。仅在置 true 后,isConnected watch 才会触发 pull。
|
||||||
|
|
@ -265,6 +273,8 @@ export const useMessagePuller = () => {
|
||||||
/** 显式取消:仅由 Index.vue onUnmounted(离开 IM / 切账号 / 路由跳出)调用 */
|
/** 显式取消:仅由 Index.vue onUnmounted(离开 IM / 切账号 / 路由跳出)调用 */
|
||||||
const cancelPull = () => {
|
const cancelPull = () => {
|
||||||
pullEpoch++
|
pullEpoch++
|
||||||
|
pullAbortController?.abort()
|
||||||
|
pullAbortController = null
|
||||||
// 旧 promise 仍在 finally 阶段跑,但 epoch 守卫已阻断后续副作用;这里立刻让 pullPromise = null 让新一轮可重入
|
// 旧 promise 仍在 finally 阶段跑,但 epoch 守卫已阻断后续副作用;这里立刻让 pullPromise = null 让新一轮可重入
|
||||||
pullPromise = null
|
pullPromise = null
|
||||||
// 同步丢弃 WS 缓冲帧;旧 pull 已不会 flushBuffer,若不清下次进 IM 第一次 pullOnce 会把旧 session 的帧回放进新 store
|
// 同步丢弃 WS 缓冲帧;旧 pull 已不会 flushBuffer,若不清下次进 IM 第一次 pullOnce 会把旧 session 的帧回放进新 store
|
||||||
|
|
@ -282,8 +292,13 @@ export const useMessagePuller = () => {
|
||||||
const startEpoch = pullEpoch
|
const startEpoch = pullEpoch
|
||||||
// 启动时的用户快照;pullByType 每批 await 后比对当前登录用户,账号变了立刻丢弃
|
// 启动时的用户快照;pullByType 每批 await 后比对当前登录用户,账号变了立刻丢弃
|
||||||
const startUserId = currentUserId
|
const startUserId = currentUserId
|
||||||
|
const abortController = new AbortController()
|
||||||
|
pullAbortController = abortController
|
||||||
// 本轮 pull 仍属于当前 session:epoch 未漂 + 用户未切;任何动新 store 状态的副作用都要先过这道关
|
// 本轮 pull 仍属于当前 session:epoch 未漂 + 用户未切;任何动新 store 状态的副作用都要先过这道关
|
||||||
const isCurrentPull = () => pullEpoch === startEpoch && getCurrentUserId() === startUserId
|
const isCurrentPull = () =>
|
||||||
|
!abortController.signal.aborted &&
|
||||||
|
pullEpoch === startEpoch &&
|
||||||
|
getCurrentUserId() === startUserId
|
||||||
pullPromise = (async () => {
|
pullPromise = (async () => {
|
||||||
try {
|
try {
|
||||||
// 旧 puller 在 cancelPull 未触发的异常路径上再进来时,先于任何副作用退出,避免污染新 session 的 loading
|
// 旧 puller 在 cancelPull 未触发的异常路径上再进来时,先于任何副作用退出,避免污染新 session 的 loading
|
||||||
|
|
@ -298,22 +313,28 @@ export const useMessagePuller = () => {
|
||||||
ImConversationType.PRIVATE,
|
ImConversationType.PRIVATE,
|
||||||
conversationStore.privateMessageMaxId,
|
conversationStore.privateMessageMaxId,
|
||||||
startEpoch,
|
startEpoch,
|
||||||
startUserId
|
startUserId,
|
||||||
|
abortController.signal
|
||||||
),
|
),
|
||||||
pullByType(
|
pullByType(
|
||||||
ImConversationType.GROUP,
|
ImConversationType.GROUP,
|
||||||
conversationStore.groupMessageMaxId,
|
conversationStore.groupMessageMaxId,
|
||||||
startEpoch,
|
startEpoch,
|
||||||
startUserId
|
startUserId,
|
||||||
|
abortController.signal
|
||||||
),
|
),
|
||||||
pullByType(
|
pullByType(
|
||||||
ImConversationType.CHANNEL,
|
ImConversationType.CHANNEL,
|
||||||
conversationStore.channelMessageMaxId,
|
conversationStore.channelMessageMaxId,
|
||||||
startEpoch,
|
startEpoch,
|
||||||
startUserId
|
startUserId,
|
||||||
|
abortController.signal
|
||||||
)
|
)
|
||||||
])
|
])
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
if (isAbortError(e)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
console.error('[IM] 拉取离线消息失败:', e)
|
console.error('[IM] 拉取离线消息失败:', e)
|
||||||
} finally {
|
} finally {
|
||||||
// 仍属本轮才复位 loading;旧轮被 cancel / 切账号时由新一轮自管,避免覆盖新 session 的 true
|
// 仍属本轮才复位 loading;旧轮被 cancel / 切账号时由新一轮自管,避免覆盖新 session 的 true
|
||||||
|
|
@ -348,7 +369,7 @@ export const useMessagePuller = () => {
|
||||||
const active = conversationStore.activeConversation
|
const active = conversationStore.activeConversation
|
||||||
if (MESSAGE_PRIVATE_READ_ENABLED && active && active.type === ImConversationType.PRIVATE) {
|
if (MESSAGE_PRIVATE_READ_ENABLED && active && active.type === ImConversationType.PRIVATE) {
|
||||||
try {
|
try {
|
||||||
const maxReadId = await apiGetPrivateMaxReadMessageId(active.targetId)
|
const maxReadId = await apiGetPrivateMaxReadMessageId(active.targetId, abortController.signal)
|
||||||
if (!isCurrentPull()) {
|
if (!isCurrentPull()) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -360,6 +381,9 @@ export const useMessagePuller = () => {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
if (isAbortError(e)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
console.warn('[IM] 拉取对方已读位置失败', e)
|
console.warn('[IM] 拉取对方已读位置失败', e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -368,8 +392,14 @@ export const useMessagePuller = () => {
|
||||||
if (isCurrentPull()) {
|
if (isCurrentPull()) {
|
||||||
pullPromise = null
|
pullPromise = null
|
||||||
initialPulled = true
|
initialPulled = true
|
||||||
|
if (pullAbortController === abortController) {
|
||||||
|
pullAbortController = null
|
||||||
|
}
|
||||||
} else if (pullEpoch === startEpoch) {
|
} else if (pullEpoch === startEpoch) {
|
||||||
pullPromise = null
|
pullPromise = null
|
||||||
|
if (pullAbortController === abortController) {
|
||||||
|
pullAbortController = null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})()
|
})()
|
||||||
|
|
|
||||||
|
|
@ -116,7 +116,7 @@ const enrichedRequests = computed(() =>
|
||||||
props.requests.map((request) => ({ request, peer: getPeer(request) }))
|
props.requests.map((request) => ({ request, peer: getPeer(request) }))
|
||||||
)
|
)
|
||||||
|
|
||||||
/** 点击「加载更多」拉下一页;store 内部按 lastRequestId 游标分页 + pending 去重 */
|
/** 点击「加载更多」拉下一页;store 内部按 maxId 游标分页 + pending 去重 */
|
||||||
const loadingMore = ref(false)
|
const loadingMore = ref(false)
|
||||||
async function handleLoadMore() {
|
async function handleLoadMore() {
|
||||||
if (loadingMore.value) {
|
if (loadingMore.value) {
|
||||||
|
|
|
||||||
|
|
@ -331,11 +331,6 @@ const isMaterial = computed(
|
||||||
conversationStore.activeConversation?.type === ImConversationType.CHANNEL
|
conversationStore.activeConversation?.type === ImConversationType.CHANNEL
|
||||||
)
|
)
|
||||||
|
|
||||||
/** 私聊 / 群聊里被转发过来的素材:用紧凑卡片宽度(标题左 + 小封面右) */
|
|
||||||
const isForwardedMaterial = computed(
|
|
||||||
() => props.message.type === ImMessageType.MATERIAL && !isMaterial.value
|
|
||||||
)
|
|
||||||
|
|
||||||
/** 当前是否在公众号 / 频道会话内:限制右键菜单只展示转发 / 删除 */
|
/** 当前是否在公众号 / 频道会话内:限制右键菜单只展示转发 / 删除 */
|
||||||
const isChannelConversation = computed(
|
const isChannelConversation = computed(
|
||||||
() => conversationStore.activeConversation?.type === ImConversationType.CHANNEL
|
() => conversationStore.activeConversation?.type === ImConversationType.CHANNEL
|
||||||
|
|
@ -968,16 +963,13 @@ async function handleResend() {
|
||||||
if (handler) {
|
if (handler) {
|
||||||
const oldQuote = getQuoteFromMessage(message.content) ?? undefined
|
const oldQuote = getQuoteFromMessage(message.content) ?? undefined
|
||||||
const context = handler.extractResendContext(message.content)
|
const context = handler.extractResendContext(message.content)
|
||||||
conversationStore.removeMessage(conversation.type, conversation.targetId, {
|
|
||||||
id: message.id,
|
|
||||||
clientMessageId: message.clientMessageId
|
|
||||||
})
|
|
||||||
await uploadAndSendMedia({
|
await uploadAndSendMedia({
|
||||||
file,
|
file,
|
||||||
type: message.type,
|
type: message.type,
|
||||||
quote: oldQuote,
|
quote: oldQuote,
|
||||||
conversation,
|
conversation,
|
||||||
context
|
context,
|
||||||
|
existingClientMessageId: message.clientMessageId
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -124,7 +124,7 @@ export const useFriendStore = defineStore('imFriendStore', {
|
||||||
},
|
},
|
||||||
/** 未处理申请数(接收方=我)—— 实时派生,「新的朋友」红点用 */
|
/** 未处理申请数(接收方=我)—— 实时派生,「新的朋友」红点用 */
|
||||||
getUnhandledRequestCount: (state): number => {
|
getUnhandledRequestCount: (state): number => {
|
||||||
const currentUserId = Number(getCurrentUserId() || 0)
|
const currentUserId = getCurrentUserId()
|
||||||
return state.friendRequests.filter(
|
return state.friendRequests.filter(
|
||||||
(request) =>
|
(request) =>
|
||||||
request.handleResult === ImFriendRequestHandleResult.UNHANDLED &&
|
request.handleResult === ImFriendRequestHandleResult.UNHANDLED &&
|
||||||
|
|
@ -480,11 +480,23 @@ export const useFriendStore = defineStore('imFriendStore', {
|
||||||
|
|
||||||
/** FRIEND_REQUEST_RECEIVED(1203):收到新申请;payload 已带申请方昵称 / 头像,按 requestId 直推 push 进列表 */
|
/** FRIEND_REQUEST_RECEIVED(1203):收到新申请;payload 已带申请方昵称 / 头像,按 requestId 直推 push 进列表 */
|
||||||
applyFriendRequestReceivedNotification(payload: FriendNotificationPayload) {
|
applyFriendRequestReceivedNotification(payload: FriendNotificationPayload) {
|
||||||
// 多端可能重复推同一 requestId,已存在则跳过
|
const currentUserId = getCurrentUserId()
|
||||||
if (this.findFriendRequest(payload.requestId!)) {
|
const existingIndex = this.friendRequests.findIndex((item) => item.id === payload.requestId)
|
||||||
|
if (existingIndex >= 0) {
|
||||||
|
const existing = this.friendRequests.splice(existingIndex, 1)[0]
|
||||||
|
this.friendRequests.unshift({
|
||||||
|
...existing,
|
||||||
|
fromUserId: payload.operatorUserId,
|
||||||
|
toUserId: currentUserId,
|
||||||
|
handleResult: ImFriendRequestHandleResult.UNHANDLED,
|
||||||
|
applyContent: payload.applyContent,
|
||||||
|
addSource: payload.addSource,
|
||||||
|
createTime: Date.now(),
|
||||||
|
fromNickname: payload.fromNickname,
|
||||||
|
fromAvatar: payload.fromAvatar
|
||||||
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const currentUserId = Number(getCurrentUserId() || 0)
|
|
||||||
this.friendRequests.unshift({
|
this.friendRequests.unshift({
|
||||||
id: payload.requestId!,
|
id: payload.requestId!,
|
||||||
fromUserId: payload.operatorUserId,
|
fromUserId: payload.operatorUserId,
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ export interface ImGroupMessageDTO {
|
||||||
receiverUserIds?: number[] // 群定向接收用户列表
|
receiverUserIds?: number[] // 群定向接收用户列表
|
||||||
readCount?: number // 群回执已读人数(type = RECEIPT 时使用)
|
readCount?: number // 群回执已读人数(type = RECEIPT 时使用)
|
||||||
receiptStatus?: number // 群回执状态(type = RECEIPT 时使用)
|
receiptStatus?: number // 群回执状态(type = RECEIPT 时使用)
|
||||||
|
readId?: number // 已读位置
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================== 本地会话 / 消息结构 ====================
|
// ==================== 本地会话 / 消息结构 ====================
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue