From 27cd042df9aca0383a49f3514bc21958dd890912 Mon Sep 17 00:00:00 2001
From: gaoluyang <2820782392@qq.com>
Date: 星期一, 29 六月 2026 15:42:23 +0800
Subject: [PATCH] 银川 1.联调产品维护页面 2.添加IM即时通讯模块

---
 src/views/im/home/components/rtc/rtc-call-participant-tile.vue |  110 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 110 insertions(+), 0 deletions(-)

diff --git a/src/views/im/home/components/rtc/rtc-call-participant-tile.vue b/src/views/im/home/components/rtc/rtc-call-participant-tile.vue
new file mode 100644
index 0000000..6a12111
--- /dev/null
+++ b/src/views/im/home/components/rtc/rtc-call-participant-tile.vue
@@ -0,0 +1,110 @@
+<script lang="ts" setup>
+import { useMediaStreamElement } from '../../composables/useMediaStreamElement'
+import { UserAvatar } from '../user'
+
+export interface CallParticipantVM {
+  userId: number
+  nickname: string
+  avatar?: string
+  isLocal: boolean
+  videoStream?: MediaStream | null
+  audioStream?: MediaStream | null
+  /** 绛夊緟鍔犲叆锛沀I 鏄剧ず涓夌偣鍔ㄧ敾 */
+  pending?: boolean
+}
+
+const props = defineProps<{
+  participant: CallParticipantVM
+  /** 鎵0鍣ㄥ紑鍏筹紱涓� false 鏃堕潤闊宠鏍煎瓙鐨勮繙绔煶棰� */
+  speakerEnabled: boolean
+}>()
+
+const setVideoRef = useMediaStreamElement<HTMLVideoElement>(() => props.participant.videoStream)
+const setAudioRef = useMediaStreamElement<HTMLAudioElement>(() => props.participant.audioStream)
+</script>
+
+<template>
+  <!--
+    缇ら�氳瘽瀹牸閲岀殑鍗曚釜鍙備笌鑰呮牸瀛愶紱
+    浼樺厛娓叉煋杩滅瑙嗛锛堟湁 videoStream 鏃堕摵婊★級锛屾棤瑙嗛闄嶇骇娓叉煋澶村儚 + 鍚嶅瓧鑳跺泭锛�
+    pending=true 鏃跺彔鍔犮�屾帴鍏ヤ腑銆嶄笁鐐瑰姩鐢诲崰浣嶏紝鐢ㄤ簬琚個璇蜂汉鏈帴閫氬墠鐨勫崰浣嶆覆鏌擄紱
+    杩滅闊抽閫氳繃闅愯棌 audio 鎾斁锛堟湰绔� isLocal 涓嶆覆鏌撻伩鍏嶅洖澹帮級
+  -->
+  <div
+    class="flex relative overflow-hidden justify-center items-center w-full h-full rounded-md bg-[#2a2a2c]"
+  >
+    <!-- 瑙嗛鍙敤锛氭覆鏌� video锛涘惁鍒欐覆鏌撳ご鍍忔垨榛樿鍗犱綅 -->
+    <video
+      v-if="participant.videoStream"
+      :ref="setVideoRef"
+      class="object-cover w-full h-full"
+      autoplay
+      playsinline
+      :muted="participant.isLocal"
+    ></video>
+    <div v-else class="flex justify-center items-center w-full h-full">
+      <UserAvatar
+        :url="participant.avatar"
+        :name="participant.nickname"
+        :size="64"
+        radius="50%"
+        :clickable="false"
+      />
+    </div>
+
+    <!-- 杩滅闊抽锛涢�氳繃 audio 鍏冪礌鎾斁锛屾湰绔潤闊抽伩鍏嶅洖澹帮紱鎵0鍣ㄥ叧闂椂鏁翠綋闈欓煶 -->
+    <audio
+      v-if="participant.audioStream && !participant.isLocal"
+      :ref="setAudioRef"
+      autoplay
+      :muted="!speakerEnabled"
+    ></audio>
+
+    <!-- 宸︿笅瑙掑悕瀛楄兌鍥� -->
+    <div
+      class="flex absolute bottom-3 left-3 gap-1.5 items-center py-[3px] pr-2.5 pl-[3px] text-13px text-white rounded-full bg-black/45 max-w-[calc(100%-60px)]"
+    >
+      <UserAvatar
+        :url="participant.avatar"
+        :name="participant.nickname"
+        :size="22"
+        radius="50%"
+        :clickable="false"
+      />
+      <span class="truncate min-w-0">{{ participant.nickname }}</span>
+    </div>
+
+    <!-- 绛夊緟瀵规柟鍔犲叆锛氫笁鐐瑰姩鐢� +銆屾帴鍏ヤ腑銆嶆枃妗堬紱浣嶇疆鍦ㄦ牸瀛愪腑蹇冧笅鏂� 60px -->
+    <div
+      v-if="participant.pending"
+      class="flex absolute left-1/2 flex-col gap-1.5 items-center -translate-x-1/2 -translate-y-1/2 top-[calc(50%+60px)]"
+    >
+      <div class="flex gap-1.5">
+        <span
+          v-for="i in 3"
+          :key="i"
+          class="tile-dot w-1.5 h-1.5 rounded-full bg-white/60"
+          :style="{ animationDelay: `${(i - 1) * 0.2}s` }"
+        ></span>
+      </div>
+      <span class="text-xs text-white/70">鎺ュ叆涓�</span>
+    </div>
+  </div>
+</template>
+
+<style scoped>
+/* 涓夌偣娣″叆娣″嚭鍔ㄧ敾锛汙keyframes 蹇呴』 CSS 瀹氫箟 */
+.tile-dot {
+  animation: tile-dot 1.4s infinite ease-in-out both;
+}
+@keyframes tile-dot {
+  0%,
+  80%,
+  100% {
+    opacity: 0.3;
+  }
+  40% {
+    opacity: 1;
+  }
+}
+</style>

--
Gitblit v1.9.3