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
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
<script lang="ts" setup>
import { IconifyIcon as Icon } from '#/packages/icons/src'
 
import { useMediaStreamElement } from '../../composables/useMediaStreamElement'
import { UserAvatar } from '../user'
 
const props = defineProps<{
  cameraEnabled: boolean
  isGroup?: boolean
  isVideo: boolean
  localStream?: MediaStream | null // 本地视频流;视频呼叫预览铺底
  micEnabled: boolean
  peerAvatar?: string
  peerNickname?: string
  speakerEnabled: boolean
}>()
 
defineEmits<{
  cancel: []
  toggleCamera: []
  toggleMic: []
  toggleSpeaker: []
}>()
 
const setLocalVideoRef = useMediaStreamElement<HTMLVideoElement>(() => props.localStream)
</script>
 
<template>
  <!-- 主叫等待对方接听的悬浮窗;1v1 私聊 320×540;群通话切大窗 720×560 -->
  <div
    class="fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 rounded-xl overflow-hidden shadow-[0_12px_36px_rgba(0,0,0,0.35)] z-[1000] flex flex-col text-white bg-gradient-to-b from-[#2a2a2c] to-[#1a1a1c]"
    :class="isGroup ? 'w-[720px] h-[560px]' : 'w-[320px] h-[540px]'"
  >
    <div class="flex relative flex-1 justify-center items-center">
      <!-- 视频呼叫:自己摄像头预览铺底,对方头像悬浮顶部 -->
      <video
        v-if="isVideo && localStream"
        :ref="setLocalVideoRef"
        class="absolute inset-0 object-cover w-full h-full scale-x-[-1]"
        autoplay
        muted
        playsinline
      ></video>
      <div
        class="flex relative z-[1] flex-col gap-4 items-center"
        :class="{ 'self-start mt-16': isVideo }"
      >
        <UserAvatar
          :url="peerAvatar"
          :name="peerNickname"
          :size="96"
          radius="8px"
          :clickable="false"
        />
        <div class="text-[17px] font-medium">{{ peerNickname || '对方' }}</div>
        <div class="text-13px text-white/60">等待对方接受邀请……</div>
      </div>
    </div>
 
    <!-- 底部操作区:麦克风 / 取消 / (摄像头 | 扬声器) -->
    <div class="flex flex-shrink-0 gap-4 justify-around items-center pt-4 px-5 pb-5">
      <div
        class="flex flex-col gap-2 items-center cursor-pointer select-none"
        @click="$emit('toggleMic')"
      >
        <!-- Iconify 里 mic 有静音变体;speaker / camera 没有同源静音变体,off 态借 tabler:*-off 表达斜线 -->
        <span
          class="flex justify-center items-center w-12 h-12 rounded-full"
          :class="micEnabled ? 'bg-white text-[#1a1a1c]' : 'bg-white/15 text-white'"
        >
          <Icon
            :icon="micEnabled ? 'ant-design:audio-outlined' : 'ant-design:audio-muted-outlined'"
            :size="22"
          />
        </span>
        <span class="text-xs text-white/70 whitespace-nowrap">
          {{ micEnabled ? '麦克风已开' : '麦克风已关' }}
        </span>
      </div>
      <div
        class="flex flex-col gap-2 items-center cursor-pointer select-none"
        @click="$emit('cancel')"
      >
        <span
          class="flex justify-center items-center w-12 h-12 text-white rounded-full bg-[#f04a4a]"
        >
          <Icon icon="ant-design:phone-outlined" :size="22" class="rotate-[135deg]" />
        </span>
        <span class="text-xs text-white/70 whitespace-nowrap">取消</span>
      </div>
      <div
        v-if="isVideo"
        class="flex flex-col gap-2 items-center cursor-pointer select-none"
        @click="$emit('toggleCamera')"
      >
        <span
          class="flex justify-center items-center w-12 h-12 rounded-full"
          :class="cameraEnabled ? 'bg-white text-[#1a1a1c]' : 'bg-white/15 text-white'"
        >
          <Icon
            :icon="cameraEnabled ? 'ant-design:video-camera-outlined' : 'tabler:video-off'"
            :size="22"
          />
        </span>
        <span class="text-xs text-white/70 whitespace-nowrap">
          {{ cameraEnabled ? '摄像头已开' : '摄像头已关' }}
        </span>
      </div>
      <div
        v-else
        class="flex flex-col gap-2 items-center cursor-pointer select-none"
        @click="$emit('toggleSpeaker')"
      >
        <span
          class="flex justify-center items-center w-12 h-12 rounded-full"
          :class="speakerEnabled ? 'bg-white text-[#1a1a1c]' : 'bg-white/15 text-white'"
        >
          <Icon
            :icon="speakerEnabled ? 'ant-design:sound-outlined' : 'tabler:volume-off'"
            :size="22"
          />
        </span>
        <span class="text-xs text-white/70 whitespace-nowrap">
          {{ speakerEnabled ? '扬声器已开' : '扬声器已关' }}
        </span>
      </div>
    </div>
  </div>
</template>