运营数据

This commit is contained in:
duoaohui
2026-06-06 11:18:31 +08:00
parent 2b036f4aa5
commit 2410f5e2e9
4 changed files with 105 additions and 13 deletions

View File

@@ -163,6 +163,11 @@ import {
} from '@/constants/meeting' } from '@/constants/meeting'
import VideoTile from '@/components/meeting/VideoTile.vue' import VideoTile from '@/components/meeting/VideoTile.vue'
import { minimizeCloudLawMeeting } from '@/utils/cloudLawMiniProgram' import { minimizeCloudLawMeeting } from '@/utils/cloudLawMiniProgram'
import {
notifyCloudLawBackendEnd,
notifyCloudLawParentCallEnded,
isCloudLawPcIframe
} 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'
import voiceOnIcon from '@/static/vocie_calling_1.png' import voiceOnIcon from '@/static/vocie_calling_1.png'
@@ -473,17 +478,9 @@ const requestCloudLawCommands = (url) => {
} }
const notifyCloudLawEnd = () => { const notifyCloudLawEnd = () => {
if (!callId.value || !cloudLawApiBase.value) return Promise.resolve() if (!callId.value) return Promise.resolve()
return new Promise(resolve => { const role = isLawRole.value ? 'law' : 'caller'
uni.request({ return notifyCloudLawBackendEnd(callId.value, role, cloudLawApiBase.value, cloudLawToken.value)
url: resolveCloudLawApiUrl(`${voiceEndBase.value}/${encodeURIComponent(callId.value)}/end`),
method: 'POST',
data: {},
header: cloudLawToken.value ? { 'Content-Type': 'application/json', token: `Bearer ${cloudLawToken.value}` } : { 'Content-Type': 'application/json' },
timeout: 5000,
complete: resolve
})
})
} }
const buildMiniProgramReturnUrl = () => { const buildMiniProgramReturnUrl = () => {
@@ -1113,8 +1110,12 @@ onMounted(() => {
const label = MEETING_ENDED_REASON_LABEL?.[reason] || '视频咨询已结束' const label = MEETING_ENDED_REASON_LABEL?.[reason] || '视频咨询已结束'
uni.showToast({ title: label, icon: 'none' }) uni.showToast({ title: label, icon: 'none' })
ended.value = true ended.value = true
// 对方挂断或会议结束时,通知小程序跳转到「我的咨询详情」 // 对方挂断或会议结束时,通知云律后端 + 父页面PC 律师浮窗)+ 小程序
notifyCloudLawEnd().finally(() => { notifyCloudLawEnd().finally(() => {
if (isLawRole.value) {
notifyCloudLawParentCallEnded(callId.value)
if (isCloudLawPcIframe()) return
}
notifyMiniProgramCallEndedOnce() notifyMiniProgramCallEndedOnce()
scheduleEndedRedirect() scheduleEndedRedirect()
}) })

View File

@@ -126,6 +126,7 @@ const joinPassword = ref('')
const autoJoin = ref(false) const autoJoin = ref(false)
const cloudLawRole = ref('') const cloudLawRole = ref('')
const cloudLawCallId = ref('') const cloudLawCallId = ref('')
const cloudLawApiBase = ref('')
const initializing = ref(true) const initializing = ref(true)
// autoJoin 失败/超时的可见错误(替代无限"正在进入会议…"转圈) // autoJoin 失败/超时的可见错误(替代无限"正在进入会议…"转圈)
const autoJoinError = ref('') const autoJoinError = ref('')
@@ -472,7 +473,8 @@ const onJoin = async (options = {}) => {
const roomCode = meetingStore.currentRoom?.room_code const roomCode = meetingStore.currentRoom?.room_code
const roleQ = cloudLawRole.value ? `&cloudLawRole=${encodeURIComponent(cloudLawRole.value)}` : '' const roleQ = cloudLawRole.value ? `&cloudLawRole=${encodeURIComponent(cloudLawRole.value)}` : ''
const callQ = cloudLawCallId.value ? `&callId=${encodeURIComponent(cloudLawCallId.value)}` : '' const callQ = cloudLawCallId.value ? `&callId=${encodeURIComponent(cloudLawCallId.value)}` : ''
uni.redirectTo({ url: `/pages/meeting/room${roomCode ? `?code=${roomCode}${roleQ}${callQ}` : `${roleQ || callQ ? `?${(roleQ + callQ).replace(/^&/, '')}` : ''}`}` }) const apiBaseQ = cloudLawApiBase.value ? `&cloudLawApiBase=${encodeURIComponent(cloudLawApiBase.value)}` : ''
uni.redirectTo({ url: `/pages/meeting/room${roomCode ? `?code=${roomCode}${roleQ}${callQ}${apiBaseQ}` : `${roleQ || callQ || apiBaseQ ? `?${(roleQ + callQ + apiBaseQ).replace(/^&/, '')}` : ''}`}` })
} catch (err) { } catch (err) {
const msg = err?.message || JSON.stringify(err) const msg = err?.message || JSON.stringify(err)
// autoJoin 模式下把错误显式呈现在页面toast 一闪而过,真机很难看清) // autoJoin 模式下把错误显式呈现在页面toast 一闪而过,真机很难看清)
@@ -498,6 +500,7 @@ onLoad(async (query) => {
joinCode.value = query?.code || '' joinCode.value = query?.code || ''
cloudLawRole.value = query?.cloudLawRole || query?.cloud_law_role || '' cloudLawRole.value = query?.cloudLawRole || query?.cloud_law_role || ''
cloudLawCallId.value = query?.callId || query?.call_id || '' cloudLawCallId.value = query?.callId || query?.call_id || ''
cloudLawApiBase.value = query?.cloudLawApiBase || query?.cloud_law_api_base || ''
if (isCloudLaw.value) { if (isCloudLaw.value) {
try { try {
uni.setNavigationBarTitle({ title: '用户视频咨询' }) uni.setNavigationBarTitle({ title: '用户视频咨询' })

View File

@@ -238,6 +238,12 @@ import {
minimizeCloudLawMeeting, minimizeCloudLawMeeting,
notifyCloudLawCallEnded notifyCloudLawCallEnded
} from '@/utils/cloudLawMiniProgram' } from '@/utils/cloudLawMiniProgram'
import {
isCloudLawLawRoleInMeeting,
isCloudLawPcIframe,
notifyCloudLawBackendEnd,
notifyCloudLawParentCallEnded
} from '@/utils/cloudLawVoiceCall'
const meetingStore = useMeetingStore() const meetingStore = useMeetingStore()
const userStore = useUserStore() const userStore = useUserStore()
@@ -245,6 +251,7 @@ const userStore = useUserStore()
/** 云律律师端:工具栏用共享屏幕替换聊天 */ /** 云律律师端:工具栏用共享屏幕替换聊天 */
const cloudLawRole = ref('') const cloudLawRole = ref('')
const cloudLawCallId = ref('') const cloudLawCallId = ref('')
const cloudLawApiBase = ref('')
const minimizingToMiniProgram = ref(false) const minimizingToMiniProgram = ref(false)
const replaceChatWithScreenShare = computed(() => String(cloudLawRole.value).toLowerCase() === 'law') const replaceChatWithScreenShare = computed(() => String(cloudLawRole.value).toLowerCase() === 'law')
const showCloudLawBack = computed(() => isCloudLawEmbed() && isCloudLawLawRole(cloudLawRole.value)) const showCloudLawBack = computed(() => isCloudLawEmbed() && isCloudLawLawRole(cloudLawRole.value))
@@ -848,6 +855,7 @@ let redirectingToJoin = false
onLoad((query) => { onLoad((query) => {
cloudLawRole.value = query?.cloudLawRole || query?.cloud_law_role || '' cloudLawRole.value = query?.cloudLawRole || query?.cloud_law_role || ''
cloudLawCallId.value = query?.callId || query?.call_id || '' cloudLawCallId.value = query?.callId || query?.call_id || ''
cloudLawApiBase.value = query?.cloudLawApiBase || query?.cloud_law_api_base || ''
if (!cloudLawRole.value) { if (!cloudLawRole.value) {
// #ifdef H5 // #ifdef H5
try { try {
@@ -899,6 +907,14 @@ onMounted(() => {
const reason = meetingStore.lastEndedReason const reason = meetingStore.lastEndedReason
const label = MEETING_ENDED_REASON_LABEL?.[reason] || '会议已结束' const label = MEETING_ENDED_REASON_LABEL?.[reason] || '会议已结束'
uni.showToast({ title: label, icon: 'none' }) uni.showToast({ title: label, icon: 'none' })
// 云律律师端(含 PC iframe用户挂断后同步云律后端 + 通知父页面释放浮窗
if (isCloudLawLawRoleInMeeting(cloudLawRole.value) && cloudLawCallId.value) {
notifyCloudLawBackendEnd(cloudLawCallId.value, 'law', cloudLawApiBase.value)
notifyCloudLawParentCallEnded(cloudLawCallId.value)
if (isCloudLawPcIframe()) {
return
}
}
setTimeout(() => redirectHome(true), 1200) setTimeout(() => redirectHome(true), 1200)
} }
}) })

View File

@@ -0,0 +1,72 @@
/** 云律视频咨询iframe / web-view 内通知云律后端结束通话,并通知父页面(律师 PC 浮窗)释放状态 */
export function getCloudLawApiBase(explicitBase) {
const base = String(explicitBase || '').trim().replace(/\/+$/, '')
if (base) return base
if (typeof window !== 'undefined' && window.location && window.location.origin) {
return window.location.origin
}
return ''
}
export function resolveCloudLawApiUrl(path, apiBase) {
const base = getCloudLawApiBase(apiBase)
const normalizedPath = path.startsWith('/') ? path : `/${path}`
return `${base}${normalizedPath}`
}
/**
* 通知云律后端结束通话(律师端 /api/law/voiceCall用户端 /api/applet/voiceConnection
*/
export function notifyCloudLawBackendEnd(callId, role = 'law', apiBase = '', token = '') {
if (!callId) return Promise.resolve(false)
const endBase = String(role).toLowerCase() === 'law' ? '/api/law/voiceCall' : '/api/applet/voiceConnection'
const url = resolveCloudLawApiUrl(`${endBase}/${encodeURIComponent(callId)}/end`, 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: 'POST',
data: {},
header,
timeout: 8000,
complete: () => resolve(true)
})
})
}
/** PC 律师端 iframe 会议结束时通知父页面outbound 浮窗)清除「会议进行中」状态 */
export function notifyCloudLawParentCallEnded(callId = '') {
if (typeof window === 'undefined') return false
try {
if (window.parent && window.parent !== window) {
window.parent.postMessage({
type: 'cloudLawCallEnded',
action: 'cloudLawCallEnded',
callId: String(callId || '')
}, '*')
return true
}
} catch (e) {
console.warn('[cloudLawVoiceCall] parent postMessage failed', e)
}
return false
}
export function isCloudLawLawRoleInMeeting(role) {
if (String(role || '').toLowerCase() === 'law') return true
try {
return typeof window !== 'undefined'
&& window.sessionStorage
&& window.sessionStorage.getItem('echo_cloud_law_meeting_role') === 'law'
} catch (e) {
return false
}
}
export function isCloudLawPcIframe() {
return typeof window !== 'undefined' && !!(window.parent && window.parent !== window)
}