更新
This commit is contained in:
@@ -84,8 +84,8 @@
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<div v-if="hasIncomingCall || callConnected" id="call-container" class="call-container">
|
||||
<TUICallKit />
|
||||
<div v-if="hasIncomingCall || callConnected" id="call-container" class="call-container phone-call-shell">
|
||||
<TUICallKit class="TUICallKit-mobile" />
|
||||
</div>
|
||||
|
||||
<div v-else-if="initialized" class="waiting-container">
|
||||
@@ -98,18 +98,30 @@
|
||||
</el-empty>
|
||||
</div>
|
||||
|
||||
<!-- 隐藏的 TUICallKit,用于接收来电信号 -->
|
||||
<div v-show="false">
|
||||
<TUICallKit v-if="initialized && !hasIncomingCall && !callConnected" />
|
||||
<!-- 离屏挂载:手机视口尺寸,避免 display:none 宽高为 0;与小程序端比例接近 -->
|
||||
<div
|
||||
v-if="initialized && !hasIncomingCall && !callConnected"
|
||||
class="phone-call-kit-host phone-call-kit-host--offscreen"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<TUICallKit class="TUICallKit-mobile" />
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onUnmounted, computed, onMounted } from 'vue'
|
||||
import { ref, onUnmounted, computed, onMounted, nextTick } from 'vue'
|
||||
import { Phone } from '@element-plus/icons-vue'
|
||||
import { TUICallKitServer, TUICallKit, STATUS } from '@tencentcloud/call-uikit-vue'
|
||||
import {
|
||||
TUICallKitServer,
|
||||
TUICallKit,
|
||||
STATUS,
|
||||
TUIStore,
|
||||
StoreName,
|
||||
NAME,
|
||||
CallRole
|
||||
} from '@tencentcloud/call-uikit-vue'
|
||||
import { ElMessage, ElNotification } from 'element-plus'
|
||||
import request from '@/utils/request'
|
||||
import useUserStore from '@/stores/modules/user'
|
||||
@@ -128,22 +140,109 @@ const hasIncomingCall = ref(false)
|
||||
const callConnected = ref(false) // 添加通话连接状态
|
||||
const incomingCallInfo = ref<any>(null)
|
||||
const statusCheckTimer = ref<any>(null)
|
||||
/** 从 TUIStore 同步:解决「未打开本页时来电,进入后 statusChanged 不触发」的问题 */
|
||||
let storeUnwatch: (() => void) | null = null
|
||||
|
||||
// 页面加载时自动初始化
|
||||
onMounted(() => {
|
||||
initPatient()
|
||||
})
|
||||
|
||||
// 定时检查状态(作为备用方案)
|
||||
/**
|
||||
* 与 SDK 内 CallStatus 一致:idle / calling / connected
|
||||
* statusChanged 只在状态「变化」时回调;晚进页面时可能已在 calling,需读 Store。
|
||||
*/
|
||||
function syncIncomingFromTUIStore(from: string) {
|
||||
try {
|
||||
const callStatus = TUIStore.getData(StoreName.CALL, NAME.CALL_STATUS) as string
|
||||
const callRole = TUIStore.getData(StoreName.CALL, NAME.CALL_ROLE) as string
|
||||
const isGroup = !!TUIStore.getData(StoreName.CALL, NAME.IS_GROUP)
|
||||
|
||||
const wasIncoming = hasIncomingCall.value
|
||||
|
||||
// 被叫振铃:calling + callee
|
||||
if (callStatus === 'calling' && callRole === CallRole.CALLEE) {
|
||||
hasIncomingCall.value = true
|
||||
incomingCallInfo.value = { isGroup, from }
|
||||
if (!wasIncoming) {
|
||||
ElMessage.info('检测到待接听的来电,请点击接听')
|
||||
ElNotification({
|
||||
title: '来电提醒',
|
||||
message: '有待接听的视频通话',
|
||||
type: 'warning',
|
||||
duration: 0,
|
||||
position: 'top-right'
|
||||
})
|
||||
}
|
||||
callConnected.value = false
|
||||
return
|
||||
}
|
||||
|
||||
// 已接通
|
||||
if (callStatus === 'connected') {
|
||||
hasIncomingCall.value = false
|
||||
incomingCallInfo.value = null
|
||||
callConnected.value = true
|
||||
return
|
||||
}
|
||||
|
||||
// 空闲
|
||||
if (callStatus === 'idle') {
|
||||
if (hasIncomingCall.value || callConnected.value) {
|
||||
hasIncomingCall.value = false
|
||||
callConnected.value = false
|
||||
incomingCallInfo.value = null
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('syncIncomingFromTUIStore:', from, e)
|
||||
}
|
||||
}
|
||||
|
||||
function onStoreCallField() {
|
||||
syncIncomingFromTUIStore('watch')
|
||||
}
|
||||
|
||||
function bindTUIStoreWatch() {
|
||||
if (storeUnwatch) {
|
||||
storeUnwatch()
|
||||
storeUnwatch = null
|
||||
}
|
||||
const opts = {
|
||||
[NAME.CALL_STATUS]: onStoreCallField,
|
||||
[NAME.CALL_ROLE]: onStoreCallField
|
||||
}
|
||||
try {
|
||||
TUIStore.watch(StoreName.CALL, opts)
|
||||
storeUnwatch = () => {
|
||||
try {
|
||||
TUIStore.unwatch(StoreName.CALL, opts)
|
||||
} catch {
|
||||
//
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('TUIStore.watch 失败:', e)
|
||||
}
|
||||
}
|
||||
|
||||
// 定时从 Store 拉状态(TIM 同步可能有延迟,前 45 秒加密轮询)
|
||||
const startStatusCheck = () => {
|
||||
if (statusCheckTimer.value) {
|
||||
clearInterval(statusCheckTimer.value)
|
||||
}
|
||||
|
||||
let ticks = 0
|
||||
const maxTicks = 90
|
||||
statusCheckTimer.value = setInterval(() => {
|
||||
// 这里可以添加状态检查逻辑
|
||||
console.log('定时检查 - 当前时间:', new Date().toLocaleTimeString())
|
||||
}, 5000)
|
||||
ticks += 1
|
||||
syncIncomingFromTUIStore('poll')
|
||||
if (ticks >= maxTicks) {
|
||||
if (statusCheckTimer.value) {
|
||||
clearInterval(statusCheckTimer.value)
|
||||
statusCheckTimer.value = null
|
||||
}
|
||||
}
|
||||
}, 500)
|
||||
}
|
||||
|
||||
// 监听状态变化
|
||||
@@ -267,10 +366,18 @@ const initPatient = async () => {
|
||||
|
||||
ElMessage.info('等待初始化完成...')
|
||||
|
||||
// 等待初始化完成
|
||||
// 等待 TIM/引擎就绪(未在本页时收到的邀请会在登录后由 SDK 同步)
|
||||
await new Promise(resolve => setTimeout(resolve, 2000))
|
||||
|
||||
initialized.value = true
|
||||
await nextTick()
|
||||
await new Promise((r) => setTimeout(r, 300))
|
||||
|
||||
// 需在 TUICallKit 挂载后订阅 Store(见模板里 v-if="initialized && ..." 的隐藏组件)
|
||||
bindTUIStoreWatch()
|
||||
syncIncomingFromTUIStore('post-init')
|
||||
startStatusCheck()
|
||||
|
||||
ElMessage.success('医疗助理端初始化成功,等待来电...')
|
||||
|
||||
console.log('=== 医疗助理端初始化完成 ===')
|
||||
@@ -278,9 +385,6 @@ const initPatient = async () => {
|
||||
console.log('等待来电中...')
|
||||
console.log('请在医生端发起群组通话,目标用户ID应该是:', userId)
|
||||
|
||||
// 启动状态检查
|
||||
startStatusCheck()
|
||||
|
||||
} catch (error: any) {
|
||||
console.error('=== 初始化失败 ===', error)
|
||||
ElMessage.error(error.message || '初始化失败')
|
||||
@@ -289,6 +393,29 @@ const initPatient = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
/** 接听后自动关闭本地麦克风(助理端只听不说) */
|
||||
async function muteLocalMicAfterAccept(): Promise<boolean> {
|
||||
await nextTick()
|
||||
await new Promise((r) => setTimeout(r, 400))
|
||||
try {
|
||||
const server = TUICallKitServer as typeof TUICallKitServer & {
|
||||
closeMicrophone?: () => Promise<void>
|
||||
}
|
||||
if (typeof server.closeMicrophone === 'function') {
|
||||
await server.closeMicrophone()
|
||||
return true
|
||||
}
|
||||
const engine = server.getTUICallEngineInstance?.()
|
||||
if (engine && typeof engine.closeMicrophone === 'function') {
|
||||
await engine.closeMicrophone()
|
||||
return true
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('接听后关闭麦克风失败(可在通话条手动关麦):', e)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// 接听来电
|
||||
const acceptCall = async () => {
|
||||
try {
|
||||
@@ -297,6 +424,10 @@ const acceptCall = async () => {
|
||||
ElMessage.success('已接听通话')
|
||||
hasIncomingCall.value = false
|
||||
callConnected.value = true // 设置通话连接状态
|
||||
const muted = await muteLocalMicAfterAccept()
|
||||
if (muted) {
|
||||
ElMessage.info('已关闭本地麦克风')
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error('接听失败:', error)
|
||||
ElMessage.error(error.message || '接听失败')
|
||||
@@ -329,6 +460,11 @@ const reset = () => {
|
||||
onUnmounted(() => {
|
||||
if (statusCheckTimer.value) {
|
||||
clearInterval(statusCheckTimer.value)
|
||||
statusCheckTimer.value = null
|
||||
}
|
||||
if (storeUnwatch) {
|
||||
storeUnwatch()
|
||||
storeUnwatch = null
|
||||
}
|
||||
|
||||
if (initialized.value) {
|
||||
@@ -370,12 +506,23 @@ onUnmounted(() => {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
/* 手机视口:375×667(逻辑像素),圆角边框模拟机身 */
|
||||
.phone-call-shell {
|
||||
width: 375px;
|
||||
height: 667px;
|
||||
max-width: 100%;
|
||||
margin: 20px auto 0;
|
||||
padding: 0;
|
||||
border-radius: 36px;
|
||||
border: 12px solid #2c2c2e;
|
||||
box-shadow:
|
||||
0 0 0 2px #3a3a3c inset,
|
||||
0 16px 48px rgba(0, 0, 0, 0.22);
|
||||
background: #000;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.call-container {
|
||||
width: 100%;
|
||||
height: 500px;
|
||||
margin-top: 20px;
|
||||
background: #f5f5f5;
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
justify-content: stretch;
|
||||
@@ -391,7 +538,7 @@ onUnmounted(() => {
|
||||
|
||||
.waiting-container {
|
||||
width: 100%;
|
||||
min-height: 500px;
|
||||
min-height: 360px;
|
||||
margin-top: 20px;
|
||||
background: #f5f5f5;
|
||||
border-radius: 8px;
|
||||
@@ -400,11 +547,29 @@ onUnmounted(() => {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
/* 离屏监听:保持真实手机宽高,利于 SDK 内部布局 */
|
||||
.phone-call-kit-host {
|
||||
width: 375px;
|
||||
height: 667px;
|
||||
overflow: hidden;
|
||||
border-radius: 28px;
|
||||
background: #000;
|
||||
}
|
||||
|
||||
.phone-call-kit-host--offscreen {
|
||||
position: fixed;
|
||||
left: -9999px;
|
||||
top: 0;
|
||||
z-index: -1;
|
||||
pointer-events: none;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
/* 确保 TUICallKit 组件正确显示 */
|
||||
:deep(.call-container) {
|
||||
.tui-call-kit {
|
||||
width: 100%;
|
||||
height: 500px;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
video {
|
||||
@@ -434,7 +599,7 @@ onUnmounted(() => {
|
||||
:deep(.tui-call-kit-window) {
|
||||
width: 100% !important;
|
||||
height: 100% !important;
|
||||
min-height: 500px !important;
|
||||
min-height: 0 !important;
|
||||
}
|
||||
|
||||
/* 确保视频容器撑满 */
|
||||
|
||||
Reference in New Issue
Block a user