Files
urbanLifeline/urbanLifelineWeb/packages/workcase_wechat/pages/meeting/Meeting.uvue
2026-01-20 16:17:39 +08:00

377 lines
12 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

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>
<!-- 半透明遮罩 + 底部弹窗 -->
<view class="meeting-modal">
<view class="modal-mask" @tap="handleMaskClick"></view>
<!-- #ifdef MP-WEIXIN -->
<!-- 微信小程序环境:底部弹窗 -->
<view class="modal-content">
<view class="modal-header">
<text class="modal-title">{{ meetingName || '视频会议' }}</text>
<view class="close-btn" @tap="confirmExit">
<text class="close-icon">×</text>
</view>
</view>
<view class="modal-body">
<view class="meeting-info">
<view class="info-item">
<text class="info-label">会议名称:</text>
<text class="info-value">{{ meetingName || '未命名会议' }}</text>
</view>
<view class="info-item">
<text class="info-label">会议ID</text>
<text class="info-value">{{ meetingId || '-' }}</text>
</view>
</view>
<view class="tips-box">
<text class="tips-icon">⚠️</text>
<text class="tips-text">微信小程序暂不支持视频会议,请在浏览器中打开</text>
</view>
<view class="url-preview">
<text class="url-label">会议链接:</text>
<text class="url-text">{{ meetingUrl }}</text>
</view>
</view>
<view class="modal-footer">
<button class="action-btn copy-btn" @tap="copyUrl">
<text>复制会议链接</text>
</button>
<button class="action-btn browser-btn" @tap="openInBrowser">
<text>在浏览器中打开</text>
</button>
</view>
</view>
<!-- #endif -->
<!-- #ifndef MP-WEIXIN -->
<!-- 非微信小程序环境全屏web-view -->
<view class="meeting-page-full">
<view class="meeting-nav" :style="{ paddingTop: statusBarHeight + 'px', height: navBarHeight + 'px' }">
<view class="nav-back" @tap="handleNavBack">
<text class="back-icon">←</text>
</view>
<text class="nav-title">{{ meetingName || '视频会议' }}</text>
<view class="nav-right" @tap="endMeeting">
<text class="end-btn">结束会议</text>
</view>
</view>
<web-view
ref="jitsiWebView"
:src="meetingUrl"
:webview-styles="webviewStyles"
@message="handleWebViewMessage"
@error="handleWebViewError"
></web-view>
</view>
<!-- #endif -->
</view>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { workcaseChatAPI } from '@/api/workcase'
import { MEET_URL } from '@/config'
const statusBarHeight = ref(44)
const navBarHeight = ref(88)
const meetingUrl = ref('')
const meetingId = ref('')
const meetingName = ref('')
const meetingEnded = ref(false) // 会议是否已结束
const webviewStyles = ref({
progress: {
color: '#667eea'
}
})
onMounted(() => {
// 获取状态栏高度
const windowInfo = uni.getWindowInfo()
statusBarHeight.value = windowInfo.statusBarHeight || 44
navBarHeight.value = statusBarHeight.value + 44
// 获取页面参数
const pages = getCurrentPages()
const currentPage = pages[pages.length - 1] as any
console.log('[MeetingView] currentPage.options:', currentPage?.options)
if (currentPage && currentPage.options) {
const originalMeetingUrl = decodeURIComponent(currentPage.options.meetingUrl || '')
meetingId.value = currentPage.options.meetingId || ''
meetingName.value = decodeURIComponent(currentPage.options.meetingName || '')
// 检测是否为微信小程序环境
// #ifdef MP-WEIXIN
// 小程序环境直接使用原始URL显示提示弹窗
meetingUrl.value = originalMeetingUrl
console.log('[MeetingView] 微信小程序环境,显示提示弹窗')
// #endif
// #ifndef MP-WEIXIN
// 非小程序环境使用HTML包装页面加载Jitsi支持事件监听
const wrapperPath = '/static/jitsi-wrapper.html'
meetingUrl.value = `${wrapperPath}?url=${encodeURIComponent(originalMeetingUrl)}`
console.log('[MeetingView] 使用包装页面:', meetingUrl.value)
// #endif
}
console.log('[MeetingView] 会议页面加载:', {
meetingId: meetingId.value,
meetingName: meetingName.value,
meetingUrl: meetingUrl.value,
urlLength: meetingUrl.value.length
})
// 检查URL是否有效
if (!meetingUrl.value) {
console.error('[MeetingView] 会议URL为空')
uni.showToast({
title: '会议URL为空',
icon: 'none'
})
}
})
// 确认退出(小程序环境)
function confirmExit() {
uni.navigateBack()
}
// 导航栏返回按钮Web环境
function handleNavBack() {
if (meetingEnded.value) {
// 会议已结束,直接返回
uni.navigateBack()
} else {
// 会议进行中,提示用户
uni.showModal({
title: '提示',
content: '会议正在进行中,确定要离开吗?',
success: (res) => {
if (res.confirm) {
uni.navigateBack()
}
}
})
}
}
// 点击遮罩关闭
function handleMaskClick() {
uni.navigateBack()
}
// 结束会议
async function endMeeting() {
if (!meetingId.value) {
uni.navigateBack()
return
}
uni.showModal({
title: '提示',
content: '确定要结束会议吗?这将关闭所有参与者的会议。',
success: async (res) => {
if (res.confirm) {
try {
uni.showLoading({ title: '结束会议中...' })
await workcaseChatAPI.endVideoMeeting(meetingId.value)
uni.hideLoading()
meetingEnded.value = true
uni.showToast({
title: '会议已结束',
icon: 'success',
duration: 2000
})
// 2秒后自动返回
setTimeout(() => uni.navigateBack(), 2000)
} catch (e) {
uni.hideLoading()
console.error('[MeetingView] 结束会议失败:', e)
uni.showToast({ title: '结束会议失败', icon: 'none' })
}
}
}
})
}
// 处理webview消息监听Jitsi事件
function handleWebViewMessage(e: any) {
console.log('[MeetingView] webview消息:', e)
const { data } = e.detail
if (!data || !Array.isArray(data) || data.length === 0) {
return
}
// 解析消息
const message = data[0]
console.log('[MeetingView] 解析消息:', message)
// 处理不同的Jitsi事件
if (message.event) {
switch (message.event) {
case 'videoConferenceLeft':
// 用户离开会议
console.log('[MeetingView] 用户离开会议')
handleUserLeftMeeting()
break
case 'videoConferenceJoined':
// 用户加入会议
console.log('[MeetingView] 用户加入会议')
break
case 'participantLeft':
// 参与者离开
console.log('[MeetingView] 参与者离开:', message.data)
break
case 'readyToClose':
// 会议准备关闭
console.log('[MeetingView] 会议准备关闭')
handleMeetingEnded()
break
}
}
}
// 处理用户离开会议
function handleUserLeftMeeting() {
// 用户主动离开会议,直接返回
uni.navigateBack()
}
// 处理会议结束
async function handleMeetingEnded() {
if (meetingEnded.value) {
return
}
meetingEnded.value = true
console.log('[MeetingView] 会议已结束,同步到数据库')
// 调用后端API标记会议结束
if (meetingId.value) {
try {
await workcaseChatAPI.endVideoMeeting(meetingId.value)
console.log('[MeetingView] 会议结束状态已同步到数据库')
} catch (e) {
console.error('[MeetingView] 同步会议结束状态失败:', e)
}
}
// 提示用户并返回
uni.showToast({
title: '会议已结束',
icon: 'success',
duration: 2000
})
setTimeout(() => uni.navigateBack(), 2000)
}
// 处理webview错误
function handleWebViewError(e: any) {
console.error('[MeetingView] webview错误:', e)
uni.showToast({ title: '会议加载失败', icon: 'none' })
}
// 复制会议链接
function copyUrl() {
if (!meetingUrl.value) {
uni.showToast({ title: '会议链接为空', icon: 'none' })
return
}
// 解析iframeUrl并构建完整的会议链接
let fullMeetingUrl = meetingUrl.value
// 检查是否为微信小程序环境
// #ifdef MP-WEIXIN
// 获取页面参数中的iframeUrl
const pages = getCurrentPages()
const currentPage = pages[pages.length - 1] as any
const iframeUrl = currentPage?.options?.iframeUrl || ''
if (iframeUrl) {
// 构建正确的会议链接MEET_URL + iframeUrl
fullMeetingUrl = `${MEET_URL}${iframeUrl}`
}
// #endif
uni.setClipboardData({
data: fullMeetingUrl,
success: () => {
uni.showToast({ title: '链接已复制', icon: 'success' })
},
fail: () => {
uni.showToast({ title: '复制失败', icon: 'none' })
}
})
}
// 在浏览器中打开
function openInBrowser() {
if (!meetingUrl.value) {
uni.showToast({ title: '会议链接为空', icon: 'none' })
return
}
// 解析iframeUrl并构建完整的会议链接
let fullMeetingUrl = meetingUrl.value
// 检查是否为微信小程序环境且会议链接不是完整的MEET_URL格式
// #ifdef MP-WEIXIN
// 获取页面参数中的iframeUrl
const pages = getCurrentPages()
const currentPage = pages[pages.length - 1] as any
const iframeUrl = currentPage?.options?.iframeUrl || ''
if (iframeUrl) {
// 构建正确的会议链接MEET_URL + iframeUrl
fullMeetingUrl = `${MEET_URL}${iframeUrl}`
}
// #endif
// #ifdef MP-WEIXIN
// 微信小程序:先复制链接,然后提示用户通过右上角菜单在浏览器中打开
uni.setClipboardData({
data: fullMeetingUrl,
success: () => {
uni.showModal({
title: '链接已复制',
content: '请按以下步骤在浏览器中打开会议:\n\n1. 点击右上角【···】菜单\n2. 选择【在浏览器中打开】\n3. 在浏览器中粘贴会议链接\n\n或直接在任意浏览器中粘贴打开',
showCancel: false,
confirmText: '知道了'
})
},
fail: () => {
uni.showToast({ title: '复制失败', icon: 'none' })
}
})
// #endif
// #ifndef MP-WEIXIN
// 非微信小程序:直接打开链接
// @ts-ignore
if (typeof plus !== 'undefined') {
// App环境使用plus打开系统浏览器
plus.runtime.openURL(fullMeetingUrl)
} else {
// H5环境新窗口打开
window.open(fullMeetingUrl, '_blank')
}
// #endif
}
</script>
<style lang="scss" scoped>
@import "./Meeting.scss"
</style>