✨ feat(im): 初始化群名片 v0.2:第二次评审(需求各种进群的小问题)
parent
65d5aacac9
commit
ce66a507ef
|
|
@ -0,0 +1,142 @@
|
||||||
|
<template>
|
||||||
|
<!--
|
||||||
|
群信息内容组件(与 UserInfo 对位)
|
||||||
|
- 头像 + 群名 + 成员数 + 成员宫格 + 动作区,纯展示 + 抛事件,业务由父级承接
|
||||||
|
- relation 走 groupStore 缓存推导:命中 = member(已加群),否则 = stranger(未加群),无 id = readonly
|
||||||
|
- 成员宫格仅 member 时拉取(陌生群拉不到,所有信息走 props.group 卡片快照)
|
||||||
|
-->
|
||||||
|
<div class="flex justify-center">
|
||||||
|
<div class="w-full max-w-[320px] flex flex-col gap-3 items-center">
|
||||||
|
<UserAvatar
|
||||||
|
:url="group.showImage || group.showImageThumb"
|
||||||
|
:name="group.showGroupName || group.name"
|
||||||
|
:size="72"
|
||||||
|
:clickable="false"
|
||||||
|
:previewable="isMember"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="w-full text-lg font-semibold leading-snug text-[var(--el-text-color-primary)] truncate text-center"
|
||||||
|
>
|
||||||
|
{{ group.showGroupName || group.name }}
|
||||||
|
</div>
|
||||||
|
<div v-if="memberCountText" class="text-13px text-[var(--el-text-color-secondary)]">
|
||||||
|
{{ memberCountText }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 成员宫格:仅 member 渲染(陌生群拉不到成员) -->
|
||||||
|
<div v-if="isMember && members.length" class="flex flex-wrap gap-2 justify-center w-full pt-2">
|
||||||
|
<GroupMemberGrid
|
||||||
|
v-for="member in members"
|
||||||
|
:key="member.userId"
|
||||||
|
:member="member"
|
||||||
|
:group-name="group.name"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 动作区:member 进入群聊 / stranger 加入群聊 / readonly 不渲染 -->
|
||||||
|
<div v-if="isMember" class="mt-4">
|
||||||
|
<el-button type="primary" @click="emit('chat', group)">进入群聊</el-button>
|
||||||
|
</div>
|
||||||
|
<div v-else-if="isStranger" class="mt-4">
|
||||||
|
<el-button type="primary" @click="emit('apply', group)">加入群聊</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { computed, ref, watch } from 'vue'
|
||||||
|
|
||||||
|
import UserAvatar from '../user/UserAvatar.vue'
|
||||||
|
import GroupMemberGrid from './GroupMemberGrid.vue'
|
||||||
|
import { useUserStore } from '@/store/modules/user'
|
||||||
|
import { CommonStatusEnum } from '@/utils/constants'
|
||||||
|
import { useFriendStore } from '../../store/friendStore'
|
||||||
|
import { useGroupStore } from '../../store/groupStore'
|
||||||
|
import { getMemberDisplayName } from '../../../utils/user'
|
||||||
|
import type { Friend, GroupLite, GroupMember } from '../../types'
|
||||||
|
import type { GroupMemberLite } from './GroupMember.vue'
|
||||||
|
|
||||||
|
defineOptions({ name: 'ImGroupInfo' })
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
group: GroupLite
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
/** member 点「进入群聊」;父级负责切会话 + 关浮层 */
|
||||||
|
chat: [group: GroupLite]
|
||||||
|
/** stranger 点「加入群聊」;父级负责弹申请理由 + 调 applyJoinGroup */
|
||||||
|
apply: [group: GroupLite]
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const userStore = useUserStore()
|
||||||
|
const groupStore = useGroupStore()
|
||||||
|
const friendStore = useFriendStore()
|
||||||
|
|
||||||
|
const members = ref<GroupMemberLite[]>([])
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否已加群:基于"自己确实在成员列表里"判断
|
||||||
|
* - 缓存未命中:直接 false(陌生群)
|
||||||
|
* - 命中且 members 已拉:精准查 self.userId 在不在
|
||||||
|
* - 命中但 members 未拉:fetchGroups 接口语义即「我加入的群」,命中视为 member(拉成员后会自动收敛)
|
||||||
|
*/
|
||||||
|
const isMember = computed(() => {
|
||||||
|
if (!props.group?.id) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
const cached = groupStore.getGroup(props.group.id)
|
||||||
|
if (!cached) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (cached.membersLoaded && cached.members) {
|
||||||
|
const myId = Number(userStore.getUser?.id) || 0
|
||||||
|
return cached.members.some(
|
||||||
|
(m) => m.userId === myId && m.status === CommonStatusEnum.ENABLE
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
/** 是否未加群:有 id 但 isMember 不成立;对比 isMember 用于动作区按钮分支 */
|
||||||
|
const isStranger = computed(() => !!props.group?.id && !isMember.value)
|
||||||
|
|
||||||
|
/** 成员数文案:member 优先用本地拉到的列表长度,stranger 用 props.group.memberCount 卡片快照 */
|
||||||
|
const memberCountText = computed(() => {
|
||||||
|
const count = isMember.value
|
||||||
|
? props.group.memberCount || members.value.length
|
||||||
|
: props.group.memberCount
|
||||||
|
return count ? `${count} 位成员` : ''
|
||||||
|
})
|
||||||
|
|
||||||
|
/** member 切群 / 首挂:拉成员;竞态用 group.id 比对丢弃陈旧响应避免上一条群成员错位 */
|
||||||
|
watch(
|
||||||
|
() => [props.group?.id, isMember.value] as const,
|
||||||
|
async ([id, member]) => {
|
||||||
|
members.value = []
|
||||||
|
if (!id || !member) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const list = await groupStore.fetchGroupMembers(id)
|
||||||
|
if (props.group?.id !== id) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
members.value = list.map((m) =>
|
||||||
|
convertGroupMemberLite(m, friendStore.getFriend(m.userId))
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
)
|
||||||
|
|
||||||
|
/** 群成员 → 列表项 */
|
||||||
|
function convertGroupMemberLite(member: GroupMember, friend: Friend | undefined): GroupMemberLite {
|
||||||
|
return {
|
||||||
|
userId: member.userId,
|
||||||
|
showName: getMemberDisplayName(member, friend),
|
||||||
|
nickname: member.nickname,
|
||||||
|
avatar: member.avatar,
|
||||||
|
status: member.status,
|
||||||
|
role: member.role
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
@ -0,0 +1,96 @@
|
||||||
|
<template>
|
||||||
|
<!--
|
||||||
|
群信息浮层(与 UserInfoCard 对位)
|
||||||
|
- 仅承担「浮层定位 + 关闭逻辑(点遮罩 / Esc)」,群信息视觉走 <GroupInfo>,与通讯录详情共用一份组件
|
||||||
|
- 触发:useImUiStore.openGroupInfoCardAtEvent(group, e)
|
||||||
|
- GroupInfo 内部按 groupStore 缓存推导 member / stranger,浮层只负责接 chat / apply 事件做业务
|
||||||
|
-->
|
||||||
|
<teleport to="body">
|
||||||
|
<div v-if="card.show" class="fixed inset-0 z-9998" @click.self="handleClose">
|
||||||
|
<div
|
||||||
|
class="fixed w-80 p-4 bg-[var(--el-bg-color-overlay)] rounded-md shadow-xl"
|
||||||
|
:style="{ left: card.position.x + 'px', top: card.position.y + 'px' }"
|
||||||
|
@click.stop
|
||||||
|
>
|
||||||
|
<GroupInfo v-if="card.group" :group="card.group" @chat="handleChat" @apply="handleApply" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</teleport>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { computed, onMounted, onUnmounted } from 'vue'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||||
|
|
||||||
|
import GroupInfo from './GroupInfo.vue'
|
||||||
|
import { useImUiStore } from '../../store/uiStore'
|
||||||
|
import { useConversationStore } from '../../store/conversationStore'
|
||||||
|
import { useGroupStore } from '../../store/groupStore'
|
||||||
|
import { applyJoinGroup } from '@/api/im/group/request'
|
||||||
|
import { ImConversationType, ImGroupAddSource } from '../../../utils/constants'
|
||||||
|
import type { GroupLite } from '../../types'
|
||||||
|
|
||||||
|
defineOptions({ name: 'ImGroupInfoCard' })
|
||||||
|
|
||||||
|
const uiStore = useImUiStore()
|
||||||
|
const conversationStore = useConversationStore()
|
||||||
|
const groupStore = useGroupStore()
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
const card = computed(() => uiStore.groupInfoCard)
|
||||||
|
|
||||||
|
/** 关闭浮层 */
|
||||||
|
function handleClose() {
|
||||||
|
uiStore.closeGroupInfoCard()
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Esc 关闭 */
|
||||||
|
function handleKeydown(e: KeyboardEvent) {
|
||||||
|
if (e.key === 'Escape' && card.value.show) {
|
||||||
|
handleClose()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => window.addEventListener('keydown', handleKeydown))
|
||||||
|
onUnmounted(() => window.removeEventListener('keydown', handleKeydown))
|
||||||
|
|
||||||
|
/** 进入群聊:取本地最新群信息(含 silent / 群备注),新建或激活会话 + 跳路由 */
|
||||||
|
function handleChat(group: GroupLite) {
|
||||||
|
const cached = groupStore.getGroup(group.id)
|
||||||
|
conversationStore.openConversation(
|
||||||
|
group.id,
|
||||||
|
ImConversationType.GROUP,
|
||||||
|
cached?.name || group.name || '',
|
||||||
|
cached?.avatar || group.showImage || '',
|
||||||
|
{ silent: !!cached?.silent }
|
||||||
|
)
|
||||||
|
if (router.currentRoute.value.name !== 'ImHomeConversation') {
|
||||||
|
router.push({ name: 'ImHomeConversation' })
|
||||||
|
}
|
||||||
|
handleClose()
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 加入群聊:先关浮层(避免与 prompt 的 mask 互相遮挡)→ 弹申请理由(可选)→ applyJoinGroup */
|
||||||
|
async function handleApply(group: GroupLite) {
|
||||||
|
handleClose()
|
||||||
|
let applyContent = ''
|
||||||
|
try {
|
||||||
|
const result = await ElMessageBox.prompt(`申请加入「${group.name || ''}」`, '申请加群', {
|
||||||
|
confirmButtonText: '发送申请',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
inputPlaceholder: '请填写验证消息(可选)',
|
||||||
|
inputValue: ''
|
||||||
|
})
|
||||||
|
applyContent = (result.value || '').trim()
|
||||||
|
} catch {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
await applyJoinGroup({
|
||||||
|
groupId: group.id,
|
||||||
|
applyContent: applyContent || undefined,
|
||||||
|
addSource: ImGroupAddSource.SHARE_LINK
|
||||||
|
})
|
||||||
|
ElMessage.success('加群申请已发送')
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
@ -1,98 +1,25 @@
|
||||||
<template>
|
<template>
|
||||||
<!--
|
<!--
|
||||||
通讯录 - 群聊详情
|
通讯录 - 群聊详情
|
||||||
- 头像 + 群名 + 成员数 + 成员宫格 + "进入群聊",纯展示不做群管理
|
- 内容走共享 <GroupInfo>,与 GroupInfoCard 浮层用同一份组件
|
||||||
- 成员列表按 group.id 懒拉,组件内部维护;切换群时清空避免上一条群成员闪现
|
- 通讯录里都是已加群(GroupInfo 内部按 groupStore 推导);chat 抛上去由父级承接
|
||||||
-->
|
-->
|
||||||
<div class="flex justify-center pt-12 px-6">
|
<div class="pt-12 px-6">
|
||||||
<div class="w-full max-w-[320px] flex flex-col gap-3 items-center">
|
<GroupInfo :group="group" @chat="emit('chat', $event)" />
|
||||||
<UserAvatar
|
|
||||||
:url="group.showImage || group.showImageThumb"
|
|
||||||
:name="group.showGroupName || group.name"
|
|
||||||
:size="72"
|
|
||||||
:clickable="false"
|
|
||||||
previewable
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
class="text-lg font-semibold leading-snug text-[var(--el-text-color-primary)] truncate w-full text-center"
|
|
||||||
>
|
|
||||||
{{ group.showGroupName || group.name }}
|
|
||||||
</div>
|
|
||||||
<div class="text-13px text-[var(--el-text-color-secondary)]"> {{ memberCount }} 位成员 </div>
|
|
||||||
<!-- 成员宫格:纯展示,宽度跟着 320 容器自动换行;不带"邀请 +"瓦片 -->
|
|
||||||
<div class="flex flex-wrap gap-2 justify-center w-full pt-2">
|
|
||||||
<!-- 注意!!!加好友话术里的群名一律用 group.name(原始名);showGroupName 是我自定义的群备注,不能带给对端 -->
|
|
||||||
<GroupMemberGrid
|
|
||||||
v-for="member in members"
|
|
||||||
:key="member.userId"
|
|
||||||
:member="member"
|
|
||||||
:group-name="group.name"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="mt-4">
|
|
||||||
<el-button type="primary" @click="emit('chat', group)">进入群聊</el-button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, ref, watch } from 'vue'
|
import GroupInfo from '../../components/group/GroupInfo.vue'
|
||||||
|
import type { GroupLite } from '../../types'
|
||||||
import UserAvatar from '../../components/user/UserAvatar.vue'
|
|
||||||
import GroupMemberGrid from '../../components/group/GroupMemberGrid.vue'
|
|
||||||
import type { Friend, GroupLite, GroupMember } from '../../types'
|
|
||||||
import type { GroupMemberLite } from '../../components/group/GroupMember.vue'
|
|
||||||
import { useFriendStore } from '../../store/friendStore'
|
|
||||||
import { useGroupStore } from '../../store/groupStore'
|
|
||||||
import { getMemberDisplayName } from '../../../utils/user'
|
|
||||||
|
|
||||||
defineOptions({ name: 'ImContactGroupDetail' })
|
defineOptions({ name: 'ImContactGroupDetail' })
|
||||||
|
|
||||||
const props = defineProps<{
|
defineProps<{
|
||||||
group: GroupLite
|
group: GroupLite
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
chat: [group: GroupLite]
|
chat: [group: GroupLite]
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const groupStore = useGroupStore()
|
|
||||||
const friendStore = useFriendStore()
|
|
||||||
|
|
||||||
const members = ref<GroupMemberLite[]>([])
|
|
||||||
|
|
||||||
/** 群人数文案:优先后端 memberCount,其次按已拉到的列表长度兜底;都没有给 0 */
|
|
||||||
const memberCount = computed(() => props.group.memberCount || members.value.length || 0)
|
|
||||||
|
|
||||||
/** 切换群 / 首挂:拉成员;竞态用 group.id 比对丢弃陈旧响应避免上一条群成员错位 */
|
|
||||||
watch(
|
|
||||||
() => props.group?.id,
|
|
||||||
async (id) => {
|
|
||||||
members.value = []
|
|
||||||
if (!id) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const list = await groupStore.fetchGroupMembers(id)
|
|
||||||
if (props.group?.id !== id) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
members.value = list.map((member) =>
|
|
||||||
convertGroupMemberLite(member, friendStore.getFriend(member.userId))
|
|
||||||
)
|
|
||||||
},
|
|
||||||
{ immediate: true }
|
|
||||||
)
|
|
||||||
|
|
||||||
/** 群成员 → 列表项 */
|
|
||||||
function convertGroupMemberLite(member: GroupMember, friend: Friend | undefined): GroupMemberLite {
|
|
||||||
return {
|
|
||||||
userId: member.userId,
|
|
||||||
showName: getMemberDisplayName(member, friend),
|
|
||||||
nickname: member.nickname,
|
|
||||||
avatar: member.avatar,
|
|
||||||
status: member.status,
|
|
||||||
role: member.role
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -700,7 +700,10 @@ async function handleQuit() {
|
||||||
}
|
}
|
||||||
const groupId = props.group.id
|
const groupId = props.group.id
|
||||||
await quitGroup(groupId)
|
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)
|
conversationStore.removeConversation(ImConversationType.GROUP, groupId)
|
||||||
groupStore.removeGroup(groupId)
|
groupStore.removeGroup(groupId)
|
||||||
message.success('已退出群聊')
|
message.success('已退出群聊')
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ import {
|
||||||
import { useConversationStore } from './conversationStore'
|
import { useConversationStore } from './conversationStore'
|
||||||
import { useGroupRequestStore } from './groupRequestStore'
|
import { useGroupRequestStore } from './groupRequestStore'
|
||||||
import { ImConversationType, ImGroupMemberRole, ImMessageType } from '../../utils/constants'
|
import { ImConversationType, ImGroupMemberRole, ImMessageType } from '../../utils/constants'
|
||||||
|
import { CommonStatusEnum } from '@/utils/constants'
|
||||||
import {
|
import {
|
||||||
getCurrentUserId,
|
getCurrentUserId,
|
||||||
imStorage,
|
imStorage,
|
||||||
|
|
@ -453,6 +454,17 @@ export const useGroupStore = defineStore('imGroupStore', {
|
||||||
this.saveGroupMembers(groupId)
|
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 兜底 */
|
/** 本地更新群成员的 displayUserName(GROUP_MEMBER_NICKNAME_UPDATE 事件);不命中则等 fetchGroupMembers 兜底 */
|
||||||
updateMemberDisplayUserName(groupId: number, userId: number, displayUserName: string) {
|
updateMemberDisplayUserName(groupId: number, userId: number, displayUserName: string) {
|
||||||
const group = this.getGroup(groupId)
|
const group = this.getGroup(groupId)
|
||||||
|
|
@ -574,7 +586,7 @@ export const useGroupStore = defineStore('imGroupStore', {
|
||||||
},
|
},
|
||||||
|
|
||||||
/** 创建群广播:创建者多端同步 + 初始成员 bootstrap;payload.memberUserIds 含自己 → 拉群详情 / 成员;本端发起者已经 upsert 过本群,跳过避免双拉 */
|
/** 创建群广播:创建者多端同步 + 初始成员 bootstrap;payload.memberUserIds 含自己 → 拉群详情 / 成员;本端发起者已经 upsert 过本群,跳过避免双拉 */
|
||||||
applyGroupCreateNotification(groupId: number, payload: GroupNotificationPayload) {
|
async applyGroupCreateNotification(groupId: number, payload: GroupNotificationPayload) {
|
||||||
if (!isSelfInPayloadMembers(payload)) {
|
if (!isSelfInPayloadMembers(payload)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -583,7 +595,8 @@ export const useGroupStore = defineStore('imGroupStore', {
|
||||||
if (selfIsOperator && this.getGroup(groupId)) {
|
if (selfIsOperator && this.getGroup(groupId)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
this.fetchGroupInfo(groupId).catch(() => undefined)
|
// 先 await fetchGroupInfo 把群 upsert 进 state.groups;否则 fetchGroupMembers 的「不是我加入的群」guard 会兜空
|
||||||
|
await this.fetchGroupInfo(groupId)
|
||||||
this.fetchGroupMembers(groupId, true).catch(() => undefined)
|
this.fetchGroupMembers(groupId, true).catch(() => undefined)
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -611,36 +624,43 @@ export const useGroupStore = defineStore('imGroupStore', {
|
||||||
},
|
},
|
||||||
|
|
||||||
/** 成员加入:被邀请者本端 group 未就位先 fetchGroupInfo bootstrap;所有人都刷成员列表(新成员 nickname / avatar 不在 payload) */
|
/** 成员加入:被邀请者本端 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)) {
|
if (isSelfInPayloadMembers(payload) && !this.getGroup(groupId)) {
|
||||||
this.fetchGroupInfo(groupId).catch(() => undefined)
|
await this.fetchGroupInfo(groupId)
|
||||||
}
|
}
|
||||||
this.fetchGroupMembers(groupId, true).catch(() => undefined)
|
this.fetchGroupMembers(groupId, true).catch(() => undefined)
|
||||||
},
|
},
|
||||||
|
|
||||||
/** 自由进群:进群者本端 group 未就位先 fetchGroupInfo bootstrap;所有人都刷成员列表 */
|
/** 自由进群:进群者本端 group 未就位先 fetchGroupInfo bootstrap;所有人都刷成员列表 */
|
||||||
applyGroupMemberEnterNotification(groupId: number, payload: GroupNotificationPayload) {
|
async applyGroupMemberEnterNotification(groupId: number, payload: GroupNotificationPayload) {
|
||||||
const selfUserId = getCurrentUserId()
|
const selfUserId = getCurrentUserId()
|
||||||
|
// 自己自由进群:必须 await fetchGroupInfo 让群入 state.groups,否则 fetchGroupMembers 的 guard 会兜空
|
||||||
if (selfUserId && payload.entrantUserId === selfUserId && !this.getGroup(groupId)) {
|
if (selfUserId && payload.entrantUserId === selfUserId && !this.getGroup(groupId)) {
|
||||||
this.fetchGroupInfo(groupId).catch(() => undefined)
|
await this.fetchGroupInfo(groupId)
|
||||||
}
|
}
|
||||||
this.fetchGroupMembers(groupId, true).catch(() => undefined)
|
this.fetchGroupMembers(groupId, true).catch(() => undefined)
|
||||||
},
|
},
|
||||||
|
|
||||||
/** 成员退群:退群者本人多端同步走 removeGroup;其他成员从本地列表移除 quitter */
|
/** 成员退群:退群者本人先把 self.status 置 DISABLE 再 removeGroup(保留状态语义 + 维持 groups 列表干净);其他成员从本地列表移除 quitter */
|
||||||
applyGroupMemberQuitNotification(groupId: number, payload: GroupNotificationPayload) {
|
applyGroupMemberQuitNotification(groupId: number, payload: GroupNotificationPayload) {
|
||||||
const selfUserId = getCurrentUserId()
|
const selfUserId = getCurrentUserId()
|
||||||
if (selfUserId && payload.operatorUserId === selfUserId) {
|
if (selfUserId && payload.operatorUserId === selfUserId) {
|
||||||
|
this.updateMemberStatus(groupId, selfUserId, CommonStatusEnum.DISABLE)
|
||||||
this.removeGroup(groupId)
|
this.removeGroup(groupId)
|
||||||
} else if (payload.operatorUserId) {
|
} else if (payload.operatorUserId) {
|
||||||
this.removeMembersLocal(groupId, [payload.operatorUserId])
|
this.removeMembersLocal(groupId, [payload.operatorUserId])
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/** 成员被移出:被踢者本人 removeGroup;其他成员从本地列表移除被踢者 */
|
/** 成员被移出:被踢者本人先把 self.status 置 DISABLE 再 removeGroup;其他成员从本地列表移除被踢者 */
|
||||||
applyGroupMemberKickNotification(groupId: number, payload: GroupNotificationPayload) {
|
applyGroupMemberKickNotification(groupId: number, payload: GroupNotificationPayload) {
|
||||||
const memberIds = payload.memberUserIds || []
|
const memberIds = payload.memberUserIds || []
|
||||||
|
const selfUserId = getCurrentUserId()
|
||||||
if (isSelfInPayloadMembers(payload)) {
|
if (isSelfInPayloadMembers(payload)) {
|
||||||
|
if (selfUserId) {
|
||||||
|
this.updateMemberStatus(groupId, selfUserId, CommonStatusEnum.DISABLE)
|
||||||
|
}
|
||||||
this.removeGroup(groupId)
|
this.removeGroup(groupId)
|
||||||
} else if (memberIds.length) {
|
} else if (memberIds.length) {
|
||||||
this.removeMembersLocal(groupId, memberIds)
|
this.removeMembersLocal(groupId, memberIds)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue