Files
nl-im-uniapp/src/components/call/GroupCallBanner.vue
2025-12-15 09:01:02 +08:00

258 lines
5.3 KiB
Vue

<template>
<view
v-if="showBanner"
class="group-call-banner"
:class="{ dark: isDark }"
>
<view class="banner-left">
<!-- 动态图标 -->
<view class="call-icon">
<view class="pulse-ring"></view>
<view class="icon-inner">
<!-- 使用 wot-ui icon 替代 SVG/Image -->
<wd-icon name="video" size="40rpx" color="#fff" />
</view>
</view>
<view class="call-info">
<view class="call-title">
<text class="title-text">通话进行中</text>
<view class="participant-count">
<text>{{ participantsCount }}人</text>
</view>
</view>
<!-- 头像堆叠 -->
<view class="avatar-stack">
<view
v-for="(user, index) in previewParticipants"
:key="user.userId"
class="stacked-avatar"
:style="{ marginLeft: index > 0 ? '-24rpx' : '0', zIndex: 10 - index }"
>
<app-avatar
:src="user.avatar"
:name="user.name"
:size="40"
round
/>
</view>
<view
v-if="participantsCount > 5"
class="more-count"
>
+{{ participantsCount - 5 }}
</view>
</view>
</view>
</view>
<view class="join-btn" @click="handleJoin">
<text>加入</text>
</view>
</view>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import { useGroupWebRTC } from '@/composables/useGroupWebRTC'
import { useTheme } from '@/composables/useTheme'
import AppAvatar from '@/components/common/AppAvatar.vue'
const props = defineProps<{
roomId: string
}>()
const { isDark } = useTheme()
const groupWebRTC = useGroupWebRTC()
const { callState, participants } = groupWebRTC
const showBanner = computed(() => {
return callState.groupId === props.roomId &&
!callState.joined &&
!callState.incoming &&
callState.roomId !== ''
})
const participantsCount = computed(() => participants.value.length)
const previewParticipants = computed(() => participants.value.slice(0, 5))
function handleJoin() {
groupWebRTC.joinCurrentCall()
}
</script>
<style lang="scss" scoped>
.group-call-banner {
margin: 24rpx 32rpx;
padding: 24rpx;
background: #fff;
border: 1rpx solid rgba(0, 0, 0, 0.05);
border-radius: 32rpx;
display: flex;
align-items: center;
justify-content: space-between;
box-shadow: 0 2rpx 16rpx rgba(0, 0, 0, 0.06);
animation: slideDown 0.3s ease-out;
&.dark {
background: #292524;
border-color: rgba(255, 255, 255, 0.05);
}
}
@keyframes slideDown {
from {
transform: translateY(-20rpx);
opacity: 0;
}
to {
transform: translateY(0);
opacity: 1;
}
}
.banner-left {
display: flex;
align-items: center;
gap: 24rpx;
}
.call-icon {
position: relative;
width: 80rpx;
height: 80rpx;
flex-shrink: 0;
.pulse-ring {
position: absolute;
inset: 0;
background: #10b981;
border-radius: 50%;
opacity: 0.2;
animation: pulse-ring 2s infinite;
}
.icon-inner {
position: relative;
width: 100%;
height: 100%;
border-radius: 50%;
background: #10b981;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 8rpx 24rpx rgba(16, 185, 129, 0.2);
overflow: hidden;
// 内部渐变光效
&::before {
content: '';
position: absolute;
inset: 0;
background: linear-gradient(180deg, rgba(255,255,255,0.2) 0%, transparent 100%);
}
}
}
@keyframes pulse-ring {
0%, 100% { transform: scale(1); opacity: 0.2; }
50% { transform: scale(1.15); opacity: 0.35; }
}
.call-info {
display: flex;
flex-direction: column;
gap: 8rpx;
}
.call-title {
display: flex;
align-items: center;
gap: 16rpx;
.title-text {
font-size: 28rpx;
font-weight: 700;
color: #1f2937;
letter-spacing: -0.5rpx;
.dark & { color: #f5f5f4; }
}
.participant-count {
padding: 4rpx 12rpx;
background: rgba(16, 185, 129, 0.1);
color: #10b981;
font-size: 20rpx;
font-weight: 600;
border-radius: 8rpx;
border: 1rpx solid rgba(16, 185, 129, 0.15);
.dark & {
background: rgba(16, 185, 129, 0.15);
border-color: rgba(16, 185, 129, 0.25);
}
}
}
.avatar-stack {
display: flex;
align-items: center;
padding-left: 24rpx;
.stacked-avatar {
border: 4rpx solid #fff;
border-radius: 50%;
position: relative;
.dark & { border-color: #292524; }
}
.more-count {
width: 40rpx;
height: 40rpx;
margin-left: -16rpx;
border-radius: 50%;
background: #f3f4f6;
color: #6b7280;
font-size: 16rpx;
font-weight: 700;
display: flex;
align-items: center;
justify-content: center;
border: 4rpx solid #fff;
position: relative;
z-index: 0;
.dark & {
background: #44403c;
color: #a8a29e;
border-color: #292524;
}
}
}
.join-btn {
display: flex;
align-items: center;
justify-content: center;
background: #10b981;
padding: 16rpx 32rpx;
border-radius: 24rpx;
box-shadow: 0 8rpx 24rpx rgba(16, 185, 129, 0.2);
transition: all 0.15s;
text {
font-size: 24rpx;
font-weight: 700;
color: #fff;
}
&:active {
transform: scale(0.95);
background: #059669;
}
}
</style>