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