temp jitsi

This commit is contained in:
2025-12-26 10:37:52 +08:00
parent e39dc03f92
commit c2b37503fc
22 changed files with 1710 additions and 416 deletions

View File

@@ -0,0 +1,45 @@
.meeting-page {
width: 100%;
height: 100vh;
background: #000;
}
.meeting-nav {
position: fixed;
top: 0;
left: 0;
right: 0;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 16px;
background: rgba(0, 0, 0, 0.8);
z-index: 1000;
.nav-back {
width: 40px;
height: 40px;
display: flex;
align-items: center;
justify-content: center;
.back-icon {
color: #fff;
font-size: 24px;
}
}
.nav-title {
color: #fff;
font-size: 16px;
font-weight: 500;
}
.nav-right {
.end-btn {
color: #ff4444;
font-size: 14px;
font-weight: 500;
}
}
}

View File

@@ -0,0 +1,117 @@
<template>
<view class="meeting-page">
<!-- 自定义导航栏 -->
<view class="meeting-nav" :style="{ paddingTop: statusBarHeight + 'px', height: navBarHeight + 'px' }">
<view class="nav-back" @tap="confirmExit">
<text class="back-icon">←</text>
</view>
<text class="nav-title">视频会议</text>
<view class="nav-right" @tap="endMeeting">
<text class="end-btn">结束会议</text>
</view>
</view>
<!-- Web-view加载Jitsi Meet -->
<web-view
:src="meetingUrl"
:webview-styles="webviewStyles"
@message="handleWebViewMessage"
@error="handleWebViewError"
></web-view>
</view>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { workcaseChatAPI } from '@/api/workcase'
const statusBarHeight = ref(44)
const navBarHeight = ref(88)
const meetingUrl = ref('')
const meetingId = ref('')
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
if (currentPage && currentPage.options) {
meetingUrl.value = decodeURIComponent(currentPage.options.meetingUrl || '')
meetingId.value = currentPage.options.meetingId || ''
}
console.log('[MeetingView] 会议页面加载:', {
meetingId: meetingId.value,
meetingUrl: meetingUrl.value
})
})
// 确认退出
function confirmExit() {
uni.showModal({
title: '提示',
content: '确定要退出会议吗?',
success: (res) => {
if (res.confirm) {
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()
uni.showToast({ title: '会议已结束', icon: 'success' })
setTimeout(() => uni.navigateBack(), 1500)
} catch (e) {
uni.hideLoading()
console.error('[MeetingView] 结束会议失败:', e)
uni.showToast({ title: '结束会议失败', icon: 'none' })
uni.navigateBack()
}
}
}
})
}
// 处理webview消息
function handleWebViewMessage(e: any) {
console.log('[MeetingView] webview消息:', e)
// 可以在这里处理Jitsi Meet发送的消息
// 例如:会议结束、参与者加入/离开等事件
}
// 处理webview错误
function handleWebViewError(e: any) {
console.error('[MeetingView] webview错误:', e)
uni.showToast({ title: '会议加载失败', icon: 'none' })
}
</script>
<style lang="scss" scoped>
@import url("./Meeting.scss")
</style>

View File

