admin-vue3/src/views/im/home/components/group/GroupInfo.vue

151 lines
5.3 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<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">
<GroupAvatar
:group-id="group.id"
:url="group.showImage || group.showImageThumb"
:name="group.showGroupName || group.name"
:size="72"
: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 GroupAvatar from './GroupAvatar.vue'
import GroupMemberGrid from './GroupMemberGrid.vue'
import { getCurrentUserId } from '@/utils/auth'
import { CommonStatusEnum } from '@/utils/constants'
import { useFriendStore } from '../../store/friendStore'
import { useGroupStore } from '../../store/groupStore'
import { getMemberDisplayName, isGroupQuit } 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 groupStore = useGroupStore()
const friendStore = useFriendStore()
const members = ref<GroupMemberLite[]>([])
/**
* 是否已加群:基于"自己确实在成员列表里"判断
* - 缓存未命中:直接 false陌生群
* - 命中且 members 已拉:精准查 self.userId 在不在
* - 命中但 members 未拉fetchGroupList 接口语义即「我加入的群」,命中视为 member拉成员后会自动收敛
*/
const isMember = computed(() => {
if (!props.group?.id) {
return false
}
const cached = groupStore.getGroup(props.group.id)
if (!cached) {
return false
}
// 历史退群群:直接判 false避免成员未加载时误显示「进入群聊」
if (isGroupQuit(cached)) {
return false
}
if (cached.membersLoaded && cached.members) {
const myId = getCurrentUserId()
return cached.members.some(
(m) => m.userId === myId && m.status === CommonStatusEnum.ENABLE
)
}
return true
})
/** 历史退群群:只读,动作区两个按钮都不渲染(既不「进入群聊」也不「加入群聊」) */
const isQuitGroup = computed(() => {
const id = props.group?.id
return id != null && isGroupQuit(groupStore.getGroup(id))
})
/** 是否未加群:有 id、非成员、且非历史退群群只有真·陌生人才给「加入群聊」 */
const isStranger = computed(() => !!props.group?.id && !isMember.value && !isQuitGroup.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.fetchGroupMemberList(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>