From cb26df3ca121d42e01785f9f35aab6518ed60668 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Thu, 7 May 2026 00:51:48 +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=E7=94=B3=E8=AF=B7=20v0.4=EF=BC=9A=E7=AC=AC?= =?UTF-8?q?=E4=BA=94=E6=8A=8A=20review=EF=BC=88=E5=A4=9A=E8=BD=AE=20findin?= =?UTF-8?q?g=20=E4=BF=AE=E5=A4=8D=20+=20=E9=80=9A=E7=9F=A5=E9=9D=99?= =?UTF-8?q?=E9=BB=98=E5=8C=96=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 邀请路径写 addSource=INVITE;群主 / 管理员邀请绕过审批;inviteGroupMember 入参去重 - getGroupRequest 越权校验加成员有效状态判断;新增 list-by-group 接口 - 申请列表按 update_time 倒序,update(null, wrapper) 路径手动刷 updateTime - addByRequestId 不再 skip 同 id,复用记录刷新并置顶 - GroupRequestListDialog 单群模式订阅 store 增量同步;GroupMemberAddDialog 审批分支文案区分 - ConversationItem 增加 [X 条进群申请] 红字前缀;MessagePanel 顶部胶囊横幅 - 1505 / 1506 通知改静默:同意走群事件渲染系统提示,拒绝不再打扰;清掉 dead inviterUserId 字段 --- .../components/group/GroupMemberAddDialog.vue | 26 ++++++++++++++++++- .../group/GroupRequestListDialog.vue | 26 +++++++++++++++++++ src/views/im/home/store/groupRequestStore.ts | 13 +++++----- src/views/im/home/store/websocketStore.ts | 19 +------------- 4 files changed, 59 insertions(+), 25 deletions(-) diff --git a/src/views/im/home/components/group/GroupMemberAddDialog.vue b/src/views/im/home/components/group/GroupMemberAddDialog.vue index 59cf1bdbd..07001b45f 100644 --- a/src/views/im/home/components/group/GroupMemberAddDialog.vue +++ b/src/views/im/home/components/group/GroupMemberAddDialog.vue @@ -86,6 +86,9 @@ import { useMessage } from '@/hooks/web/useMessage' import { CommonStatusEnum } from '@/utils/constants' import { inviteGroupMember } from '@/api/im/group/member' +import { useUserStore } from '@/store/modules/user' +import { ImGroupMemberRole } from '@/views/im/utils/constants' +import { useGroupStore } from '../../store/groupStore' import FriendItem from '../friend/FriendItem.vue' import type { FriendLite } from '../../types' import type { GroupMemberLite } from './GroupMember.vue' @@ -116,6 +119,8 @@ const emit = defineEmits<{ }>() const message = useMessage() +const userStore = useUserStore() +const groupStore = useGroupStore() /** 弹窗显隐:把父侧 v-model 转双向计算 */ const visible = computed({ @@ -123,6 +128,24 @@ const visible = computed({ set: (value) => emit('update:modelValue', value) }) +/** + * 是否走审批:群开启 joinApproval + 当前用户是普通成员;群主 / 管理员邀请绕过审批(对齐后端) + * + * 用于切换提交后的提示文案:审批分支后端只创建待审批记录,没把人拉进群,提示「等待审批」更准确 + */ +const willGoApproval = computed(() => { + if (!props.groupId) { + return false + } + const group = groupStore.getGroup(props.groupId) + if (!group?.joinApproval) { + return false + } + const myId = Number(userStore.getUser?.id) || 0 + const myRole = props.members.find((m) => m.userId === myId)?.role + return myRole !== ImGroupMemberRole.OWNER && myRole !== ImGroupMemberRole.ADMIN +}) + const searchText = ref('') const submitting = ref(false) const workingFriends = ref([]) // 工作副本(带 checked / disabled 标记),与 prop 隔离 @@ -196,7 +219,8 @@ async function handleOk() { submitting.value = true try { await inviteGroupMember({ groupId: props.groupId, memberUserIds }) - message.success('邀请成功') + // 审批分支后端仅落待审批记录,没把人拉进群;普通入群直接邀请成功 + message.success(willGoApproval.value ? '邀请已发起,等待群主 / 管理员审批' : '邀请成功') emit('reload', memberUserIds) visible.value = false } finally { diff --git a/src/views/im/home/components/group/GroupRequestListDialog.vue b/src/views/im/home/components/group/GroupRequestListDialog.vue index ff058672a..59d2bff74 100644 --- a/src/views/im/home/components/group/GroupRequestListDialog.vue +++ b/src/views/im/home/components/group/GroupRequestListDialog.vue @@ -203,6 +203,32 @@ watch( { immediate: true } ) +/** + * 单群模式下订阅 store 中归属本群的未处理列表变化:增 / 减都 refetch 一次拿到最新 handleResult + * + * 触发场景:① WS 1503 收到新申请 → store 头部 unshift;② 其他管理员 / 远端处理 → store 移除该项 + * 本端 agreeRequest / refuseRequest 内部也会 removeByRequestId 从而触发;fetchList 拉到的 handleResult + * 与 updateLocalResult 写的一致,不冲突;仅多一次网络请求,可接受 + */ +// TODO @AI:减少缩写,类似 r、curr、prev;还是完整点,不会有啥影响的; +watch( + () => + props.groupId && visible.value + ? groupRequestStore.unhandledList + .filter((r) => r.groupId === props.groupId) + .map((r) => r.id) + .join(',') + : null, + (curr, prev) => { + if (curr === null || prev === undefined || curr === prev) { + return + } + if (props.groupId) { + void fetchList(props.groupId) + } + } +) + async function fetchList(groupId: number) { loading.value = true try { diff --git a/src/views/im/home/store/groupRequestStore.ts b/src/views/im/home/store/groupRequestStore.ts index c8a3fc5f6..8cdea382d 100644 --- a/src/views/im/home/store/groupRequestStore.ts +++ b/src/views/im/home/store/groupRequestStore.ts @@ -51,17 +51,18 @@ export const useGroupRequestStore = defineStore('imGroupRequestStore', { this.loaded = true }, - /** WS 收到 1503:按 requestId 单查 + push 进列表头;payload 已带申请方昵称 / 头像可减一次回查 */ + /** + * WS 收到 1503:按 requestId 单查 + 排到列表头 + * + * 同一对 group_id, user_id 的申请记录会被复用,再次申请 / 邀请时 requestId 不变但 applyContent / inviterUserId + * 等会刷新;不能因 id 已存在就跳过,必须 fetch 最新内容并把它顶到最前面(与后端 update_time 倒序对齐) + */ async addByRequestId(requestId: number) { - const exists = this.unhandledList.some((r) => r.id === requestId) - if (exists) { - return - } const request = await apiGetMyGroupRequest(requestId) if (!request) { return } - this.unhandledList.unshift(request) + this.unhandledList = [request, ...this.unhandledList.filter((r) => r.id !== requestId)] }, /** WS 收到 1505 / 1506 或本端处理完一条:按 requestId 从列表移除 */ diff --git a/src/views/im/home/store/websocketStore.ts b/src/views/im/home/store/websocketStore.ts index 150ce13c7..88dfd9b30 100644 --- a/src/views/im/home/store/websocketStore.ts +++ b/src/views/im/home/store/websocketStore.ts @@ -1,5 +1,4 @@ import { defineStore, acceptHMRUpdate } from 'pinia' -import { ElNotification } from 'element-plus' import { store } from '@/store' import { getRefreshToken } from '@/utils/auth' import { useUserStore } from '@/store/modules/user' @@ -565,39 +564,23 @@ export const useImWebSocketStore = defineStore('imWebSocketStore', { * * 对应后端 ImPrivateMessageDTO.ofGroupNotification 系列: * - 1503:admin 侧拉单条 push 进 unhandledList;申请人侧不收 - * - 1505 / 1506:双端都收 — admin 侧从 unhandledList 移除;申请人侧弹 toast + * - 1505 / 1506:admin 侧从 unhandledList 移除;同意走 1509 / 1510 群事件渲染系统提示,拒绝静默不打扰 */ handleGroupRequestNotification(websocketMessage: ImPrivateMessageDTO) { const payload = JSON.parse(websocketMessage.content || '{}') as { requestId?: number - groupId?: number - userId?: number - handleContent?: string } if (!payload.requestId) { return } const groupRequestStore = useGroupRequestStore() - const userStore = useUserStore() - const myId = Number(userStore.getUser?.id) || 0 switch (websocketMessage.type) { case ImMessageType.GROUP_REQUEST_RECEIVED: groupRequestStore.addByRequestId(payload.requestId).catch(() => undefined) break case ImMessageType.GROUP_REQUEST_APPROVED: - groupRequestStore.removeByRequestId(payload.requestId) - if (payload.userId === myId) { - ElNotification.success({ title: '入群申请已通过', message: '可以开始群聊了' }) - } - break case ImMessageType.GROUP_REQUEST_REJECTED: groupRequestStore.removeByRequestId(payload.requestId) - if (payload.userId === myId) { - ElNotification.warning({ - title: '入群申请被拒绝', - message: payload.handleContent || '' - }) - } break default: break