92 lines
3.0 KiB
Vue
92 lines
3.0 KiB
Vue
<template>
|
||
<div class="ft-body">
|
||
<div class="ft-stage">
|
||
<!-- 空闲:联系人选择 -->
|
||
<template v-if="phase === 'idle'">
|
||
<div class="ft-title">选择联系人开始视频通话</div>
|
||
<div class="ft-grid">
|
||
<div v-for="c in contacts" :key="c.id" class="ft-cell">
|
||
<span class="contact-big-avatar">{{ c.name.slice(0, 1) }}</span>
|
||
<div>{{ c.name }}</div>
|
||
<button class="btn primary" @click="startCall(c)">📹 呼叫</button>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<!-- 通话中 -->
|
||
<template v-else-if="phase !== 'idle'">
|
||
<div class="ft-video" :class="{ live: phase === 'connected' }">
|
||
<span class="contact-big-avatar ft-avatar">{{ contact?.name?.slice(0, 1) }}</span>
|
||
</div>
|
||
<div class="ft-status">{{ statusText }}</div>
|
||
<div v-if="phase !== 'ended'" class="ft-self" :class="{ hidden: camOff }">
|
||
<span>🧑</span>
|
||
</div>
|
||
<div class="ft-controls">
|
||
<button class="ft-ctl" :class="{ off: micMuted }" title="静音" @click="micMuted = !micMuted">🎙</button>
|
||
<button class="ft-ctl" :class="{ off: camOff }" title="关闭视频" @click="camOff = !camOff">📷</button>
|
||
<button class="ft-ctl end" title="结束通话" @click="endCall">📞</button>
|
||
</div>
|
||
</template>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { ref, reactive, onMounted, onUnmounted, nextTick, computed } from 'vue'
|
||
import { store } from '../../composables/useStore'
|
||
|
||
const props = defineProps<{ win: any }>()
|
||
|
||
const phase = ref('idle') // idle | calling | connected | ended
|
||
const contact = ref<any>(null)
|
||
const micMuted = ref(false)
|
||
const camOff = ref(false)
|
||
const callSeconds = ref(0)
|
||
let callTimer: any = null
|
||
let connectTimer: any = null
|
||
|
||
const contacts = computed(() => store.get('contacts', []))
|
||
|
||
const statusText = computed(() => {
|
||
if (!contact.value) return ''
|
||
if (phase.value === 'calling') return `正在呼叫 ${contact.value.name}…`
|
||
if (phase.value === 'connected') {
|
||
return `${contact.value.name} · ${String(Math.floor(callSeconds.value / 60)).padStart(2, '0')}:${String(callSeconds.value % 60).padStart(2, '0')}`
|
||
}
|
||
if (phase.value === 'ended') return '通话已结束'
|
||
return ''
|
||
})
|
||
|
||
function startCall(c: any) {
|
||
contact.value = c
|
||
phase.value = 'calling'
|
||
connectTimer = setTimeout(() => {
|
||
if (phase.value === 'calling') {
|
||
phase.value = 'connected'
|
||
callSeconds.value = 0
|
||
callTimer = setInterval(() => { callSeconds.value++ }, 1000)
|
||
}
|
||
}, 2500)
|
||
}
|
||
|
||
function endCall() {
|
||
clearInterval(callTimer)
|
||
clearTimeout(connectTimer)
|
||
phase.value = 'ended'
|
||
setTimeout(() => { phase.value = 'idle'; micMuted.value = false; camOff.value = false }, 1200)
|
||
}
|
||
|
||
props.win.appState = { startCall }
|
||
|
||
onMounted(() => {
|
||
const args = props.win.data
|
||
if (args?.call) startCall(args.call)
|
||
})
|
||
|
||
onUnmounted(() => {
|
||
clearInterval(callTimer)
|
||
clearTimeout(connectTimer)
|
||
})
|
||
</script>
|