✨ feat(im): 初始化群申请 v0.4:第五把 review(多轮 finding 修复 + 通知静默化)
- 邀请路径写 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 字段im
parent
b2ba42049b
commit
cb26df3ca1
|
|
@ -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<FriendCheckable[]>([]) // 工作副本(带 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 {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 从列表移除 */
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue