From 787ccc59ba89bacc075562a161ecf02bc76ebadc Mon Sep 17 00:00:00 2001
From: gaoluyang <2820782392@qq.com>
Date: 星期五, 24 七月 2026 17:15:53 +0800
Subject: [PATCH] 银川 1.采购申请添加入库状态查询条件
---
src/views/im/home/components/rtc/rtc-call-running.vue | 291 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 291 insertions(+), 0 deletions(-)
diff --git a/src/views/im/home/components/rtc/rtc-call-running.vue b/src/views/im/home/components/rtc/rtc-call-running.vue
new file mode 100644
index 0000000..e0b8c2b
--- /dev/null
+++ b/src/views/im/home/components/rtc/rtc-call-running.vue
@@ -0,0 +1,291 @@
+<script lang="ts" setup>
+import { computed, onUnmounted, ref, watch } from 'vue'
+
+import { IconifyIcon as Icon } from '#/packages/icons/src'
+
+import { formatCallDuration } from '#/views/im/utils/time'
+
+import { useMediaStreamElement } from '../../composables/useMediaStreamElement'
+import { UserAvatar } from '../user'
+import RtcCallParticipantTile, { type CallParticipantVM } from './rtc-call-participant-tile.vue'
+
+const props = defineProps<{
+ cameraEnabled: boolean
+ hangingUp?: boolean
+ /** 鏄惁缇ら�氳瘽锛涘喅瀹氱綉鏍� / 鍗曠偣甯冨眬 + 娴獥澶у皬 */
+ isGroup: boolean
+ isVideo: boolean
+ localStream?: MediaStream | null
+ micEnabled: boolean
+ /** 缃戞牸瑙嗗浘鐢細鎵�鏈夊弬涓庤�咃紙鍚嚜宸憋級 */
+ participants: CallParticipantVM[]
+ peerAvatar?: string
+ /** 1v1 瑙嗗浘鐢� */
+ peerNickname?: string
+ /** 鏄惁澶勪簬缃戠粶閲嶈繛涓紱鏄剧ず椤堕儴榛勮壊鎻愮ず鏉� */
+ reconnecting?: boolean
+ remoteAudioStream?: MediaStream | null
+ remoteVideoStream?: MediaStream | null
+ /** 鏄惁姝e湪灞忓箷鍏变韩锛涙寜閽珮浜� + 鏂囨鍒囨崲 */
+ screenShareEnabled?: boolean
+ /** 鎵0鍣ㄥ紑鍏筹紱true 鏃舵甯告挱鏀捐繙绔煶棰戯紝false 鏃舵墍鏈夎繙绔� audio 鍏冪礌闈欓煶 */
+ speakerEnabled: boolean
+ startedAt: number
+}>()
+
+defineEmits<{
+ addMember: []
+ hangup: []
+ toggleCamera: []
+ toggleMic: []
+ toggleScreenShare: []
+ toggleSpeaker: []
+}>()
+
+/** 缃戞牸鍒楁暟锛涙寜浜烘暟鑷�傚簲锛涜繑鍥� UnoCSS class 瀛楅潰閲忚 JIT 鎵弿鍣ㄩ潤鎬佽瘑鍒� */
+const gridColsClass = computed(() => {
+ const n = props.participants.length
+ if (n <= 1) return 'grid-cols-1'
+ if (n <= 4) return 'grid-cols-2'
+ return 'grid-cols-3'
+})
+
+const setLocalVideoRef = useMediaStreamElement<HTMLVideoElement>(() => props.localStream)
+const setRemoteVideoRef = useMediaStreamElement<HTMLVideoElement>(() => props.remoteVideoStream)
+const setRemoteAudioRef = useMediaStreamElement<HTMLAudioElement>(() => props.remoteAudioStream)
+
+/** 1v1 瑙嗛锛氭槸鍚︽湁杩滅瑙嗛娴� */
+const hasRemoteVideo = computed(() => !props.isGroup && !!props.remoteVideoStream)
+
+const now = ref(Date.now()) // 閫氳瘽鏃堕暱锛涗粎 1v1 璇煶瑙嗗浘闇�瑕佸睍绀猴紝鍏跺畠瑙嗗浘涓嶅惎 tick
+let tick = 0
+watch(
+ () => props.isGroup || props.isVideo,
+ (suppressTick) => {
+ if (suppressTick) {
+ if (tick) {
+ clearInterval(tick)
+ tick = 0
+ }
+ return
+ }
+ now.value = Date.now()
+ tick = window.setInterval(() => {
+ now.value = Date.now()
+ }, 1000)
+ },
+ { immediate: true }
+)
+onUnmounted(() => {
+ if (tick) {
+ clearInterval(tick)
+ }
+})
+
+/** 閫氳瘽鏃堕暱 MM:SS / HH:MM:SS */
+const formattedDuration = computed(() =>
+ formatCallDuration(Math.floor((now.value - props.startedAt) / 1000))
+)
+</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-[#1a1a1c]"
+ :class="isGroup ? 'w-[720px] h-[560px]' : 'w-[320px] h-[540px]'"
+ >
+ <!-- 閲嶈繛涓í骞咃紱缃戠粶鎶栧姩鏃舵樉绀猴紝鐩村埌 Reconnected 浜嬩欢娓呴櫎 -->
+ <div
+ v-if="reconnecting"
+ class="inline-flex absolute top-3 left-1/2 z-10 gap-2 items-center px-3.5 py-1.5 text-13px text-[#ffd45e] rounded-full -translate-x-1/2 bg-[rgba(255,196,0,0.18)]"
+ >
+ <span class="reconnect-dot w-2 h-2 rounded-full bg-[#ffd45e]"></span>
+ 缃戠粶涓嶄匠锛屾鍦ㄩ噸杩炩�︹��
+ </div>
+ <div class="flex relative flex-1 justify-center items-center">
+ <!-- 缇ら�氳瘽锛氱綉鏍煎竷灞�锛屽垪鏁伴殢浜烘暟鑷�傚簲 -->
+ <div
+ v-if="isGroup"
+ class="grid gap-1 p-1 w-full h-full content-stretch"
+ :class="gridColsClass"
+ >
+ <RtcCallParticipantTile
+ v-for="participant in participants"
+ :key="participant.userId"
+ :participant="participant"
+ :speaker-enabled="speakerEnabled"
+ />
+ </div>
+
+ <!-- 1v1 瑙嗛锛氳繙绔摵搴� + 鏈湴缂╃暐 -->
+ <template v-else-if="isVideo">
+ <div v-show="hasRemoteVideo" class="absolute inset-0">
+ <video
+ :ref="setRemoteVideoRef"
+ class="object-cover w-full h-full"
+ autoplay
+ playsinline
+ ></video>
+ </div>
+ <div v-if="!hasRemoteVideo" class="flex z-[1] flex-col gap-4 items-center">
+ <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
+ v-if="localStream"
+ class="absolute top-4 right-4 z-[2] overflow-hidden w-30 rounded-lg aspect-[9/16] bg-[#333]"
+ >
+ <video
+ :ref="setLocalVideoRef"
+ class="object-cover w-full h-full scale-x-[-1]"
+ autoplay
+ muted
+ playsinline
+ ></video>
+ </div>
+ </template>
+
+ <!-- 1v1 璇煶锛氬ご鍍� + 鏃堕暱 -->
+ <template v-else>
+ <div class="flex z-[1] flex-col gap-4 items-center">
+ <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">{{ formattedDuration }}</div>
+ </div>
+ </template>
+ <audio
+ v-if="!isGroup && remoteAudioStream"
+ :ref="setRemoteAudioRef"
+ autoplay
+ :muted="!speakerEnabled"
+ ></audio>
+ </div>
+
+ <!-- 搴曢儴鎿嶄綔鍖猴細楹﹀厠椋� / 鎵0鍣� / 鎽勫儚澶� / (缇よ亰锛氬叡浜睆骞� / 娣诲姞鎴愬憳) / 鎸傛柇 -->
+ <div
+ class="flex flex-shrink-0 gap-3 justify-around items-center pt-4 px-4 pb-5 bg-black/20"
+ >
+ <div
+ class="flex flex-col gap-2 items-center cursor-pointer select-none min-w-[64px]"
+ @click="$emit('toggleMic')"
+ >
+ <span
+ class="flex justify-center items-center w-[52px] h-[52px] 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 min-w-[64px]"
+ @click="$emit('toggleSpeaker')"
+ >
+ <span
+ class="flex justify-center items-center w-[52px] h-[52px] 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 ? '鎵0鍣ㄥ凡寮�' : '鎵0鍣ㄥ凡鍏�' }}
+ </span>
+ </div>
+ <!-- 鎽勫儚澶存寜閽細绉佽亰瑙嗛鍥哄畾鏄剧ず锛涚兢鑱婁笉璁鸿捣濮� mediaType 閮芥樉绀猴紝璁╃敤鎴锋寜闇�寮� -->
+ <div
+ v-if="isVideo || isGroup"
+ class="flex flex-col gap-2 items-center cursor-pointer select-none min-w-[64px]"
+ @click="$emit('toggleCamera')"
+ >
+ <span
+ class="flex justify-center items-center w-[52px] h-[52px] 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>
+ <!-- 缇ら�氳瘽鎵嶆湁锛氬叡浜睆骞� / 娣诲姞鎴愬憳锛涘叡浜腑鎸夐挳楂樹寒 + 鏂囨鎹㈡垚銆屽仠姝㈠叡浜�� -->
+ <template v-if="isGroup">
+ <div
+ class="flex flex-col gap-2 items-center cursor-pointer select-none min-w-[64px]"
+ @click="$emit('toggleScreenShare')"
+ >
+ <span
+ class="flex justify-center items-center w-[52px] h-[52px] rounded-full"
+ :class="screenShareEnabled ? 'bg-[#07c160] text-white' : 'bg-white/15 text-white'"
+ >
+ <Icon
+ :icon="screenShareEnabled ? 'ant-design:laptop-outlined' : 'tabler:device-laptop-off'"
+ :size="22"
+ />
+ </span>
+ <span class="text-xs text-white/70 whitespace-nowrap">
+ {{ screenShareEnabled ? '鍋滄鍏变韩' : '鍏变韩灞忓箷' }}
+ </span>
+ </div>
+ <div
+ class="flex flex-col gap-2 items-center cursor-pointer select-none min-w-[64px]"
+ @click="$emit('addMember')"
+ >
+ <span class="flex justify-center items-center w-[52px] h-[52px] text-white rounded-full bg-white/15">
+ <Icon icon="ant-design:plus-outlined" :size="22" />
+ </span>
+ <span class="text-xs text-white/70 whitespace-nowrap">娣诲姞鎴愬憳</span>
+ </div>
+ </template>
+ <div
+ class="flex flex-col gap-2 items-center cursor-pointer select-none min-w-[64px]"
+ :class="{ 'opacity-60 pointer-events-none': hangingUp }"
+ @click="$emit('hangup')"
+ >
+ <span class="flex justify-center items-center w-[52px] h-[52px] 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>
+ </div>
+</template>
+
+<style scoped>
+/* 閲嶈繛灏忕偣娣″叆娣″嚭锛汙keyframes 蹇呴』 CSS 瀹氫箟 */
+.reconnect-dot {
+ animation: reconnect-pulse 1s ease-in-out infinite;
+}
+@keyframes reconnect-pulse {
+ 0%,
+ 100% {
+ opacity: 0.3;
+ }
+ 50% {
+ opacity: 1;
+ }
+}
+</style>
--
Gitblit v1.9.3