电话咨询

This commit is contained in:
duoaohui
2026-06-04 15:52:22 +08:00
parent 495f0aae1b
commit cae3876d8b
4 changed files with 188 additions and 71 deletions

View File

@@ -162,6 +162,7 @@ import {
MEETING_ENDED_REASON_LABEL MEETING_ENDED_REASON_LABEL
} 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 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'
@@ -1003,27 +1004,8 @@ const minimizeToMiniProgram = () => {
minimizingToMiniProgram.value = true minimizingToMiniProgram.value = true
stopCommandPolling() stopCommandPolling()
meetingStore.leave().catch(() => {}) meetingStore.leave().catch(() => {})
const mp = getMiniProgram() if (minimizeCloudLawMeeting(callId.value)) {
try { return
if (mp && typeof mp.postMessage === 'function') {
mp.postMessage({
data: {
type: 'cloudLawMinimized',
action: 'cloudLawMinimized',
callId: callId.value
}
})
}
} catch (e) {
console.warn('[CloudLawVideoCall] minimize postMessage failed', e)
}
try {
if (mp && typeof mp.navigateBack === 'function') {
mp.navigateBack({ delta: 1 })
return
}
} catch (e) {
console.warn('[CloudLawVideoCall] minimize navigateBack failed', e)
} }
minimizingToMiniProgram.value = false minimizingToMiniProgram.value = false
uni.showToast({ title: '请使用左上角返回', icon: 'none' }) uni.showToast({ title: '请使用左上角返回', icon: 'none' })

View File

@@ -115,6 +115,7 @@ import { ref, reactive, computed, onBeforeUnmount, nextTick } from 'vue'
import { onLoad } from '@dcloudio/uni-app' import { onLoad } from '@dcloudio/uni-app'
import { useMeetingStore } from '@/store/meeting' import { useMeetingStore } from '@/store/meeting'
import { useUserStore } from '@/store/user' import { useUserStore } from '@/store/user'
import { isCloudLawEmbed, isCloudLawLawRole, minimizeCloudLawMeeting } from '@/utils/cloudLawMiniProgram'
const meetingStore = useMeetingStore() const meetingStore = useMeetingStore()
const userStore = useUserStore() const userStore = useUserStore()
@@ -124,6 +125,7 @@ const joinCode = ref('')
const joinPassword = ref('') const joinPassword = ref('')
const autoJoin = ref(false) const autoJoin = ref(false)
const cloudLawRole = ref('') const cloudLawRole = ref('')
const cloudLawCallId = ref('')
const initializing = ref(true) const initializing = ref(true)
// autoJoin 失败/超时的可见错误(替代无限"正在进入会议…"转圈) // autoJoin 失败/超时的可见错误(替代无限"正在进入会议…"转圈)
const autoJoinError = ref('') const autoJoinError = ref('')
@@ -372,6 +374,10 @@ const onToggleVideoDefault = (e) => { mediaPrefs.startVideo = e.detail.value }
const onToggleAudioDefault = (e) => { mediaPrefs.startAudio = e.detail.value } const onToggleAudioDefault = (e) => { mediaPrefs.startAudio = e.detail.value }
const onCancel = () => { const onCancel = () => {
if (isCloudLawEmbed() && isCloudLawLawRole(cloudLawRole.value)) {
minimizeCloudLawMeeting(cloudLawCallId.value)
return
}
uni.navigateBack() uni.navigateBack()
} }
@@ -398,16 +404,10 @@ const retryAutoJoin = () => {
const exitAutoJoin = () => { const exitAutoJoin = () => {
clearAutoJoinWatchdog() clearAutoJoinWatchdog()
// 云律小程序 web-view 内:返回到小程序;否则回上一步 if (isCloudLawEmbed() && isCloudLawLawRole(cloudLawRole.value)) {
try { minimizeCloudLawMeeting(cloudLawCallId.value)
const embedded = typeof window !== 'undefined' && window.sessionStorage return
&& window.sessionStorage.getItem('echo_cloud_law_meeting_embed') === '1' }
const mp = typeof window !== 'undefined' && window.wx && window.wx.miniProgram ? window.wx.miniProgram : null
if (embedded && mp && typeof mp.navigateBack === 'function') {
mp.navigateBack({ delta: 1 })
return
}
} catch (e) {}
onCancel() onCancel()
} }
@@ -469,7 +469,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)}` : ''
uni.redirectTo({ url: `/pages/meeting/room${roomCode ? `?code=${roomCode}${roleQ}` : (roleQ ? `?${roleQ.slice(1)}` : '')}` }) const callQ = cloudLawCallId.value ? `&callId=${encodeURIComponent(cloudLawCallId.value)}` : ''
uni.redirectTo({ url: `/pages/meeting/room${roomCode ? `?code=${roomCode}${roleQ}${callQ}` : `${roleQ || callQ ? `?${(roleQ + callQ).replace(/^&/, '')}` : ''}`}` })
} catch (err) { } catch (err) {
const msg = err?.message || JSON.stringify(err) const msg = err?.message || JSON.stringify(err)
// autoJoin 模式下把错误显式呈现在页面toast 一闪而过,真机很难看清) // autoJoin 模式下把错误显式呈现在页面toast 一闪而过,真机很难看清)
@@ -494,6 +495,7 @@ onLoad(async (query) => {
mode.value = query?.mode === 'join' ? 'join' : 'create' mode.value = query?.mode === 'join' ? 'join' : 'create'
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 || ''
autoJoin.value = query?.autoJoin === '1' || query?.autoJoin === 'true' || query?.autoJoin === true autoJoin.value = query?.autoJoin === '1' || query?.autoJoin === 'true' || query?.autoJoin === true
// #ifdef H5 // #ifdef H5
if (String(cloudLawRole.value).toLowerCase() === 'law') { if (String(cloudLawRole.value).toLowerCase() === 'law') {

View File

@@ -15,6 +15,10 @@
<!-- 顶部信息栏 --> <!-- 顶部信息栏 -->
<view class="top-bar"> <view class="top-bar">
<view class="top-left"> <view class="top-left">
<view v-if="showCloudLawBack" class="cloud-law-back" @click="onCloudLawBack">
<text class="cloud-law-back-icon"></text>
</view>
<view class="top-left-info">
<text class="meeting-title">{{ meetingTitle }}</text> <text class="meeting-title">{{ meetingTitle }}</text>
<view class="code-pill" @click="openInvite"> <view class="code-pill" @click="openInvite">
<text class="code-text">{{ formattedCode }}</text> <text class="code-text">{{ formattedCode }}</text>
@@ -23,6 +27,7 @@
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path> <path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
</svg> </svg>
</view> </view>
</view>
</view> </view>
<view class="top-right"> <view class="top-right">
<view class="state-pill" :class="stateClass"> <view class="state-pill" :class="stateClass">
@@ -227,13 +232,22 @@ import InviteDialog from '@/components/meeting/InviteDialog.vue'
import NetworkBadge from '@/components/meeting/NetworkBadge.vue' import NetworkBadge from '@/components/meeting/NetworkBadge.vue'
import ChatPanel from '@/components/meeting/ChatPanel.vue' import ChatPanel from '@/components/meeting/ChatPanel.vue'
import SelfVideoFloat from '@/components/meeting/SelfVideoFloat.vue' import SelfVideoFloat from '@/components/meeting/SelfVideoFloat.vue'
import {
isCloudLawEmbed,
isCloudLawLawRole,
minimizeCloudLawMeeting,
notifyCloudLawCallEnded
} from '@/utils/cloudLawMiniProgram'
const meetingStore = useMeetingStore() const meetingStore = useMeetingStore()
const userStore = useUserStore() const userStore = useUserStore()
/** 云律律师端:工具栏用共享屏幕替换聊天 */ /** 云律律师端:工具栏用共享屏幕替换聊天 */
const cloudLawRole = ref('') const cloudLawRole = ref('')
const cloudLawCallId = ref('')
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 memberVisible = ref(false) const memberVisible = ref(false)
const inviteVisible = ref(false) const inviteVisible = ref(false)
@@ -740,7 +754,20 @@ const onTransferHost = async (uid) => {
} }
} }
const onLeaveClick = () => { leaveDialogVisible.value = true } const onCloudLawBack = () => {
if (minimizingToMiniProgram.value) return
minimizingToMiniProgram.value = true
meetingStore.leave().catch(() => {})
minimizeCloudLawMeeting(cloudLawCallId.value)
}
const onLeaveClick = () => {
if (showCloudLawBack.value) {
onCloudLawBack()
return
}
leaveDialogVisible.value = true
}
const confirmLeaveSelf = async () => { const confirmLeaveSelf = async () => {
leaveDialogVisible.value = false leaveDialogVisible.value = false
@@ -775,53 +802,27 @@ const confirmEndOrLeave = async () => {
} }
} }
// 是否运行在云律小程序 web-view 内SSO 进入时打的标记)
const isCloudLawEmbed = () => {
try {
return typeof window !== 'undefined'
&& !!window.sessionStorage
&& window.sessionStorage.getItem('echo_cloud_law_meeting_embed') === '1'
} catch (e) {
return false
}
}
const getMiniProgram = () => {
return typeof window !== 'undefined' && window.wx && window.wx.miniProgram ? window.wx.miniProgram : null
}
// 退出会议视频界面:先给提示,再退出。 // 退出会议视频界面:先给提示,再退出。
// 云律小程序 web-view 内 → 通知并 navigateBack 回到小程序(关闭会议 web-view 页); // 云律律师端「返回」→ 最小化并弹出「返回会议」;真正挂断/结束 → cloudLawCallEnded。
// 普通浏览器/PC → 回 EchoChat 首页。 const leaveAndExit = (tip, ended = true) => {
const leaveAndExit = (tip) => {
if (tip) { if (tip) {
uni.showToast({ title: tip, icon: 'none', duration: 1200 }) uni.showToast({ title: tip, icon: 'none', duration: 1200 })
} }
// 留一点时间让提示可见,再退出视频界面 setTimeout(() => redirectHome(ended), tip ? 600 : 800)
setTimeout(redirectHome, tip ? 600 : 800)
} }
let meetingExited = false let meetingExited = false
const redirectHome = () => { const redirectHome = (ended = true) => {
if (meetingExited) return if (meetingExited) return
const miniProgram = getMiniProgram() if (isCloudLawEmbed() && isCloudLawLawRole(cloudLawRole.value)) {
if (isCloudLawEmbed() && miniProgram) {
meetingExited = true meetingExited = true
try { const minimize = minimizingToMiniProgram.value || !ended
miniProgram.postMessage({ data: { type: 'cloudLawCallEnded', action: 'cloudLawCallEnded' } }) if (minimize) {
} catch (e) {} if (minimizeCloudLawMeeting(cloudLawCallId.value)) return
try { } else if (notifyCloudLawCallEnded(cloudLawCallId.value)) {
if (typeof miniProgram.navigateBack === 'function') { return
miniProgram.navigateBack({ delta: 1 })
return
}
if (typeof miniProgram.redirectTo === 'function') {
miniProgram.redirectTo({ url: '/pages/index/home_page/home_page' })
return
}
} catch (e) {
meetingExited = false
} }
meetingExited = false
} }
uni.reLaunch({ url: '/pages/index/index' }) uni.reLaunch({ url: '/pages/index/index' })
} }
@@ -838,6 +839,7 @@ const syncViewportHeight = () => {
// ============ 生命周期 ============ // ============ 生命周期 ============
let stateStopWatch = null let stateStopWatch = null
let popstateHandler = null
// P2-3 修复redirectTo 后必须 return避免 onMounted 继续执行闪默认 UI // P2-3 修复redirectTo 后必须 return避免 onMounted 继续执行闪默认 UI
// 使用 module-scoped 标记onMounted 读取后判断是否提前退出 // 使用 module-scoped 标记onMounted 读取后判断是否提前退出
@@ -845,6 +847,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 || ''
if (!cloudLawRole.value) { if (!cloudLawRole.value) {
// #ifdef H5 // #ifdef H5
try { try {
@@ -888,12 +891,31 @@ onMounted(() => {
// 会议被主持人/后端结束ENDED 状态)时,弹提示并回首页 // 会议被主持人/后端结束ENDED 状态)时,弹提示并回首页
stateStopWatch = watch(() => meetingStore.localState, (s) => { stateStopWatch = watch(() => meetingStore.localState, (s) => {
if (s === MEETING_LOCAL_STATE_ENDED) { if (s === MEETING_LOCAL_STATE_ENDED) {
if (minimizingToMiniProgram.value) {
minimizingToMiniProgram.value = false
redirectHome(false)
return
}
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' })
setTimeout(redirectHome, 1200) setTimeout(() => redirectHome(true), 1200)
} }
}) })
// #ifdef H5
if (showCloudLawBack.value) {
try {
history.pushState({ cloudLawMeeting: 1 }, '')
popstateHandler = () => {
if (!minimizingToMiniProgram.value) {
onCloudLawBack()
}
}
window.addEventListener('popstate', popstateHandler)
} catch (e) {}
}
// #endif
}) })
onBeforeUnmount(() => { onBeforeUnmount(() => {
@@ -903,6 +925,12 @@ onBeforeUnmount(() => {
} }
if (viewportCleanup) { viewportCleanup(); viewportCleanup = null } if (viewportCleanup) { viewportCleanup(); viewportCleanup = null }
if (stateStopWatch) { stateStopWatch(); stateStopWatch = null } if (stateStopWatch) { stateStopWatch(); stateStopWatch = null }
// #ifdef H5
if (popstateHandler) {
window.removeEventListener('popstate', popstateHandler)
popstateHandler = null
}
// #endif
}) })
onUnload(() => { onUnload(() => {
@@ -949,6 +977,23 @@ onUnload(() => {
z-index: 1000; z-index: 1000;
} }
.cloud-law-back {
width: 64rpx;
height: 64rpx;
margin-right: 12rpx;
border-radius: 50%;
background: rgba(255, 255, 255, 0.12);
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.cloud-law-back-icon {
font-size: 44rpx;
line-height: 1;
color: #FFFFFF;
font-weight: 300;
}
.top-bar { .top-bar {
display: flex; display: flex;
align-items: center; align-items: center;
@@ -962,6 +1007,14 @@ onUnload(() => {
} }
.top-left { .top-left {
display: flex;
flex-direction: row;
align-items: center;
gap: 6rpx;
min-width: 0;
flex: 1;
}
.top-left-info {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 6rpx; gap: 6rpx;

View File

@@ -0,0 +1,80 @@
/** 云律小程序 web-view 内与原生壳通信(返回会议浮窗 / 挂断) */
export function getMiniProgram() {
return typeof window !== 'undefined' && window.wx && window.wx.miniProgram ? window.wx.miniProgram : null
}
export function isCloudLawEmbed() {
try {
return typeof window !== 'undefined'
&& !!window.sessionStorage
&& window.sessionStorage.getItem('echo_cloud_law_meeting_embed') === '1'
} catch (e) {
return false
}
}
export function isCloudLawLawRole(role) {
if (String(role || '').toLowerCase() === 'law') return true
try {
return window.sessionStorage.getItem('echo_cloud_law_meeting_role') === 'law'
} catch (e) {
return false
}
}
/** 律师端返回上一页:会议仍在进行,通知小程序弹出「返回会议」浮窗 */
export function minimizeCloudLawMeeting(callId = '') {
const mp = getMiniProgram()
if (!isCloudLawEmbed() || !mp) return false
try {
if (typeof mp.postMessage === 'function') {
mp.postMessage({
data: {
type: 'cloudLawMinimized',
action: 'cloudLawMinimized',
callId: String(callId || '')
}
})
}
} catch (e) {
console.warn('[cloudLawMiniProgram] minimize postMessage failed', e)
}
try {
if (typeof mp.navigateBack === 'function') {
mp.navigateBack({ delta: 1 })
return true
}
} catch (e) {
console.warn('[cloudLawMiniProgram] minimize navigateBack failed', e)
}
return false
}
/** 会议真正结束:通知小程序释放占用 */
export function notifyCloudLawCallEnded(callId = '') {
const mp = getMiniProgram()
if (!isCloudLawEmbed() || !mp) return false
try {
if (typeof mp.postMessage === 'function') {
mp.postMessage({
data: {
type: 'cloudLawCallEnded',
action: 'cloudLawCallEnded',
callId: String(callId || '')
}
})
}
} catch (e) {
console.warn('[cloudLawMiniProgram] ended postMessage failed', e)
}
try {
if (typeof mp.navigateBack === 'function') {
mp.navigateBack({ delta: 1 })
return true
}
} catch (e) {
console.warn('[cloudLawMiniProgram] ended navigateBack failed', e)
}
return false
}