Files
urbanLifeline/urbanLifelineWeb/packages/workcase/src/views/public/JitsiMeeting/JitsiMeetingView.vue
2025-12-27 15:36:40 +08:00

366 lines
12 KiB
Vue
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="jitsi-meeting-view">
<!-- 加载中 -->
<div v-if="loading" class="loading-container">
<div class="loading-spinner"></div>
<div class="loading-text">正在加载会议...</div>
</div>
<!-- 错误提示 -->
<div v-else-if="error" class="error-container">
<div class="error-icon"></div>
<div class="error-title">无法加入会议</div>
<div class="error-message">{{ error }}</div>
<button class="error-btn" @click="retryJoin">重新尝试</button>
</div>
<!-- Jitsi 会议容器 -->
<div v-else ref="jitsiContainer" id="jitsi-meet-container" class="meeting-container"></div>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, onUnmounted, nextTick } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { workcaseChatAPI } from '@/api/workcase'
// @ts-ignore
import { TokenManager } from 'shared/api'
import axios from 'axios'
const route = useRoute()
const router = useRouter()
const loading = ref(true)
const error = ref('')
const meetingId = ref('')
const roomId = ref('')
const jitsiContainer = ref<HTMLDivElement | null>(null)
let jitsiApi: any = null
// 从URL获取参数
const getMeetingParams = () => {
// 优先从query参数获取
meetingId.value = route.query.meetingId as string || ''
roomId.value = route.query.roomId as string || ''
const tokenParam = route.query.token as string || ''
console.log('[JitsiMeetingView] URL参数:', {
meetingId: meetingId.value,
roomId: roomId.value,
hasToken: !!tokenParam
})
return { meetingId: meetingId.value, roomId: roomId.value, token: tokenParam }
}
// 使用token刷新登录状态
const refreshLoginWithToken = async (token: string) => {
try {
console.log('[JitsiMeetingView] 使用token刷新登录状态...')
const response = await axios.post(
'/api/urban-lifeline/auth/refresh',
{},
{
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
}
}
)
if (response.data?.success && response.data?.data) {
const loginDomain = response.data.data
const newToken = loginDomain.token
// 保存到localStorage
localStorage.setItem('token', newToken)
localStorage.setItem('loginDomain', JSON.stringify(loginDomain))
// 使用TokenManager设置token
TokenManager.setToken(newToken)
console.log('[JitsiMeetingView] 登录状态刷新成功')
return true
} else {
console.error('[JitsiMeetingView] 刷新登录失败:', response.data?.message)
return false
}
} catch (err: any) {
console.error('[JitsiMeetingView] 刷新登录异常:', err)
return false
}
}
// 加载 Jitsi External API 脚本
const loadJitsiScript = (): Promise<void> => {
return new Promise((resolve, reject) => {
// 检查是否已经加载
if ((window as any).JitsiMeetExternalAPI) {
resolve()
return
}
const script = document.createElement('script')
script.src = 'http://localhost:8280/external_api.js'
script.async = true
script.onload = () => {
console.log('[JitsiMeetingView] Jitsi External API 脚本加载成功')
resolve()
}
script.onerror = () => {
reject(new Error('加载 Jitsi External API 失败'))
}
document.head.appendChild(script)
})
}
// 初始化 Jitsi Meet
const initJitsiMeet = async (jitsiServerUrl: string, roomName: string, jwt: string, displayName: string) => {
try {
console.log('[JitsiMeetingView] 初始化 Jitsi Meet:', {
server: jitsiServerUrl,
room: roomName,
name: displayName
})
// 加载 External API 脚本
await loadJitsiScript()
const JitsiMeetExternalAPI = (window as any).JitsiMeetExternalAPI
// 解析 URL 获取协议和域名
const urlObj = new URL(jitsiServerUrl)
const domain = urlObj.host // 获取 host (localhost:8280)
const useHttps = urlObj.protocol === 'https:'
console.log('[JitsiMeetingView] 解析服务器配置:', {
domain,
protocol: urlObj.protocol,
useHttps
})
// 配置选项 - 关键!指定是否使用 HTTPS
// 👇 替换你原有的 options 全部代码,保留原有逻辑,仅修改配置项
const options: any = {
roomName: roomName,
width: '100%',
height: '100%',
parentNode: jitsiContainer.value,
jwt: jwt,
// ✅ 修复1核心正确的顶层配置项是 useHTTPS不是 https强制关闭HTTPS
useHTTPS: false,
// ✅ 修复2禁用WebSocket的HTTPS彻底阻止wss://请求(局域网必加)
useWebSocket: false,
configOverwrite: {
startWithAudioMuted: false,
startWithVideoMuted: false,
enableWelcomePage: false,
prejoinPageEnabled: false,
disableDeepLinking: true,
enableChat: true,
enableScreenSharing: true,
// ✅ 修复3叠加禁用彻底阻断API内部的HTTPS强制逻辑局域网核心
useHTTPS: false,
// ✅ 修复4禁用第三方HTTPS资源请求避免混合内容报错
disableThirdPartyRequests: false,
// ✅ 修复5关闭服务端的HTTPS重定向检测
disableHttpsRedirect: true
},
interfaceConfigOverwrite: {
SHOW_JITSI_WATERMARK: false,
SHOW_WATERMARK_FOR_GUESTS: false,
DISABLE_JOIN_LEAVE_NOTIFICATIONS: false
},
userInfo: {
displayName: displayName
}
}
console.log('[JitsiMeetingView] 创建 JitsiMeetExternalAPI 实例https=' + useHttps)
jitsiApi = new JitsiMeetExternalAPI(domain, options)
// 监听会议准备就绪事件
jitsiApi.addEventListener('videoConferenceJoined', (event: any) => {
console.log('[JitsiMeetingView] 用户已加入会议:', event)
})
// 监听用户离开会议事件
jitsiApi.addEventListener('videoConferenceLeft', (event: any) => {
console.log('[JitsiMeetingView] 用户离开会议:', event)
handleLeaveMeeting()
})
// 监听准备关闭事件
jitsiApi.addEventListener('readyToClose', () => {
console.log('[JitsiMeetingView] Jitsi 准备关闭')
handleLeaveMeeting()
})
console.log('[JitsiMeetingView] Jitsi Meet 初始化完成')
} catch (err: any) {
console.error('[JitsiMeetingView] 初始化 Jitsi Meet 失败:', err)
error.value = err.message || '初始化会议失败'
loading.value = false
}
}
// 加入会议
const joinMeeting = async () => {
try {
loading.value = true
error.value = ''
const { meetingId: mid, roomId: rid, token } = getMeetingParams()
if (!mid) {
error.value = '缺少会议ID参数'
loading.value = false
return
}
// 检查是否有loginDomain
const hasLoginDomain = !!localStorage.getItem('loginDomain')
const hasToken = !!localStorage.getItem('token') || !!token
console.log('[JitsiMeetingView] 登录状态检查:', {
hasLoginDomain,
hasToken
})
// 如果没有loginDomain但有token小程序或外部链接访问先刷新登录
if (!hasLoginDomain && token) {
const refreshed = await refreshLoginWithToken(token)
if (!refreshed) {
error.value = '登录验证失败,请重新获取会议链接'
loading.value = false
return
}
}
// 检查登录状态
if (!TokenManager.hasToken()) {
error.value = '未登录,请先登录'
loading.value = false
// 重定向到登录页
const currentUrl = window.location.href
window.location.href = `/login?redirect=${encodeURIComponent(currentUrl)}`
return
}
// 调用后端接口加入会议
console.log('[JitsiMeetingView] 正在加入会议:', mid)
const result = await workcaseChatAPI.joinVideoMeeting(mid)
if (result.success && result.data) {
const meetingData = result.data
// 检查会议数据
if (!meetingData.jitsiServerUrl || !meetingData.jitsiRoomName || !meetingData.jwtToken) {
error.value = '会议数据不完整,无法加入'
loading.value = false
return
}
// 获取用户名
const loginDomain = JSON.parse(localStorage.getItem('loginDomain') || '{}')
const displayName = loginDomain.user?.userName || '访客'
// 先设置 loading 为 false让容器渲染出来
loading.value = false
// 等待下一个 tick确保 DOM 已渲染
await nextTick()
// 检查容器是否已经渲染
if (!jitsiContainer.value) {
error.value = '会议容器未准备好'
return
}
// 使用 Jitsi External API 初始化会议
await initJitsiMeet(
meetingData.jitsiServerUrl,
meetingData.jitsiRoomName,
meetingData.jwtToken,
displayName
)
} else {
error.value = result.message || '加入会议失败'
loading.value = false
}
} catch (err: any) {
console.error('[JitsiMeetingView] 加入会议异常:', err)
error.value = err.message || '加入会议失败'
loading.value = false
}
}
// 处理离开会议(不结束)
const handleLeaveMeeting = () => {
console.log('[JitsiMeetingView] 处理离开会议,返回聊天室:', roomId.value)
// 清理 Jitsi API
if (jitsiApi) {
jitsiApi.dispose()
jitsiApi = null
}
// 如果有roomId返回到对应的聊天室
if (roomId.value) {
router.push(`/chatRoom?roomId=${roomId.value}`)
} else {
// 没有roomId尝试返回上一页或关闭窗口
if (window.history.length > 1) {
router.back()
} else {
window.close()
}
}
}
// 处理结束会议
const handleEndMeeting = async () => {
try {
console.log('[JitsiMeetingView] 处理结束会议:', meetingId.value)
if (meetingId.value) {
// 调用后端接口结束会议
const result = await workcaseChatAPI.endVideoMeeting(meetingId.value)
console.log('[JitsiMeetingView] 结束会议结果:', result)
}
// 返回上一页或关闭窗口
handleLeaveMeeting()
} catch (err) {
console.error('[JitsiMeetingView] 结束会议失败:', err)
// 即使失败也返回上一页
handleLeaveMeeting()
}
}
// 重新尝试加入
const retryJoin = () => {
joinMeeting()
}
// 生命周期
onMounted(() => {
console.log('[JitsiMeetingView] 组件挂载')
joinMeeting()
})
onUnmounted(() => {
console.log('[JitsiMeetingView] 组件卸载')
// 清理 Jitsi API
if (jitsiApi) {
jitsiApi.dispose()
jitsiApi = null
}
})
</script>
<style scoped lang="scss">
@import url('./JitsiMeetingView.scss')
</style>