gaoluyang
2026-06-29 27cd042df9aca0383a49f3514bc21958dd890912
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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
<script lang="ts" setup>
import type { ImRtcCallNotification } from '../../store/rtcStore'
 
import { computed } from 'vue'
 
import { DICT_TYPE } from '#/packages/constants/src'
import { getDictLabel } from '#/packages/effects/hooks/src'
import { IconifyIcon as Icon } from '#/packages/icons/src'
 
import { useGroupCallMembers } from '../../composables/useGroupCallMembers'
import { UserAvatar } from '../user'
 
const props = defineProps<{
  accepting?: boolean
  isGroup?: boolean
  payload: ImRtcCallNotification | null
  rejecting?: boolean
}>()
 
defineEmits<{ accept: []; reject: [] }>()
 
/** 来电提示文案;区分语音 / 视频 */
const tipText = computed(() => {
  if (!props.payload) return ''
  return `邀请你${getDictLabel(DICT_TYPE.IM_RTC_CALL_MEDIA_TYPE, props.payload.mediaType)}通话`
})
 
/** 接听按钮禁用态 */
const acceptDisabled = computed(() => !!props.accepting || !!props.rejecting)
 
/** 拒绝按钮禁用态 */
const rejectDisabled = computed(() => !!props.rejecting || !!props.accepting)
 
// 群通话成员;缓存为空时用 INVITE 载荷里的主叫兜底,避免空白
const callMembers = useGroupCallMembers(
  computed(() => (props.isGroup ? props.payload?.groupId : undefined)),
  computed(() => props.payload?.inviterUserId)
)
</script>
 
<template>
  <!--
    被叫端来电小条;参考微信,右上角固定悬浮;
    群聊:左头像 + 中(邀请人 + 文案一行 / 通话成员行)+ 右下角拒绝 / 接听;
    私聊:左头像 + 中(邀请人 + 文案两行)+ 右下角拒绝 / 接听
  -->
  <div
    class="fixed top-5 right-5 rounded-2xl overflow-hidden shadow-[0_12px_36px_rgba(0,0,0,0.4)] z-[9999] flex gap-3 items-end p-3 text-white bg-[#2a2a2c]"
    :class="isGroup ? 'w-[360px]' : 'w-[340px]'"
  >
    <!-- 左:邀请者头像 -->
    <UserAvatar
      :url="payload?.inviterAvatar"
      :name="payload?.inviterNickname"
      :size="48"
      radius="8px"
      :clickable="false"
      class="self-start"
    />
 
    <!-- 中:群聊单行(邀请人 + 文案)+ 通话成员;私聊两行(名 / 文案) -->
    <div class="flex flex-col flex-1 gap-1 self-start min-w-0">
      <!-- 名 + 文案:群单行内联,私聊上下两行 -->
      <div v-if="isGroup" class="text-sm truncate">
        <span class="font-medium">{{ payload?.inviterNickname || '对方' }}</span>
        <span class="ml-1 text-white/60">{{ tipText }}</span>
      </div>
      <template v-else>
        <div class="text-sm font-medium truncate">{{ payload?.inviterNickname || '对方' }}</div>
        <div class="text-13px text-white/60 truncate">{{ tipText }}</div>
      </template>
 
      <!-- 群通话成员行;私聊无;接入中的人半透明展示 -->
      <template v-if="isGroup && callMembers.length > 0">
        <div class="mt-1 text-xs text-white/45">通话成员</div>
        <div class="flex flex-wrap gap-1">
          <UserAvatar
            v-for="member in callMembers"
            :key="member.userId"
            :url="member.avatar"
            :name="member.nickname"
            :size="22"
            radius="4px"
            :clickable="false"
            :class="{ 'opacity-50': member.pending }"
            :title="member.pending ? `${member.nickname}(接入中)` : member.nickname"
          />
        </div>
      </template>
    </div>
 
    <!-- 右下角:拒绝 / 接听 -->
    <div class="flex flex-shrink-0 gap-2 items-center">
      <button
        class="flex flex-shrink-0 justify-center items-center w-10 h-10 text-white rounded-full transition-opacity bg-[#f04a4a] hover:opacity-90"
        :class="{ 'opacity-60 cursor-not-allowed': rejectDisabled }"
        :disabled="rejectDisabled"
        @click="$emit('reject')"
      >
        <Icon icon="ant-design:phone-outlined" :size="18" class="rotate-[135deg]" />
      </button>
      <button
        class="flex flex-shrink-0 justify-center items-center w-10 h-10 text-white rounded-full transition-opacity bg-[#2ec27e] hover:opacity-90"
        :class="{ 'opacity-60 cursor-not-allowed': acceptDisabled }"
        :disabled="acceptDisabled"
        @click="$emit('accept')"
      >
        <Icon icon="ant-design:phone-outlined" :size="18" />
      </button>
    </div>
  </div>
</template>