运营数据

This commit is contained in:
duoaohui
2026-06-06 11:39:15 +08:00
parent 9e9573a35d
commit 3b0f1f84ff
3 changed files with 163 additions and 2 deletions

View File

@@ -166,7 +166,8 @@ import { minimizeCloudLawMeeting } from '@/utils/cloudLawMiniProgram'
import {
notifyCloudLawBackendEnd,
notifyCloudLawParentCallEnded,
isCloudLawPcIframe
isCloudLawPcIframe,
notifyNativeMiniProgramHangup
} from '@/utils/cloudLawVoiceCall'
import bgImage from '@/static/bg.png'
import hangupIcon from '@/static/cacel.png'
@@ -1017,6 +1018,10 @@ const hangup = async () => {
userInitiatedHangup.value = true
leaving.value = true
stopCommandPolling()
if (!isLawRole.value) {
// 优先触发小程序原生 /endweb-view 跨域直连云律 API 不可靠)
notifyNativeMiniProgramHangup()
}
try {
// 先通知云律后端结束本次通话:
// - 用户端:/api/applet/voiceConnection/{callId}/end

View File

@@ -242,7 +242,8 @@ import {
isCloudLawLawRoleInMeeting,
isCloudLawPcIframe,
notifyCloudLawBackendEnd,
notifyCloudLawParentCallEnded
notifyCloudLawParentCallEnded,
queryCloudLawCallStatus
} from '@/utils/cloudLawVoiceCall'
const meetingStore = useMeetingStore()
@@ -843,6 +844,82 @@ const syncViewportHeight = () => {
// #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
@@ -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
if (showCloudLawBack.value) {
try {
@@ -941,6 +1037,15 @@ onBeforeUnmount(() => {
}
if (viewportCleanup) { viewportCleanup(); viewportCleanup = null }
if (stateStopWatch) { stateStopWatch(); stateStopWatch = null }
stopCloudLawStatusPoll()
if (cloudLawParticipantStopWatch) {
cloudLawParticipantStopWatch()
cloudLawParticipantStopWatch = null
}
if (cloudLawSoloCheckTimer) {
clearTimeout(cloudLawSoloCheckTimer)
cloudLawSoloCheckTimer = null
}
// #ifdef H5
if (popstateHandler) {
window.removeEventListener('popstate', popstateHandler)

View File

@@ -70,3 +70,54 @@ export function isCloudLawLawRoleInMeeting(role) {
export function isCloudLawPcIframe() {
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`)
}