更新
This commit is contained in:
@@ -10,6 +10,7 @@ import {
|
||||
} from '@trtc/calls-uikit-vue'
|
||||
|
||||
import App from './App.vue'
|
||||
import { assertCallPolicy, installAppointmentCallGuard } from './appointment-call-guard'
|
||||
import './style.css'
|
||||
|
||||
type CallPhase = 'ready' | 'starting' | 'dialing' | 'connected' | 'ended' | 'error'
|
||||
@@ -20,6 +21,8 @@ type TranscriptionState = 'idle' | 'starting' | 'recording' | 'stopping' | 'erro
|
||||
type LocalRecordingState = 'idle' | 'starting' | 'recording' | 'stopping' | 'uploading' | 'error'
|
||||
|
||||
interface NormalizedCallConfig {
|
||||
appointmentId: number
|
||||
policy: Record<string, unknown>
|
||||
SDKAppID: number
|
||||
userID: string
|
||||
userSig: string
|
||||
@@ -77,6 +80,7 @@ interface BridgeMessage {
|
||||
event:
|
||||
| 'ready'
|
||||
| 'call-start-request'
|
||||
| 'call-policy-request'
|
||||
| 'status'
|
||||
| 'room'
|
||||
| 'hangup'
|
||||
@@ -86,6 +90,8 @@ interface BridgeMessage {
|
||||
| 'transcription-segment'
|
||||
| 'transcription-stop'
|
||||
diagnosisId?: number | string
|
||||
callType?: number
|
||||
requestId?: string
|
||||
status?: string
|
||||
roomId?: string
|
||||
message?: string
|
||||
@@ -179,6 +185,13 @@ const messages = ref<UiChatMessage[]>([])
|
||||
const chatReady = ref(false)
|
||||
const chatBusy = ref(false)
|
||||
const notice = ref('')
|
||||
const canVideoCall = ref(false)
|
||||
const callDisabledReason = ref('正在确认本次挂号通话权限')
|
||||
const registerCallGuard = installAppointmentCallGuard(TUICallKitAPI)
|
||||
let releaseCallGuard: (() => void) | undefined
|
||||
let contextGeneration = 0
|
||||
let policySequence = 0
|
||||
const pendingCallPolicies = new Map<string, (policy: Record<string, unknown>) => void>()
|
||||
const hasMoreMessages = ref(false)
|
||||
const transcriptionState = ref<TranscriptionState>('idle')
|
||||
const localRecordingState = ref<LocalRecordingState>('idle')
|
||||
@@ -381,6 +394,13 @@ function normalizeConfig(config: DoctorCallConfig): NormalizedCallConfig {
|
||||
: '患者'
|
||||
return {
|
||||
SDKAppID,
|
||||
appointmentId: Number(config.appointmentId || 0),
|
||||
policy: {
|
||||
can_video_call: config.can_video_call,
|
||||
can_audio_call: config.can_audio_call,
|
||||
appointment_type: config.appointment_type,
|
||||
call_disabled_reason: config.call_disabled_reason,
|
||||
},
|
||||
userID: cleanString(config.userID ?? config.userId, '医生用户ID'),
|
||||
userSig: cleanString(config.userSig, '用户签名'),
|
||||
targetUserId: cleanString(config.targetUserId ?? config.patientUserId, '患者用户ID'),
|
||||
@@ -1740,13 +1760,42 @@ TUICallKitAPI.setCallback({
|
||||
TUICallKitAPI.setLanguage('zh-cn')
|
||||
TUICallKitAPI.enableFloatWindow(false)
|
||||
|
||||
function requestHostCallStart(): Promise<boolean> {
|
||||
function syncCallPolicy(policy: Record<string, unknown>): void {
|
||||
canVideoCall.value = policy.can_video_call === true
|
||||
callDisabledReason.value = policy.appointment_type === 'text'
|
||||
? '图文问诊,不支持音视频通话'
|
||||
: String(policy.call_disabled_reason || (canVideoCall.value ? '' : '本次挂号不支持视频通话'))
|
||||
}
|
||||
|
||||
function requestCallPolicy(): Promise<Record<string, unknown>> {
|
||||
if (!window.qtVideoBridge?.notify) return Promise.reject(new Error('无法确认本次挂号通话权限'))
|
||||
const requestId = `${contextGeneration}:${++policySequence}`
|
||||
return new Promise((resolve) => {
|
||||
pendingCallPolicies.set(requestId, resolve)
|
||||
emit({ source: 'doctor-call', event: 'call-policy-request', requestId, diagnosisId: activeConfig?.diagnosisId })
|
||||
window.setTimeout(() => {
|
||||
const pending = pendingCallPolicies.get(requestId)
|
||||
if (!pending) return
|
||||
pendingCallPolicies.delete(requestId)
|
||||
pending({ call_disabled_reason: '通话权限确认超时,请重试' })
|
||||
}, 15000)
|
||||
})
|
||||
}
|
||||
|
||||
function callPolicyResult(requestId: string, policy: Record<string, unknown>): void {
|
||||
const resolve = pendingCallPolicies.get(requestId)
|
||||
if (!resolve) return
|
||||
pendingCallPolicies.delete(requestId)
|
||||
resolve(policy)
|
||||
}
|
||||
|
||||
function requestHostCallStart(callType = 2): Promise<boolean> {
|
||||
if (!activeConfig) return Promise.resolve(false)
|
||||
if (!window.qtVideoBridge?.notify) return Promise.resolve(true)
|
||||
if (!window.qtVideoBridge?.notify) return Promise.resolve(false)
|
||||
return new Promise<boolean>((resolve) => {
|
||||
const currentResolver = resolve
|
||||
resolveHostCallReady = currentResolver
|
||||
emit({ source: 'doctor-call', event: 'call-start-request', diagnosisId: activeConfig?.diagnosisId })
|
||||
emit({ source: 'doctor-call', event: 'call-start-request', diagnosisId: activeConfig?.diagnosisId, callType })
|
||||
window.setTimeout(() => {
|
||||
if (resolveHostCallReady !== currentResolver) return
|
||||
resolveHostCallReady = null
|
||||
@@ -1765,9 +1814,12 @@ function hostCallReady(ok: boolean, message = ''): void {
|
||||
async function startVideo(): Promise<void> {
|
||||
if (!activeConfig) throw new Error('问诊配置尚未准备好')
|
||||
if (starting || !endNotified) throw new Error('已有视频通话正在进行')
|
||||
const config = activeConfig
|
||||
const generation = contextGeneration
|
||||
starting = true
|
||||
try {
|
||||
if (hangupNotification) await hangupNotification
|
||||
if (activeConfig !== config || generation !== contextGeneration) throw new Error('当前问诊已切换')
|
||||
if (!activeConfig || !endNotified) throw new Error('已有视频通话正在进行')
|
||||
hangupNotification = null
|
||||
callCycleGeneration += 1
|
||||
@@ -1782,8 +1834,6 @@ async function startVideo(): Promise<void> {
|
||||
localRecordingSessionId = ''
|
||||
localRecordingMimeType = ''
|
||||
notice.value = ''
|
||||
const allowed = await requestHostCallStart()
|
||||
if (!allowed) throw new Error(notice.value || '服务器未能创建视频通话记录')
|
||||
await TUICallKitAPI.init({
|
||||
SDKAppID: activeConfig.SDKAppID,
|
||||
userID: activeConfig.userID,
|
||||
@@ -1791,6 +1841,7 @@ async function startVideo(): Promise<void> {
|
||||
...(chat ? { tim: chat, isFromChat: true } : {}),
|
||||
})
|
||||
await nextTick()
|
||||
if (activeConfig !== config || generation !== contextGeneration) throw new Error('当前问诊已切换')
|
||||
phase.value = 'dialing'
|
||||
statusText.value = '正在呼叫患者'
|
||||
appendVideoCallStatus('dialing', '正在呼叫患者')
|
||||
@@ -1807,11 +1858,12 @@ async function startVideo(): Promise<void> {
|
||||
emit({ source: 'doctor-call', event: 'status', diagnosisId: activeConfig.diagnosisId, status: 'dialing' })
|
||||
} catch (error) {
|
||||
const message = safeErrorMessage(error, '无法发起视频通话')
|
||||
if (activeConfig !== config || generation !== contextGeneration) throw new Error(message)
|
||||
phase.value = 'error'
|
||||
statusText.value = message
|
||||
appendVideoCallStatus('failed', `视频通话发起失败:${message}`)
|
||||
endNotified = true
|
||||
emit({ source: 'doctor-call', event: 'error', diagnosisId: activeConfig.diagnosisId, message })
|
||||
emit({ source: 'doctor-call', event: 'error', diagnosisId: activeConfig?.diagnosisId, message })
|
||||
throw new Error(message)
|
||||
} finally {
|
||||
starting = false
|
||||
@@ -1867,6 +1919,31 @@ function roomBindingResult(roomId: string, ok: boolean, message: string): void {
|
||||
async function open(config: DoctorCallConfig): Promise<void> {
|
||||
if (activeConfig) await close()
|
||||
activeConfig = normalizeConfig(config)
|
||||
const current = activeConfig
|
||||
const generation = ++contextGeneration
|
||||
syncCallPolicy(current.policy)
|
||||
releaseCallGuard = registerCallGuard(async (type) => {
|
||||
const assertCurrent = () => {
|
||||
if (activeConfig !== current || generation !== contextGeneration) {
|
||||
throw new Error('当前问诊已切换,请重新发起通话')
|
||||
}
|
||||
if (document.visibilityState === 'hidden') throw new Error('请先返回本次挂号的聊天窗口')
|
||||
}
|
||||
const refresh = async () => {
|
||||
assertCurrent()
|
||||
const policy = await requestCallPolicy()
|
||||
assertCurrent()
|
||||
syncCallPolicy(policy)
|
||||
if (policy.appointmentId !== current.appointmentId) throw new Error('本次挂号已变更,请重新打开聊天窗口')
|
||||
assertCallPolicy(policy, type)
|
||||
}
|
||||
await refresh()
|
||||
if (!await requestHostCallStart(type)) throw new Error(notice.value || '服务器未能创建通话记录')
|
||||
assertCurrent()
|
||||
// Refresh again after asynchronous record creation, immediately before SDK dispatch.
|
||||
await refresh()
|
||||
assertCurrent()
|
||||
})
|
||||
mode.value = activeConfig.mode
|
||||
patientName.value = activeConfig.patientName
|
||||
patientCase.value = activeConfig.patientCase
|
||||
@@ -1908,6 +1985,13 @@ async function open(config: DoctorCallConfig): Promise<void> {
|
||||
}
|
||||
|
||||
async function close(): Promise<void> {
|
||||
releaseCallGuard?.()
|
||||
releaseCallGuard = undefined
|
||||
contextGeneration += 1
|
||||
for (const resolve of pendingCallPolicies.values()) resolve({})
|
||||
pendingCallPolicies.clear()
|
||||
resolveHostCallReady?.(false)
|
||||
resolveHostCallReady = null
|
||||
if (!endNotified) await hangup()
|
||||
if (hangupNotification) await hangupNotification
|
||||
unsubscribeTranscriber()
|
||||
@@ -1933,6 +2017,7 @@ window.doctorConsultation = {
|
||||
startVideo,
|
||||
hangup,
|
||||
hostCallReady,
|
||||
callPolicyResult,
|
||||
recordingResult,
|
||||
roomBindingResult,
|
||||
screenshotResult,
|
||||
@@ -1951,6 +2036,8 @@ createApp(App, {
|
||||
chatReady: readonly(chatReady),
|
||||
chatBusy: readonly(chatBusy),
|
||||
notice: readonly(notice),
|
||||
canVideoCall: readonly(canVideoCall),
|
||||
callDisabledReason: readonly(callDisabledReason),
|
||||
hasMoreMessages: readonly(hasMoreMessages),
|
||||
transcriptionState: readonly(transcriptionState),
|
||||
localRecordingState: readonly(localRecordingState),
|
||||
|
||||
Reference in New Issue
Block a user