Files
oa_app/pages/chat/call.vue
T
2026-09-23 16:52:48 +08:00

106 lines
3.2 KiB
Vue

<template>
<view class="page call">
<view class="stage">
<web-view v-if="roomSrc" :src="roomSrc" class="wv" />
<view v-else class="wait">
<view class="av">{{ initial }}</view>
<text class="name">{{ peerName }}</text>
<text class="st">{{ statusText }}</text>
</view>
</view>
<view class="acts">
<view v-if="ringing" class="btn ok" @click="accept">接听</view>
<view class="btn no" @click="hang">挂断</view>
</view>
</view>
</template>
<script>
import { store } from '../../utils/store.js'
export default {
data() {
return { off: null, tick: 0 }
},
computed: {
call() {
this.tick
return store.call
},
ringing() {
return this.call && this.call.status === 'ringing'
},
peerName() {
const c = this.call
if (!c) return '通话'
const p = store.person(c.peerId)
return (p && p.name) || '同事'
},
initial() {
return this.peerName.slice(0, 1)
},
statusText() {
const s = this.call && this.call.status
if (s === 'calling') return '正在呼叫…'
if (s === 'ringing') return '邀请你通话'
if (s === 'active') return '通话中'
return '连接中'
},
roomSrc() {
const c = this.call
if (!c || !c.token || !c.url) return ''
if (c.status !== 'active' && c.direction !== 'out') return ''
return 'https://meet.livekit.io/custom/?liveKitUrl=' + encodeURIComponent(c.url) + '&token=' + encodeURIComponent(c.token)
}
},
onShow() {
if (this.off) this.off()
this.off = store.on(() => {
this.tick += 1
if (!store.call) {
const pages = getCurrentPages()
if (pages.length > 1) uni.navigateBack()
}
})
if (!store.call) uni.navigateBack()
},
onHide() {
if (this.off) this.off()
this.off = null
},
onUnload() {
if (this.off) this.off()
if (store.call) store.endCall(true)
},
methods: {
async accept() {
try {
uni.showLoading({ title: '接通中…', mask: true })
await store.acceptCall()
} catch (e) {
uni.showToast({ title: (e && e.message) || '接听失败', icon: 'none' })
}
try { uni.hideLoading() } catch (_) {}
},
hang() {
store.endCall(true)
uni.navigateBack()
}
}
}
</script>
<style scoped>
.call { height: 100vh; background: #0f172a; color: #fff; display: flex; flex-direction: column; }
.stage { flex: 1; position: relative; }
.wv { position: absolute; inset: 0; }
.wait { height: 100%; display: flex; flex-direction: column; align-items: center; justify-content: center; }
.av { width: 160rpx; height: 160rpx; border-radius: 50%; background: #2563eb; display: flex; align-items: center; justify-content: center; font-size: 64rpx; font-weight: 700; }
.name { margin-top: 24rpx; font-size: 36rpx; font-weight: 700; }
.st { margin-top: 8rpx; color: rgba(255,255,255,0.65); }
.acts { display: flex; justify-content: center; gap: 32rpx; padding: 32rpx 24rpx calc(32rpx + env(safe-area-inset-bottom)); }
.btn { min-width: 200rpx; height: 80rpx; border-radius: 999rpx; display: flex; align-items: center; justify-content: center; font-size: 28rpx; font-weight: 600; }
.ok { background: #006c49; }
.no { background: #ba1a1a; }
</style>