@@ -1,184 +0,0 @@
.page {
min-height: 100vh;
background: #f4f5f7;
}
.nav {
position: fixed;
top: 0;
left: 0;
right: 0;
background: #fff;
display: flex;
flex-direction: row;
align-items: flex-end;
padding-left: 24rpx;
padding-right: 24rpx;
padding-bottom: 16rpx;
box-sizing: border-box;
z-index: 100;
}
.nav-back {
width: 60rpx;
height: 64rpx;
display: flex;
align-items: center;
justify-content: center;
}
.nav-back-icon {
width: 20rpx;
height: 20rpx;
border-left: 4rpx solid #333;
border-bottom: 4rpx solid #333;
transform: rotate(45deg);
}
.nav-title {
flex: 1;
font-size: 34rpx;
font-weight: 500;
color: #333;
text-align: center;
line-height: 64rpx;
}
.nav-capsule {
width: 174rpx;
height: 64rpx;
}
.meeting-container {
margin-top: 176rpx;
padding: 48rpx 32rpx;
min-height: calc(100vh - 176rpx);
display: flex;
flex-direction: column;
}
.meeting-info {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 80rpx 40rpx;
background: #fff;
border-radius: 20rpx;
}
.meeting-icon {
width: 200rpx;
height: 200rpx;
border-radius: 50%;
background: linear-gradient(145deg, #e8f7ff 0%, #c5e4ff 100%);
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 48rpx;
box-shadow: 0 10rpx 40rpx rgba(180,220,255,0.5);
}
.icon-text {
font-size: 96rpx;
}
.meeting-name {
font-size: 44rpx;
font-weight: 900;
color: #1d72d3;
margin-bottom: 24rpx;
}
.meeting-desc {
font-size: 28rpx;
color: #999;
margin-bottom: 64rpx;
}
.meeting-actions {
margin-bottom: 80rpx;
}
.join-btn {
height: 96rpx;
padding: 0 60rpx;
background: linear-gradient(90deg, #173294 0%, #4a6fd9 100%);
border-radius: 48rpx;
display: flex;
align-items: center;
justify-content: center;
}
.join-text {
font-size: 32rpx;
font-weight: 600;
color: #fff;
}
.meeting-tips {
display: flex;
flex-direction: column;
gap: 16rpx;
padding: 32rpx;
background: #f5f8ff;
border-radius: 16rpx;
width: 100%;
box-sizing: border-box;
}
.tip-item {
font-size: 26rpx;
color: #666;
line-height: 1.6;
}
.in-meeting {
flex: 1;
display: flex;
flex-direction: column;
}
.meeting-webview {
flex: 1;
width: 100%;
border-radius: 20rpx;
overflow: hidden;
}
.meeting-controls {
display: flex;
justify-content: center;
gap: 48rpx;
padding: 32rpx;
background: #fff;
border-radius: 20rpx;
margin-top: 24rpx;
}
.control-btn {
display: flex;
flex-direction: column;
align-items: center;
gap: 12rpx;
padding: 24rpx 40rpx;
background: #f5f8ff;
border-radius: 16rpx;
}
.control-btn.active {
background: #fff7e6;
}
.leave-btn {
background: #fff1f0;
}
.control-icon {
font-size: 48rpx;
}
.control-label {
font-size: 24rpx;
color: #666;
}

View File

@@ -1,191 +0,0 @@
<template>
<!-- #ifdef APP -->
<scroll-view style="flex:1">
<!-- #endif -->
<view class="page">
<!-- 自定义导航栏 -->
<view class="nav" :style="{ paddingTop: headerPaddingTop + 'px', height: headerTotalHeight + 'px' }">
<view class="nav-back" @tap="goBack">
<view class="nav-back-icon"></view>
</view>
<text class="nav-title">视频会议</text>
<view class="nav-capsule"></view>
</view>
<!-- 会议内容区 -->
<view class="meeting-container" :style="{ marginTop: headerTotalHeight + 'px' }">
<!-- 会议信息 -->
<view class="meeting-info" v-if="!isInMeeting">
<view class="meeting-icon">
<text class="icon-text">📹</text>
</view>
<text class="meeting-name">{{ meetingName || '视频会议' }}</text>
<text class="meeting-desc">与客服进行实时视频沟通</text>
<view class="meeting-actions">
<view class="join-btn" @tap="joinMeeting">
<text class="join-text">加入会议</text>
</view>
</view>
<view class="meeting-tips">
<text class="tip-item">• 请确保网络连接稳定</text>
<text class="tip-item">• 允许摄像头和麦克风权限</text>
<text class="tip-item">• 建议在安静环境下进行会议</text>
</view>
</view>
<!-- 会议中状态 -->
<view class="in-meeting" v-else>
<!-- Jitsi Meet iframe 容器 -->
<web-view v-if="iframeUrl" :src="iframeUrl" class="meeting-webview"></web-view>
<!-- 会议控制栏 -->
<view class="meeting-controls">
<view class="control-btn" :class="{ active: isMuted }" @tap="toggleMute">
<text class="control-icon">{{ isMuted ? '🔇' : '🔊' }}</text>
<text class="control-label">{{ isMuted ? '取消静音' : '静音' }}</text>
</view>
<view class="control-btn" :class="{ active: isVideoOff }" @tap="toggleVideo">
<text class="control-icon">{{ isVideoOff ? '📷' : '📹' }}</text>
<text class="control-label">{{ isVideoOff ? '开启视频' : '关闭视频' }}</text>
</view>
<view class="control-btn leave-btn" @tap="leaveMeeting">
<text class="control-icon">📞</text>
<text class="control-label">离开会议</text>
</view>
</view>
</view>
</view>
</view>
<!-- #ifdef APP -->
</scroll-view>
<!-- #endif -->
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import type { VideoMeetingVO } from '@/types/workcase'
// 响应式数据
const headerPaddingTop = ref<number>(44)
const headerTotalHeight = ref<number>(88)
const roomId = ref<string>('')
const workcaseId = ref<string>('')
const meetingName = ref<string>('视频会议')
const isInMeeting = ref<boolean>(false)
const iframeUrl = ref<string>('')
const isMuted = ref<boolean>(false)
const isVideoOff = ref<boolean>(false)
// 会议信息
const meeting = ref<VideoMeetingVO>({})
// 生命周期
onMounted(() => {
uni.getSystemInfo({
success: (res) => {
// #ifdef MP-WEIXIN
try {
const menuButtonInfo = uni.getMenuButtonBoundingClientRect()
headerPaddingTop.value = menuButtonInfo.top
headerTotalHeight.value = menuButtonInfo.bottom + 8
} catch (e) {
headerPaddingTop.value = res.statusBarHeight || 44
headerTotalHeight.value = (res.statusBarHeight || 44) + 44
}
// #endif
// #ifndef MP-WEIXIN
headerPaddingTop.value = res.statusBarHeight || 44
headerTotalHeight.value = (res.statusBarHeight || 44) + 44
// #endif
}
})
// 获取页面参数
const pages = getCurrentPages()
const currentPage = pages[pages.length - 1] as any
if (currentPage && currentPage.options) {
roomId.value = currentPage.options.roomId || ''
workcaseId.value = currentPage.options.workcaseId || ''
}
loadMeetingInfo()
})
// 加载会议信息
function loadMeetingInfo() {
console.log('加载会议信息:', roomId.value)
// TODO: 调用 workcaseChatAPI 获取会议信息
}
// 加入会议
function joinMeeting() {
uni.showLoading({ title: '正在加入会议...' })
// 模拟加入会议
setTimeout(() => {
uni.hideLoading()
isInMeeting.value = true
// TODO: 实际调用API创建/加入会议获取iframeUrl
// iframeUrl.value = meeting.value.iframeUrl || ''
uni.showToast({
title: '已加入会议',
icon: 'success'
})
}, 1000)
}
// 离开会议
function leaveMeeting() {
uni.showModal({
title: '离开会议',
content: '确定要离开当前会议吗?',
success: (res) => {
if (res.confirm) {
isInMeeting.value = false
iframeUrl.value = ''
uni.showToast({
title: '已离开会议',
icon: 'success'
})
}
}
})
}
// 切换静音
function toggleMute() {
isMuted.value = !isMuted.value
// TODO: 调用Jitsi API控制静音
}
// 切换视频
function toggleVideo() {
isVideoOff.value = !isVideoOff.value
// TODO: 调用Jitsi API控制视频
}
// 返回上一页
function goBack() {
if (isInMeeting.value) {
uni.showModal({
title: '离开会议',
content: '返回将离开当前会议,确定吗?',
success: (res) => {
if (res.confirm) {
uni.navigateBack()
}
}
})
} else {
uni.navigateBack()
}
}
</script>
<style lang="scss" scoped>
@import "./Meeting.scss";
</style>