From ce66a507efa155c94d51d8db106202fbeca5db24 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Thu, 7 May 2026 17:25:03 +0800 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20feat(im):=20=E5=88=9D=E5=A7=8B?= =?UTF-8?q?=E5=8C=96=E7=BE=A4=E5=90=8D=E7=89=87=20v0.2=EF=BC=9A=E7=AC=AC?= =?UTF-8?q?=E4=BA=8C=E6=AC=A1=E8=AF=84=E5=AE=A1=EF=BC=88=E9=9C=80=E6=B1=82?= =?UTF-8?q?=E5=90=84=E7=A7=8D=E8=BF=9B=E7=BE=A4=E7=9A=84=E5=B0=8F=E9=97=AE?= =?UTF-8?q?=E9=A2=98=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../im/home/components/group/GroupInfo.vue | 142 ++++++++++++++++++ .../home/components/group/GroupInfoCard.vue | 96 ++++++++++++ .../im/home/pages/contact/GroupDetail.vue | 87 +---------- .../conversation/ConversationGroupSide.vue | 5 +- src/views/im/home/store/groupStore.ts | 36 ++++- 5 files changed, 277 insertions(+), 89 deletions(-) create mode 100644 src/views/im/home/components/group/GroupInfo.vue create mode 100644 src/views/im/home/components/group/GroupInfoCard.vue diff --git a/src/views/im/home/components/group/GroupInfo.vue b/src/views/im/home/components/group/GroupInfo.vue new file mode 100644 index 000000000..2cf079ac1 --- /dev/null +++ b/src/views/im/home/components/group/GroupInfo.vue @@ -0,0 +1,142 @@ + + + diff --git a/src/views/im/home/components/group/GroupInfoCard.vue b/src/views/im/home/components/group/GroupInfoCard.vue new file mode 100644 index 000000000..c91c19c8a --- /dev/null +++ b/src/views/im/home/components/group/GroupInfoCard.vue @@ -0,0 +1,96 @@ + + + diff --git a/src/views/im/home/pages/contact/GroupDetail.vue b/src/views/im/home/pages/contact/GroupDetail.vue index 9bb21afbb..25d9cdf9b 100644 --- a/src/views/im/home/pages/contact/GroupDetail.vue +++ b/src/views/im/home/pages/contact/GroupDetail.vue @@ -1,98 +1,25 @@ diff --git a/src/views/im/home/pages/conversation/components/conversation/ConversationGroupSide.vue b/src/views/im/home/pages/conversation/components/conversation/ConversationGroupSide.vue index e1f8f14fe..6be42eecd 100644 --- a/src/views/im/home/pages/conversation/components/conversation/ConversationGroupSide.vue +++ b/src/views/im/home/pages/conversation/components/conversation/ConversationGroupSide.vue @@ -700,7 +700,10 @@ async function handleQuit() { } const groupId = props.group.id await quitGroup(groupId) - // 同步清本地:会话列表 + 群 store;不清的话冷启动前还会残留这条群 / 会话 + // 本地立即响应:先把 self.member 置 DISABLE(让 GroupInfo 等 isMember 收敛),再清会话 + 群 store + if (myId.value) { + groupStore.updateMemberStatus(groupId, myId.value, CommonStatusEnum.DISABLE) + } conversationStore.removeConversation(ImConversationType.GROUP, groupId) groupStore.removeGroup(groupId) message.success('已退出群聊') diff --git a/src/views/im/home/store/groupStore.ts b/src/views/im/home/store/groupStore.ts index 6e0ac6ada..d94ee0aa0 100644 --- a/src/views/im/home/store/groupStore.ts +++ b/src/views/im/home/store/groupStore.ts @@ -15,6 +15,7 @@ import { import { useConversationStore } from './conversationStore' import { useGroupRequestStore } from './groupRequestStore' import { ImConversationType, ImGroupMemberRole, ImMessageType } from '../../utils/constants' +import { CommonStatusEnum } from '@/utils/constants' import { getCurrentUserId, imStorage, @@ -453,6 +454,17 @@ export const useGroupStore = defineStore('imGroupStore', { this.saveGroupMembers(groupId) }, + /** 本地更新群成员的 status(自己退群 / 被踢的本地预置;让 isMember 立即收敛到 stranger,不依赖 removeGroup 的整群移除) */ + updateMemberStatus(groupId: number, userId: number, status: number) { + const group = this.getGroup(groupId) + const member = group?.members?.find((m) => m.userId === userId) + if (!member || member.status === status) { + return + } + member.status = status + this.saveGroupMembers(groupId) + }, + /** 本地更新群成员的 displayUserName(GROUP_MEMBER_NICKNAME_UPDATE 事件);不命中则等 fetchGroupMembers 兜底 */ updateMemberDisplayUserName(groupId: number, userId: number, displayUserName: string) { const group = this.getGroup(groupId) @@ -574,7 +586,7 @@ export const useGroupStore = defineStore('imGroupStore', { }, /** 创建群广播:创建者多端同步 + 初始成员 bootstrap;payload.memberUserIds 含自己 → 拉群详情 / 成员;本端发起者已经 upsert 过本群,跳过避免双拉 */ - applyGroupCreateNotification(groupId: number, payload: GroupNotificationPayload) { + async applyGroupCreateNotification(groupId: number, payload: GroupNotificationPayload) { if (!isSelfInPayloadMembers(payload)) { return } @@ -583,7 +595,8 @@ export const useGroupStore = defineStore('imGroupStore', { if (selfIsOperator && this.getGroup(groupId)) { return } - this.fetchGroupInfo(groupId).catch(() => undefined) + // 先 await fetchGroupInfo 把群 upsert 进 state.groups;否则 fetchGroupMembers 的「不是我加入的群」guard 会兜空 + await this.fetchGroupInfo(groupId) this.fetchGroupMembers(groupId, true).catch(() => undefined) }, @@ -611,36 +624,43 @@ export const useGroupStore = defineStore('imGroupStore', { }, /** 成员加入:被邀请者本端 group 未就位先 fetchGroupInfo bootstrap;所有人都刷成员列表(新成员 nickname / avatar 不在 payload) */ - applyGroupMemberInviteNotification(groupId: number, payload: GroupNotificationPayload) { + async applyGroupMemberInviteNotification(groupId: number, payload: GroupNotificationPayload) { + // 自己刚被拉进来:必须 await fetchGroupInfo 让群入 state.groups,否则 fetchGroupMembers 的 guard 会兜空 if (isSelfInPayloadMembers(payload) && !this.getGroup(groupId)) { - this.fetchGroupInfo(groupId).catch(() => undefined) + await this.fetchGroupInfo(groupId) } this.fetchGroupMembers(groupId, true).catch(() => undefined) }, /** 自由进群:进群者本端 group 未就位先 fetchGroupInfo bootstrap;所有人都刷成员列表 */ - applyGroupMemberEnterNotification(groupId: number, payload: GroupNotificationPayload) { + async applyGroupMemberEnterNotification(groupId: number, payload: GroupNotificationPayload) { const selfUserId = getCurrentUserId() + // 自己自由进群:必须 await fetchGroupInfo 让群入 state.groups,否则 fetchGroupMembers 的 guard 会兜空 if (selfUserId && payload.entrantUserId === selfUserId && !this.getGroup(groupId)) { - this.fetchGroupInfo(groupId).catch(() => undefined) + await this.fetchGroupInfo(groupId) } this.fetchGroupMembers(groupId, true).catch(() => undefined) }, - /** 成员退群:退群者本人多端同步走 removeGroup;其他成员从本地列表移除 quitter */ + /** 成员退群:退群者本人先把 self.status 置 DISABLE 再 removeGroup(保留状态语义 + 维持 groups 列表干净);其他成员从本地列表移除 quitter */ applyGroupMemberQuitNotification(groupId: number, payload: GroupNotificationPayload) { const selfUserId = getCurrentUserId() if (selfUserId && payload.operatorUserId === selfUserId) { + this.updateMemberStatus(groupId, selfUserId, CommonStatusEnum.DISABLE) this.removeGroup(groupId) } else if (payload.operatorUserId) { this.removeMembersLocal(groupId, [payload.operatorUserId]) } }, - /** 成员被移出:被踢者本人 removeGroup;其他成员从本地列表移除被踢者 */ + /** 成员被移出:被踢者本人先把 self.status 置 DISABLE 再 removeGroup;其他成员从本地列表移除被踢者 */ applyGroupMemberKickNotification(groupId: number, payload: GroupNotificationPayload) { const memberIds = payload.memberUserIds || [] + const selfUserId = getCurrentUserId() if (isSelfInPayloadMembers(payload)) { + if (selfUserId) { + this.updateMemberStatus(groupId, selfUserId, CommonStatusEnum.DISABLE) + } this.removeGroup(groupId) } else if (memberIds.length) { this.removeMembersLocal(groupId, memberIds)