feat:更新白板
This commit is contained in:
@@ -6,7 +6,8 @@ VITE_APP_PORT = 80
|
||||
VITE_BASE_PATH = '/'
|
||||
|
||||
# 应用模板管理后台/生产环境
|
||||
VITE_APP_BASE_API = 'https://xsynergy.gxtech.ltd'
|
||||
# VITE_APP_BASE_API = 'https://xsynergy.gxtech.ltd'
|
||||
VITE_APP_BASE_API = ''
|
||||
|
||||
# 公网为“web” 私有化为不跳转为“private” 私有化跳转为“skip”
|
||||
VITE_APP_COOPERATION_TYPE = 'skip'
|
||||
|
||||
@@ -11,10 +11,27 @@ import router from '@/router';
|
||||
import { useMeterStore } from '@/stores/modules/meter.js'
|
||||
|
||||
axios.defaults.headers["Content-Type"] = "application/json;charset=utf-8";
|
||||
|
||||
// 动态获取 baseURL
|
||||
const getBaseURL = () => {
|
||||
// 开发环境使用配置的完整 URL
|
||||
if (import.meta.env.DEV) {
|
||||
return import.meta.env.VITE_APP_BASE_API;
|
||||
}
|
||||
|
||||
// 生产环境使用相对路径
|
||||
// 返回空字符串,让浏览器自动使用当前域名
|
||||
return '';
|
||||
|
||||
// 或者如果后端 API 有固定路径前缀,可以这样设置:
|
||||
// return '/api'; // 这样请求会变成 https://当前域名/api/xxx
|
||||
};
|
||||
|
||||
// 创建axios实例
|
||||
const service = axios.create({
|
||||
// axios中请求配置有baseURL选项,表示请求URL公共部分
|
||||
baseURL: import.meta.env.VITE_APP_BASE_API,
|
||||
// baseURL: import.meta.env.VITE_APP_BASE_API,
|
||||
baseURL: getBaseURL(),
|
||||
// 超时
|
||||
// timeout: 10000,
|
||||
});
|
||||
|
||||
@@ -11,24 +11,16 @@
|
||||
<div v-if="!status" class="meeting-container">
|
||||
<!-- 视频容器 -->
|
||||
<div class="video-layout" :class="{
|
||||
'screen-sharing-active': hasActiveScreenShare || isWhiteboardActive || enlargedParticipant,
|
||||
'screen-sharing-active': hasActiveContent,
|
||||
'enlarged-mode': enlargedParticipant
|
||||
}">
|
||||
<!-- 左侧共享屏幕/白板/放大视频区域 -->
|
||||
<div class="screen-share-area" v-if="hasActiveScreenShare || isWhiteboardActive || enlargedParticipant">
|
||||
<div class="screen-share-area" v-if="hasActiveContent">
|
||||
<div class="screen-share-header">
|
||||
<h3 v-if="enlargedParticipant">
|
||||
<span v-if="enlargedParticipant.identity === hostUid">我的放大视频</span>
|
||||
<span v-else>放大视图 - {{ enlargedParticipant.identity }}</span>
|
||||
</h3>
|
||||
<h3 v-else-if="isWhiteboardActive">共享白板</h3>
|
||||
<h3 v-else>共享屏幕</h3>
|
||||
<div class="sharing-user" v-if="!enlargedParticipant">
|
||||
<span v-if="isWhiteboardActive || hasActiveScreenShare">
|
||||
由 {{ screenSharingUser }} 共享
|
||||
</span>
|
||||
<h3>{{ currentTopLayerTitle }}</h3>
|
||||
<div class="sharing-user" v-if="screenSharingUser">
|
||||
<span>由 {{ screenSharingUser }} 共享</span>
|
||||
</div>
|
||||
<!-- v-if="enlargedParticipant && enlargedParticipant.identity === hostUid" -->
|
||||
<el-button
|
||||
v-if="enlargedParticipant && enlargedParticipant.identity === hostUid"
|
||||
@click="closeEnlargedView"
|
||||
@@ -36,100 +28,110 @@
|
||||
size="small"
|
||||
class="close-enlarge-btn"
|
||||
>
|
||||
<!-- 关闭放大 -->
|
||||
<img src="@/assets/images/shrink.png" style='width:16px;height:15px' alt="" />
|
||||
</el-button>
|
||||
</div>
|
||||
<div class="screen-share-video">
|
||||
<!-- 放大视频模式 -->
|
||||
<div v-if="enlargedParticipant" class="enlarged-video-container">
|
||||
<div class="video-wrapper enlarged-video-wrapper">
|
||||
<!-- 放大视频激光笔 Canvas -->
|
||||
<canvas
|
||||
v-if="enlargedParticipant"
|
||||
ref="enlargedLaserPointerCanvas"
|
||||
class="laser-pointer-canvas enlarged-laser-canvas"
|
||||
@dblclick="handleEnlargedCanvasDoubleClick"
|
||||
@mousedown="handleEnlargedCanvasMouseDown"
|
||||
@mousemove="handleEnlargedCanvasMouseMove"
|
||||
@mouseup="handleEnlargedCanvasMouseUp"
|
||||
@mouseleave="handleEnlargedCanvasMouseLeave"
|
||||
></canvas>
|
||||
<video
|
||||
v-if="enlargedParticipant.hasCameraTrack"
|
||||
:ref="el => setEnlargedVideoRef(el)"
|
||||
autoplay
|
||||
playsinline
|
||||
class="enlarged-video-element"
|
||||
@loadedmetadata="handleEnlargedVideoLoaded">
|
||||
</video>
|
||||
<!-- 如果没有视频轨道,显示提示 -->
|
||||
<div v-if="!enlargedParticipant.hasCameraTrack" class="video-placeholder">
|
||||
<i class="el-icon-user"></i>
|
||||
<span>暂无视频流</span>
|
||||
<!-- 内容层级容器 -->
|
||||
<div class="content-layers-container">
|
||||
<!-- 桌面共享或放大视频层 -->
|
||||
<div v-if="hasScreenShareOrEnlarged"
|
||||
class="content-layer screen-video-layer"
|
||||
:class="{ 'active-layer': isScreenVideoTopLayer }">
|
||||
|
||||
<!-- 放大视频模式 -->
|
||||
<div v-if="enlargedParticipant" class="enlarged-video-container">
|
||||
<div class="video-wrapper enlarged-video-wrapper">
|
||||
<!-- 放大视频激光笔 Canvas -->
|
||||
<canvas
|
||||
v-if="enlargedParticipant"
|
||||
ref="enlargedLaserPointerCanvas"
|
||||
class="laser-pointer-canvas enlarged-laser-canvas"
|
||||
@dblclick="handleEnlargedCanvasDoubleClick"
|
||||
@mousedown="handleEnlargedCanvasMouseDown"
|
||||
@mousemove="handleEnlargedCanvasMouseMove"
|
||||
@mouseup="handleEnlargedCanvasMouseUp"
|
||||
@mouseleave="handleEnlargedCanvasMouseLeave"
|
||||
></canvas>
|
||||
<video
|
||||
v-if="enlargedParticipant.hasCameraTrack"
|
||||
:ref="el => setEnlargedVideoRef(el)"
|
||||
autoplay
|
||||
playsinline
|
||||
class="enlarged-video-element"
|
||||
@loadedmetadata="handleEnlargedVideoLoaded">
|
||||
</video>
|
||||
<!-- 如果没有视频轨道,显示提示 -->
|
||||
<div v-if="!enlargedParticipant.hasCameraTrack" class="video-placeholder">
|
||||
<i class="el-icon-user"></i>
|
||||
<span>暂无视频流</span>
|
||||
</div>
|
||||
<div class="video-overlay">
|
||||
<span class="participant-name">
|
||||
{{ enlargedParticipant.identity === hostUid ? '我' : enlargedParticipant.identity }}
|
||||
</span>
|
||||
<span class="audio-indicator" :class="{ 'muted': !enlargedParticipant.audioEnabled }">
|
||||
<i :class="enlargedParticipant.audioEnabled ? 'el-icon-microphone' : 'el-icon-turn-off-microphone'"></i>
|
||||
</span>
|
||||
<!-- 激光笔状态指示 -->
|
||||
<span v-if="isLaserPointerActive && enlargedParticipant" class="laser-pointer-indicator">
|
||||
<i class="el-icon-aim"></i> 激光笔模式中
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="video-overlay">
|
||||
<span class="participant-name">
|
||||
{{ enlargedParticipant.identity === hostUid ? '我' : enlargedParticipant.identity }}
|
||||
</span>
|
||||
<span class="audio-indicator" :class="{ 'muted': !enlargedParticipant.audioEnabled }">
|
||||
<i :class="enlargedParticipant.audioEnabled ? 'el-icon-microphone' : 'el-icon-turn-off-microphone'"></i>
|
||||
</span>
|
||||
<!-- 激光笔状态指示 -->
|
||||
<span v-if="isLaserPointerActive && enlargedParticipant" class="laser-pointer-indicator">
|
||||
<i class="el-icon-aim"></i> 激光笔模式中
|
||||
</span>
|
||||
|
||||
<!-- 屏幕共享模式 -->
|
||||
<div v-else class="video-wrapper screen-share-wrapper">
|
||||
<!-- 激光笔 Canvas -->
|
||||
<canvas
|
||||
ref="laserPointerCanvas"
|
||||
class="laser-pointer-canvas"
|
||||
@dblclick="handleCanvasDoubleClick"
|
||||
@mousedown="handleCanvasMouseDown"
|
||||
@mousemove="handleCanvasMouseMove"
|
||||
@mouseup="handleCanvasMouseUp"
|
||||
@mouseleave="handleCanvasMouseLeave"
|
||||
></canvas>
|
||||
<div class="video-tracks">
|
||||
<!-- 屏幕共享视频 -->
|
||||
<video
|
||||
v-if="activeScreenShareTrack"
|
||||
:ref="el => setScreenShareVideoRef(el)"
|
||||
autoplay
|
||||
playsinline
|
||||
class="screen-share-element"
|
||||
style='border:1px solid red'
|
||||
@loadedmetadata="handleScreenShareLoaded">
|
||||
</video>
|
||||
<!-- 如果没有屏幕共享,显示提示 -->
|
||||
<div v-if="!activeScreenShareTrack" class="video-placeholder">
|
||||
<i class="el-icon-monitor"></i>
|
||||
<span>暂无屏幕共享</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="video-overlay">
|
||||
<span class="participant-name">{{ screenSharingUser }}</span>
|
||||
<!-- 激光笔状态指示 -->
|
||||
<span v-if="isLaserPointerActive" class="laser-pointer-indicator">
|
||||
<i class="el-icon-aim"></i> 激光笔模式中
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 白板模式 -->
|
||||
<div v-else-if="isWhiteboardActive" class="whiteboard-container">
|
||||
<tabulaRase
|
||||
ref="whiteboardRef"
|
||||
:roomId="roomId"
|
||||
:userId="hostUid"
|
||||
class="whiteboard-component"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 屏幕共享模式 -->
|
||||
<div v-else class="video-wrapper screen-share-wrapper">
|
||||
<!-- 激光笔 Canvas -->
|
||||
<canvas
|
||||
ref="laserPointerCanvas"
|
||||
class="laser-pointer-canvas"
|
||||
@dblclick="handleCanvasDoubleClick"
|
||||
@mousedown="handleCanvasMouseDown"
|
||||
@mousemove="handleCanvasMouseMove"
|
||||
@mouseup="handleCanvasMouseUp"
|
||||
@mouseleave="handleCanvasMouseLeave"
|
||||
></canvas>
|
||||
<div class="video-tracks">
|
||||
<!-- 屏幕共享视频 -->
|
||||
<video
|
||||
v-if="activeScreenShareTrack"
|
||||
:ref="el => setScreenShareVideoRef(el)"
|
||||
autoplay
|
||||
playsinline
|
||||
class="screen-share-element"
|
||||
@loadedmetadata="handleScreenShareLoaded">
|
||||
</video>
|
||||
<!-- 如果没有屏幕共享,显示提示 -->
|
||||
<div v-if="!activeScreenShareTrack" class="video-placeholder">
|
||||
<i class="el-icon-monitor"></i>
|
||||
<span>暂无屏幕共享</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="video-overlay">
|
||||
<span class="participant-name">{{ screenSharingUser }}</span>
|
||||
<!-- 激光笔状态指示 -->
|
||||
<span v-if="isLaserPointerActive" class="laser-pointer-indicator">
|
||||
<i class="el-icon-aim"></i> 激光笔模式中
|
||||
</span>
|
||||
<!-- 白板层 -->
|
||||
<div v-if="isWhiteboardActive"
|
||||
class="content-layer whiteboard-layer"
|
||||
:class="{ 'active-layer': isWhiteboardTopLayer }">
|
||||
<tabulaRase
|
||||
ref="whiteboardRef"
|
||||
:roomId="roomId"
|
||||
:userId="hostUid"
|
||||
class="whiteboard-component"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -212,7 +214,7 @@
|
||||
</div>
|
||||
<div class="video-wrapper">
|
||||
<div class="video-tracks">
|
||||
<!-- 摄像头视频 - 使用 ref 绑定 -->
|
||||
<!-- 摄像头视频 -->
|
||||
<video
|
||||
v-if="participant.hasCameraTrack"
|
||||
:ref="el => setParticipantVideoRef(el, participant.identity, 'camera')"
|
||||
@@ -248,6 +250,7 @@
|
||||
<!-- 固定在底部的控制按钮 -->
|
||||
<div class="fixed-controls">
|
||||
<div class="controls-container">
|
||||
<!-- 摄像头和麦克风 -->
|
||||
<div class="microphone-control-group">
|
||||
<el-button @click="toggleCamera" :type="cameraEnabled ? 'danger' : 'info'" class="control-btn microphone-btn" size="large">
|
||||
{{ cameraEnabled ? '关闭摄像头' : '开启摄像头' }}
|
||||
@@ -306,6 +309,8 @@
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</div>
|
||||
|
||||
<!-- 屏幕共享按钮 -->
|
||||
<el-button
|
||||
@click="toggleScreenShare"
|
||||
:type="isScreenSharing ? 'danger' : (isGlobalScreenSharing ? 'primary' : 'info')"
|
||||
@@ -317,10 +322,11 @@
|
||||
<span v-else-if="isGlobalScreenSharing">他人共享中</span>
|
||||
<span v-else>共享屏幕</span>
|
||||
</el-button>
|
||||
|
||||
<!-- 白板按钮 - 移除禁用条件 -->
|
||||
<el-button
|
||||
@click="toggleWhiteboard"
|
||||
:type="isWhiteboardActive ? 'danger' : 'info'"
|
||||
:disabled="cameraEnabled || !canWhiteboardShare"
|
||||
class="control-btn"
|
||||
size="large"
|
||||
>
|
||||
@@ -438,6 +444,8 @@ const enlargedVideo = ref(null); // 放大视频元素引用
|
||||
const enlargedLaserPointerCanvas = ref(null); // 放大视频激光笔 Canvas
|
||||
const enlargedLaserPointerContext = ref(null); // 放大视频激光笔上下文
|
||||
|
||||
const lastActivatedLayer = ref(''); // 记录最后激活的图层:'screenVideo' 或 'whiteboard'
|
||||
|
||||
// 路径更新节流处理
|
||||
let lastPublishTime = 0;
|
||||
const publishThrottleTime = 100; // 100ms 节流
|
||||
@@ -467,7 +475,8 @@ const laserPointerConfig = reactive({
|
||||
const WHITEBOARD_MESSAGE_TYPES = {
|
||||
OPEN: 'open_whiteboard',
|
||||
CLOSE: 'close_whiteboard',
|
||||
SYNC: 'sync_whiteboard'
|
||||
SYNC: 'sync_whiteboard',
|
||||
ACTIVATE_LAYER: 'activate_layer'//图层激活消息
|
||||
};
|
||||
// 在 script 中添加激光笔消息类型和同步功能
|
||||
const LASER_POINTER_MESSAGE_TYPES = {
|
||||
@@ -480,6 +489,52 @@ const VIDEO_ENLARGE_MESSAGE_TYPES = {
|
||||
SHRINK: 'shrink_video'
|
||||
};
|
||||
|
||||
//判断是否有活动内容
|
||||
const hasActiveContent = computed(() => {
|
||||
return hasScreenShareOrEnlarged.value || isWhiteboardActive.value;
|
||||
});
|
||||
|
||||
// 是否有桌面共享或放大视频
|
||||
const hasScreenShareOrEnlarged = computed(() => {
|
||||
return hasActiveScreenShare.value || enlargedParticipant.value;
|
||||
});
|
||||
|
||||
// 判断各图层是否为顶层
|
||||
const isScreenVideoTopLayer = computed(() => {
|
||||
return hasScreenShareOrEnlarged.value && lastActivatedLayer.value === 'screenVideo';
|
||||
});
|
||||
|
||||
const isWhiteboardTopLayer = computed(() => {
|
||||
return isWhiteboardActive.value && lastActivatedLayer.value === 'whiteboard';
|
||||
});
|
||||
|
||||
// 当前顶层标题
|
||||
const currentTopLayerTitle = computed(() => {
|
||||
if (hasScreenShareOrEnlarged.value && isScreenVideoTopLayer.value) {
|
||||
if (enlargedParticipant.value) {
|
||||
if (enlargedParticipant.value.identity === hostUid.value) {
|
||||
return '我的放大视频';
|
||||
} else {
|
||||
return `放大视图 - ${enlargedParticipant.value.identity}`;
|
||||
}
|
||||
} else {
|
||||
return '共享屏幕';
|
||||
}
|
||||
} else if (isWhiteboardActive.value && isWhiteboardTopLayer.value) {
|
||||
return '共享白板';
|
||||
} else {
|
||||
// 默认显示第一个活动的内容
|
||||
if (hasScreenShareOrEnlarged.value) {
|
||||
if (enlargedParticipant.value) return '放大视频';
|
||||
return '共享屏幕';
|
||||
}
|
||||
if (isWhiteboardActive.value) return '共享白板';
|
||||
return '共享内容';
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
const participantCount = computed(() => {
|
||||
return remoteParticipants.value.size + 1; // 包括自己
|
||||
});
|
||||
@@ -508,7 +563,8 @@ const canScreenShare = computed(() => {
|
||||
|
||||
// 是否允许白板共享
|
||||
const canWhiteboardShare = computed(() => {
|
||||
return !enlargedParticipant.value;
|
||||
// return !enlargedParticipant.value;
|
||||
return true; // 白板现在完全独立,没有限制
|
||||
});
|
||||
|
||||
// 添加一个计算属性来显示屏幕共享状态提示
|
||||
@@ -556,6 +612,38 @@ const room = new Room({
|
||||
|
||||
emitter.on('whiteboardFailed',whiteboardFailedHandle);
|
||||
|
||||
// 激活图层函数
|
||||
function activateLayer(layerType, isLocalAction = true) {
|
||||
const oldLayer = lastActivatedLayer.value;
|
||||
lastActivatedLayer.value = layerType;
|
||||
// 如果是本地操作,通过MQTT通知其他用户
|
||||
if (isLocalAction) {
|
||||
publishLayerActivation(layerType);
|
||||
}
|
||||
}
|
||||
// 发布图层激活消息
|
||||
function publishLayerActivation(layerType) {
|
||||
try {
|
||||
const message = {
|
||||
type: WHITEBOARD_MESSAGE_TYPES.ACTIVATE_LAYER,
|
||||
roomId: roomId.value,
|
||||
sender: hostUid.value,
|
||||
senderName: hostUid.value,
|
||||
timestamp: Date.now(),
|
||||
payload: {
|
||||
layerType: layerType,
|
||||
hasScreenShare: hasActiveScreenShare.value,
|
||||
hasEnlargedVideo: !!enlargedParticipant.value,
|
||||
hasWhiteboard: isWhiteboardActive.value
|
||||
}
|
||||
};
|
||||
|
||||
mqttClient.publish(`xSynergy/shareWhiteboard/${room.name}`, message);
|
||||
} catch (error) {
|
||||
console.error('发布图层激活消息失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/** 登录成功回调 */
|
||||
function handleLoginSuccess() {
|
||||
showLogin.value = false;
|
||||
@@ -589,10 +677,10 @@ function enlargeParticipant(participant) {
|
||||
ElMessage.info('已自动停止屏幕共享,开启视频放大模式');
|
||||
}
|
||||
// 如果正在使用白板,自动退出
|
||||
if (isWhiteboardActive.value) {
|
||||
exitWhiteboard();
|
||||
ElMessage.info('已自动退出白板,开启视频放大模式');
|
||||
}
|
||||
// if (isWhiteboardActive.value) {
|
||||
// exitWhiteboard();
|
||||
// ElMessage.info('已自动退出白板,开启视频放大模式');
|
||||
// }
|
||||
|
||||
// 如果正在放大其他用户,先关闭
|
||||
if (enlargedParticipant.value && enlargedParticipant.value.identity !== hostUid.value) {
|
||||
@@ -600,6 +688,7 @@ function enlargeParticipant(participant) {
|
||||
}
|
||||
|
||||
enlargedParticipant.value = participant;
|
||||
activateLayer('screenVideo', true); // 激活屏幕视频图层
|
||||
ElMessage.success(`已放大您的视频`);
|
||||
|
||||
// 发布放大消息给其他用户
|
||||
@@ -1762,6 +1851,9 @@ function handleWhiteboardMessage(payload, topic) {
|
||||
case WHITEBOARD_MESSAGE_TYPES.SYNC:
|
||||
handleWhiteboardSync(data);
|
||||
break;
|
||||
case WHITEBOARD_MESSAGE_TYPES.ACTIVATE_LAYER:
|
||||
handleRemoteLayerActivation(data);
|
||||
break;
|
||||
default:
|
||||
console.warn('未知的白板消息类型:', data.type);
|
||||
}
|
||||
@@ -1770,19 +1862,36 @@ function handleWhiteboardMessage(payload, topic) {
|
||||
}
|
||||
}
|
||||
|
||||
// 处理远程图层激活
|
||||
function handleRemoteLayerActivation(data) {
|
||||
const { layerType, hasScreenShare, hasEnlargedVideo, hasWhiteboard } = data.payload;
|
||||
// 根据远程状态更新本地图层激活
|
||||
if (layerType === 'screenVideo' && (hasScreenShare || hasEnlargedVideo)) {
|
||||
// 只有当远程用户确实有屏幕共享或放大视频时才激活
|
||||
activateLayer('screenVideo', false);
|
||||
// console.log('远程激活屏幕视频层');
|
||||
} else if (layerType === 'whiteboard' && hasWhiteboard) {
|
||||
// 只有当远程用户确实有白板时才激活
|
||||
activateLayer('whiteboard', false);
|
||||
// console.log('远程激活白板层');
|
||||
}
|
||||
}
|
||||
|
||||
// 处理远程打开白板
|
||||
function handleRemoteWhiteboardOpen(data) {
|
||||
ElMessage.info(`${data.senderName || data.sender} 开启了白板`);
|
||||
isWhiteboardActive.value = true;
|
||||
// 当远程用户开启白板时,激活白板图层
|
||||
activateLayer('whiteboard', false);
|
||||
// 如果正在屏幕共享,自动停止
|
||||
if (isScreenSharing.value) {
|
||||
room.localParticipant.setScreenShareEnabled(false);
|
||||
isScreenSharing.value = false;
|
||||
}
|
||||
// if (isScreenSharing.value) {
|
||||
// room.localParticipant.setScreenShareEnabled(false);
|
||||
// isScreenSharing.value = false;
|
||||
// }
|
||||
// 如果正在放大视图,自动关闭
|
||||
if (enlargedParticipant.value) {
|
||||
closeEnlargedView();
|
||||
}
|
||||
// if (enlargedParticipant.value) {
|
||||
// closeEnlargedView();
|
||||
// }
|
||||
}
|
||||
|
||||
// 处理远程关闭白板
|
||||
@@ -1791,6 +1900,17 @@ function handleRemoteWhiteboardClose(data) {
|
||||
// if(data.roomType == '1'){
|
||||
// isWhiteboardActive.value = false;
|
||||
// }
|
||||
//当远程用户关闭白板时,如果本地有屏幕共享,确保屏幕共享重新显示
|
||||
nextTick(() => {
|
||||
if (hasActiveScreenShare.value && !isWhiteboardActive.value) {
|
||||
activateLayer('screenVideo',false);
|
||||
|
||||
// 确保屏幕共享视频元素重新附加轨道
|
||||
if (activeScreenShareTrack.value && screenShareVideo.value) {
|
||||
attachTrackToVideo(screenShareVideo.value, activeScreenShareTrack.value);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 处理白板同步消息
|
||||
@@ -1863,18 +1983,19 @@ async function toggleWhiteboard() {
|
||||
async function startWhiteboard() {
|
||||
try {
|
||||
// 如果正在屏幕共享,先停止
|
||||
if (isScreenSharing.value) {
|
||||
await room.localParticipant.setScreenShareEnabled(false);
|
||||
isScreenSharing.value = false;
|
||||
ElMessage.info('已停止屏幕共享,开启白板');
|
||||
}
|
||||
// if (isScreenSharing.value) {
|
||||
// await room.localParticipant.setScreenShareEnabled(false);
|
||||
// isScreenSharing.value = false;
|
||||
// ElMessage.info('已停止屏幕共享,开启白板');
|
||||
// }
|
||||
// 如果正在放大视图,先关闭
|
||||
if (enlargedParticipant.value) {
|
||||
closeEnlargedView();
|
||||
}
|
||||
// if (enlargedParticipant.value) {
|
||||
// closeEnlargedView();
|
||||
// }
|
||||
|
||||
// 激活白板状态
|
||||
isWhiteboardActive.value = true;
|
||||
activateLayer('whiteboard', true); // 激活白板图层
|
||||
const success = publishWhiteboardMessage(WHITEBOARD_MESSAGE_TYPES.OPEN, {
|
||||
action: 'open',
|
||||
whiteboardId: roomId.value,
|
||||
@@ -1905,6 +2026,10 @@ async function exitWhiteboard() {
|
||||
if (whiteboardRef.value && whiteboardRef.value.cleanup) {
|
||||
whiteboardRef.value.cleanup();
|
||||
}
|
||||
// 如果关闭的是当前顶层,且还有屏幕共享或放大视频,则激活屏幕视频层
|
||||
if (lastActivatedLayer.value === 'whiteboard' && hasScreenShareOrEnlarged.value) {
|
||||
activateLayer('screenVideo',false);
|
||||
}
|
||||
ElMessage.success('已退出白板');
|
||||
} catch (error) {
|
||||
if (error !== 'cancel') {
|
||||
@@ -2026,6 +2151,10 @@ function handleTrackSubscribed(track, publication, participant) {
|
||||
// 设置全局屏幕共享用户
|
||||
globalScreenSharingUser.value = participant.identity;
|
||||
isGlobalScreenSharing.value = true;
|
||||
// 如果是远程用户的屏幕共享,激活屏幕视频层
|
||||
if (participant.identity !== hostUid.value) {
|
||||
activateLayer('screenVideo', false);
|
||||
}
|
||||
}
|
||||
|
||||
// 立即附加到视频元素(如果元素已存在)
|
||||
@@ -2208,6 +2337,10 @@ function updateScreenShareState(participant, track) {
|
||||
// 有新的屏幕共享轨道
|
||||
screenSharingUser.value = participant.identity;
|
||||
activeScreenShareTrack.value = track;
|
||||
// 重要:确保屏幕共享层被激活(如果白板已关闭)
|
||||
if (!isWhiteboardActive.value) {
|
||||
activateLayer('screenVideo',false);
|
||||
}
|
||||
// 附加到屏幕共享视频元素
|
||||
if (screenShareVideo.value) {
|
||||
attachTrackToVideo(screenShareVideo.value, track);
|
||||
@@ -2658,10 +2791,10 @@ async function toggleScreenShare() {
|
||||
ElMessage.error('当前处于视频放大模式,无法进行屏幕共享');
|
||||
return;
|
||||
}
|
||||
if(isWhiteboardActive.value){
|
||||
ElMessage.error('请先关闭白板');
|
||||
return;
|
||||
}
|
||||
// if(isWhiteboardActive.value){
|
||||
// ElMessage.error('请先关闭白板');
|
||||
// return;
|
||||
// }
|
||||
// 检查是否已经有其他用户在共享屏幕
|
||||
if (!isScreenSharing.value && isGlobalScreenSharing.value && globalScreenSharingUser.value !== hostUid.value) {
|
||||
ElMessage.error(`当前 ${globalScreenSharingUser.value} 正在共享屏幕,请等待其结束后再共享`);
|
||||
@@ -2682,6 +2815,7 @@ async function toggleScreenShare() {
|
||||
// 设置全局屏幕共享状态
|
||||
globalScreenSharingUser.value = hostUid.value;
|
||||
isGlobalScreenSharing.value = true;
|
||||
activateLayer('screenVideo', true);// 激活屏幕视频图层
|
||||
ElMessage.success('屏幕共享已开始');
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -2900,6 +3034,30 @@ watch(enlargedParticipant, (newVal) => {
|
||||
cleanupEnlargedLaserPointer();
|
||||
}
|
||||
});
|
||||
|
||||
// 监听屏幕共享或放大视频状态变化
|
||||
watch(hasScreenShareOrEnlarged, (newVal) => {
|
||||
if (newVal && !lastActivatedLayer.value) {
|
||||
// 如果首次有屏幕共享或放大视频,且没有激活图层,则激活
|
||||
activateLayer('screenVideo',false);
|
||||
} else if (!newVal && lastActivatedLayer.value === 'screenVideo') {
|
||||
// 如果屏幕视频层关闭且当前激活的是它,则检查是否有白板
|
||||
if (isWhiteboardActive.value) {
|
||||
activateLayer('whiteboard',false);
|
||||
} else {
|
||||
lastActivatedLayer.value = '';
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 监听白板状态变化
|
||||
watch(isWhiteboardActive, (newVal) => {
|
||||
if (newVal && !lastActivatedLayer.value) {
|
||||
// 如果首次有白板,且没有激活图层,则激活
|
||||
activateLayer('whiteboard',false);
|
||||
}
|
||||
});
|
||||
|
||||
// 添加专门的关闭激光笔函数
|
||||
function closeLaserPointer() {
|
||||
isLaserPointerActive.value = false;
|
||||
@@ -2957,6 +3115,80 @@ onMounted(async () => {
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
/* 内容层级容器 */
|
||||
.content-layers-container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/* 内容图层通用样式 */
|
||||
.content-layer {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
/* 默认图层在底层 */
|
||||
z-index: 1;
|
||||
|
||||
/* 激活的图层在最顶层 */
|
||||
&.active-layer {
|
||||
z-index: 10;
|
||||
}
|
||||
}
|
||||
|
||||
/* 屏幕视频层(桌面共享或放大视频) */
|
||||
.screen-video-layer {
|
||||
.enlarged-video-wrapper,
|
||||
.screen-share-wrapper {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.enlarged-video-element,
|
||||
.screen-share-element {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
background: #000;
|
||||
border-radius: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
/* 白板层 */
|
||||
.whiteboard-layer {
|
||||
.whiteboard-component {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
/* 当图层非激活状态时的视觉提示 */
|
||||
.content-layer:not(.active-layer) {
|
||||
opacity: 0;
|
||||
pointer-events: none; /* 非激活图层不接受交互 */
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
/* 激活图层完全显示 */
|
||||
.content-layer.active-layer {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
/* 激光笔 Canvas 样式 */
|
||||
.laser-pointer-canvas {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 20; /* 激光笔在所有内容之上 */
|
||||
pointer-events: auto;
|
||||
cursor: crosshair;
|
||||
}
|
||||
|
||||
.enlarged-laser-canvas {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
@@ -3048,14 +3280,14 @@ onMounted(async () => {
|
||||
// }
|
||||
|
||||
/* 激光笔 Canvas 样式 */
|
||||
.laser-pointer-canvas {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 10;
|
||||
pointer-events: auto;
|
||||
cursor: crosshair;
|
||||
}
|
||||
// .laser-pointer-canvas {
|
||||
// position: absolute;
|
||||
// top: 0;
|
||||
// left: 0;
|
||||
// z-index: 10;
|
||||
// pointer-events: auto;
|
||||
// cursor: crosshair;
|
||||
// }
|
||||
/* 激光笔状态指示器 */
|
||||
.laser-pointer-indicator {
|
||||
color: #ff0000;
|
||||
|
||||
@@ -129,6 +129,7 @@
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
<template>
|
||||
<div class="wrapper-content">
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, nextTick, onUnmounted, onMounted } from "vue";
|
||||
import { ElLoading, ElMessage } from "element-plus";
|
||||
import { useRoute } from "vue-router";
|
||||
import { mqttClient } from "@/utils/mqtt";
|
||||
|
||||
const props = defineProps({
|
||||
roomId: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
|
||||
})
|
||||
const route = useRoute();
|
||||
|
||||
|
||||
onMounted(async () => {
|
||||
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -17,14 +17,14 @@ export default defineConfig(({ mode, command }) => {
|
||||
hmr: { overlay: false },
|
||||
proxy: {
|
||||
'/dev-api': {
|
||||
target: 'https://xsynergy.gxtech.ltd', // 从环境变量读取
|
||||
target: 'https://xsynergy.gxtech.ltd',
|
||||
changeOrigin: true,
|
||||
ws: true,
|
||||
rewrite: (path) =>
|
||||
path.replace(new RegExp(`^/dev-api`), '')
|
||||
},
|
||||
'/livekit-api': {
|
||||
target: 'https://meeting.cnsdt.com/api/v1', // 从环境变量读取
|
||||
target: 'https://meeting.cnsdt.com/api/v1',
|
||||
changeOrigin: true,
|
||||
ws: true,
|
||||
rewrite: (path) =>
|
||||
|
||||
Reference in New Issue
Block a user