运营数据
This commit is contained in:
@@ -166,7 +166,8 @@ import { minimizeCloudLawMeeting } from '@/utils/cloudLawMiniProgram'
|
|||||||
import {
|
import {
|
||||||
notifyCloudLawBackendEnd,
|
notifyCloudLawBackendEnd,
|
||||||
notifyCloudLawParentCallEnded,
|
notifyCloudLawParentCallEnded,
|
||||||
isCloudLawPcIframe
|
isCloudLawPcIframe,
|
||||||
|
notifyNativeMiniProgramHangup
|
||||||
} from '@/utils/cloudLawVoiceCall'
|
} from '@/utils/cloudLawVoiceCall'
|
||||||
import bgImage from '@/static/bg.png'
|
import bgImage from '@/static/bg.png'
|
||||||
import hangupIcon from '@/static/cacel.png'
|
import hangupIcon from '@/static/cacel.png'
|
||||||
@@ -1017,6 +1018,10 @@ const hangup = async () => {
|
|||||||
userInitiatedHangup.value = true
|
userInitiatedHangup.value = true
|
||||||
leaving.value = true
|
leaving.value = true
|
||||||
stopCommandPolling()
|
stopCommandPolling()
|
||||||
|
if (!isLawRole.value) {
|
||||||
|
// 优先触发小程序原生 /end(web-view 跨域直连云律 API 不可靠)
|
||||||
|
notifyNativeMiniProgramHangup()
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
// 先通知云律后端结束本次通话:
|
// 先通知云律后端结束本次通话:
|
||||||
// - 用户端:/api/applet/voiceConnection/{callId}/end
|
// - 用户端:/api/applet/voiceConnection/{callId}/end
|
||||||
|
|||||||
@@ -242,7 +242,8 @@ import {
|
|||||||
isCloudLawLawRoleInMeeting,
|
isCloudLawLawRoleInMeeting,
|
||||||
isCloudLawPcIframe,
|
isCloudLawPcIframe,
|
||||||
notifyCloudLawBackendEnd,
|
notifyCloudLawBackendEnd,
|
||||||
notifyCloudLawParentCallEnded
|
notifyCloudLawParentCallEnded,
|
||||||
|
queryCloudLawCallStatus
|
||||||
} from '@/utils/cloudLawVoiceCall'
|
} from '@/utils/cloudLawVoiceCall'
|
||||||
|
|
||||||
const meetingStore = useMeetingStore()
|
const meetingStore = useMeetingStore()
|
||||||
@@ -843,6 +844,82 @@ const syncViewportHeight = () => {
|
|||||||
// #endif
|
// #endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let cloudLawStatusPollTimer = null
|
||||||
|
let cloudLawParticipantStopWatch = null
|
||||||
|
let cloudLawSoloCheckTimer = null
|
||||||
|
let cloudLawConsultEnding = false
|
||||||
|
const hadRemoteParticipant = ref(false)
|
||||||
|
|
||||||
|
const getMyUserId = () => userStore.userInfo?.id || userStore.userInfo?.user_id
|
||||||
|
|
||||||
|
const countRemoteActiveParticipants = () => {
|
||||||
|
const myUid = getMyUserId()
|
||||||
|
return meetingStore.activeParticipants.filter(p => String(p.user_id) !== String(myUid)).length
|
||||||
|
}
|
||||||
|
|
||||||
|
const stopCloudLawStatusPoll = () => {
|
||||||
|
if (cloudLawStatusPollTimer) {
|
||||||
|
clearInterval(cloudLawStatusPollTimer)
|
||||||
|
cloudLawStatusPollTimer = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleCloudLawConsultEnded = async (tip = '用户已退出咨询') => {
|
||||||
|
if (cloudLawConsultEnding || !isCloudLawLawRoleInMeeting(cloudLawRole.value)) return
|
||||||
|
cloudLawConsultEnding = true
|
||||||
|
stopCloudLawStatusPoll()
|
||||||
|
if (cloudLawSoloCheckTimer) {
|
||||||
|
clearTimeout(cloudLawSoloCheckTimer)
|
||||||
|
cloudLawSoloCheckTimer = null
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
if (cloudLawCallId.value) {
|
||||||
|
await notifyCloudLawBackendEnd(cloudLawCallId.value, 'law', cloudLawApiBase.value)
|
||||||
|
notifyCloudLawParentCallEnded(cloudLawCallId.value)
|
||||||
|
}
|
||||||
|
if (meetingStore.isInMeeting) {
|
||||||
|
await meetingStore.leave().catch(() => {})
|
||||||
|
}
|
||||||
|
uni.showToast({ title: tip, icon: 'none' })
|
||||||
|
if (!isCloudLawPcIframe()) {
|
||||||
|
setTimeout(() => redirectHome(true), 800)
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
cloudLawConsultEnding = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const pollCloudLawCallStatusOnce = async () => {
|
||||||
|
if (!isCloudLawLawRoleInMeeting(cloudLawRole.value) || !cloudLawCallId.value || cloudLawConsultEnding) return
|
||||||
|
if (meetingStore.localState === MEETING_LOCAL_STATE_ENDED || meetingStore.localState === MEETING_LOCAL_STATE_LEAVING) return
|
||||||
|
const data = await queryCloudLawCallStatus(cloudLawCallId.value, 'law', cloudLawApiBase.value)
|
||||||
|
const status = String(data?.status || data?.callStatus || '').toLowerCase()
|
||||||
|
if (['ended', 'cancelled', 'failed'].includes(status)) {
|
||||||
|
await handleCloudLawConsultEnded('咨询已结束')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const startCloudLawStatusPoll = () => {
|
||||||
|
if (!isCloudLawLawRoleInMeeting(cloudLawRole.value) || !cloudLawCallId.value) return
|
||||||
|
stopCloudLawStatusPoll()
|
||||||
|
pollCloudLawCallStatusOnce()
|
||||||
|
cloudLawStatusPollTimer = setInterval(pollCloudLawCallStatusOnce, 1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
const scheduleCloudLawSoloCheck = () => {
|
||||||
|
if (cloudLawSoloCheckTimer) {
|
||||||
|
clearTimeout(cloudLawSoloCheckTimer)
|
||||||
|
}
|
||||||
|
cloudLawSoloCheckTimer = setTimeout(async () => {
|
||||||
|
cloudLawSoloCheckTimer = null
|
||||||
|
if (!hadRemoteParticipant.value || cloudLawConsultEnding) return
|
||||||
|
if (meetingStore.localState !== MEETING_LOCAL_STATE_CONNECTED) return
|
||||||
|
if (countRemoteActiveParticipants() === 0) {
|
||||||
|
await handleCloudLawConsultEnded('用户已退出咨询')
|
||||||
|
}
|
||||||
|
}, 500)
|
||||||
|
}
|
||||||
|
|
||||||
// ============ 生命周期 ============
|
// ============ 生命周期 ============
|
||||||
|
|
||||||
let stateStopWatch = null
|
let stateStopWatch = null
|
||||||
@@ -919,6 +996,25 @@ onMounted(() => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 云律律师端:轮询通话状态 + 对方离会后自动收尾(用户挂断不一定能推到 room.ended)
|
||||||
|
if (isCloudLawLawRoleInMeeting(cloudLawRole.value)) {
|
||||||
|
startCloudLawStatusPoll()
|
||||||
|
cloudLawParticipantStopWatch = watch(
|
||||||
|
() => meetingStore.activeParticipants,
|
||||||
|
() => {
|
||||||
|
const remoteCount = countRemoteActiveParticipants()
|
||||||
|
if (remoteCount > 0) {
|
||||||
|
hadRemoteParticipant.value = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (hadRemoteParticipant.value && remoteCount === 0) {
|
||||||
|
scheduleCloudLawSoloCheck()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ deep: true }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// #ifdef H5
|
// #ifdef H5
|
||||||
if (showCloudLawBack.value) {
|
if (showCloudLawBack.value) {
|
||||||
try {
|
try {
|
||||||
@@ -941,6 +1037,15 @@ onBeforeUnmount(() => {
|
|||||||
}
|
}
|
||||||
if (viewportCleanup) { viewportCleanup(); viewportCleanup = null }
|
if (viewportCleanup) { viewportCleanup(); viewportCleanup = null }
|
||||||
if (stateStopWatch) { stateStopWatch(); stateStopWatch = null }
|
if (stateStopWatch) { stateStopWatch(); stateStopWatch = null }
|
||||||
|
stopCloudLawStatusPoll()
|
||||||
|
if (cloudLawParticipantStopWatch) {
|
||||||
|
cloudLawParticipantStopWatch()
|
||||||
|
cloudLawParticipantStopWatch = null
|
||||||
|
}
|
||||||
|
if (cloudLawSoloCheckTimer) {
|
||||||
|
clearTimeout(cloudLawSoloCheckTimer)
|
||||||
|
cloudLawSoloCheckTimer = null
|
||||||
|
}
|
||||||
// #ifdef H5
|
// #ifdef H5
|
||||||
if (popstateHandler) {
|
if (popstateHandler) {
|
||||||
window.removeEventListener('popstate', popstateHandler)
|
window.removeEventListener('popstate', popstateHandler)
|
||||||
|
|||||||
@@ -70,3 +70,54 @@ export function isCloudLawLawRoleInMeeting(role) {
|
|||||||
export function isCloudLawPcIframe() {
|
export function isCloudLawPcIframe() {
|
||||||
return typeof window !== 'undefined' && !!(window.parent && window.parent !== window)
|
return typeof window !== 'undefined' && !!(window.parent && window.parent !== window)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 查询云律通话状态(律师端 /api/law/voiceCall,用户端 /api/applet/voiceConnection) */
|
||||||
|
export function queryCloudLawCallStatus(callId, role = 'law', apiBase = '', token = '') {
|
||||||
|
if (!callId) return Promise.resolve(null)
|
||||||
|
const endBase = String(role).toLowerCase() === 'law' ? '/api/law/voiceCall' : '/api/applet/voiceConnection'
|
||||||
|
const url = resolveCloudLawApiUrl(`${endBase}/${encodeURIComponent(callId)}/status`, apiBase)
|
||||||
|
const header = { 'Content-Type': 'application/json' }
|
||||||
|
if (token) {
|
||||||
|
header.token = token.startsWith('Bearer') ? token : `Bearer ${token}`
|
||||||
|
}
|
||||||
|
return new Promise(resolve => {
|
||||||
|
uni.request({
|
||||||
|
url,
|
||||||
|
method: 'GET',
|
||||||
|
header,
|
||||||
|
timeout: 8000,
|
||||||
|
success: (res) => {
|
||||||
|
const body = res?.data || {}
|
||||||
|
const payload = body.data !== undefined ? body.data : body
|
||||||
|
resolve(payload && typeof payload === 'object' ? payload : null)
|
||||||
|
},
|
||||||
|
fail: () => resolve(null)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const CLOUD_LAW_HANGUP_QUERY = 'cloudLawHangup'
|
||||||
|
|
||||||
|
/** 小程序 web-view 内挂断:改 URL 触发原生页 @load,立即调 /end(不依赖 web-view 销毁) */
|
||||||
|
export function notifyNativeMiniProgramHangup() {
|
||||||
|
// #ifdef H5
|
||||||
|
if (typeof window === 'undefined') return false
|
||||||
|
try {
|
||||||
|
const mp = window.wx && window.wx.miniProgram
|
||||||
|
if (!mp) return false
|
||||||
|
const href = String(window.location.href || '')
|
||||||
|
if (href.includes(`${CLOUD_LAW_HANGUP_QUERY}=1`)) return true
|
||||||
|
const url = new URL(href)
|
||||||
|
url.searchParams.set(CLOUD_LAW_HANGUP_QUERY, '1')
|
||||||
|
window.location.replace(url.toString())
|
||||||
|
return true
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('[cloudLawVoiceCall] native hangup bridge failed', e)
|
||||||
|
}
|
||||||
|
// #endif
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isCloudLawHangupBridgeUrl(url) {
|
||||||
|
return String(url || '').includes(`${CLOUD_LAW_HANGUP_QUERY}=1`)
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user