feat:更新协同合作
This commit is contained in:
@@ -1,12 +1,23 @@
|
||||
import request from '@/views/conferencingRoom/utils/request.js'
|
||||
import { tansParams } from "@/utils/ruoyi";
|
||||
// import request from '@/views/conferencingRoom/utils/request.js'
|
||||
// import { tansParams } from "@/utils/ruoyi";
|
||||
|
||||
// 获取roomtoken
|
||||
import request from '@/utils/request'
|
||||
|
||||
// 创建房间获取token
|
||||
export function getRoomToken(data) {
|
||||
return request({
|
||||
url: `/room/token`,
|
||||
url: `/api/v1/rooms/create`,
|
||||
method: 'post',
|
||||
data: tansParams(data),
|
||||
data: data,
|
||||
})
|
||||
}
|
||||
|
||||
//邀请用户参与房间
|
||||
export function getInvite(room_uid,data) {
|
||||
return request({
|
||||
url: `/api/v1/meeting/${room_uid}/invite`,
|
||||
method: 'post',
|
||||
data: data,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -26,3 +37,30 @@ export function getRoomList() {
|
||||
method: 'get',
|
||||
})
|
||||
}
|
||||
|
||||
//修改状态 加入 退出
|
||||
export function getStatusApi(room_uid,data) {
|
||||
return request({
|
||||
url: `/api/v1/meeting/${room_uid}/status`,
|
||||
method: 'post',
|
||||
data: data,
|
||||
})
|
||||
}
|
||||
|
||||
//参与者获取token
|
||||
export function getTokenApi(room_uid) {
|
||||
return request({
|
||||
url: `/api/v1/meeting/${room_uid}/token`,
|
||||
method: 'get',
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
//用户退出房间
|
||||
export function exitRoomApi(room_uid) {
|
||||
return request({
|
||||
url: `/api/v1/meeting/${room_uid}/leave`,
|
||||
method: 'post',
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,11 @@
|
||||
import { createApp } from 'vue'
|
||||
import { createPinia } from 'pinia'
|
||||
|
||||
|
||||
import {
|
||||
deepClone
|
||||
} from '@/utils/ruoyi'
|
||||
|
||||
import ElementPlus from 'element-plus'
|
||||
import 'element-plus/dist/index.css'
|
||||
import "@/assets/styles/index.scss"; // global css
|
||||
|
||||
@@ -35,7 +35,7 @@ const router = createRouter({
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
name: "Coordinate",
|
||||
name: "ConferencingRoom",
|
||||
component: () => import("@/views/conferencingRoom/index.vue")
|
||||
}
|
||||
]
|
||||
|
||||
19
src/stores/modules/room.js
Normal file
19
src/stores/modules/room.js
Normal file
@@ -0,0 +1,19 @@
|
||||
import { defineStore } from 'pinia'
|
||||
|
||||
export const useRoomStore = defineStore('room', {
|
||||
state: () => ({
|
||||
roomId: '',
|
||||
token: '',
|
||||
userUid: '',
|
||||
detailUid: '',
|
||||
}),
|
||||
actions: {
|
||||
setUserUid(data) {
|
||||
this.userUid = data
|
||||
},
|
||||
setDetailUid(data) {
|
||||
this.detailUid = data
|
||||
}
|
||||
}
|
||||
|
||||
})
|
||||
@@ -99,13 +99,11 @@ service.interceptors.request.use(
|
||||
|
||||
service.interceptors.response.use(
|
||||
(response) => {
|
||||
console.log(response,'response')
|
||||
// 1. 检查响应是否存在
|
||||
if (!response) {
|
||||
ElMessage.error('无响应数据');
|
||||
return Promise.reject(new Error('无响应数据'));
|
||||
}
|
||||
console.log(response.data,'response.data')
|
||||
// 2. 安全获取响应数据和状态码
|
||||
const responseData = response.data || {};
|
||||
const statusCode = response.status;
|
||||
|
||||
3
src/views/conferencingRoom/business/index.js
Normal file
3
src/views/conferencingRoom/business/index.js
Normal file
@@ -0,0 +1,3 @@
|
||||
export function generateUUID() {
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
893
src/views/conferencingRoom/index_old.vue
Normal file
893
src/views/conferencingRoom/index_old.vue
Normal file
@@ -0,0 +1,893 @@
|
||||
<template>
|
||||
<div id="audio"></div>
|
||||
<el-row>
|
||||
<el-col :span="16">
|
||||
<el-table :data="rooms.tableData" style="width: 100%">
|
||||
<el-table-column prop="name" label="房间名称" />
|
||||
<el-table-column prop="empty_timeout" label="空房间关闭时间" />
|
||||
<el-table-column prop="creation_time" label="创建时间" />
|
||||
<el-table-column prop="turn_password" label="口令" />
|
||||
</el-table>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<div class="form" v-show="status">
|
||||
<el-form :model="formModel" label-position="right" :label-width="90">
|
||||
<el-form-item label="房间名称:">
|
||||
<el-input v-model="formModel.room" placeholder="输入房间名称" style="width: 300px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="用户编码:">
|
||||
<el-input v-model="formModel.uid" placeholder="输入用户编码" style="width: 300px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="选项:">
|
||||
<el-radio-group v-model="formModel.creatRoom">
|
||||
<el-radio label="0">
|
||||
<span>创建房间</span>
|
||||
</el-radio>
|
||||
<el-radio label="1">
|
||||
<span>加入房间</span>
|
||||
</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-button type="primary" @click="joinRoomBtn">
|
||||
{{ formModel.creatRoom === "0" ? "创建房间" : "加入房间" }}
|
||||
</el-button>
|
||||
</el-form>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<div v-if="!status" class="form">
|
||||
<el-result icon="success" :title="formModel.room" sub-title="已加入房间" />
|
||||
<el-input v-model="msgDate.some" placeholder="输入要发送的消息"></el-input>
|
||||
<el-button @click="postMessage" type="success">发送文本消息</el-button>
|
||||
|
||||
<!-- 视频容器 -->
|
||||
<div class="video-container">
|
||||
<!-- 本地视频 -->
|
||||
<div class="video-wrapper">
|
||||
<h3>我的视频 ({{ formModel.uid }})</h3>
|
||||
<video ref="localVideo" autoplay muted playsinline class="video-element"></video>
|
||||
<div class="video-controls">
|
||||
<el-button @click="toggleCamera" :type="cameraEnabled ? 'danger' : 'success'" size="small">
|
||||
{{ cameraEnabled ? '关闭摄像头' : '开启摄像头' }}
|
||||
</el-button>
|
||||
<el-button @click="toggleMicrophone" :type="microphoneEnabled ? 'danger' : 'success'" size="small">
|
||||
{{ microphoneEnabled ? '关闭麦克风' : '开启麦克风' }}
|
||||
</el-button>
|
||||
<el-button @click="toggleScreenShare" :type="isScreenSharing ? 'danger' : 'primary'" size="small">
|
||||
{{ isScreenSharing ? '停止共享' : '共享屏幕' }}
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 远程视频-->
|
||||
<div class="video-wrapper" v-for="participant in remoteParticipantsArray" :key="participant.identity">
|
||||
<h3>{{ participant.identity }}</h3>
|
||||
<div class="video-tracks">
|
||||
<!-- 摄像头视频 - 使用 ref 绑定 -->
|
||||
<video
|
||||
v-if="participant.hasCameraTrack"
|
||||
:ref="el => setParticipantVideoRef(el, participant.identity, 'camera')"
|
||||
autoplay
|
||||
playsinline
|
||||
class="video-element"
|
||||
@loadedmetadata="() => handleVideoLoaded(participant.identity, 'camera')">
|
||||
</video>
|
||||
<!-- 屏幕共享视频 - 使用 ref 绑定 -->
|
||||
<video
|
||||
v-if="participant.hasScreenTrack"
|
||||
:ref="el => setParticipantVideoRef(el, participant.identity, 'screen')"
|
||||
autoplay
|
||||
playsinline
|
||||
class="video-element"
|
||||
@loadedmetadata="() => handleVideoLoaded(participant.identity, 'screen')">
|
||||
</video>
|
||||
<!-- 如果没有视频轨道,显示提示 -->
|
||||
<div v-if="!participant.hasCameraTrack && !participant.hasScreenTrack" class="video-placeholder">
|
||||
暂无视频流
|
||||
</div>
|
||||
</div>
|
||||
<div class="participant-info">
|
||||
<span>音频: {{ participant.audioEnabled ? '开启' : '关闭' }}</span>
|
||||
<span>视频: {{ participant.videoEnabled ? '开启' : '关闭' }}</span>
|
||||
<span>状态: {{ participant.isSpeaking ? '发言中' : '安静' }}</span>
|
||||
<span>轨道: 摄像头{{ participant.hasCameraTrack ? '有' : '无' }} | 屏幕{{ participant.hasScreenTrack ? '有' : '无' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 显示连接状态 -->
|
||||
<div v-if="connectionStatus" :class="['status', connectionStatus.type]">
|
||||
{{ connectionStatus.message }}
|
||||
</div>
|
||||
|
||||
<!-- 房间信息 -->
|
||||
<div class="room-info" v-if="!status">
|
||||
<h4>房间信息</h4>
|
||||
<p>房间名称: {{ room.name }}</p>
|
||||
<p>参与者数量: {{ participantCount }}</p>
|
||||
<p>连接状态: {{ room.connectionState }}</p>
|
||||
<p>远程参与者: {{ remoteParticipantsArray.map(p => p.identity).join(', ') || '无' }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { reactive, ref, onMounted, onUnmounted, nextTick, computed } from "vue";
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { getRoomToken, getRoomList } from "@/api/conferencingRoom.js"
|
||||
import { Room, RoomEvent, ParticipantEvent, Track } from "livekit-client";
|
||||
|
||||
// LiveKit 服务器配置
|
||||
const wsURL = "wss://meeting.cnsdt.com:443";
|
||||
|
||||
// 响应式数据
|
||||
const formModel = reactive({
|
||||
room: 'thehome',
|
||||
uid: 'xtqxk',
|
||||
creatRoom: '0'
|
||||
});
|
||||
|
||||
const status = ref(true);
|
||||
const connectionStatus = ref(null);
|
||||
const rooms = reactive({ tableData: [] });
|
||||
const msgDate = reactive({ some: '' });
|
||||
|
||||
// 视频相关引用和数据
|
||||
const localVideo = ref(null);
|
||||
const cameraEnabled = ref(false);
|
||||
const microphoneEnabled = ref(false);
|
||||
const isScreenSharing = ref(false);
|
||||
|
||||
// 远程参与者管理
|
||||
const remoteParticipants = ref(new Map());
|
||||
// 视频元素引用映射
|
||||
const videoElementsMap = ref(new Map());
|
||||
|
||||
// 计算属性
|
||||
const participantCount = computed(() => {
|
||||
return remoteParticipants.value.size + 1; // 包括自己
|
||||
});
|
||||
|
||||
const remoteParticipantsArray = computed(() => {
|
||||
return Array.from(remoteParticipants.value.values());
|
||||
});
|
||||
|
||||
// 创建 Room 实例
|
||||
const room = new Room({
|
||||
adaptiveStream: true,
|
||||
dynacast: true,
|
||||
videoCaptureDefaults: {
|
||||
resolution: { width: 1280, height: 720 }
|
||||
},
|
||||
publishDefaults: {
|
||||
screenShareEncoding: {
|
||||
maxBitrate: 3_000_000,
|
||||
maxFramerate: 30,
|
||||
},
|
||||
videoEncoding: {
|
||||
maxBitrate: 2_500_000,
|
||||
maxFramerate: 30,
|
||||
},
|
||||
}
|
||||
});
|
||||
|
||||
// 设置视频元素引用
|
||||
function setParticipantVideoRef(el, identity, type) {
|
||||
if (!el) return;
|
||||
|
||||
if (!videoElementsMap.value.has(identity)) {
|
||||
videoElementsMap.value.set(identity, {});
|
||||
}
|
||||
|
||||
const elements = videoElementsMap.value.get(identity);
|
||||
elements[type] = el;
|
||||
videoElementsMap.value.set(identity, elements);
|
||||
|
||||
// 如果已经有轨道数据,立即附加
|
||||
const participantData = remoteParticipants.value.get(identity);
|
||||
|
||||
if (participantData) {
|
||||
if (type === 'camera' && participantData.cameraTrack) {
|
||||
attachTrackToVideo(el, participantData.cameraTrack);
|
||||
} else if (type === 'screen' && participantData.screenTrack) {
|
||||
attachTrackToVideo(el, participantData.screenTrack);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 设置事件监听器
|
||||
function setupRoomListeners() {
|
||||
room.removeAllListeners();
|
||||
|
||||
room
|
||||
.on(RoomEvent.Connected, handleConnected)
|
||||
.on(RoomEvent.Disconnected, handleDisconnected)
|
||||
.on(RoomEvent.Reconnected, handleReconnected)
|
||||
.on(RoomEvent.TrackSubscribed, handleTrackSubscribed)
|
||||
.on(RoomEvent.TrackUnsubscribed, handleTrackUnsubscribed)
|
||||
.on(RoomEvent.ParticipantConnected, handleParticipantConnected)
|
||||
.on(RoomEvent.ParticipantDisconnected, handleParticipantDisconnected)
|
||||
.on(RoomEvent.LocalTrackPublished, handleLocalTrackPublished)
|
||||
.on(RoomEvent.LocalTrackUnpublished, handleLocalTrackUnpublished)
|
||||
.on(RoomEvent.TrackMuted, handleTrackMuted)
|
||||
.on(RoomEvent.TrackUnmuted, handleTrackUnmuted)
|
||||
.on(RoomEvent.ActiveSpeakersChanged, handleActiveSpeakersChanged)
|
||||
.on(RoomEvent.DataReceived, handleDataReceived)
|
||||
.on(RoomEvent.ConnectionStateChanged, handleConnectionStateChanged);
|
||||
}
|
||||
|
||||
// 事件处理函数
|
||||
async function handleConnected() {
|
||||
console.log("成功连接到房间:", room.name);
|
||||
status.value = false;
|
||||
connectionStatus.value = {
|
||||
type: 'success',
|
||||
message: `已成功连接到房间: ${room.name}`
|
||||
};
|
||||
ElMessage.success('已成功连接到房间');
|
||||
|
||||
// 初始化现有远程参与者
|
||||
room.remoteParticipants.forEach(participant => {
|
||||
addRemoteParticipant(participant);
|
||||
setupParticipantListeners(participant);
|
||||
// 立即检查并更新参与者的轨道状态
|
||||
updateParticipantTracks(participant);
|
||||
});
|
||||
|
||||
// 自动开启摄像头(仅创建房间时)
|
||||
if (formModel.creatRoom === "0") {
|
||||
try {
|
||||
await enableCamera();
|
||||
ElMessage.success('摄像头已自动开启');
|
||||
} catch (error) {
|
||||
console.warn('自动开启摄像头失败:', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function handleDisconnected(reason) {
|
||||
console.log("断开连接:", reason);
|
||||
status.value = true;
|
||||
cameraEnabled.value = false;
|
||||
microphoneEnabled.value = false;
|
||||
isScreenSharing.value = false;
|
||||
remoteParticipants.value.clear();
|
||||
videoElementsMap.value.clear();
|
||||
connectionStatus.value = {
|
||||
type: 'error',
|
||||
message: `连接已断开: ${reason}`
|
||||
};
|
||||
ElMessage.error('连接已断开');
|
||||
}
|
||||
|
||||
function handleReconnected() {
|
||||
console.log("已重新连接");
|
||||
connectionStatus.value = {
|
||||
type: 'success',
|
||||
message: '已重新连接到房间'
|
||||
};
|
||||
ElMessage.success('已重新连接到房间');
|
||||
}
|
||||
|
||||
// 处理轨道订阅事件
|
||||
function handleTrackSubscribed(track, publication, participant) {
|
||||
console.log("轨道已订阅:", track.kind, "轨道来源:", publication.source, "来自:", participant.identity);
|
||||
|
||||
if (track && track.kind === Track.Kind.Video) {
|
||||
// 更新参与者轨道信息
|
||||
updateParticipantTrack(participant, publication.source, track);
|
||||
|
||||
// 立即附加到视频元素(如果元素已存在)
|
||||
attachTrackToParticipantVideo(participant.identity, publication.source, track);
|
||||
|
||||
console.log('成功订阅远程视频轨道:', publication.source, participant.identity);
|
||||
}
|
||||
|
||||
// 更新参与者的轨道状态
|
||||
updateParticipantTracks(participant);
|
||||
}
|
||||
|
||||
function handleTrackUnsubscribed(track, publication, participant) {
|
||||
console.log("轨道取消订阅:", track.kind, "来自:", participant.identity);
|
||||
|
||||
// 移除对应的轨道信息
|
||||
if (track.kind === Track.Kind.Video) {
|
||||
removeParticipantTrack(participant, publication.source);
|
||||
|
||||
// 清理视频元素
|
||||
detachTrackFromParticipantVideo(participant.identity, publication.source);
|
||||
}
|
||||
|
||||
updateParticipantTracks(participant);
|
||||
}
|
||||
|
||||
function handleParticipantConnected(participant) {
|
||||
console.log("新参与者连接:", participant.identity);
|
||||
addRemoteParticipant(participant);
|
||||
setupParticipantListeners(participant);
|
||||
|
||||
// 立即检查参与者的轨道状态
|
||||
updateParticipantTracks(participant);
|
||||
|
||||
ElMessage.info(`新用户加入: ${participant.identity}`);
|
||||
}
|
||||
|
||||
function handleParticipantDisconnected(participant) {
|
||||
console.log("参与者断开连接:", participant.identity);
|
||||
removeRemoteParticipant(participant);
|
||||
ElMessage.info(`用户离开: ${participant.identity}`);
|
||||
}
|
||||
|
||||
function handleLocalTrackPublished(publication) {
|
||||
console.log("本地轨道发布:", publication.kind, "来源:", publication.source);
|
||||
if (publication.kind === Track.Kind.Video && publication.track) {
|
||||
attachLocalVideoTrack(publication.track);
|
||||
}
|
||||
}
|
||||
|
||||
function handleLocalTrackUnpublished(publication) {
|
||||
console.log("本地轨道取消发布:", publication.kind);
|
||||
if (publication.kind === Track.Kind.Video) {
|
||||
cameraEnabled.value = false;
|
||||
} else if (publication.kind === Track.Kind.Audio) {
|
||||
microphoneEnabled.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function handleTrackMuted(publication, participant) {
|
||||
console.log("轨道静音:", publication.kind, participant.identity);
|
||||
updateParticipantTracks(participant);
|
||||
}
|
||||
|
||||
function handleTrackUnmuted(publication, participant) {
|
||||
console.log("轨道取消静音:", publication.kind, participant.identity);
|
||||
updateParticipantTracks(participant);
|
||||
}
|
||||
|
||||
function setupParticipantListeners(participant) {
|
||||
participant
|
||||
.on(ParticipantEvent.TrackSubscribed, (track, publication) => {
|
||||
handleTrackSubscribed(track, publication, participant);
|
||||
})
|
||||
.on(ParticipantEvent.TrackUnsubscribed, (track, publication) => {
|
||||
handleTrackUnsubscribed(track, publication, participant);
|
||||
})
|
||||
.on(ParticipantEvent.TrackMuted, (publication) => {
|
||||
handleTrackMuted(publication, participant);
|
||||
})
|
||||
.on(ParticipantEvent.TrackUnmuted, (publication) => {
|
||||
handleTrackUnmuted(publication, participant);
|
||||
})
|
||||
.on(ParticipantEvent.IsSpeakingChanged, (speaking) => {
|
||||
updateParticipantSpeaking(participant, speaking);
|
||||
});
|
||||
}
|
||||
|
||||
function handleActiveSpeakersChanged(speakers) {
|
||||
console.log("活跃说话者变化:", speakers.map(s => s.identity));
|
||||
remoteParticipants.value.forEach((data, identity) => {
|
||||
const isSpeaking = speakers.some(speaker => speaker.identity === identity);
|
||||
if (data.isSpeaking !== isSpeaking) {
|
||||
data.isSpeaking = isSpeaking;
|
||||
remoteParticipants.value.set(identity, { ...data });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function handleDataReceived(payload, participant, kind) {
|
||||
try {
|
||||
const decoder = new TextDecoder();
|
||||
const strData = decoder.decode(payload);
|
||||
console.log("接收到消息:", strData, "来自:", participant.identity);
|
||||
ElMessage.info(`收到消息 from ${participant.identity}: ${strData}`);
|
||||
} catch (error) {
|
||||
console.error('处理接收消息失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
function handleConnectionStateChanged(state) {
|
||||
console.log("连接状态变化:", state);
|
||||
connectionStatus.value = {
|
||||
type: 'info',
|
||||
message: `连接状态: ${state}`
|
||||
};
|
||||
}
|
||||
|
||||
// 参与者管理函数
|
||||
function addRemoteParticipant(participant) {
|
||||
if (!participant || participant.identity === room.localParticipant?.identity) {
|
||||
return;
|
||||
}
|
||||
|
||||
const participantData = {
|
||||
identity: participant.identity,
|
||||
cameraTrack: null,
|
||||
screenTrack: null,
|
||||
hasCameraTrack: false,
|
||||
hasScreenTrack: false,
|
||||
audioEnabled: participant.isMicrophoneEnabled,
|
||||
videoEnabled: participant.isCameraEnabled,
|
||||
isSpeaking: false
|
||||
};
|
||||
|
||||
remoteParticipants.value.set(participant.identity, participantData);
|
||||
console.log("添加远程参与者:", participant.identity);
|
||||
}
|
||||
|
||||
function removeRemoteParticipant(participant) {
|
||||
if (remoteParticipants.value.has(participant.identity)) {
|
||||
// 清理视频元素
|
||||
const identity = participant.identity;
|
||||
detachTrackFromParticipantVideo(identity, 'camera');
|
||||
detachTrackFromParticipantVideo(identity, 'screen');
|
||||
|
||||
remoteParticipants.value.delete(identity);
|
||||
videoElementsMap.value.delete(identity);
|
||||
console.log("移除远程参与者:", participant.identity);
|
||||
}
|
||||
}
|
||||
|
||||
// 更新参与者轨道信息
|
||||
function updateParticipantTrack(participant, source, track) {
|
||||
const data = remoteParticipants.value.get(participant.identity);
|
||||
if (!data) return;
|
||||
|
||||
if (source === Track.Source.Camera) {
|
||||
data.cameraTrack = track;
|
||||
data.hasCameraTrack = true;
|
||||
} else if (source === Track.Source.ScreenShare) {
|
||||
data.screenTrack = track;
|
||||
data.hasScreenTrack = true;
|
||||
}
|
||||
|
||||
remoteParticipants.value.set(participant.identity, { ...data });
|
||||
}
|
||||
|
||||
function removeParticipantTrack(participant, source) {
|
||||
const data = remoteParticipants.value.get(participant.identity);
|
||||
if (!data) return;
|
||||
|
||||
if (source === Track.Source.Camera) {
|
||||
data.cameraTrack = null;
|
||||
data.hasCameraTrack = false;
|
||||
} else if (source === Track.Source.ScreenShare) {
|
||||
data.screenTrack = null;
|
||||
data.hasScreenTrack = false;
|
||||
}
|
||||
|
||||
remoteParticipants.value.set(participant.identity, { ...data });
|
||||
}
|
||||
|
||||
// 附加轨道到视频元素
|
||||
function attachTrackToVideo(videoElement, track) {
|
||||
if (!videoElement || !track) return;
|
||||
|
||||
try {
|
||||
// 使用 MediaStream 方式附加轨道
|
||||
const mediaStream = new MediaStream();
|
||||
mediaStream.addTrack(track.mediaStreamTrack);
|
||||
videoElement.srcObject = mediaStream;
|
||||
|
||||
console.log('成功附加轨道到视频元素');
|
||||
} catch (error) {
|
||||
console.error('附加轨道到视频元素失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// 附加轨道到参与者的视频元素
|
||||
function attachTrackToParticipantVideo(identity, source, track) {
|
||||
const videoElements = videoElementsMap.value.get(identity);
|
||||
if (!videoElements) return;
|
||||
|
||||
const type = source === Track.Source.Camera ? 'camera' : 'screen';
|
||||
const videoElement = videoElements[type];
|
||||
|
||||
if (videoElement) {
|
||||
attachTrackToVideo(videoElement, track);
|
||||
}
|
||||
}
|
||||
|
||||
// 从参与者的视频元素分离轨道
|
||||
function detachTrackFromParticipantVideo(identity, source) {
|
||||
const videoElements = videoElementsMap.value.get(identity);
|
||||
if (!videoElements) return;
|
||||
|
||||
const type = source === Track.Source.Camera ? 'camera' : 'screen';
|
||||
const videoElement = videoElements[type];
|
||||
|
||||
if (videoElement && videoElement.srcObject) {
|
||||
videoElement.srcObject = null;
|
||||
}
|
||||
}
|
||||
|
||||
function updateParticipantTracks(participant) {
|
||||
const data = remoteParticipants.value.get(participant.identity);
|
||||
if (!data) return;
|
||||
|
||||
// 检查视频轨道状态
|
||||
let hasCamera = false;
|
||||
let hasScreen = false;
|
||||
|
||||
// 检查已发布的轨道
|
||||
participant.videoTrackPublications.forEach(publication => {
|
||||
if (publication.isSubscribed && publication.track) {
|
||||
if (publication.source === Track.Source.Camera) {
|
||||
hasCamera = true;
|
||||
// 确保轨道信息更新
|
||||
if (!data.cameraTrack) {
|
||||
data.cameraTrack = publication.track;
|
||||
}
|
||||
} else if (publication.source === Track.Source.ScreenShare) {
|
||||
hasScreen = true;
|
||||
if (!data.screenTrack) {
|
||||
data.screenTrack = publication.track;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
data.hasCameraTrack = hasCamera;
|
||||
data.hasScreenTrack = hasScreen;
|
||||
data.audioEnabled = participant.isMicrophoneEnabled;
|
||||
data.videoEnabled = participant.isCameraEnabled;
|
||||
|
||||
remoteParticipants.value.set(participant.identity, { ...data });
|
||||
|
||||
console.log("更新参与者轨道状态:", participant.identity, {
|
||||
hasCameraTrack: data.hasCameraTrack,
|
||||
hasScreenTrack: data.hasScreenTrack,
|
||||
audioEnabled: data.audioEnabled,
|
||||
videoEnabled: data.videoEnabled
|
||||
});
|
||||
}
|
||||
|
||||
function updateParticipantSpeaking(participant, speaking) {
|
||||
const data = remoteParticipants.value.get(participant.identity);
|
||||
if (data && data.isSpeaking !== speaking) {
|
||||
data.isSpeaking = speaking;
|
||||
remoteParticipants.value.set(participant.identity, { ...data });
|
||||
}
|
||||
}
|
||||
|
||||
function handleVideoLoaded(identity, type) {
|
||||
console.log(`视频加载完成: ${identity}的${type}视频`);
|
||||
}
|
||||
|
||||
// 视频轨道处理函数
|
||||
function attachLocalVideoTrack(track) {
|
||||
if (localVideo.value && track) {
|
||||
try {
|
||||
const mediaStream = new MediaStream();
|
||||
mediaStream.addTrack(track.mediaStreamTrack);
|
||||
localVideo.value.srcObject = mediaStream;
|
||||
cameraEnabled.value = true;
|
||||
} catch (error) {
|
||||
console.error('附加本地视频轨道失败:', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 媒体控制函数
|
||||
async function enableCamera() {
|
||||
try {
|
||||
await room.localParticipant.setCameraEnabled(true);
|
||||
cameraEnabled.value = true;
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('开启摄像头失败:', error);
|
||||
cameraEnabled.value = false;
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async function toggleCamera() {
|
||||
try {
|
||||
if (cameraEnabled.value) {
|
||||
// 关闭摄像头
|
||||
await room.localParticipant.setCameraEnabled(false);
|
||||
cameraEnabled.value = false;
|
||||
|
||||
// 清理本地视频元素
|
||||
if (localVideo.value && localVideo.value.srcObject) {
|
||||
localVideo.value.srcObject.getTracks().forEach(track => track.stop());
|
||||
localVideo.value.srcObject = null;
|
||||
}
|
||||
|
||||
ElMessage.info('摄像头已关闭');
|
||||
} else {
|
||||
// 确保视频元素存在
|
||||
if (!localVideo.value) {
|
||||
console.warn('本地视频元素未找到,等待DOM更新');
|
||||
await nextTick();
|
||||
}
|
||||
|
||||
// 开启摄像头
|
||||
await room.localParticipant.setCameraEnabled(true);
|
||||
cameraEnabled.value = true;
|
||||
ElMessage.success('摄像头已开启');
|
||||
|
||||
// 手动获取并附加视频轨道,增加延迟
|
||||
setTimeout(() => {
|
||||
attachLocalCameraTrack();
|
||||
}, 200);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('切换摄像头失败:', error);
|
||||
ElMessage.error('切换摄像头失败: ' + error.message);
|
||||
}
|
||||
}
|
||||
|
||||
async function attachLocalCameraTrack() {
|
||||
try {
|
||||
// 等待一小段时间确保轨道已经创建
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
|
||||
// 获取本地参与者的摄像头轨道发布
|
||||
const videoPublications = Array.from(room.localParticipant.videoTrackPublications.values());
|
||||
const cameraPublication = videoPublications.find(pub =>
|
||||
pub.source === Track.Source.Camera && pub.track
|
||||
);
|
||||
|
||||
if (cameraPublication && cameraPublication.track) {
|
||||
console.log('找到摄像头轨道,手动附加到视频元素');
|
||||
attachLocalVideoTrack(cameraPublication.track);
|
||||
} else {
|
||||
console.log('未找到摄像头轨道,等待事件触发');
|
||||
// 如果没有找到,等待更长时间再检查
|
||||
setTimeout(() => {
|
||||
const videoPublications = Array.from(room.localParticipant.videoTrackPublications.values());
|
||||
const cameraPublication = videoPublications.find(pub =>
|
||||
pub.source === Track.Source.Camera && pub.track
|
||||
);
|
||||
if (cameraPublication && cameraPublication.track) {
|
||||
console.log('延迟找到摄像头轨道,手动附加');
|
||||
attachLocalVideoTrack(cameraPublication.track);
|
||||
} else {
|
||||
console.warn('最终未找到摄像头轨道');
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('手动附加摄像头轨道失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
async function toggleMicrophone() {
|
||||
try {
|
||||
if (microphoneEnabled.value) {
|
||||
await room.localParticipant.setMicrophoneEnabled(false);
|
||||
microphoneEnabled.value = false;
|
||||
ElMessage.info('麦克风已关闭');
|
||||
} else {
|
||||
await room.localParticipant.setMicrophoneEnabled(true);
|
||||
microphoneEnabled.value = true;
|
||||
ElMessage.success('麦克风已开启');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('切换麦克风失败:', error);
|
||||
ElMessage.error('切换麦克风失败: ' + error.message);
|
||||
}
|
||||
}
|
||||
|
||||
async function toggleScreenShare() {
|
||||
try {
|
||||
if (isScreenSharing.value) {
|
||||
await room.localParticipant.setScreenShareEnabled(false);
|
||||
isScreenSharing.value = false;
|
||||
ElMessage.info('屏幕共享已停止');
|
||||
} else {
|
||||
await room.localParticipant.setScreenShareEnabled(true);
|
||||
isScreenSharing.value = true;
|
||||
ElMessage.success('屏幕共享已开始');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('切换屏幕共享失败:', error);
|
||||
ElMessage.error('切换屏幕共享失败: ' + error.message);
|
||||
}
|
||||
}
|
||||
|
||||
// 房间管理函数
|
||||
async function getRoomsList() {
|
||||
try {
|
||||
const res = await getRoomList();
|
||||
console.log('房间列表:', res);
|
||||
if (res.data.rooms) {
|
||||
rooms.tableData = res.data.rooms;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取房间列表失败:', error);
|
||||
ElMessage.error('获取房间列表失败');
|
||||
}
|
||||
}
|
||||
|
||||
async function joinRoomBtn() {
|
||||
try {
|
||||
if (!formModel.room.trim() || !formModel.uid.trim()) {
|
||||
ElMessage.error('请填写房间名称和用户编码');
|
||||
return;
|
||||
}
|
||||
|
||||
connectionStatus.value = { type: 'info', message: '正在获取 token...' };
|
||||
|
||||
const res = await getRoomToken(formModel);
|
||||
const token = res.data.access_token;
|
||||
|
||||
if (!token) {
|
||||
throw new Error('获取 token 失败');
|
||||
}
|
||||
|
||||
connectionStatus.value = { type: 'info', message: '正在连接房间...' };
|
||||
|
||||
setupRoomListeners();
|
||||
|
||||
await room.connect(wsURL, token, {
|
||||
autoSubscribe: true,
|
||||
});
|
||||
|
||||
await getRoomsList();
|
||||
|
||||
} catch (error) {
|
||||
console.error('连接失败:', error);
|
||||
connectionStatus.value = {
|
||||
type: 'error',
|
||||
message: `连接失败: ${error.message}`
|
||||
};
|
||||
ElMessage.error(`连接失败: ${error.message}`);
|
||||
status.value = true;
|
||||
}
|
||||
}
|
||||
|
||||
function postMessage() {
|
||||
try {
|
||||
if (!msgDate.some.trim()) {
|
||||
ElMessage.warning('请输入消息内容');
|
||||
return;
|
||||
}
|
||||
|
||||
const encoder = new TextEncoder();
|
||||
const data = encoder.encode(JSON.stringify({
|
||||
message: msgDate.some,
|
||||
timestamp: new Date().toISOString(),
|
||||
from: formModel.uid
|
||||
}));
|
||||
|
||||
room.localParticipant.publishData(data, { reliable: true });
|
||||
ElMessage.success('消息发送成功');
|
||||
msgDate.some = '';
|
||||
|
||||
} catch (error) {
|
||||
console.error('发送消息失败:', error);
|
||||
ElMessage.error('发送消息失败');
|
||||
}
|
||||
}
|
||||
|
||||
// 生命周期
|
||||
onUnmounted(() => {
|
||||
if (room) {
|
||||
room.disconnect();
|
||||
}
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
// getRoomsList();
|
||||
// setupRoomListeners();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.form {
|
||||
margin: 20px auto;
|
||||
width: 100%;
|
||||
max-width: 1200px;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.video-container {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 20px;
|
||||
margin-top: 20px;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.video-wrapper {
|
||||
border: 2px solid #e4e7ed;
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
background: #f5f7fa;
|
||||
min-width: 320px;
|
||||
max-width: 400px;
|
||||
}
|
||||
|
||||
.video-wrapper h3 {
|
||||
margin: 0 0 10px 0;
|
||||
text-align: center;
|
||||
color: #606266;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.video-element {
|
||||
width: 100%;
|
||||
height: 225px;
|
||||
background: #000;
|
||||
border-radius: 4px;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.video-placeholder {
|
||||
width: 100%;
|
||||
height: 225px;
|
||||
background: #e4e7ed;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #909399;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.video-controls {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
justify-content: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.participant-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
margin-top: 8px;
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.participant-info span {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.status {
|
||||
padding: 10px;
|
||||
margin: 10px 0;
|
||||
border-radius: 4px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.status.success {
|
||||
background-color: #f0f9ff;
|
||||
color: #67c23a;
|
||||
border: 1px solid #b3e19d;
|
||||
}
|
||||
|
||||
.status.error {
|
||||
background-color: #fef0f0;
|
||||
color: #f56c6c;
|
||||
border: 1px solid #fbc4c4;
|
||||
}
|
||||
|
||||
.status.info {
|
||||
background-color: #f4f4f5;
|
||||
color: #909399;
|
||||
border: 1px solid #d3d4d6;
|
||||
}
|
||||
|
||||
.room-info {
|
||||
margin-top: 20px;
|
||||
padding: 15px;
|
||||
background: #f8f9fa;
|
||||
border-radius: 8px;
|
||||
border-left: 4px solid #409eff;
|
||||
}
|
||||
|
||||
.room-info h4 {
|
||||
margin: 0 0 10px 0;
|
||||
color: #409eff;
|
||||
}
|
||||
|
||||
.room-info p {
|
||||
margin: 5px 0;
|
||||
font-size: 14px;
|
||||
}
|
||||
</style>
|
||||
@@ -117,15 +117,6 @@
|
||||
<div class="list-item-text text-out-of-hiding-1">
|
||||
{{ item.name }}
|
||||
</div>
|
||||
|
||||
<!-- <el-icon
|
||||
:size="18"
|
||||
color="#0d74ff"
|
||||
style="cursor: pointer"
|
||||
@click="clickDetail(item)"
|
||||
>
|
||||
<View />
|
||||
</el-icon> -->
|
||||
<el-link
|
||||
:href="item.prefix + item.path"
|
||||
type="primary"
|
||||
@@ -227,7 +218,7 @@
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- <el-dialog
|
||||
<el-dialog
|
||||
v-model="inviteDialog"
|
||||
title="远程协作"
|
||||
width="400px"
|
||||
@@ -239,8 +230,8 @@
|
||||
<div style="width: 100%; margin-bottom: 30px; font-size: 20px">
|
||||
"
|
||||
{{
|
||||
socketInformation.data?.inviteNickName
|
||||
? socketInformation.data?.inviteNickName
|
||||
socketInformation.room_name
|
||||
? socketInformation.room_name
|
||||
: ''
|
||||
}}
|
||||
" 邀请您参加远程协作
|
||||
@@ -264,35 +255,24 @@
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</el-dialog> -->
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { onActivated, onMounted, reactive, toRefs, watch, getCurrentInstance } from 'vue'
|
||||
// import {
|
||||
// getFileList,
|
||||
// getRemoveSolution,
|
||||
// getUserStatus,
|
||||
// refuseAssistance,
|
||||
// } from '@/api/module/cooperation/v1/index'
|
||||
// import { getAppId, getModuleId } from '@/utils/index'
|
||||
|
||||
import leftTab from './components/leftTab/index.vue'
|
||||
import { getInfo } from '@/api/login.js'
|
||||
|
||||
// import useUserStore from '@/store/modules/user'
|
||||
// import { getToken } from '@/utils/auth'
|
||||
// import useWebSocketStore from '@/store/modules/WebSocket'
|
||||
// import usePermissionStore from '@/store/modules/permission'
|
||||
// import logo from '@/assets/logo/logo.png'
|
||||
|
||||
// import { getRtcSetDetail } from '@/api/conferencingRoom'
|
||||
|
||||
import { getStatusApi } from '@/api/conferencingRoom.js'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { useRoomStore } from '@/stores/modules/room'
|
||||
import useUserStore from '@/stores/modules/user'
|
||||
import { mqttClient } from "@/utils/mqtt.js";
|
||||
const roomStore = useRoomStore()
|
||||
const userStore = useUserStore()
|
||||
const router = useRouter()
|
||||
const { proxy } = getCurrentInstance()
|
||||
// const WebSocketStore = useWebSocketStore()
|
||||
// const { sys_user_sex } = proxy.useDict('sys_user_sex')
|
||||
|
||||
const state = reactive({
|
||||
detail: {},
|
||||
weekName: ['周日', '周一', '周二', '周三', '周四', '周五', '周六'],
|
||||
@@ -306,82 +286,40 @@ const state = reactive({
|
||||
cooperation: import.meta.env.VITE_APP_COOPERATION_TYPE,
|
||||
})
|
||||
|
||||
|
||||
/** 查看文件详情 */
|
||||
const clickDetail = (item) => {
|
||||
// let urls = item.prefix + item.path
|
||||
// let status = proxy.judgeType(urls)
|
||||
// if (status == 2 || status == 1) {
|
||||
// state.imgMp4Ref.showModel('查看', urls)
|
||||
// } else if (status == 3) {
|
||||
// state.documentRef.showModel('查看', urls)
|
||||
// } else if (status == 4) {
|
||||
// state.model3dRef.showModel('查看', import.meta.env.VITE_APP_FILE_API + item.path)
|
||||
// }
|
||||
const isEmptyObject = (obj) => {
|
||||
return !obj || Object.keys(obj).length === 0
|
||||
}
|
||||
|
||||
/** 发起协作邀请 */
|
||||
const clickInitiate = () => {
|
||||
console.log('发起协作邀请')
|
||||
// state.loadText = '初始化协作房间中'
|
||||
// state.load = true
|
||||
// getUserStatus({ userIds: state.detail.userId })
|
||||
// .then((res) => {
|
||||
// res.data.forEach((i) => { i.status = 1 })
|
||||
// if (res.data.every((i) => i.status == 1)) {
|
||||
// proxy.$modal.msgSuccess('邀请已发送')
|
||||
// if (state.cooperation == 'skip') {
|
||||
// getRtcSetDetail().then((res) => {
|
||||
// const queryParams = new URLSearchParams({
|
||||
// type: 1,
|
||||
// userList: JSON.stringify([{ nickName: state.detail.nickName, userId: state.detail.userId }]),
|
||||
// appid: getAppId(),
|
||||
// Modinstid: getModuleId(),
|
||||
// token: getToken(),
|
||||
// userId: useUserStore().userId,
|
||||
// }).toString()
|
||||
// let url = import.meta.env.VITE_APP_ENV == 'development'
|
||||
// ? `/ar/app/conferencingRoom?${queryParams}`
|
||||
// : `${res.data.webMeetingUrl}?${queryParams}`
|
||||
// window.open(url, '_blank')
|
||||
// })
|
||||
// state.load = false
|
||||
// } else {
|
||||
// proxy.$router.push({
|
||||
// path: '/conferencingRoom',
|
||||
// query: {
|
||||
// type: 1,
|
||||
// userList: JSON.stringify([{ nickName: state.detail.nickName, userId: state.detail.userId }]),
|
||||
// appid: getAppId(),
|
||||
// Modinstid: getModuleId(),
|
||||
// token: getToken(),
|
||||
// userId: useUserStore().userId,
|
||||
// },
|
||||
// })
|
||||
// }
|
||||
// } else {
|
||||
// proxy.$modal.msgError(
|
||||
// res.data[0].status == 0 ? '当前用户不在线' : '当前用户正在其他协作中'
|
||||
// )
|
||||
// state.load = false
|
||||
// }
|
||||
// })
|
||||
// .catch(() => {
|
||||
// state.load = false
|
||||
// proxy.$modal.closeLoading()
|
||||
// })
|
||||
}
|
||||
|
||||
/** 删除文件 */
|
||||
const clickDeleteFile = (row) => {
|
||||
// proxy.$modal
|
||||
// .confirm('是否确认删除 “ ' + row.name + ' ” 文件?')
|
||||
// .then(async () => {
|
||||
// await getRemoveSolution({ assistanceId: state.detail.assistanceId, fileId: row.fileId })
|
||||
// proxy.$modal.msgSuccess('删除成功')
|
||||
// getTheFileList(state.detail)
|
||||
// })
|
||||
// .catch(() => {})
|
||||
let userData = null
|
||||
try {
|
||||
userData = JSON.parse(localStorage.getItem('userData')) || null
|
||||
} catch (e) {
|
||||
console.error('解析 userData 失败:', e)
|
||||
}
|
||||
if (isEmptyObject(state.detail)) {
|
||||
ElMessage({
|
||||
message: '请先选择人员',
|
||||
type: 'warning',
|
||||
})
|
||||
return
|
||||
}
|
||||
if(state.detail.uid == userData.uid){
|
||||
ElMessage({
|
||||
message: '不能邀请自己',
|
||||
type: 'warning',
|
||||
})
|
||||
return
|
||||
}
|
||||
roomStore.setUserUid(userData.uid)
|
||||
roomStore.setDetailUid(state.detail.uid)
|
||||
router.push({
|
||||
path: '/conferencingRoom',
|
||||
query:{
|
||||
type:1//创建房间,加入房间 2
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/** 修改展示列表 */
|
||||
@@ -399,14 +337,10 @@ const updateDetail = async (details) => {
|
||||
state.isShow = true
|
||||
} else {
|
||||
const res = await getInfo(details.uid)
|
||||
console.log(res,'res---+++')
|
||||
// state.detail = proxy.deepClone(res.data)
|
||||
state.detail = res.data
|
||||
|
||||
// getInfo(details.uid)
|
||||
// .then((res) => {
|
||||
// console.log(res,'人员详细信息')
|
||||
// // state.detail = proxy.deepClone(res.data)
|
||||
// })
|
||||
// .finally(() => { state.isShow = true })
|
||||
}
|
||||
@@ -415,15 +349,6 @@ const updateDetail = async (details) => {
|
||||
}
|
||||
}
|
||||
|
||||
/** 获取文件列表 */
|
||||
const getTheFileList = (details) => {
|
||||
// let detail = proxy.deepClone(details)
|
||||
// getFileList({ assistanceId: detail.assistanceId }).then((resser) => {
|
||||
// detail.fileList = resser.data
|
||||
// state.detail = proxy.deepClone(detail)
|
||||
// })
|
||||
}
|
||||
|
||||
/** 获取通话时长 */
|
||||
const getTime = () => {
|
||||
let begin = new Date(state.detail.beginTime).getTime()
|
||||
@@ -442,97 +367,77 @@ const getTime = () => {
|
||||
}
|
||||
|
||||
/** 加入会议 */
|
||||
const clickJoin = () => {
|
||||
// proxy.$modal.loading('加入会议中,请稍候...')
|
||||
// if (state.cooperation == 'skip') {
|
||||
// getRtcSetDetail().then((res) => {
|
||||
// const queryParams = new URLSearchParams({
|
||||
// type: 2,
|
||||
// assistanceId: state.socketInformation.data.assistanceId,
|
||||
// channelName: state.socketInformation.data.channelName,
|
||||
// nickName: useUserStore().nickName,
|
||||
// appid: getAppId(),
|
||||
// Modinstid: getModuleId(),
|
||||
// token: getToken(),
|
||||
// userId: useUserStore().userId,
|
||||
// }).toString()
|
||||
// let url = import.meta.env.VITE_APP_ENV == 'development'
|
||||
// ? `/ar/app/conferencingRoom?${queryParams}`
|
||||
// : `${res.data.webMeetingUrl}?${queryParams}`
|
||||
// window.open(url, '_blank')
|
||||
// })
|
||||
// } else {
|
||||
// proxy.$router.push({
|
||||
// path: '/conferencingRoom',
|
||||
// query: {
|
||||
// type: 2,
|
||||
// assistanceId: state.socketInformation.data.assistanceId,
|
||||
// channelName: state.socketInformation.data.channelName,
|
||||
// nickName: useUserStore().nickName,
|
||||
// appid: getAppId(),
|
||||
// Modinstid: getModuleId(),
|
||||
// token: getToken(),
|
||||
// userId: useUserStore().userId,
|
||||
// },
|
||||
// })
|
||||
// }
|
||||
// state.inviteDialog = false
|
||||
// proxy.$modal.closeLoading()
|
||||
const clickJoin = async () => {
|
||||
const res = await getStatusApi(state.socketInformation.room_uid,{status:1})
|
||||
if(res.meta.code == 200){
|
||||
ElMessage({
|
||||
message: '成功加入该协作',
|
||||
type: 'success',
|
||||
})
|
||||
state.inviteDialog = false
|
||||
router.push({
|
||||
path: '/conferencingRoom',
|
||||
query:{
|
||||
type:2,//创建房间,加入房间 2
|
||||
room_uid:state.socketInformation.room_uid
|
||||
}
|
||||
})
|
||||
}
|
||||
state.inviteDialog = false
|
||||
|
||||
}
|
||||
|
||||
/** 拒绝加入 */
|
||||
const clickRefuseJoin = () => {
|
||||
// refuseAssistance({
|
||||
// roomId: state.socketInformation.data.channelName,
|
||||
// assistanceId: state.socketInformation.data.assistanceId,
|
||||
// inviteUserId: state.socketInformation.data.inviteUserId,
|
||||
// }).then(() => {
|
||||
// proxy.$modal.msgSuccess('已拒绝加入该协作')
|
||||
// state.inviteDialog = false
|
||||
// })
|
||||
const clickRefuseJoin = async () => {
|
||||
//status 1: 同意加入, 5: 拒绝加入
|
||||
const res = await getStatusApi(state.socketInformation.room_uid,{status:5})
|
||||
if(res.meta.code == 200){
|
||||
ElMessage({
|
||||
message: '已拒绝加入该协作',
|
||||
type: 'error',
|
||||
})
|
||||
state.inviteDialog = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 处理 socket 消息 */
|
||||
/** 处理 mqtt 消息 */
|
||||
const processingSocket = (message) => {
|
||||
// if (message) {
|
||||
// state.socketInformation = JSON.parse(message.data)
|
||||
// if (state.socketInformation.type == 'invite_join') {
|
||||
// state.inviteDialog = true
|
||||
// showNotification()
|
||||
// }
|
||||
// if (state.socketInformation.type == 'initiator_out') {
|
||||
// state.inviteDialog = false
|
||||
// }
|
||||
// }
|
||||
if (message) {
|
||||
state.socketInformation = JSON.parse(message)
|
||||
console.log(state.socketInformation,'state.socketInformation')
|
||||
state.inviteDialog = true
|
||||
showNotification(state.socketInformation)
|
||||
}
|
||||
}
|
||||
|
||||
/** 浏览器通知 */
|
||||
const showNotification = () => {
|
||||
// if ('Notification' in window) {
|
||||
// Notification.requestPermission().then((permission) => {
|
||||
// if (permission === 'granted') {
|
||||
// const notification = new Notification('协作邀请', {
|
||||
// body: state.socketInformation.data.inviteNickName + '邀请您参加远程协作',
|
||||
// // icon: logo,
|
||||
// })
|
||||
// notification.onclick = () => { clickJoin() }
|
||||
// }
|
||||
// })
|
||||
// }
|
||||
const showNotification = (data) => {
|
||||
if ('Notification' in window) {
|
||||
Notification.requestPermission().then((permission) => {
|
||||
if (permission === 'granted') {
|
||||
const notification = new Notification('协作邀请', {
|
||||
// body: String(data.room_name) + '邀请您参加远程协作'
|
||||
body: '远程协作有新的邀请'
|
||||
// icon: logo,
|
||||
})
|
||||
notification.onclick = () => { clickJoin() }
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// watch(() => WebSocketStore.messages, (message) => {
|
||||
// if (state.cooperation != 'web') processingSocket(message)
|
||||
// })
|
||||
|
||||
// onMounted(() => {
|
||||
// state.isLinkKnow = proxy.getCurrentApplicationConfig('cooperation_link_know')
|
||||
// state.load = false
|
||||
// })
|
||||
|
||||
// onActivated(() => {})
|
||||
// 暴露给模板
|
||||
const { detail, weekName, tabValue, isShow, load, loadText, isLinkKnow, socketInformation, inviteDialog, cooperation } = toRefs(state)
|
||||
onMounted(async () => {
|
||||
await mqttClient.connect(`room${Math.random().toString(16).substr(2, 8)}`);
|
||||
const res = await userStore.getInfo()
|
||||
const topic = `xSynergy/ROOM/+/rooms/${res.uid}`;
|
||||
mqttClient.subscribe(topic, async (shapeData) => {
|
||||
processingSocket(shapeData.toString())
|
||||
});
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {computed} from 'vue'
|
||||
let message = computed(() => {
|
||||
return '找不到网页!'
|
||||
})
|
||||
|
||||
@@ -82,9 +82,12 @@ function handleLogin() {
|
||||
.then(async (res) => {
|
||||
const userInfo = JSON.parse(localStorage.getItem('userData'))
|
||||
router.push({
|
||||
path: '/whiteboard',
|
||||
query: { room_uid: 'nxst-ok4j' }
|
||||
path: '/coordinate',
|
||||
})
|
||||
// router.push({
|
||||
// path: '/whiteboard',
|
||||
// query: { room_uid: 'nxst-ok4j' }
|
||||
// })
|
||||
|
||||
})
|
||||
.catch((e) => {
|
||||
@@ -117,9 +120,12 @@ onMounted(async () => {
|
||||
if (res.meta.code === 401) {
|
||||
showLogin.value = true;
|
||||
} else {
|
||||
// router.push({
|
||||
// path: '/whiteboard',
|
||||
// query: { room_uid: 'nxst-ok4j' }
|
||||
// })
|
||||
router.push({
|
||||
path: '/whiteboard',
|
||||
query: { room_uid: 'nxst-ok4j' }
|
||||
path: '/coordinate',
|
||||
})
|
||||
}
|
||||
loginView.value = false
|
||||
|
||||
Reference in New Issue
Block a user