聊天室修改视频会议
This commit is contained in:
@@ -11,7 +11,16 @@
|
|||||||
"setting" : {
|
"setting" : {
|
||||||
"urlCheck" : false
|
"urlCheck" : false
|
||||||
},
|
},
|
||||||
"usingComponents" : true
|
"usingComponents" : true,
|
||||||
|
"permission" : {
|
||||||
|
"scope.camera" : {
|
||||||
|
"desc" : "用于视频会议时开启摄像头"
|
||||||
|
},
|
||||||
|
"scope.record" : {
|
||||||
|
"desc" : "用于视频会议时开启麦克风"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"requiredPrivateInfos" : []
|
||||||
},
|
},
|
||||||
"mp-alipay" : {
|
"mp-alipay" : {
|
||||||
"usingComponents" : true
|
"usingComponents" : true
|
||||||
@@ -35,6 +44,19 @@
|
|||||||
"xxhdpi" : "",
|
"xxhdpi" : "",
|
||||||
"xxxhdpi" : ""
|
"xxxhdpi" : ""
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"android" : {
|
||||||
|
"permissions" : [
|
||||||
|
"<uses-permission android:name=\"android.permission.CAMERA\"/>",
|
||||||
|
"<uses-permission android:name=\"android.permission.RECORD_AUDIO\"/>",
|
||||||
|
"<uses-permission android:name=\"android.permission.MODIFY_AUDIO_SETTINGS\"/>"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"ios" : {
|
||||||
|
"privacyDescription" : {
|
||||||
|
"NSCameraUsageDescription" : "用于视频会议时开启摄像头",
|
||||||
|
"NSMicrophoneUsageDescription" : "用于视频会议时开启麦克风"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,10 +36,26 @@
|
|||||||
{
|
{
|
||||||
"path": "pages/meeting/Meeting",
|
"path": "pages/meeting/Meeting",
|
||||||
"style": {
|
"style": {
|
||||||
"navigationStyle": "custom"
|
"navigationStyle": "custom",
|
||||||
|
"disableScroll": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/meeting/MeetingCreate",
|
||||||
|
"style": {
|
||||||
|
"navigationStyle": "custom",
|
||||||
|
"navigationBarTitleText": "创建会议"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"permission": {
|
||||||
|
"scope.camera": {
|
||||||
|
"desc": "用于拍摄/视频通话等功能"
|
||||||
|
},
|
||||||
|
"scope.record": {
|
||||||
|
"desc": "用于录制音频/语音通话等功能"
|
||||||
|
}
|
||||||
|
},
|
||||||
"globalStyle": {
|
"globalStyle": {
|
||||||
"navigationBarTextStyle": "white",
|
"navigationBarTextStyle": "white",
|
||||||
"navigationBarTitleText": "泰豪小电",
|
"navigationBarTitleText": "泰豪小电",
|
||||||
|
|||||||
@@ -561,8 +561,9 @@ async function startMeeting() {
|
|||||||
// 已有活跃会议,直接加入
|
// 已有活跃会议,直接加入
|
||||||
const meetingUrl = res.data.iframeUrl
|
const meetingUrl = res.data.iframeUrl
|
||||||
const meetingId = res.data.meetingId
|
const meetingId = res.data.meetingId
|
||||||
|
const meetingName = res.data.meetingName || '视频会议'
|
||||||
uni.navigateTo({
|
uni.navigateTo({
|
||||||
url: `/pages/meeting/Meeting?meetingUrl=${encodeURIComponent(meetingUrl)}&meetingId=${meetingId}`,
|
url: `/pages/meeting/Meeting?meetingUrl=${encodeURIComponent(meetingUrl)}&meetingId=${meetingId}&meetingName=${encodeURIComponent(meetingName)}`,
|
||||||
fail: (err) => {
|
fail: (err) => {
|
||||||
console.error('[chatRoom] 跳转会议页面失败:', err)
|
console.error('[chatRoom] 跳转会议页面失败:', err)
|
||||||
uni.showToast({ title: '打开会议失败', icon: 'none' })
|
uni.showToast({ title: '打开会议失败', icon: 'none' })
|
||||||
@@ -586,27 +587,40 @@ async function startMeeting() {
|
|||||||
|
|
||||||
// 加入会议(从MeetingCard点击加入)
|
// 加入会议(从MeetingCard点击加入)
|
||||||
async function handleJoinMeeting(meetingId: string) {
|
async function handleJoinMeeting(meetingId: string) {
|
||||||
|
console.log('[handleJoinMeeting] 开始加入会议, meetingId:', meetingId)
|
||||||
try {
|
try {
|
||||||
// 调用加入会议接口获取iframe URL
|
// 调用加入会议接口获取iframe URL
|
||||||
const joinRes = await workcaseChatAPI.joinMeeting(meetingId)
|
const joinRes = await workcaseChatAPI.joinMeeting(meetingId)
|
||||||
if (joinRes.success && joinRes.data?.iframeUrl) {
|
console.log('[handleJoinMeeting] API响应:', JSON.stringify(joinRes))
|
||||||
const meetingUrl = joinRes.data.iframeUrl
|
|
||||||
|
// 兼容两种判断方式:success 字段或 code === 200
|
||||||
|
const isSuccess = joinRes.success || joinRes.code === 200 || joinRes.code === 0
|
||||||
|
const meetingData = joinRes.data
|
||||||
|
|
||||||
|
if (isSuccess && meetingData && meetingData.iframeUrl) {
|
||||||
|
const meetingUrl = meetingData.iframeUrl
|
||||||
|
const meetingName = meetingData.meetingName || '视频会议'
|
||||||
|
console.log('[handleJoinMeeting] 获取到会议URL:', meetingUrl, '会议名称:', meetingName)
|
||||||
// 跳转到会议页面
|
// 跳转到会议页面
|
||||||
uni.navigateTo({
|
uni.navigateTo({
|
||||||
url: `/pages/meeting/Meeting?meetingUrl=${encodeURIComponent(meetingUrl)}&meetingId=${meetingId}`,
|
url: `/pages/meeting/Meeting?meetingUrl=${encodeURIComponent(meetingUrl)}&meetingId=${meetingId}&meetingName=${encodeURIComponent(meetingName)}`,
|
||||||
|
success: () => {
|
||||||
|
console.log('[handleJoinMeeting] 跳转成功')
|
||||||
|
},
|
||||||
fail: (err) => {
|
fail: (err) => {
|
||||||
console.error('[chatRoom] 跳转会议页面失败:', err)
|
console.error('[handleJoinMeeting] 跳转会议页面失败:', err)
|
||||||
uni.showToast({ title: '打开会议失败', icon: 'none' })
|
uni.showToast({ title: '打开会议失败', icon: 'none' })
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
|
console.error('[handleJoinMeeting] 加入会议失败, isSuccess:', isSuccess, 'data:', meetingData)
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: joinRes.message || '加入会议失败',
|
title: joinRes.message || '加入会议失败:未获取到会议地址',
|
||||||
icon: 'none'
|
icon: 'none'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[chatRoom] 加入会议失败:', error)
|
console.error('[handleJoinMeeting] 加入会议异常:', error)
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: '加入会议失败',
|
title: '加入会议失败',
|
||||||
icon: 'none'
|
icon: 'none'
|
||||||
|
|||||||
@@ -40,4 +40,186 @@
|
|||||||
color: #ff4444;
|
color: #ff4444;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 弹窗模式样式 */
|
||||||
|
.meeting-modal {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
z-index: 9999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-mask {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: rgba(0, 0, 0, 0.5);
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content {
|
||||||
|
position: fixed;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
width: 100%;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 16px 16px 0 0;
|
||||||
|
animation: slideUp 0.3s ease-out;
|
||||||
|
max-height: 70vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slideUp {
|
||||||
|
from {
|
||||||
|
transform: translateY(100%);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-header {
|
||||||
|
position: relative;
|
||||||
|
padding: 20px 20px 16px;
|
||||||
|
border-bottom: 1px solid #f0f0f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-title {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333;
|
||||||
|
padding-right: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close-btn {
|
||||||
|
position: absolute;
|
||||||
|
right: 16px;
|
||||||
|
top: 16px;
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close-icon {
|
||||||
|
font-size: 28px;
|
||||||
|
color: #999;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-body {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meeting-info {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-label {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #666;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-value {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #333;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tips-box {
|
||||||
|
background: #fff7e6;
|
||||||
|
border: 1px solid #ffd591;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 12px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tips-icon {
|
||||||
|
font-size: 20px;
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tips-text {
|
||||||
|
font-size: 13px;
|
||||||
|
color: #d48806;
|
||||||
|
flex: 1;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.url-preview {
|
||||||
|
background: #f5f5f5;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 12px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.url-label {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #999;
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.url-text {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #666;
|
||||||
|
word-break: break-all;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-footer {
|
||||||
|
padding: 0 20px 32px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-btn {
|
||||||
|
width: 100%;
|
||||||
|
height: 48px;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 16px;
|
||||||
|
border: none;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.copy-btn {
|
||||||
|
background: #667eea;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.browser-btn {
|
||||||
|
background: #fff;
|
||||||
|
color: #667eea;
|
||||||
|
border: 1px solid #667eea;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 全屏页面样式(非小程序环境) */
|
||||||
|
.meeting-page-full {
|
||||||
|
width: 100%;
|
||||||
|
height: 100vh;
|
||||||
|
background: #000;
|
||||||
}
|
}
|
||||||
@@ -1,23 +1,73 @@
|
|||||||
<template>
|
<template>
|
||||||
<view class="meeting-page">
|
<!-- 半透明遮罩 + 底部弹窗 -->
|
||||||
<!-- 自定义导航栏 -->
|
<view class="meeting-modal">
|
||||||
<view class="meeting-nav" :style="{ paddingTop: statusBarHeight + 'px', height: navBarHeight + 'px' }">
|
<view class="modal-mask" @tap="handleMaskClick"></view>
|
||||||
<view class="nav-back" @tap="confirmExit">
|
|
||||||
<text class="back-icon">←</text>
|
<!-- #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>
|
||||||
<text class="nav-title">视频会议</text>
|
|
||||||
<view class="nav-right" @tap="endMeeting">
|
<view class="modal-body">
|
||||||
<text class="end-btn">结束会议</text>
|
<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>
|
||||||
</view>
|
</view>
|
||||||
|
<!-- #endif -->
|
||||||
|
|
||||||
<!-- Web-view加载Jitsi Meet -->
|
<!-- #ifndef MP-WEIXIN -->
|
||||||
<web-view
|
<!-- 非微信小程序环境:全屏web-view -->
|
||||||
:src="meetingUrl"
|
<view class="meeting-page-full">
|
||||||
:webview-styles="webviewStyles"
|
<view class="meeting-nav" :style="{ paddingTop: statusBarHeight + 'px', height: navBarHeight + 'px' }">
|
||||||
@message="handleWebViewMessage"
|
<view class="nav-back" @tap="handleNavBack">
|
||||||
@error="handleWebViewError"
|
<text class="back-icon">←</text>
|
||||||
></web-view>
|
</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>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -29,6 +79,8 @@ const statusBarHeight = ref(44)
|
|||||||
const navBarHeight = ref(88)
|
const navBarHeight = ref(88)
|
||||||
const meetingUrl = ref('')
|
const meetingUrl = ref('')
|
||||||
const meetingId = ref('')
|
const meetingId = ref('')
|
||||||
|
const meetingName = ref('')
|
||||||
|
const meetingEnded = ref(false) // 会议是否已结束
|
||||||
|
|
||||||
const webviewStyles = ref({
|
const webviewStyles = ref({
|
||||||
progress: {
|
progress: {
|
||||||
@@ -45,28 +97,72 @@ onMounted(() => {
|
|||||||
// 获取页面参数
|
// 获取页面参数
|
||||||
const pages = getCurrentPages()
|
const pages = getCurrentPages()
|
||||||
const currentPage = pages[pages.length - 1] as any
|
const currentPage = pages[pages.length - 1] as any
|
||||||
|
console.log('[MeetingView] currentPage.options:', currentPage?.options)
|
||||||
|
|
||||||
if (currentPage && currentPage.options) {
|
if (currentPage && currentPage.options) {
|
||||||
meetingUrl.value = decodeURIComponent(currentPage.options.meetingUrl || '')
|
const originalMeetingUrl = decodeURIComponent(currentPage.options.meetingUrl || '')
|
||||||
meetingId.value = currentPage.options.meetingId || ''
|
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] 会议页面加载:', {
|
console.log('[MeetingView] 会议页面加载:', {
|
||||||
meetingId: meetingId.value,
|
meetingId: meetingId.value,
|
||||||
meetingUrl: meetingUrl.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() {
|
function confirmExit() {
|
||||||
uni.showModal({
|
uni.navigateBack()
|
||||||
title: '提示',
|
}
|
||||||
content: '确定要退出会议吗?',
|
|
||||||
success: (res) => {
|
// 导航栏返回按钮(Web环境)
|
||||||
if (res.confirm) {
|
function handleNavBack() {
|
||||||
uni.navigateBack()
|
if (meetingEnded.value) {
|
||||||
|
// 会议已结束,直接返回
|
||||||
|
uni.navigateBack()
|
||||||
|
} else {
|
||||||
|
// 会议进行中,提示用户
|
||||||
|
uni.showModal({
|
||||||
|
title: '提示',
|
||||||
|
content: '会议正在进行中,确定要离开吗?',
|
||||||
|
success: (res) => {
|
||||||
|
if (res.confirm) {
|
||||||
|
uni.navigateBack()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
})
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 点击遮罩关闭
|
||||||
|
function handleMaskClick() {
|
||||||
|
uni.navigateBack()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 结束会议
|
// 结束会议
|
||||||
@@ -85,24 +181,98 @@ async function endMeeting() {
|
|||||||
uni.showLoading({ title: '结束会议中...' })
|
uni.showLoading({ title: '结束会议中...' })
|
||||||
await workcaseChatAPI.endVideoMeeting(meetingId.value)
|
await workcaseChatAPI.endVideoMeeting(meetingId.value)
|
||||||
uni.hideLoading()
|
uni.hideLoading()
|
||||||
uni.showToast({ title: '会议已结束', icon: 'success' })
|
|
||||||
setTimeout(() => uni.navigateBack(), 1500)
|
meetingEnded.value = true
|
||||||
|
|
||||||
|
uni.showToast({
|
||||||
|
title: '会议已结束',
|
||||||
|
icon: 'success',
|
||||||
|
duration: 2000
|
||||||
|
})
|
||||||
|
|
||||||
|
// 2秒后自动返回
|
||||||
|
setTimeout(() => uni.navigateBack(), 2000)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
uni.hideLoading()
|
uni.hideLoading()
|
||||||
console.error('[MeetingView] 结束会议失败:', e)
|
console.error('[MeetingView] 结束会议失败:', e)
|
||||||
uni.showToast({ title: '结束会议失败', icon: 'none' })
|
uni.showToast({ title: '结束会议失败', icon: 'none' })
|
||||||
uni.navigateBack()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 处理webview消息
|
// 处理webview消息(监听Jitsi事件)
|
||||||
function handleWebViewMessage(e: any) {
|
function handleWebViewMessage(e: any) {
|
||||||
console.log('[MeetingView] webview消息:', e)
|
console.log('[MeetingView] webview消息:', e)
|
||||||
// 可以在这里处理Jitsi Meet发送的消息
|
|
||||||
// 例如:会议结束、参与者加入/离开等事件
|
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错误
|
// 处理webview错误
|
||||||
@@ -110,6 +280,62 @@ function handleWebViewError(e: any) {
|
|||||||
console.error('[MeetingView] webview错误:', e)
|
console.error('[MeetingView] webview错误:', e)
|
||||||
uni.showToast({ title: '会议加载失败', icon: 'none' })
|
uni.showToast({ title: '会议加载失败', icon: 'none' })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 复制会议链接
|
||||||
|
function copyUrl() {
|
||||||
|
if (!meetingUrl.value) {
|
||||||
|
uni.showToast({ title: '会议链接为空', icon: 'none' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
uni.setClipboardData({
|
||||||
|
data: meetingUrl.value,
|
||||||
|
success: () => {
|
||||||
|
uni.showToast({ title: '链接已复制', icon: 'success' })
|
||||||
|
},
|
||||||
|
fail: () => {
|
||||||
|
uni.showToast({ title: '复制失败', icon: 'none' })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 在浏览器中打开
|
||||||
|
function openInBrowser() {
|
||||||
|
if (!meetingUrl.value) {
|
||||||
|
uni.showToast({ title: '会议链接为空', icon: 'none' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// #ifdef MP-WEIXIN
|
||||||
|
// 微信小程序:先复制链接,然后提示用户通过右上角菜单在浏览器中打开
|
||||||
|
uni.setClipboardData({
|
||||||
|
data: meetingUrl.value,
|
||||||
|
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(meetingUrl.value)
|
||||||
|
} else {
|
||||||
|
// H5环境:新窗口打开
|
||||||
|
window.open(meetingUrl.value, '_blank')
|
||||||
|
}
|
||||||
|
// #endif
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
<button
|
<button
|
||||||
type="primary"
|
type="primary"
|
||||||
:disabled="!canJoinMeeting"
|
:disabled="!canJoinMeeting"
|
||||||
@click="handleJoinMeeting"
|
@tap="handleJoinMeeting"
|
||||||
size="mini"
|
size="mini"
|
||||||
>
|
>
|
||||||
<text>{{ buttonText }}</text>
|
<text>{{ buttonText }}</text>
|
||||||
@@ -194,7 +194,14 @@ const buttonText = computed((): string => {
|
|||||||
* 加入会议
|
* 加入会议
|
||||||
*/
|
*/
|
||||||
function handleJoinMeeting() {
|
function handleJoinMeeting() {
|
||||||
|
console.log('[MeetingCard] handleJoinMeeting 被点击', {
|
||||||
|
meetingId: props.meeting.meetingId,
|
||||||
|
canJoin: canJoinMeeting.value,
|
||||||
|
meeting: props.meeting
|
||||||
|
})
|
||||||
|
|
||||||
if (!props.meeting.meetingId) {
|
if (!props.meeting.meetingId) {
|
||||||
|
console.error('[MeetingCard] 会议ID不存在')
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: '会议ID不存在',
|
title: '会议ID不存在',
|
||||||
icon: 'none'
|
icon: 'none'
|
||||||
@@ -203,9 +210,14 @@ function handleJoinMeeting() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!canJoinMeeting.value) {
|
if (!canJoinMeeting.value) {
|
||||||
|
console.warn('[MeetingCard] 不允许加入会议', {
|
||||||
|
status: props.meeting.status,
|
||||||
|
canJoin: canJoinMeeting.value
|
||||||
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log('[MeetingCard] 触发join事件, meetingId:', props.meeting.meetingId)
|
||||||
emit('join', props.meeting.meetingId)
|
emit('join', props.meeting.meetingId)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -0,0 +1,171 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||||
|
<title>视频会议</title>
|
||||||
|
<style>
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
html, body {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
#jitsi-container {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="jitsi-container"></div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// 从URL参数获取Jitsi会议链接
|
||||||
|
function getQueryParam(name) {
|
||||||
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
|
return urlParams.get(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
const jitsiUrl = getQueryParam('url');
|
||||||
|
if (!jitsiUrl) {
|
||||||
|
console.error('未提供Jitsi会议URL');
|
||||||
|
alert('会议链接无效');
|
||||||
|
} else {
|
||||||
|
console.log('加载Jitsi会议:', jitsiUrl);
|
||||||
|
|
||||||
|
// 解析Jitsi URL获取域名和房间名
|
||||||
|
try {
|
||||||
|
const url = new URL(jitsiUrl);
|
||||||
|
const domain = url.hostname;
|
||||||
|
const roomName = url.pathname.substring(1); // 去掉开头的 /
|
||||||
|
|
||||||
|
console.log('Jitsi域名:', domain);
|
||||||
|
console.log('房间名:', roomName);
|
||||||
|
|
||||||
|
// 动态加载Jitsi External API
|
||||||
|
const script = document.createElement('script');
|
||||||
|
script.src = `https://${domain}/external_api.js`;
|
||||||
|
script.onload = function() {
|
||||||
|
console.log('Jitsi API加载成功');
|
||||||
|
initJitsi(domain, roomName);
|
||||||
|
};
|
||||||
|
script.onerror = function() {
|
||||||
|
console.error('Jitsi API加载失败');
|
||||||
|
// 如果API加载失败,直接用iframe加载
|
||||||
|
loadJitsiIframe(jitsiUrl);
|
||||||
|
};
|
||||||
|
document.head.appendChild(script);
|
||||||
|
} catch (e) {
|
||||||
|
console.error('解析Jitsi URL失败:', e);
|
||||||
|
// 解析失败,直接用iframe加载
|
||||||
|
loadJitsiIframe(jitsiUrl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用Jitsi External API初始化会议
|
||||||
|
function initJitsi(domain, roomName) {
|
||||||
|
const options = {
|
||||||
|
roomName: roomName,
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
parentNode: document.querySelector('#jitsi-container'),
|
||||||
|
interfaceConfigOverwrite: {
|
||||||
|
SHOW_JITSI_WATERMARK: false,
|
||||||
|
SHOW_WATERMARK_FOR_GUESTS: false,
|
||||||
|
TOOLBAR_BUTTONS: [
|
||||||
|
'microphone', 'camera', 'closedcaptions', 'desktop', 'fullscreen',
|
||||||
|
'fodeviceselection', 'hangup', 'profile', 'chat', 'recording',
|
||||||
|
'livestreaming', 'etherpad', 'sharedvideo', 'settings', 'raisehand',
|
||||||
|
'videoquality', 'filmstrip', 'invite', 'feedback', 'stats', 'shortcuts',
|
||||||
|
'tileview', 'videobackgroundblur', 'download', 'help', 'mute-everyone'
|
||||||
|
]
|
||||||
|
},
|
||||||
|
configOverwrite: {
|
||||||
|
startWithAudioMuted: false,
|
||||||
|
startWithVideoMuted: false
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const api = new JitsiMeetExternalAPI(domain, options);
|
||||||
|
|
||||||
|
// 监听会议事件
|
||||||
|
api.addEventListener('videoConferenceJoined', function(event) {
|
||||||
|
console.log('用户加入会议:', event);
|
||||||
|
sendMessageToUniApp({ event: 'videoConferenceJoined', data: event });
|
||||||
|
});
|
||||||
|
|
||||||
|
api.addEventListener('videoConferenceLeft', function(event) {
|
||||||
|
console.log('用户离开会议:', event);
|
||||||
|
sendMessageToUniApp({ event: 'videoConferenceLeft', data: event });
|
||||||
|
});
|
||||||
|
|
||||||
|
api.addEventListener('participantLeft', function(event) {
|
||||||
|
console.log('参与者离开:', event);
|
||||||
|
sendMessageToUniApp({ event: 'participantLeft', data: event });
|
||||||
|
});
|
||||||
|
|
||||||
|
api.addEventListener('readyToClose', function() {
|
||||||
|
console.log('会议准备关闭');
|
||||||
|
sendMessageToUniApp({ event: 'readyToClose' });
|
||||||
|
});
|
||||||
|
|
||||||
|
// 保存API实例供外部调用
|
||||||
|
window.jitsiApi = api;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用普通iframe加载(fallback方案)
|
||||||
|
function loadJitsiIframe(url) {
|
||||||
|
console.log('使用iframe加载Jitsi');
|
||||||
|
const iframe = document.createElement('iframe');
|
||||||
|
iframe.src = url;
|
||||||
|
iframe.style.width = '100%';
|
||||||
|
iframe.style.height = '100%';
|
||||||
|
iframe.style.border = 'none';
|
||||||
|
iframe.allow = 'camera; microphone; fullscreen; display-capture';
|
||||||
|
document.getElementById('jitsi-container').appendChild(iframe);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发送消息给uni-app
|
||||||
|
function sendMessageToUniApp(message) {
|
||||||
|
console.log('发送消息给uni-app:', message);
|
||||||
|
|
||||||
|
// 尝试多种方式发送消息
|
||||||
|
if (window.uni && window.uni.postMessage) {
|
||||||
|
// uni-app环境
|
||||||
|
window.uni.postMessage({
|
||||||
|
data: message
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers.uni) {
|
||||||
|
// iOS环境
|
||||||
|
window.webkit.messageHandlers.uni.postMessage(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (window.parent !== window) {
|
||||||
|
// iframe环境
|
||||||
|
window.parent.postMessage(message, '*');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 监听来自uni-app的消息
|
||||||
|
window.addEventListener('message', function(event) {
|
||||||
|
console.log('收到消息:', event.data);
|
||||||
|
|
||||||
|
if (event.data && event.data.action) {
|
||||||
|
switch (event.data.action) {
|
||||||
|
case 'hangup':
|
||||||
|
if (window.jitsiApi) {
|
||||||
|
window.jitsiApi.executeCommand('hangup');
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -1 +0,0 @@
|
|||||||
{"code":"import {} from \"vue\";\nexport default defineComponent({\n onLaunch: function () {\n uni.__f__('log', 'at App.uvue:7', 'App Launch');\n // 检查是否已选择模式\n this.checkModeSelection();\n },\n onShow: function () {\n uni.__f__('log', 'at App.uvue:12', 'App Show');\n },\n onHide: function () {\n uni.__f__('log', 'at App.uvue:15', 'App Hide');\n },\n onExit: function () {\n uni.__f__('log', 'at App.uvue:36', 'App Exit');\n },\n methods: {\n // 检查并选择模式\n checkModeSelection() {\n const mode = uni.getStorageSync('userMode');\n if (!mode) {\n this.showModeSelector();\n }\n },\n // 显示模式选择器\n showModeSelector() {\n uni.showActionSheet({\n itemList: ['员工模式 (17857100375)', '访客模式 (17857100376)'],\n success: (res) => {\n let wechatId = '';\n let userMode = '';\n let phone = '';\n if (res.tapIndex === 0) {\n wechatId = '17857100375';\n phone = '17857100375';\n userMode = 'staff';\n }\n else {\n wechatId = '17857100376';\n phone = '17857100376';\n userMode = 'guest';\n }\n // 存储选择\n uni.setStorageSync('userMode', userMode);\n uni.setStorageSync('wechatId', wechatId);\n uni.setStorageSync('phone', phone);\n uni.__f__('log', 'at App.uvue:67', '已选择模式:', userMode, 'wechatId:', wechatId);\n uni.showToast({\n title: userMode === 'staff' ? '员工模式' : '访客模式',\n icon: 'success'\n });\n },\n fail: () => {\n // 用户取消,默认使用访客模式\n uni.setStorageSync('userMode', 'guest');\n uni.setStorageSync('wechatId', '17857100376');\n uni.__f__('log', 'at App.uvue:77', '默认使用访客模式');\n }\n });\n }\n }\n});\n//# sourceMappingURL=F:/Project/urbanLifeline/urbanLifelineWeb/packages/workcase_wechat/App.uvue?vue&type=script&lang.uts.js.map","references":[],"uniExtApis":["uni.__f__","uni.getStorageSync","uni.setStorageSync","uni.showToast","uni.showActionSheet"],"map":"{\"version\":3,\"file\":\"App.uvue?vue&type=script&lang.uts.js\",\"sourceRoot\":\"\",\"sources\":[\"App.uvue?vue&type=script&lang.uts\"],\"names\":[],\"mappings\":\";AAIC,+BAAe;IACd,QAAQ,EAAE;QACT,GAAG,CAAC,KAAK,CAAC,KAAK,EAAC,eAAe,EAAC,YAAY,CAAC,CAAA;QAC7C,YAAY;QACZ,IAAI,CAAC,kBAAkB,EAAE,CAAA;IAC1B,CAAC;IACD,MAAM,EAAE;QACP,GAAG,CAAC,KAAK,CAAC,KAAK,EAAC,gBAAgB,EAAC,UAAU,CAAC,CAAA;IAC7C,CAAC;IACD,MAAM,EAAE;QACP,GAAG,CAAC,KAAK,CAAC,KAAK,EAAC,gBAAgB,EAAC,UAAU,CAAC,CAAA;IAC7C,CAAC;IAmBD,MAAM,EAAE;QACP,GAAG,CAAC,KAAK,CAAC,KAAK,EAAC,gBAAgB,EAAC,UAAU,CAAC,CAAA;IAC7C,CAAC;IACD,OAAO,EAAE;QACR,UAAU;QACV,kBAAkB;YACjB,MAAM,IAAI,GAAG,GAAG,CAAC,cAAc,CAAC,UAAU,CAAC,CAAA;YAC3C,IAAI,CAAC,IAAI,EAAE;gBACV,IAAI,CAAC,gBAAgB,EAAE,CAAA;aACvB;QACF,CAAC;QACD,UAAU;QACV,gBAAgB;YACf,GAAG,CAAC,eAAe,CAAC;gBACnB,QAAQ,EAAE,CAAC,oBAAoB,EAAE,oBAAoB,CAAC;gBACtD,OAAO,EAAE,CAAC,GAAG;oBACZ,IAAI,QAAQ,GAAG,EAAE,CAAA;oBACjB,IAAI,QAAQ,GAAG,EAAE,CAAA;oBACjB,IAAI,KAAK,GAAG,EAAE,CAAA;oBACd,IAAI,GAAG,CAAC,QAAQ,KAAK,CAAC,EAAE;wBACvB,QAAQ,GAAG,aAAa,CAAA;wBACxB,KAAK,GAAG,aAAa,CAAA;wBACrB,QAAQ,GAAG,OAAO,CAAA;qBAClB;yBAAM;wBACN,QAAQ,GAAG,aAAa,CAAA;wBACxB,KAAK,GAAG,aAAa,CAAA;wBACrB,QAAQ,GAAG,OAAO,CAAA;qBAClB;oBACD,OAAO;oBACP,GAAG,CAAC,cAAc,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAA;oBACxC,GAAG,CAAC,cAAc,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAA;oBACxC,GAAG,CAAC,cAAc,CAAC,OAAO,EAAE,KAAK,CAAC,CAAA;oBAClC,GAAG,CAAC,KAAK,CAAC,KAAK,EAAC,gBAAgB,EAAC,QAAQ,EAAE,QAAQ,EAAE,WAAW,EAAE,QAAQ,CAAC,CAAA;oBAC3E,GAAG,CAAC,SAAS,CAAC;wBACb,KAAK,EAAE,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM;wBAC7C,IAAI,EAAE,SAAS;qBACf,CAAC,CAAA;gBACH,CAAC;gBACD,IAAI,EAAE;oBACL,gBAAgB;oBAChB,GAAG,CAAC,cAAc,CAAC,UAAU,EAAE,OAAO,CAAC,CAAA;oBACvC,GAAG,CAAC,cAAc,CAAC,UAAU,EAAE,aAAa,CAAC,CAAA;oBAC7C,GAAG,CAAC,KAAK,CAAC,KAAK,EAAC,gBAAgB,EAAC,UAAU,CAAC,CAAA;gBAC7C,CAAC;aACD,CAAC,CAAA;QACH,CAAC;KACD;CACD,EAAA\"}"}
|
|
||||||
Reference in New Issue
Block a user