gaoluyang
2026-06-24 bfdc0e0e6d5e47aa501f9b6fadd143d9c97cf00a
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
import { computed, type ComputedRef, type Ref } from 'vue'
 
import { ImConversationType } from '../../utils/constants'
import { getSenderAvatar, getSenderDisplayName } from '../../utils/user'
import { useRtcStore } from '../store/rtcStore'
 
/** 群通话成员视图模型:已加入 + 接入中;pending 头像 UI 半透明,joined 不透明 */
export interface GroupCallMember {
  userId: number
  nickname: string
  avatar?: string
  pending: boolean
}
 
/**
 * 群通话成员列表 computed:joined 在前,未 joined 的 invitee 在后;
 * 缓存为空(来电首屏渲染早于 syncGroupActiveCall 回写)时用 fallback 主叫兜底,避免空白
 *
 * @param groupId 群编号
 * @param fallbackInviterId 兜底主叫;缓存为空时填一个头像,标记为已加入而非 pending
 */
export function useGroupCallMembers(
  groupId: Ref<number | undefined>,
  fallbackInviterId?: Ref<number | undefined>
): ComputedRef<GroupCallMember[]> {
  const rtcStore = useRtcStore()
  return computed(() => {
    const gid = groupId.value
    if (!gid) {
      return []
    }
    const groupCall = rtcStore.getGroupCall(gid)
    const joinedIds = groupCall?.joinedUserIds ?? []
    const inviteeIds = groupCall?.inviteeIds ?? []
    const joinedSet = new Set(joinedIds)
    const orderedIds = [...joinedIds, ...inviteeIds.filter((id) => !joinedSet.has(id))]
    if (orderedIds.length > 0) {
      return orderedIds.map((userId) => toVM(userId, gid, !joinedSet.has(userId)))
    }
    const fallback = fallbackInviterId?.value
    return fallback ? [toVM(fallback, gid, false)] : []
  })
}
 
/** 把 userId 翻译成视图模型,统一走 user.ts helper 解析昵称 / 头像 */
function toVM(userId: number, groupId: number, pending: boolean): GroupCallMember {
  return {
    userId,
    nickname: getSenderDisplayName(userId, ImConversationType.GROUP, groupId),
    avatar: getSenderAvatar(userId, ImConversationType.GROUP, groupId) || undefined,
    pending
  }
}