diff --git a/frontend/src/pages/cloud-law/video-call.vue b/frontend/src/pages/cloud-law/video-call.vue index 36c4473..41f5c02 100644 --- a/frontend/src/pages/cloud-law/video-call.vue +++ b/frontend/src/pages/cloud-law/video-call.vue @@ -7,8 +7,8 @@ - - + + - + + + + + {{ voiceInitial }} + {{ voiceName }} + + + + - - - 视频咨询 - {{ formattedCode }} - - - - {{ stateText }} - + + + {{ durationText }} + - {{ durationText }} + + + + + {{ durationText }} + + + + + + + 麦克风{{ audioEnabled ? '已开' : '已关' }} + + + + + + + 扬声器已开 + - - - {{ videoEnabled ? '摄' : '关' }} + + 摄像头{{ videoEnabled ? '已开' : '已关' }} + + + + + + + + + + + + + + + + + + + 摄像头已关 + + + - + 挂断 - - - {{ audioEnabled ? '麦' : '静' }} + + + + + + + + + + + 麦克风{{ audioEnabled ? '已开' : '已关' }} - 麦克风{{ audioEnabled ? '已开' : '已关' }} @@ -99,6 +150,12 @@ import { MEETING_ENDED_REASON_LABEL } from '@/constants/meeting' import VideoTile from '@/components/meeting/VideoTile.vue' +import bgImage from '@/static/bg.png' +import hangupIcon from '@/static/cacel.png' +import voiceOnIcon from '@/static/vocie_calling_1.png' +import voiceOffIcon from '@/static/vocie_stop.png' +import videoOnIcon from '@/static/vodie_calling_1.png' +import videoOffIcon from '@/static/vodie_stop.png' const meetingStore = useMeetingStore() const userStore = useUserStore() @@ -130,6 +187,10 @@ const hostId = computed(() => meetingStore.currentRoom?.host_id || '') const audioEnabled = computed(() => !!meetingStore.localAudioEnabled) const videoEnabled = computed(() => !!meetingStore.localVideoEnabled) const speakingMap = computed(() => meetingStore.speakingMap || {}) +const isVoiceMode = computed(() => !videoEnabled.value) +const speakerIcon = voiceOnIcon +const cameraIcon = computed(() => videoEnabled.value ? videoOnIcon : videoOffIcon) +const micIcon = computed(() => audioEnabled.value ? voiceOnIcon : voiceOffIcon) const entryInitial = computed(() => { const name = userStore.userInfo?.nickname || userStore.userInfo?.username || '在' @@ -226,6 +287,14 @@ const tiles = computed(() => { const selfTile = computed(() => tiles.value.find(t => t.isLocal) || null) const primaryTile = computed(() => tiles.value.find(t => !t.isLocal && t.videoTrack) || tiles.value.find(t => !t.isLocal) || selfTile.value) +const voicePeer = computed(() => participants.value.find(p => !sameUser(p.user_id, myUserId.value)) || null) +const voiceName = computed(() => { + if (voicePeer.value?.user_name) return voicePeer.value.user_name + if (primaryTile.value && !primaryTile.value.isLocal) return primaryTile.value.name + return '在线律师' +}) +const voiceAvatar = computed(() => voicePeer.value?.user_avatar || '') +const voiceInitial = computed(() => String(voiceName.value || '在').slice(0, 1)) const isReady = computed(() => meetingStore.localState === MEETING_LOCAL_STATE_CONNECTED && !joinError.value && !ended.value) @@ -467,6 +536,51 @@ onUnload(() => { inset: 0; background: #050b1d; } +.voice-stage { + position: absolute; + inset: 0; + display: flex; + align-items: flex-start; + justify-content: center; + overflow: hidden; +} +.voice-bg { + position: absolute; + inset: 0; + width: 100%; + height: 100%; +} +.voice-profile { + position: relative; + z-index: 2; + margin-top: calc(126rpx + env(safe-area-inset-top)); + display: flex; + flex-direction: column; + align-items: center; +} +.voice-avatar { + width: 132rpx; + height: 132rpx; + border-radius: 50%; + border: 4rpx solid rgba(255, 255, 255, 0.86); + box-shadow: 0 12rpx 36rpx rgba(0, 0, 0, 0.28); +} +.voice-avatar-fallback { + display: flex; + align-items: center; + justify-content: center; + background: linear-gradient(135deg, #43b1ff 0%, #1974e8 100%); + color: #ffffff; + font-size: 54rpx; + font-weight: 700; +} +.voice-name { + margin-top: 24rpx; + font-size: 28rpx; + line-height: 1; + color: #ffffff; + font-weight: 600; +} .main-video { width: 100%; height: 100%; @@ -482,13 +596,13 @@ onUnload(() => { } .self-preview { position: absolute; - right: 24rpx; - bottom: 218rpx; - width: 210rpx; - height: 280rpx; - border-radius: 20rpx; + right: 30rpx; + top: calc(104rpx + env(safe-area-inset-top)); + width: 220rpx; + height: 310rpx; + border-radius: 10rpx; overflow: hidden; - border: 2rpx solid rgba(108, 151, 255, 0.72); + border: 0; box-shadow: 0 10rpx 40rpx rgba(0, 0, 0, 0.38); background: #0b1220; z-index: 5; @@ -498,70 +612,78 @@ onUnload(() => { height: 100%; border-radius: 0; } -.top-info { +.video-topbar { position: absolute; left: 0; right: 0; top: 0; - padding: calc(26rpx + env(safe-area-inset-top)) 28rpx 40rpx; + height: calc(88rpx + env(safe-area-inset-top)); + padding: env(safe-area-inset-top) 46rpx 0; display: flex; - align-items: flex-start; + align-items: center; justify-content: space-between; - gap: 20rpx; background: linear-gradient(180deg, rgba(0, 0, 0, 0.55) 0%, rgba(0, 0, 0, 0) 100%); box-sizing: border-box; z-index: 6; } -.call-title { - display: block; - font-size: 32rpx; - font-weight: 700; - line-height: 1.2; +.top-window-icon { + position: relative; + width: 42rpx; + height: 42rpx; + border: 4rpx solid rgba(255, 255, 255, 0.9); + border-radius: 6rpx; + box-sizing: border-box; } -.call-code { - display: block; - margin-top: 8rpx; - font-size: 22rpx; - color: rgba(255, 255, 255, 0.72); - letter-spacing: 1rpx; +.top-window-icon::after { + content: ''; + position: absolute; + right: -16rpx; + bottom: -14rpx; + width: 24rpx; + height: 24rpx; + border: 4rpx solid rgba(255, 255, 255, 0.9); + border-radius: 5rpx; + box-sizing: border-box; } -.status-pill { - display: inline-flex; +.top-duration { + font-size: 36rpx; + color: #ffffff; + font-weight: 500; + text-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.35); +} +.top-plus { + color: #ffffff; + font-size: 56rpx; + line-height: 1; + font-weight: 300; +} +.voice-topbar { + position: absolute; + left: 0; + right: 0; + top: 0; + height: calc(88rpx + env(safe-area-inset-top)); + padding: env(safe-area-inset-top) 26rpx 0; + display: flex; align-items: center; - gap: 8rpx; - padding: 8rpx 16rpx; - border-radius: 999rpx; - background: rgba(21, 128, 61, 0.72); - color: #baf7d0; - font-size: 22rpx; - white-space: nowrap; + box-sizing: border-box; + z-index: 6; } -.status-pill.connecting, -.status-pill.reconnecting { - background: rgba(37, 99, 235, 0.72); - color: #dbeafe; +.voice-back { + color: #ffffff; + font-size: 54rpx; + line-height: 1; + font-weight: 300; } -.status-pill.ended, -.status-pill.leaving { - background: rgba(107, 114, 128, 0.72); - color: #f3f4f6; -} -.status-dot { - width: 12rpx; - height: 12rpx; - border-radius: 50%; - background: currentColor; - box-shadow: 0 0 12rpx currentColor; -} -.timer { +.voice-duration { position: absolute; left: 50%; - bottom: 196rpx; + bottom: calc(216rpx + env(safe-area-inset-bottom)); transform: translateX(-50%); - padding: 8rpx 20rpx; + padding: 10rpx 20rpx; border-radius: 999rpx; - background: rgba(255, 255, 255, 0.18); - color: rgba(255, 255, 255, 0.86); + background: rgba(255, 255, 255, 0.16); + color: rgba(255, 255, 255, 0.78); font-size: 24rpx; line-height: 1; z-index: 7; @@ -571,51 +693,88 @@ onUnload(() => { left: 0; right: 0; bottom: 0; - padding: 34rpx 44rpx calc(34rpx + env(safe-area-inset-bottom)); - display: flex; - align-items: center; - justify-content: space-between; - background: linear-gradient(0deg, rgba(4, 10, 27, 0.96) 0%, rgba(4, 10, 27, 0.72) 72%, rgba(4, 10, 27, 0) 100%); box-sizing: border-box; z-index: 8; } +.video-controls { + padding: 28rpx 64rpx calc(122rpx + env(safe-area-inset-bottom)); + display: grid; + grid-template-columns: repeat(3, 1fr); + column-gap: 46rpx; + background: linear-gradient(0deg, rgba(0, 0, 0, 0.54) 0%, rgba(0, 0, 0, 0.22) 62%, rgba(0, 0, 0, 0) 100%); +} +.voice-controls { + padding: 0 74rpx calc(54rpx + env(safe-area-inset-bottom)); + display: grid; + grid-template-columns: 1fr 132rpx 1fr; + align-items: end; + column-gap: 54rpx; + background: transparent; +} +.voice-control-col { + display: flex; + flex-direction: column; + gap: 36rpx; +} .control-item { display: flex; flex-direction: column; align-items: center; - gap: 14rpx; - min-width: 150rpx; + gap: 18rpx; } .control-btn, .hangup-btn { - width: 108rpx; - height: 108rpx; + width: 124rpx; + height: 124rpx; border-radius: 50%; display: flex; align-items: center; justify-content: center; - color: #ffffff; - box-shadow: inset 0 0 0 2rpx rgba(255, 255, 255, 0.08); + box-sizing: border-box; } -.control-btn { - background: rgba(24, 64, 140, 0.9); +.white-btn { + background: rgba(255, 255, 255, 0.96); + box-shadow: 0 10rpx 26rpx rgba(0, 0, 0, 0.18); +} +.dark-btn { + width: 104rpx; + height: 104rpx; + background: rgba(13, 36, 89, 0.78); + border: 1rpx solid rgba(255, 255, 255, 0.06); + box-shadow: inset 0 0 0 2rpx rgba(255, 255, 255, 0.04); } .control-btn.disabled { - background: rgba(18, 32, 70, 0.94); - color: rgba(255, 255, 255, 0.55); + opacity: 0.94; } .hangup-btn { - background: #ff2727; - box-shadow: 0 10rpx 34rpx rgba(255, 39, 39, 0.38); + width: 132rpx; + height: 132rpx; + background: #ff2f3f; + box-shadow: 0 14rpx 36rpx rgba(255, 47, 63, 0.38); } -.control-icon, -.hangup-icon { - font-size: 34rpx; - font-weight: 700; +.hangup-center { + position: absolute; + left: 50%; + bottom: calc(32rpx + env(safe-area-inset-bottom)); + transform: translateX(-50%); +} +.control-image { + width: 58rpx; + height: 58rpx; +} +.dark-btn .control-image { + width: 50rpx; + height: 50rpx; +} +.hangup-image { + width: 74rpx; + height: 74rpx; } .control-label { font-size: 24rpx; - color: rgba(255, 255, 255, 0.74); + line-height: 1; + color: rgba(255, 255, 255, 0.82); + text-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.3); } .loading { opacity: 0.68;