zhangwencui
92 分钟以前 f08a81f01bc4a4104dd9ef04e071bb0bd0eb8afe
src/views/im/home/components/group/group-info.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,154 @@
<script lang="ts" setup>
import type { Friend, GroupLite, GroupMember } from '../../types'
import type { GroupMemberLite } from './group-member.vue'
import { computed, ref, watch } from 'vue'
import { CommonStatusEnum } from '#/packages/constants/src'
import { Button } from 'ant-design-vue'
import { getCurrentUserId } from '#/views/im/utils/auth'
import { getMemberDisplayName, isGroupQuit } from '../../../utils/user'
import { useFriendStore } from '../../store/friendStore'
import { useGroupStore } from '../../store/groupStore'
import GroupAvatar from './group-avatar.vue'
import GroupMemberGrid from './group-member-grid.vue'
defineOptions({ name: 'ImGroupInfo' })
const props = defineProps<{
  group: GroupLite
}>()
const emit = defineEmits<{
  /** stranger ç‚¹ã€ŒåŠ å…¥ç¾¤èŠã€ï¼›çˆ¶çº§è´Ÿè´£å¼¹ç”³è¯·ç†ç”± + è°ƒ applyJoinGroup */
  apply: [group: GroupLite]
  /** member ç‚¹ã€Œè¿›å…¥ç¾¤èŠã€ï¼›çˆ¶çº§è´Ÿè´£åˆ‡ä¼šè¯ + å…³æµ®å±‚ */
  chat: [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, true)
    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>
<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(--ant-color-text)] truncate text-center"
      >
        {{ group.showGroupName || group.name }}
      </div>
      <div v-if="memberCountText" class="text-13px text-[var(--ant-color-text-secondary)]">
        {{ memberCountText }}
      </div>
      <!-- æˆå‘˜å®«æ ¼ï¼šä»… member æ¸²æŸ“(陌生群拉不到成员) -->
      <div
        v-if="isMember && members.length > 0"
        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">
        <Button type="primary" @click="emit('chat', group)">进入群聊</Button>
      </div>
      <div v-else-if="isStranger" class="mt-4">
        <Button type="primary" @click="emit('apply', group)">加入群聊</Button>
      </div>
    </div>
  </div>
</template>