changed padding, fixed media check issue
This commit is contained in:
163
src/components/mediaCheck.vue
Normal file
163
src/components/mediaCheck.vue
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<el-dialog
|
||||||
|
custom-class="check-media"
|
||||||
|
destroy-on-close
|
||||||
|
v-model="showCheckMediaVideo"
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
title="摄像头和麦克风"
|
||||||
|
:fullscreen="screenWidth < 900"
|
||||||
|
width="60%"
|
||||||
|
>
|
||||||
|
<el-alert
|
||||||
|
v-if="isVideoAvailable === undefined || isAudioAvailable === undefined"
|
||||||
|
>检测中。</el-alert
|
||||||
|
>
|
||||||
|
<el-alert
|
||||||
|
v-else-if="isVideoAvailable === true && isAudioAvailable === true"
|
||||||
|
type="success"
|
||||||
|
>摄像头工作正常,麦克风工作正常。</el-alert
|
||||||
|
>
|
||||||
|
<el-alert
|
||||||
|
v-else-if="isVideoAvailable === true && isAudioAvailable === false"
|
||||||
|
type="warning"
|
||||||
|
>摄像头工作正常,麦克风无法正常工作。</el-alert
|
||||||
|
>
|
||||||
|
<el-alert
|
||||||
|
v-else-if="isVideoAvailable === false && isAudioAvailable === true"
|
||||||
|
type="warning"
|
||||||
|
>摄像头无法正常工作,麦克风工作正常。</el-alert
|
||||||
|
>
|
||||||
|
<el-alert v-else type="error"
|
||||||
|
>摄像头无法正常工作,麦克风无法正常工作。</el-alert
|
||||||
|
>
|
||||||
|
<!-- <div id="row"> -->
|
||||||
|
<div id="check-media-wrap" ref="checkMediaWrapRef">
|
||||||
|
<video id="check-media-video" ref="checkMediaVideoRef"></video>
|
||||||
|
<!-- <el-icon class="microphone-icon" :size="50"> -->
|
||||||
|
<Microphone
|
||||||
|
v-if="isAudioAvailable === undefined"
|
||||||
|
class="microphone-loading"
|
||||||
|
/>
|
||||||
|
<Microphone
|
||||||
|
v-else-if="isAudioAvailable === true"
|
||||||
|
class="microphone-on"
|
||||||
|
/>
|
||||||
|
<Mute v-else class="microphone-off" />
|
||||||
|
<!-- </el-icon> -->
|
||||||
|
</div>
|
||||||
|
<!-- </div> -->
|
||||||
|
<template #footer>
|
||||||
|
<span class="dialog-footer">
|
||||||
|
<el-button type="primary" @click="close">确认</el-button>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script setup name="MediaCheck">
|
||||||
|
import { nextTick, onMounted, ref } from "vue";
|
||||||
|
import {
|
||||||
|
FullScreen,
|
||||||
|
CloseBold,
|
||||||
|
Microphone,
|
||||||
|
Mute,
|
||||||
|
} from "@element-plus/icons-vue";
|
||||||
|
const screenWidth = ref(0);
|
||||||
|
screenWidth.value = screen.width;
|
||||||
|
const checkMediaVideoRef = ref(null);
|
||||||
|
const checkMediaWrapRef = ref(null);
|
||||||
|
const showCheckMediaVideo = ref(true);
|
||||||
|
const isVideoAvailable = ref(undefined);
|
||||||
|
const isAudioAvailable = ref(undefined);
|
||||||
|
const videoStream = ref(null);
|
||||||
|
const audioStream = ref(null);
|
||||||
|
// TODO:可能有摄像头占用问题。
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
nextTick(async () => {
|
||||||
|
const videoWidth = checkMediaWrapRef.value.offsetWidth;
|
||||||
|
const videoHeight = checkMediaWrapRef.value.offsetHeight;
|
||||||
|
try {
|
||||||
|
videoStream.value = await navigator.mediaDevices.getUserMedia({
|
||||||
|
video: { width: videoWidth, height: videoHeight },
|
||||||
|
});
|
||||||
|
checkMediaVideoRef.value.srcObject = videoStream.value;
|
||||||
|
checkMediaVideoRef.value.play();
|
||||||
|
isVideoAvailable.value = true;
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
isVideoAvailable.value = false;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
audioStream.value = navigator.mediaDevices.getUserMedia({ audio: true });
|
||||||
|
isAudioAvailable.value = true;
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
isAudioAvailable.value = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
const close = () => {
|
||||||
|
try {
|
||||||
|
videoStream.value.getTracks().forEach((track) => track.stop());
|
||||||
|
audioStream.value.getTracks().forEach((track) => track.stop());
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
showCheckMediaVideo.value = false;
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
// :deep(.check-media) {
|
||||||
|
:deep(.el-dialog__body) {
|
||||||
|
text-align: center;
|
||||||
|
#check-media-wrap {
|
||||||
|
display: inline-block;
|
||||||
|
margin: 1vw auto;
|
||||||
|
border-radius: 1vw;
|
||||||
|
overflow: hidden;
|
||||||
|
width: 36vw;
|
||||||
|
height: 24vw;
|
||||||
|
background-color: #000;
|
||||||
|
position: relative;
|
||||||
|
// #check-media-video {
|
||||||
|
// width: 100%;
|
||||||
|
// height: 100%;
|
||||||
|
// }
|
||||||
|
svg {
|
||||||
|
position: absolute;
|
||||||
|
left: 1vw;
|
||||||
|
bottom: 1vw;
|
||||||
|
width: 3vw;
|
||||||
|
height: 3vw;
|
||||||
|
}
|
||||||
|
.microphone-loading {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
.microphone-on {
|
||||||
|
color: springgreen;
|
||||||
|
}
|
||||||
|
.microphone-off {
|
||||||
|
color: indianred;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// }
|
||||||
|
|
||||||
|
@media screen and (max-width: 900px) {
|
||||||
|
:deep(.check-media) {
|
||||||
|
.el-dialog__body {
|
||||||
|
#check-media-wrap {
|
||||||
|
margin: 2vw auto;
|
||||||
|
width: 80vw;
|
||||||
|
height: 54vw;
|
||||||
|
svg {
|
||||||
|
width: 6vw;
|
||||||
|
height: 6vw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
@ -1,592 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div id="app-container" v-loading="isMeetingLoading" ref="appContainer">
|
|
||||||
<div class="row">
|
|
||||||
<div class="meeting-container" ref="meetingContainerRef">
|
|
||||||
<div id="video-element" ref="videoElementRef"></div>
|
|
||||||
<el-button @click="setFullScreen" class="fullscreen-btn">
|
|
||||||
全屏</el-button
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
<div class="chat-right" ref="chatRightRef">
|
|
||||||
<messageList :messageList="messages"></messageList>
|
|
||||||
<div class="option-bar">
|
|
||||||
<div
|
|
||||||
class="display-message"
|
|
||||||
v-html="editingMessage"
|
|
||||||
@click="openRichMessageEditor"
|
|
||||||
></div>
|
|
||||||
<el-button
|
|
||||||
type="success"
|
|
||||||
@click="sendMessage"
|
|
||||||
:disabled="editingMessage.length === 0"
|
|
||||||
>发送</el-button
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<el-tabs class="tabs" type="border-card">
|
|
||||||
<el-tab-pane label="会议介绍">
|
|
||||||
<div class="meeting-info meeting-note" v-html="meetingNote"></div>
|
|
||||||
</el-tab-pane>
|
|
||||||
<el-tab-pane label="会议日程"
|
|
||||||
><div
|
|
||||||
class="meeting-info meeting-schedule"
|
|
||||||
v-html="meetingSchedule"
|
|
||||||
></div
|
|
||||||
></el-tab-pane>
|
|
||||||
<el-tab-pane label="专家介绍"
|
|
||||||
><div class="meeting-info expert-info" v-html="expertInfo"></div
|
|
||||||
></el-tab-pane>
|
|
||||||
<el-tab-pane label="聊天" class="chat-pane" v-if="screenWidth < 768"
|
|
||||||
><div class="meeting-info chat">
|
|
||||||
<messageList :messageList="messages"></messageList>
|
|
||||||
<div class="option-bar">
|
|
||||||
<el-input
|
|
||||||
v-model="editingMessage"
|
|
||||||
style="margin-right: 15px"
|
|
||||||
></el-input>
|
|
||||||
<!-- <svg-icon :icon-class="search" style="height: 30px; width: 16px" /> -->
|
|
||||||
<el-button
|
|
||||||
type="success"
|
|
||||||
@click="sendMessage"
|
|
||||||
:disabled="editingMessage.length === 0"
|
|
||||||
>发送</el-button
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</div></el-tab-pane
|
|
||||||
>
|
|
||||||
</el-tabs>
|
|
||||||
|
|
||||||
<el-dialog title="富文本输入框" v-model="showRichEditor">
|
|
||||||
<wangEditor v-model="richMessage" width="100%" :focus="true"></wangEditor>
|
|
||||||
<template #footer>
|
|
||||||
<span class="dialog-footer">
|
|
||||||
<el-button type="warning" @click="cancelRichMessage">取消</el-button>
|
|
||||||
<el-button type="primary" @click="saveRichMessage">确定</el-button>
|
|
||||||
</span>
|
|
||||||
</template>
|
|
||||||
</el-dialog>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<script setup>
|
|
||||||
import { computed, reactive, watch, ref, onMounted, onUnmounted } from "vue";
|
|
||||||
import { useStore } from "vuex";
|
|
||||||
import axios from "axios";
|
|
||||||
import { signMeeting, generateSignature } from "@/api/meeting";
|
|
||||||
import questions from "@/components/questions";
|
|
||||||
import wangEditor from "@/components/wangEditor";
|
|
||||||
import messageList from "@/components/messageList";
|
|
||||||
import dayjs from "dayjs";
|
|
||||||
import ZoomMtgEmbedded from "@zoomus/websdk/embedded";
|
|
||||||
import _ from "lodash";
|
|
||||||
|
|
||||||
import ReconnectingWebSocket from "reconnecting-websocket";
|
|
||||||
import { ElMessage, ElMessageBox } from "element-plus";
|
|
||||||
import { useRoute } from "vue-router";
|
|
||||||
const route = useRoute();
|
|
||||||
const store = useStore();
|
|
||||||
|
|
||||||
const meetingNote = computed(() => store.getters.meetingNote); // 会议介绍
|
|
||||||
const meetingSchedule = computed(() => store.getters.meetingSchedule); // 会议日程
|
|
||||||
const expertInfo = computed(() => store.getters.expertInfo); // 专家信息
|
|
||||||
const isMeetingLoading = ref(true); // 视频课件是否正在加载
|
|
||||||
const templateId = ref(6); // 布局 id
|
|
||||||
if (store.getters.templateId) {
|
|
||||||
templateId.value = store.getters.templateId;
|
|
||||||
}
|
|
||||||
const showRichEditor = ref(false);
|
|
||||||
const joinAccount = ref(""); // 参会账号
|
|
||||||
const joinName = ref(""); // 参会名称
|
|
||||||
const screenWidth = ref(0); // 屏幕宽度
|
|
||||||
const meetingWidth = ref(0); // 视频课件容器元素宽度
|
|
||||||
const meetingHeight = ref(0); // 视频课件容器元素高度
|
|
||||||
screenWidth.value = document.body.offsetWidth;
|
|
||||||
|
|
||||||
joinAccount.value = store.getters.username;
|
|
||||||
joinName.value = store.getters.nickname;
|
|
||||||
// 设置会议背景大小、图片
|
|
||||||
const meetingContainerRef = ref(null); // 会议容器元素(包含背景)
|
|
||||||
const videoElementRef = ref(null); // 视频课件容器元素
|
|
||||||
const chatRightRef = ref(null); // 右侧聊天元素
|
|
||||||
onMounted(() => {
|
|
||||||
meetingContainerRef.value.style.background = `url(${store.getters.templateBackgroundPic}) 0% 0% / cover no-repeat`; // 设置背景图片
|
|
||||||
setTextLabel();
|
|
||||||
meetingWidth.value = meetingContainerRef.value.offsetWidth * 0.95; // zoom 会议组件宽度是父元素宽度的 95%
|
|
||||||
meetingHeight.value = (meetingWidth.value * 9) / 16; // 根据 zoom 会议组件长宽比 16:9 计算出会议组件高度
|
|
||||||
meetingContainerRef.value.style.paddingTop = `${meetingHeight.value * 0.2}px`; // 父元素顶部padding 是会议组件高度的20%
|
|
||||||
videoElementRef.value.style.width = meetingWidth.value + "px";
|
|
||||||
videoElementRef.value.style.height = meetingHeight.value + "px";
|
|
||||||
});
|
|
||||||
|
|
||||||
// 设置文本标签
|
|
||||||
const setTextLabel = () => {
|
|
||||||
document.querySelectorAll(".text-tag").forEach((el) => {
|
|
||||||
el.remove();
|
|
||||||
});
|
|
||||||
store.getters.textLabelList.forEach((item) => {
|
|
||||||
const labelObj = JSON.parse(item.textLabel);
|
|
||||||
const textEl = document.createElement("div");
|
|
||||||
textEl.id = `${_.uniqueId("tag-")}`;
|
|
||||||
textEl.innerHTML = labelObj.content;
|
|
||||||
textEl.className = "text-tag";
|
|
||||||
textEl.style.backgroundColor = labelObj.backgroundColor;
|
|
||||||
textEl.style.visibility =
|
|
||||||
labelObj.visibility === "1" ? "visible" : "hidden";
|
|
||||||
textEl.style.left = `${labelObj.x}`;
|
|
||||||
textEl.style.top = `${labelObj.y}`;
|
|
||||||
meetingContainerRef.value.appendChild(textEl);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// 会议配置
|
|
||||||
const meetingConfig = reactive({
|
|
||||||
client: ZoomMtgEmbedded.createClient(),
|
|
||||||
// This Sample App has been updated to use SDK App type credentials https://marketplace.zoom.us/docs/guides/build/sdk-app
|
|
||||||
sdkKey: "99Spa64AWHYVZD95imUpVyMD0KF9CpEIrIb1",
|
|
||||||
// meetingNumber: store.getters.meetingNumber,
|
|
||||||
meetingNumber: store.getters.meetingNumber,
|
|
||||||
passWord: store.state.password,
|
|
||||||
// sdkKey: "99Spa64AWHYVZD95imUpVyMD0KF9CpEIrIb1",
|
|
||||||
// meetingNumber: "97097842319",
|
|
||||||
// passWord: "111916",
|
|
||||||
// role: route.name === "Host" ? 1 : 0,
|
|
||||||
role: 1,
|
|
||||||
// signatureEndpoint: "http://120.26.107.74:4000",
|
|
||||||
userEmail: store.getters.email,
|
|
||||||
// userEmail: "934510341@qq.com",
|
|
||||||
userName: joinName.value,
|
|
||||||
// userName: "afasde1",
|
|
||||||
// pass in the registrant's token if your meeting or webinar requires registration. More info here:
|
|
||||||
// Meetings: https://marketplace.zoom.us/docs/sdk/native-sdks/web/component-view/meetings#join-registered
|
|
||||||
// Webinars: https://marketplace.zoom.us/docs/sdk/native-sdks/web/component-view/webinars#join-registered
|
|
||||||
// registrantToken:
|
|
||||||
// "Xhi0bKUzyNBnJwe2EJWZ0JZ3IYqtPgyyWE1CXW3z2X4.DQMAAAAWm3t-jxZqSS14R0dCSFRDNkFibVQwakpHbFd3AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
|
|
||||||
registrantToken: "",
|
|
||||||
});
|
|
||||||
console.log(meetingConfig);
|
|
||||||
|
|
||||||
// 生成签名
|
|
||||||
const getSignature = async () => {
|
|
||||||
const { sign } = await generateSignature({
|
|
||||||
meetingNumber: store.getters.meetingNumber,
|
|
||||||
role: meetingConfig.role,
|
|
||||||
});
|
|
||||||
console.log(sign);
|
|
||||||
startMeeting(sign);
|
|
||||||
// axios
|
|
||||||
// .post("http://localhost:4000", {
|
|
||||||
// meetingNumber: store.getters.meetingNumber,
|
|
||||||
// role: meetingConfig.role,
|
|
||||||
// })
|
|
||||||
// .then((res) => {
|
|
||||||
// console.log(res.data.signature);
|
|
||||||
// startMeeting(res.data.signature);
|
|
||||||
// })
|
|
||||||
// .catch((error) => {
|
|
||||||
// console.log(error);
|
|
||||||
// });
|
|
||||||
};
|
|
||||||
|
|
||||||
// 开始会议
|
|
||||||
const startMeeting = async (signature) => {
|
|
||||||
let meetingSDKElement = document.getElementById("video-element");
|
|
||||||
try {
|
|
||||||
meetingConfig.client.init({
|
|
||||||
debug: true,
|
|
||||||
zoomAppRoot: meetingSDKElement,
|
|
||||||
language: "zh-CN",
|
|
||||||
customize: {
|
|
||||||
video: {
|
|
||||||
isResizable: false,
|
|
||||||
popper: {
|
|
||||||
disableDraggable: true,
|
|
||||||
},
|
|
||||||
viewSizes: {
|
|
||||||
default: {
|
|
||||||
width: meetingWidth.value,
|
|
||||||
height: meetingHeight.value - 45,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
meetingInfo: [
|
|
||||||
"topic",
|
|
||||||
"host",
|
|
||||||
"mn",
|
|
||||||
"pwd",
|
|
||||||
"telPwd",
|
|
||||||
"invite",
|
|
||||||
"participant",
|
|
||||||
"dc",
|
|
||||||
"enctype",
|
|
||||||
],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
await meetingConfig.client.join({
|
|
||||||
sdkKey: meetingConfig.sdkKey,
|
|
||||||
signature: signature,
|
|
||||||
meetingNumber: meetingConfig.meetingNumber,
|
|
||||||
password: meetingConfig.passWord,
|
|
||||||
userName: meetingConfig.userName,
|
|
||||||
userEmail: meetingConfig.userEmail,
|
|
||||||
tk: meetingConfig.registrantToken,
|
|
||||||
});
|
|
||||||
document.querySelector("#suspension-view-tab-thumbnail-gallery").click();
|
|
||||||
|
|
||||||
initDesktopLayout();
|
|
||||||
const safeButton = document.querySelector('button[title="安全"]');
|
|
||||||
if (safeButton) {
|
|
||||||
safeButton.click();
|
|
||||||
const allowScreenShare = document.querySelectorAll(
|
|
||||||
".zmwebsdk-MuiButtonBase-root.zmwebsdk-MuiListItem-root.zmwebsdk-MuiMenuItem-root.zmwebsdk-MuiMenuItem-gutters.zmwebsdk-MuiListItem-gutters.zmwebsdk-MuiListItem-button"
|
|
||||||
)[1];
|
|
||||||
if (allowScreenShare.childElementCount === 0) {
|
|
||||||
allowScreenShare.click();
|
|
||||||
}
|
|
||||||
console.log(allowScreenShare);
|
|
||||||
}
|
|
||||||
|
|
||||||
isMeetingLoading.value = false;
|
|
||||||
} catch (error) {
|
|
||||||
isMeetingLoading.value = false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const setFullScreen = () => {
|
|
||||||
videoElementRef.value.requestFullscreen();
|
|
||||||
};
|
|
||||||
onMounted(() => {
|
|
||||||
getSignature();
|
|
||||||
});
|
|
||||||
// 初始化
|
|
||||||
const initDesktopLayout = () => {
|
|
||||||
// 切换到 gallery view
|
|
||||||
// 初始化高度
|
|
||||||
|
|
||||||
const heightEl = document.querySelectorAll(
|
|
||||||
".zmwebsdk-MuiBox-root.zmwebsdk-MuiBox-root"
|
|
||||||
)[2];
|
|
||||||
heightEl.style.height = `${meetingHeight.value - 45}px`;
|
|
||||||
console.log(meetingHeight.value);
|
|
||||||
|
|
||||||
// 初始化宽度
|
|
||||||
// zmwebsdk-MuiPaper-root zmwebsdk-makeStyles-root-21 zmwebsdk-MuiPaper-elevation1 zmwebsdk-MuiPaper-rounded
|
|
||||||
document.querySelectorAll(
|
|
||||||
".zmwebsdk-MuiPaper-root.zmwebsdk-MuiPaper-elevation1.zmwebsdk-MuiPaper-rounded"
|
|
||||||
)[1].style.width = `${meetingWidth.value - 4}px`;
|
|
||||||
// 加载完成显示 video element
|
|
||||||
document.querySelector("#video-element").style.visibility = "visible";
|
|
||||||
};
|
|
||||||
// 检测屏幕共享开启状态变化
|
|
||||||
const inSharing = ref(false); // 是否开启屏幕共享
|
|
||||||
setInterval(() => {
|
|
||||||
const elSharing = document.querySelector(
|
|
||||||
"div[class*=zmwebsdk-makeStyles-inSharing]"
|
|
||||||
);
|
|
||||||
if (elSharing) {
|
|
||||||
inSharing.value = true;
|
|
||||||
} else {
|
|
||||||
inSharing.value = false;
|
|
||||||
}
|
|
||||||
}, 500);
|
|
||||||
|
|
||||||
const avatarList = ref("");
|
|
||||||
setInterval(() => {
|
|
||||||
const avatarElList = document.querySelectorAll(
|
|
||||||
'ul[class^="zmwebsdk-makeStyles-avatarList"] li'
|
|
||||||
);
|
|
||||||
const arr = [];
|
|
||||||
avatarElList.forEach((el) => {
|
|
||||||
arr.push({
|
|
||||||
videoOn: el.className.includes("videoOn"),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
avatarList.value = arr
|
|
||||||
.map((el) => {
|
|
||||||
return el.videoOn ? "on" : "off";
|
|
||||||
})
|
|
||||||
.join(",");
|
|
||||||
// console.log(avatarList.value);
|
|
||||||
}, 500);
|
|
||||||
watch(avatarList, (val) => {
|
|
||||||
if (inSharing.value) {
|
|
||||||
document
|
|
||||||
.querySelectorAll(`ul[class^="zmwebsdk-makeStyles-avatarList"] li`)
|
|
||||||
.forEach((el) => {
|
|
||||||
if (el.className.includes("videoOn")) {
|
|
||||||
el.style.visibility = "visible";
|
|
||||||
} else {
|
|
||||||
el.style.visibility = "hidden";
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 根据id设置布局
|
|
||||||
const setLayout = (templateId) => {
|
|
||||||
console.log(templateId);
|
|
||||||
const v_s_wrap_el = document.querySelectorAll(
|
|
||||||
".zmwebsdk-MuiBox-root.zmwebsdk-MuiBox-root"
|
|
||||||
)[2]; // 包含视频和课件的容器
|
|
||||||
v_s_wrap_el.style.flexDirection = "";
|
|
||||||
|
|
||||||
const v_wrap_el = v_s_wrap_el.lastChild;
|
|
||||||
if (!inSharing.value) return;
|
|
||||||
|
|
||||||
const s_wrap_el = document.querySelector(
|
|
||||||
`div[class*="zmwebsdk-makeStyles-inSharing"]`
|
|
||||||
);
|
|
||||||
if (templateId === "1") {
|
|
||||||
// 课件|视频 对半分
|
|
||||||
v_wrap_el.style.width = `${meetingWidth.value / 2 - 2}px`;
|
|
||||||
} else if (templateId === "2") {
|
|
||||||
// 视频|课件 对半分
|
|
||||||
v_wrap_el.style.width = `${meetingWidth.value / 2 - 2}px`;
|
|
||||||
v_s_wrap_el.style.flexDirection = "row-reverse";
|
|
||||||
} else if (templateId === "3") {
|
|
||||||
// 课件|视频 左4/5 | 右边1/5
|
|
||||||
v_wrap_el.style.width = `${(meetingWidth.value - 4) / 5}px`;
|
|
||||||
} else if (templateId === "4") {
|
|
||||||
// 视频|课件 左1/4 | 右边3/4
|
|
||||||
v_wrap_el.style.width = `${(meetingWidth.value - 4) / 5}px`;
|
|
||||||
v_s_wrap_el.style.flexDirection = "row-reverse";
|
|
||||||
} else if (templateId === "5") {
|
|
||||||
// 只显示课件
|
|
||||||
v_wrap_el.style.display = `none`;
|
|
||||||
} else if (templateId === "6") {
|
|
||||||
// 只显示视频
|
|
||||||
s_wrap_el.style.display = "none";
|
|
||||||
v_wrap_el.style.width = `${meetingWidth.value - 4}px`;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
watch(inSharing, (newVal) => {
|
|
||||||
console.log(newVal);
|
|
||||||
if (newVal) {
|
|
||||||
setLayout(templateId.value);
|
|
||||||
} else {
|
|
||||||
const galleryViewButton = document.querySelector(
|
|
||||||
"#suspension-view-tab-thumbnail-gallery"
|
|
||||||
);
|
|
||||||
if (galleryViewButton) galleryViewButton.click();
|
|
||||||
const v_s_wrap_el = document.querySelectorAll(
|
|
||||||
".zmwebsdk-MuiBox-root.zmwebsdk-MuiBox-root"
|
|
||||||
)[2];
|
|
||||||
if (!v_s_wrap_el) return;
|
|
||||||
const v_wrap_el = v_s_wrap_el.lastChild;
|
|
||||||
v_wrap_el.style.width = "";
|
|
||||||
v_s_wrap_el.style.flexDirection = "";
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
let socket = reactive({});
|
|
||||||
const initWebSocket = () => {
|
|
||||||
// 建立websocket连接
|
|
||||||
socket = new ReconnectingWebSocket(
|
|
||||||
`wss://meeting.chuhuankj.com/wss/websocket/meeting/${store.getters.meetingId}/${joinAccount.value}`
|
|
||||||
);
|
|
||||||
socket.addEventListener("open", () => {
|
|
||||||
// socket.send("Hello Server!");
|
|
||||||
console.log("websocket,已连接");
|
|
||||||
});
|
|
||||||
|
|
||||||
// 监听websocket消息
|
|
||||||
socket.addEventListener("message", async (event) => {
|
|
||||||
const data = JSON.parse(JSON.parse(event.data));
|
|
||||||
console.log(data);
|
|
||||||
// 会议信息更新时
|
|
||||||
if (data.type === "isRefreshMeeting") {
|
|
||||||
await store.dispatch("getMeetingInfo", store.getters.meetingId);
|
|
||||||
meetingContainerRef.value.style.background = ` url(${store.getters.templateBackgroundPic}) 0% 0% / cover no-repeat`;
|
|
||||||
templateId.value = store.getters.templateId;
|
|
||||||
setLayout(templateId.value);
|
|
||||||
setTextLabel();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 收到聊天消息时
|
|
||||||
else if (data.type === "isChat") {
|
|
||||||
console.log(JSON.parse(JSON.parse(JSON.parse(data.content).msg)));
|
|
||||||
messages.value.push({
|
|
||||||
...JSON.parse(JSON.parse(JSON.parse(data.content).msg)),
|
|
||||||
id: _.uniqueId(),
|
|
||||||
time: dayjs().format("YYYY-MM-DD HH:mm:ss"),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 会议结束时
|
|
||||||
else if (data.type === "isCloseMeeting") {
|
|
||||||
showExamDialog.value = false;
|
|
||||||
showQuestionnaireDialog.value = false;
|
|
||||||
ElMessageBox.alert("会议已结束");
|
|
||||||
socket.close();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
socket.addEventListener("close", (event) => {
|
|
||||||
console.log(event, "close");
|
|
||||||
});
|
|
||||||
socket.addEventListener("error", (event) => {
|
|
||||||
console.log(event, "error");
|
|
||||||
});
|
|
||||||
};
|
|
||||||
initWebSocket();
|
|
||||||
|
|
||||||
/* 聊天功能 */
|
|
||||||
const messages = ref([]); // 消息列表
|
|
||||||
const editingMessage = ref(""); // 正在编辑的内容
|
|
||||||
const richMessage = ref(""); // 正在编辑的富文本内容--主持人独有
|
|
||||||
|
|
||||||
// 保存富文本聊天内容
|
|
||||||
const saveRichMessage = () => {
|
|
||||||
showRichEditor.value = false;
|
|
||||||
editingMessage.value = richMessage.value;
|
|
||||||
richMessage.value = "";
|
|
||||||
};
|
|
||||||
// 取消保存富文本
|
|
||||||
const cancelRichMessage = () => {
|
|
||||||
showRichEditor.value = false;
|
|
||||||
};
|
|
||||||
// 打开富文本编辑器窗口
|
|
||||||
const openRichMessageEditor = () => {
|
|
||||||
showRichEditor.value = true;
|
|
||||||
richMessage.value = editingMessage.value;
|
|
||||||
};
|
|
||||||
// 发送消息
|
|
||||||
const sendMessage = () => {
|
|
||||||
socket.send(
|
|
||||||
JSON.stringify(
|
|
||||||
JSON.stringify({
|
|
||||||
account: joinName.value,
|
|
||||||
msg: editingMessage.value,
|
|
||||||
})
|
|
||||||
)
|
|
||||||
);
|
|
||||||
// socket.send(editingMessage.value);
|
|
||||||
messages.value.push({
|
|
||||||
id: _.uniqueId(),
|
|
||||||
account: joinName.value,
|
|
||||||
msg: editingMessage.value,
|
|
||||||
isMe: true,
|
|
||||||
time: dayjs().format("YYYY-MM-DD HH:mm:ss"),
|
|
||||||
});
|
|
||||||
editingMessage.value = "";
|
|
||||||
};
|
|
||||||
|
|
||||||
const leaveConference = () => {
|
|
||||||
meetingConfig.client.leaveMeeting();
|
|
||||||
};
|
|
||||||
window.addEventListener("beforeunload", leaveConference);
|
|
||||||
onUnmounted(() => {
|
|
||||||
window.removeEventListener("beforeunload", leaveConference);
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
<style scoped lang="scss">
|
|
||||||
#app-container {
|
|
||||||
width: 100%;
|
|
||||||
margin: 0 auto;
|
|
||||||
:deep(.text-tag) {
|
|
||||||
position: absolute;
|
|
||||||
z-index: 999;
|
|
||||||
border-radius: 4px;
|
|
||||||
padding: 5px;
|
|
||||||
background-color: #fff;
|
|
||||||
* {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.row {
|
|
||||||
width: 100%;
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
.meeting-container {
|
|
||||||
position: relative;
|
|
||||||
width: 80%;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.chat-right {
|
|
||||||
width: 20%;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: space-between;
|
|
||||||
.option-bar {
|
|
||||||
margin-top: 0;
|
|
||||||
margin: 5px;
|
|
||||||
.display-message {
|
|
||||||
border: 1px solid #666;
|
|
||||||
width: 80%;
|
|
||||||
min-height: 100%;
|
|
||||||
border-radius: 5px;
|
|
||||||
margin-right: 5px;
|
|
||||||
word-wrap: break-word;
|
|
||||||
:deep(p) {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
:deep(.message-list) {
|
|
||||||
overflow-y: scroll;
|
|
||||||
margin-bottom: 0;
|
|
||||||
// height: 80%;
|
|
||||||
flex: 1;
|
|
||||||
p {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (max-width: 768px) {
|
|
||||||
.meeting-container {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
.chat-right {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#video-element {
|
|
||||||
// visibility: hidden;
|
|
||||||
// height: 600px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tabs {
|
|
||||||
width: 100%;
|
|
||||||
.meeting-info {
|
|
||||||
:deep(p) {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// :deep(.el-tabs__content) {
|
|
||||||
// .chat {
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
.option-bar {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-around;
|
|
||||||
}
|
|
||||||
:deep(.zmwebsdk-MuiPaper-root) {
|
|
||||||
background: transparent;
|
|
||||||
box-shadow: 0 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.zmwebsdk-MuiToolbar-root) {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
:deep(.zmwebsdk-makeStyles-singleView-7) {
|
|
||||||
// background-color: transparent;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
:deep(.zmwebsdk-MuiPaper-root.zmwebsdk-makeStyles-root-50.zmwebsdk-MuiPaper-elevation1.zmwebsdk-MuiPaper-rounded) {
|
|
||||||
width: 20%;
|
|
||||||
background: #ccc;
|
|
||||||
}
|
|
||||||
.fullscreen-btn {
|
|
||||||
position: absolute;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
}
|
|
||||||
</style>
|
|
@ -1,562 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div
|
|
||||||
id="app-container"
|
|
||||||
v-loading="isMeetingLoading"
|
|
||||||
element-loading-text="会议正在加载中"
|
|
||||||
ref="appContainer"
|
|
||||||
>
|
|
||||||
<div class="row">
|
|
||||||
<div class="meeting-container" ref="meetingContainerRef">
|
|
||||||
<div id="video-element" ref="videoElementRef"></div>
|
|
||||||
<el-button @click="setFullScreen" class="fullscreen-btn">
|
|
||||||
全屏</el-button
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
<div class="chat-right" ref="chatRightRef">
|
|
||||||
<messageList :messageList="messages"></messageList>
|
|
||||||
<div class="option-bar">
|
|
||||||
<div
|
|
||||||
class="display-message"
|
|
||||||
v-html="editingMessage"
|
|
||||||
@click="openRichMessageEditor"
|
|
||||||
></div>
|
|
||||||
<el-button
|
|
||||||
type="success"
|
|
||||||
@click="sendMessage"
|
|
||||||
:disabled="editingMessage.length === 0"
|
|
||||||
>发送</el-button
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<el-tabs class="tabs" type="border-card">
|
|
||||||
<el-tab-pane label="会议介绍">
|
|
||||||
<div class="meeting-info meeting-note" v-html="meetingNote"></div>
|
|
||||||
</el-tab-pane>
|
|
||||||
<el-tab-pane label="会议日程"
|
|
||||||
><div
|
|
||||||
class="meeting-info meeting-schedule"
|
|
||||||
v-html="meetingSchedule"
|
|
||||||
></div
|
|
||||||
></el-tab-pane>
|
|
||||||
<el-tab-pane label="专家介绍"
|
|
||||||
><div class="meeting-info expert-info" v-html="expertInfo"></div
|
|
||||||
></el-tab-pane>
|
|
||||||
<el-tab-pane label="聊天" class="chat-pane" v-if="screenWidth < 768"
|
|
||||||
><div class="meeting-info chat">
|
|
||||||
<messageList :messageList="messages"></messageList>
|
|
||||||
<div class="option-bar">
|
|
||||||
<el-input
|
|
||||||
v-model="richMessage"
|
|
||||||
style="margin-right: 15px"
|
|
||||||
></el-input>
|
|
||||||
<!-- <wangEditor
|
|
||||||
v-model="editingMessage"
|
|
||||||
height="100px"
|
|
||||||
style="margin-right: 15px"
|
|
||||||
></wangEditor> -->
|
|
||||||
<!-- <svg-icon :icon-class="search" style="height: 30px; width: 16px" /> -->
|
|
||||||
<el-button
|
|
||||||
type="success"
|
|
||||||
@click="sendMessage"
|
|
||||||
:disabled="editingMessage.length === 0"
|
|
||||||
>发送</el-button
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</div></el-tab-pane
|
|
||||||
>
|
|
||||||
</el-tabs>
|
|
||||||
|
|
||||||
<el-dialog title="富文本输入框" v-model="showRichEditor">
|
|
||||||
<wangEditor v-model="richMessage" width="100%" :focus="true"></wangEditor>
|
|
||||||
<template #footer>
|
|
||||||
<span class="dialog-footer">
|
|
||||||
<el-button type="warning" @click="cancelRichMessage">取消</el-button>
|
|
||||||
<el-button type="primary" @click="saveRichMessage">确定</el-button>
|
|
||||||
</span>
|
|
||||||
</template>
|
|
||||||
</el-dialog>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<script setup>
|
|
||||||
import { computed, reactive, watch, ref, onMounted, onUnmounted } from "vue";
|
|
||||||
import { useStore } from "vuex";
|
|
||||||
import axios from "axios";
|
|
||||||
import { signMeeting, generateSignature } from "@/api/meeting";
|
|
||||||
import wangEditor from "@/components/wangEditor";
|
|
||||||
import messageList from "@/components/messageList";
|
|
||||||
import dayjs from "dayjs";
|
|
||||||
import ReconnectingWebSocket from "reconnecting-websocket";
|
|
||||||
import ZoomMtgEmbedded from "@zoomus/websdk/embedded";
|
|
||||||
import _ from "lodash";
|
|
||||||
// import { FullScreen } from "@element-plus/icons-vue";
|
|
||||||
import { ElMessage, ElMessageBox } from "element-plus";
|
|
||||||
import { useRoute } from "vue-router";
|
|
||||||
const route = useRoute();
|
|
||||||
const store = useStore();
|
|
||||||
console.log(route.name);
|
|
||||||
const showRichEditor = ref(false);
|
|
||||||
const meetingNote = computed(() => store.getters.meetingNote); // 会议介绍
|
|
||||||
const meetingSchedule = computed(() => store.getters.meetingSchedule); // 会议日程
|
|
||||||
const expertInfo = computed(() => store.getters.expertInfo); // 专家信息
|
|
||||||
const isMeetingLoading = ref(true); // 视频课件是否正在加载
|
|
||||||
const templateId = ref(1); // 布局 id
|
|
||||||
if (store.getters.templateId) {
|
|
||||||
templateId.value = store.getters.templateId;
|
|
||||||
}
|
|
||||||
const joinAccount = ref(""); // 参会账号
|
|
||||||
const joinName = ref(""); // 参会名称
|
|
||||||
const screenWidth = ref(0); // 屏幕宽度
|
|
||||||
const meetingWidth = ref(0); // 视频课件容器元素宽度
|
|
||||||
const meetingHeight = ref(0); // 视频课件容器元素高度
|
|
||||||
screenWidth.value = document.body.offsetWidth;
|
|
||||||
|
|
||||||
// screenWidth.value = window.innerWidth;
|
|
||||||
if (route.name === "Host") {
|
|
||||||
joinAccount.value = store.getters.username;
|
|
||||||
joinName.value = store.getters.nickname;
|
|
||||||
} else {
|
|
||||||
joinAccount.value =
|
|
||||||
store.getters.icCard || store.getters.phone || store.getters.nickname;
|
|
||||||
joinName.value =
|
|
||||||
store.getters.nickname || store.getters.phone || store.getters.icCard;
|
|
||||||
}
|
|
||||||
// 设置会议背景大小、图片
|
|
||||||
const meetingContainerRef = ref(null); // 会议容器元素(包含背景)
|
|
||||||
const videoElementRef = ref(null); // 视频课件容器元素
|
|
||||||
const chatRightRef = ref(null); // 右侧聊天元素
|
|
||||||
const appContainer = ref(null);
|
|
||||||
onMounted(() => {
|
|
||||||
// appContainer.value.style.width = window.innerWidth;
|
|
||||||
console.log(window.innerWidth);
|
|
||||||
meetingContainerRef.value.style.background = `url(${store.getters.templateBackgroundPic}) 0% 0% / cover no-repeat`; // 设置背景图片
|
|
||||||
setTextLabel();
|
|
||||||
meetingWidth.value = meetingContainerRef.value.offsetWidth * 0.95;
|
|
||||||
meetingHeight.value = (meetingWidth.value * 9) / 16;
|
|
||||||
videoElementRef.value.style.width = meetingWidth.value + "px";
|
|
||||||
videoElementRef.value.style.height = meetingHeight.value + "px";
|
|
||||||
chatRightRef.value.style.height = meetingHeight.value + "px";
|
|
||||||
});
|
|
||||||
|
|
||||||
// 设置文本标签
|
|
||||||
const setTextLabel = () => {
|
|
||||||
document.querySelectorAll(".text-tag").forEach((el) => {
|
|
||||||
el.remove();
|
|
||||||
});
|
|
||||||
store.getters.textLabelList.forEach((item) => {
|
|
||||||
const labelObj = JSON.parse(item.textLabel);
|
|
||||||
const textEl = document.createElement("div");
|
|
||||||
console.log(labelObj);
|
|
||||||
textEl.id = `${_.uniqueId("tag-")}`;
|
|
||||||
textEl.innerHTML = labelObj.content;
|
|
||||||
textEl.className = "text-tag";
|
|
||||||
// console.log(labelObj);
|
|
||||||
textEl.style.backgroundColor = labelObj.backgroundColor;
|
|
||||||
textEl.style.visibility =
|
|
||||||
labelObj.visibility === "1" ? "visible" : "hidden";
|
|
||||||
|
|
||||||
textEl.style.left = `${labelObj.x}`;
|
|
||||||
textEl.style.top = `${labelObj.y}`;
|
|
||||||
meetingContainerRef.value.appendChild(textEl);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
// console.log(to.);
|
|
||||||
// 会议配置
|
|
||||||
const meetingConfig = reactive({
|
|
||||||
client: ZoomMtgEmbedded.createClient(),
|
|
||||||
// This Sample App has been updated to use SDK App type credentials https://marketplace.zoom.us/docs/guides/build/sdk-app
|
|
||||||
sdkKey: "99Spa64AWHYVZD95imUpVyMD0KF9CpEIrIb1",
|
|
||||||
meetingNumber: store.getters.meetingNumber,
|
|
||||||
passWord: store.state.password,
|
|
||||||
role: route.name === "Host" ? 1 : 0,
|
|
||||||
// signatureEndpoint: "http://120.26.107.74:4000",
|
|
||||||
// userEmail:store.getters,
|
|
||||||
userName: joinName.value,
|
|
||||||
// pass in the registrant's token if your meeting or webinar requires registration. More info here:
|
|
||||||
// Meetings: https://marketplace.zoom.us/docs/sdk/native-sdks/web/component-view/meetings#join-registered
|
|
||||||
// Webinars: https://marketplace.zoom.us/docs/sdk/native-sdks/web/component-view/webinars#join-registered
|
|
||||||
registrantToken: "",
|
|
||||||
});
|
|
||||||
console.log(meetingConfig);
|
|
||||||
const checkMedia = async () => {
|
|
||||||
const result = {};
|
|
||||||
try {
|
|
||||||
await navigator.mediaDevices.getUserMedia({ video: true });
|
|
||||||
result.video = true;
|
|
||||||
} catch (error) {
|
|
||||||
result.video = false;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
await navigator.mediaDevices.getUserMedia({ audio: true });
|
|
||||||
result.audio = true;
|
|
||||||
} catch (error) {
|
|
||||||
result.audio = false;
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
};
|
|
||||||
|
|
||||||
const getSignature = async () => {
|
|
||||||
const { sign } = await generateSignature({
|
|
||||||
meetingNumber: store.getters.meetingNumber,
|
|
||||||
role: meetingConfig.role,
|
|
||||||
});
|
|
||||||
startMeeting(sign);
|
|
||||||
// axios
|
|
||||||
// .post("http://localhost:4000", {
|
|
||||||
// meetingNumber: store.getters.meetingNumber,
|
|
||||||
// role: meetingConfig.role,
|
|
||||||
// })
|
|
||||||
// .then((res) => {
|
|
||||||
// console.log(res.data.signature);
|
|
||||||
// startMeeting(res.data.signature);
|
|
||||||
// })
|
|
||||||
// .catch((error) => {
|
|
||||||
// console.log(error);
|
|
||||||
// });
|
|
||||||
};
|
|
||||||
|
|
||||||
const startMeeting = async (signature) => {
|
|
||||||
let meetingSDKElement = document.getElementById("video-element");
|
|
||||||
try {
|
|
||||||
meetingConfig.client.init({
|
|
||||||
debug: true,
|
|
||||||
zoomAppRoot: meetingSDKElement,
|
|
||||||
language: "zh-CN",
|
|
||||||
customize: {
|
|
||||||
video: {
|
|
||||||
isResizable: false,
|
|
||||||
popper: {
|
|
||||||
disableDraggable: true,
|
|
||||||
},
|
|
||||||
viewSizes: {
|
|
||||||
default: {
|
|
||||||
width: meetingWidth.value,
|
|
||||||
height: meetingHeight.value - 45,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
meetingInfo: [
|
|
||||||
"topic",
|
|
||||||
"host",
|
|
||||||
"mn",
|
|
||||||
"pwd",
|
|
||||||
"telPwd",
|
|
||||||
"invite",
|
|
||||||
"participant",
|
|
||||||
"dc",
|
|
||||||
"enctype",
|
|
||||||
],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
await meetingConfig.client.join({
|
|
||||||
sdkKey: meetingConfig.sdkKey,
|
|
||||||
signature: signature,
|
|
||||||
meetingNumber: meetingConfig.meetingNumber,
|
|
||||||
password: meetingConfig.passWord,
|
|
||||||
userName: meetingConfig.userName,
|
|
||||||
userEmail: meetingConfig.userEmail,
|
|
||||||
tk: meetingConfig.registrantToken,
|
|
||||||
});
|
|
||||||
initDesktopLayout();
|
|
||||||
isMeetingLoading.value = false;
|
|
||||||
} catch (error) {
|
|
||||||
isMeetingLoading.value = false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const setFullScreen = () => {
|
|
||||||
videoElementRef.value.requestFullscreen();
|
|
||||||
};
|
|
||||||
onMounted(() => {
|
|
||||||
getSignature();
|
|
||||||
});
|
|
||||||
// 初始化
|
|
||||||
const initDesktopLayout = () => {
|
|
||||||
console.log(meetingHeight.value);
|
|
||||||
// 切换到 gallery view
|
|
||||||
document.querySelector("#suspension-view-tab-thumbnail-gallery").click();
|
|
||||||
// 初始化高度
|
|
||||||
const heightEl =
|
|
||||||
document.querySelector(
|
|
||||||
".zmwebsdk-MuiBox-root.zmwebsdk-MuiBox-root.zmwebsdk-makeStyles-root-166.zmwebsdk-makeStyles-root-170"
|
|
||||||
) ||
|
|
||||||
document.querySelector(
|
|
||||||
".zmwebsdk-MuiBox-root.zmwebsdk-MuiBox-root.zmwebsdk-makeStyles-root-176.zmwebsdk-makeStyles-root-252"
|
|
||||||
);
|
|
||||||
heightEl.style.height = `${meetingHeight.value - 45}px`;
|
|
||||||
|
|
||||||
// 初始化宽度
|
|
||||||
|
|
||||||
document.querySelector(
|
|
||||||
".zmwebsdk-MuiPaper-root.zmwebsdk-makeStyles-root-21.zmwebsdk-MuiPaper-elevation1.zmwebsdk-MuiPaper-rounded"
|
|
||||||
).style.width = `${meetingWidth.value - 4}px`;
|
|
||||||
// 加载完成显示 video element
|
|
||||||
document.querySelector("#video-element").style.visibility = "visible";
|
|
||||||
};
|
|
||||||
// 检测屏幕共享开启状态变化
|
|
||||||
const inSharing = ref(false); // 是否开启屏幕共享
|
|
||||||
setInterval(() => {
|
|
||||||
const elSharing = document.querySelector(".zmwebsdk-makeStyles-inSharing-46");
|
|
||||||
// console.log(elSharing);
|
|
||||||
if (elSharing) {
|
|
||||||
inSharing.value = true;
|
|
||||||
} else {
|
|
||||||
inSharing.value = false;
|
|
||||||
}
|
|
||||||
}, 500);
|
|
||||||
|
|
||||||
// 根据id设置布局
|
|
||||||
const setLayout = (templateId) => {
|
|
||||||
console.log(templateId);
|
|
||||||
const v_s_wrap_el =
|
|
||||||
document.querySelector(
|
|
||||||
".zmwebsdk-MuiBox-root.zmwebsdk-MuiBox-root.zmwebsdk-makeStyles-root-166"
|
|
||||||
) ||
|
|
||||||
document.querySelector(
|
|
||||||
".zmwebsdk-MuiBox-root.zmwebsdk-MuiBox-root.zmwebsdk-makeStyles-root-176"
|
|
||||||
); // 包含视频和课件的容器
|
|
||||||
v_s_wrap_el.style.flexDirection = "";
|
|
||||||
|
|
||||||
const v_wrap_el =
|
|
||||||
document.querySelector(".zmwebsdk-makeStyles-wrap-167") ||
|
|
||||||
document.querySelector(".zmwebsdk-makeStyles-wrap-177");
|
|
||||||
if (!inSharing.value) return;
|
|
||||||
|
|
||||||
const s_wrap_el = document.querySelector(".zmwebsdk-makeStyles-inSharing-46");
|
|
||||||
if (templateId === "1") {
|
|
||||||
// 课件|视频 对半分
|
|
||||||
v_wrap_el.style.width = `${meetingWidth.value / 2 - 2}px`;
|
|
||||||
} else if (templateId === "2") {
|
|
||||||
// 视频|课件 对半分
|
|
||||||
v_wrap_el.style.width = `${meetingWidth.value / 2 - 2}px`;
|
|
||||||
v_s_wrap_el.style.flexDirection = "row-reverse";
|
|
||||||
} else if (templateId === "3") {
|
|
||||||
// 课件|视频 左3/4 | 右边1/4
|
|
||||||
v_wrap_el.style.width = `${(meetingWidth.value - 4) / 4}px`;
|
|
||||||
} else if (templateId === "4") {
|
|
||||||
// 视频|课件 左1/4 | 右边3/4
|
|
||||||
v_wrap_el.style.width = `${(meetingWidth.value - 4) / 4}px`;
|
|
||||||
v_s_wrap_el.style.flexDirection = "row-reverse";
|
|
||||||
} else if (templateId === "5") {
|
|
||||||
// 只显示课件
|
|
||||||
v_wrap_el.style.display = `none`;
|
|
||||||
} else if (templateId === "6") {
|
|
||||||
// 只显示视频
|
|
||||||
s_wrap_el.style.display = "none";
|
|
||||||
v_wrap_el.style.width = `${meetingWidth.value - 4}px`;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
watch(inSharing, (newVal) => {
|
|
||||||
console.log(newVal);
|
|
||||||
if (newVal) {
|
|
||||||
setLayout(templateId.value);
|
|
||||||
} else {
|
|
||||||
const galleryViewButton = document.querySelector(
|
|
||||||
"#suspension-view-tab-thumbnail-gallery"
|
|
||||||
);
|
|
||||||
galleryViewButton.click();
|
|
||||||
document.querySelector(".zmwebsdk-makeStyles-wrap-171").style.width = "";
|
|
||||||
document.querySelector(
|
|
||||||
".zmwebsdk-MuiBox-root.zmwebsdk-makeStyles-root-166.zmwebsdk-makeStyles-root-170"
|
|
||||||
).style.flexDirection = "";
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
let socket = reactive({});
|
|
||||||
const initWebSocket = () => {
|
|
||||||
// 建立websocket连接
|
|
||||||
socket = new ReconnectingWebSocket(
|
|
||||||
`wss://meeting.chuhuankj.com/wss/websocket/meeting/${store.getters.meetingId}/${joinAccount.value}`
|
|
||||||
);
|
|
||||||
socket.addEventListener("open", () => {
|
|
||||||
// socket.send("Hello Server!");
|
|
||||||
console.log("websocket,已连接");
|
|
||||||
});
|
|
||||||
|
|
||||||
// 监听websocket消息
|
|
||||||
socket.addEventListener("message", async (event) => {
|
|
||||||
const data = JSON.parse(JSON.parse(event.data));
|
|
||||||
console.log(data);
|
|
||||||
// 会议信息更新时
|
|
||||||
if (data.type === "isRefreshMeeting") {
|
|
||||||
await store.dispatch("getMeetingInfo", store.getters.meetingId);
|
|
||||||
meetingContainerRef.value.style.background = ` url(${store.getters.templateBackgroundPic}) 0% 0% / cover no-repeat`;
|
|
||||||
templateId.value = store.getters.templateId;
|
|
||||||
setLayout(templateId.value);
|
|
||||||
setTextLabel();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 收到聊天消息时
|
|
||||||
else if (data.type === "isChat") {
|
|
||||||
console.log(JSON.parse(data.content));
|
|
||||||
messages.value.push({
|
|
||||||
...JSON.parse(data.content),
|
|
||||||
id: _.uniqueId(),
|
|
||||||
time: dayjs().format("YYYY-MM-DD HH:mm:ss"),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 会议结束时
|
|
||||||
else if (data.type === "isCloseMeeting") {
|
|
||||||
ElMessageBox.alert("会议已结束");
|
|
||||||
socket.close();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
socket.addEventListener("close", (event) => {
|
|
||||||
console.log(event, "close");
|
|
||||||
});
|
|
||||||
socket.addEventListener("error", (event) => {
|
|
||||||
console.log(event, "error");
|
|
||||||
});
|
|
||||||
};
|
|
||||||
initWebSocket();
|
|
||||||
|
|
||||||
/* 聊天功能 */
|
|
||||||
const messages = ref([]); // 消息列表
|
|
||||||
const editingMessage = ref(""); // 要发送的内容
|
|
||||||
const richMessage = ref(""); // 正在编辑的富文本内容--主持人独有
|
|
||||||
|
|
||||||
// 保存富文本聊天内容
|
|
||||||
const saveRichMessage = () => {
|
|
||||||
showRichEditor.value = false;
|
|
||||||
editingMessage.value = richMessage.value;
|
|
||||||
richMessage.value = "";
|
|
||||||
};
|
|
||||||
// 取消保存富文本
|
|
||||||
const cancelRichMessage = () => {
|
|
||||||
showRichEditor.value = false;
|
|
||||||
};
|
|
||||||
// 打开富文本编辑器窗口
|
|
||||||
const openRichMessageEditor = () => {
|
|
||||||
showRichEditor.value = true;
|
|
||||||
richMessage.value = editingMessage.value;
|
|
||||||
};
|
|
||||||
// 发送消息
|
|
||||||
const sendMessage = () => {
|
|
||||||
console.log(socket);
|
|
||||||
socket.send(editingMessage.value);
|
|
||||||
messages.value.push({
|
|
||||||
id: _.uniqueId(),
|
|
||||||
account: joinName.value,
|
|
||||||
msg: editingMessage.value,
|
|
||||||
isMe: true,
|
|
||||||
time: dayjs().format("YYYY-MM-DD HH:mm:ss"),
|
|
||||||
});
|
|
||||||
editingMessage.value = "";
|
|
||||||
};
|
|
||||||
|
|
||||||
const leaveConference = () => {
|
|
||||||
meetingConfig.client.leaveMeeting();
|
|
||||||
};
|
|
||||||
window.addEventListener("beforeunload", leaveConference);
|
|
||||||
onUnmounted(() => {
|
|
||||||
window.removeEventListener("beforeunload", leaveConference);
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
<style scoped lang="scss">
|
|
||||||
#app-container {
|
|
||||||
width: 100%;
|
|
||||||
margin: 0 auto;
|
|
||||||
:deep(.text-tag) {
|
|
||||||
position: absolute;
|
|
||||||
z-index: 999;
|
|
||||||
border-radius: 4px;
|
|
||||||
padding: 5px;
|
|
||||||
background-color: #fff;
|
|
||||||
* {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.row {
|
|
||||||
width: 100%;
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
.meeting-container {
|
|
||||||
position: relative;
|
|
||||||
width: 80%;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.chat-right {
|
|
||||||
width: 20%;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: space-between;
|
|
||||||
.option-bar {
|
|
||||||
margin-top: 0;
|
|
||||||
margin: 5px;
|
|
||||||
.display-message {
|
|
||||||
border: 1px solid #666;
|
|
||||||
width: 80%;
|
|
||||||
min-height: 100%;
|
|
||||||
border-radius: 5px;
|
|
||||||
margin-right: 5px;
|
|
||||||
word-wrap: break-word;
|
|
||||||
:deep(p) {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
:deep(.message-list) {
|
|
||||||
overflow-y: scroll;
|
|
||||||
margin-bottom: 0;
|
|
||||||
// height: 80%;
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (max-width: 768px) {
|
|
||||||
.meeting-container {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
.chat-right {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#video-element {
|
|
||||||
// visibility: hidden;
|
|
||||||
// height: 600px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tabs {
|
|
||||||
width: 100%;
|
|
||||||
.meeting-info {
|
|
||||||
:deep(p) {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// :deep(.el-tabs__content) {
|
|
||||||
// .chat {
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
.option-bar {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-around;
|
|
||||||
}
|
|
||||||
:deep(.zmwebsdk-MuiPaper-root) {
|
|
||||||
background: transparent;
|
|
||||||
box-shadow: 0 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.zmwebsdk-MuiToolbar-root) {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
:deep(.zmwebsdk-makeStyles-singleView-7) {
|
|
||||||
// background-color: transparent;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
.fullscreen-btn {
|
|
||||||
position: absolute;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
}
|
|
||||||
</style>
|
|
@ -80,62 +80,7 @@
|
|||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
|
<mediaCheck />
|
||||||
<el-dialog
|
|
||||||
custom-class="check-media"
|
|
||||||
destroy-on-close
|
|
||||||
v-model="showCheckMediaVideo"
|
|
||||||
:close-on-click-modal="false"
|
|
||||||
title="摄像头和麦克风"
|
|
||||||
:fullscreen="screenWidth < 900"
|
|
||||||
width="60%"
|
|
||||||
>
|
|
||||||
<el-alert
|
|
||||||
v-if="isVideoAvailable === undefined || isAudioAvailable === undefined"
|
|
||||||
>检测中。</el-alert
|
|
||||||
>
|
|
||||||
<el-alert
|
|
||||||
v-else-if="isVideoAvailable === true && isAudioAvailable === true"
|
|
||||||
type="success"
|
|
||||||
>摄像头工作正常,麦克风工作正常。</el-alert
|
|
||||||
>
|
|
||||||
<el-alert
|
|
||||||
v-else-if="isVideoAvailable === true && isAudioAvailable === false"
|
|
||||||
type="warning"
|
|
||||||
>摄像头工作正常,麦克风无法正常工作。</el-alert
|
|
||||||
>
|
|
||||||
<el-alert
|
|
||||||
v-else-if="isVideoAvailable === false && isAudioAvailable === true"
|
|
||||||
type="warning"
|
|
||||||
>摄像头无法正常工作,麦克风工作正常。</el-alert
|
|
||||||
>
|
|
||||||
<el-alert v-else type="error"
|
|
||||||
>摄像头无法正常工作,麦克风无法正常工作。</el-alert
|
|
||||||
>
|
|
||||||
<!-- <div id="row"> -->
|
|
||||||
<div id="check-media-wrap" ref="checkMediaWrapRef">
|
|
||||||
<video id="check-media-video" ref="checkMediaVideoRef"></video>
|
|
||||||
<!-- <el-icon class="microphone-icon" :size="50"> -->
|
|
||||||
<Microphone
|
|
||||||
v-if="isAudioAvailable === undefined"
|
|
||||||
class="microphone-loading"
|
|
||||||
/>
|
|
||||||
<Microphone
|
|
||||||
v-else-if="isAudioAvailable === true"
|
|
||||||
class="microphone-on"
|
|
||||||
/>
|
|
||||||
<Mute v-else class="microphone-off" />
|
|
||||||
<!-- </el-icon> -->
|
|
||||||
</div>
|
|
||||||
<!-- </div> -->
|
|
||||||
<template #footer>
|
|
||||||
<span class="dialog-footer">
|
|
||||||
<el-button type="primary" @click="showCheckMediaVideo = false"
|
|
||||||
>关闭</el-button
|
|
||||||
>
|
|
||||||
</span>
|
|
||||||
</template>
|
|
||||||
</el-dialog>
|
|
||||||
|
|
||||||
<questions
|
<questions
|
||||||
mode="1"
|
mode="1"
|
||||||
@ -190,6 +135,7 @@ import {
|
|||||||
} from "@element-plus/icons-vue";
|
} from "@element-plus/icons-vue";
|
||||||
import { signMeeting, generateSignature } from "@/api/meeting";
|
import { signMeeting, generateSignature } from "@/api/meeting";
|
||||||
import Chat from "@/components/chat";
|
import Chat from "@/components/chat";
|
||||||
|
import mediaCheck from "@/components/mediaCheck.vue";
|
||||||
import questions from "@/components/questions";
|
import questions from "@/components/questions";
|
||||||
import {
|
import {
|
||||||
getQuestionsList,
|
getQuestionsList,
|
||||||
@ -306,7 +252,7 @@ const startMeeting = async () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const setSize = () => {
|
const setSize = () => {
|
||||||
meetingWidth.value = meetingContainerRef.value.offsetWidth * 0.9;
|
meetingWidth.value = meetingContainerRef.value.offsetWidth * 0.96;
|
||||||
meetingHeight.value = (meetingWidth.value * 9) / 16;
|
meetingHeight.value = (meetingWidth.value * 9) / 16;
|
||||||
|
|
||||||
// videoElementRef.value.style.width = `${meetingWidth.value}px`;
|
// videoElementRef.value.style.width = `${meetingWidth.value}px`;
|
||||||
@ -328,41 +274,6 @@ const setSize = () => {
|
|||||||
window.client = meetingConfig.client;
|
window.client = meetingConfig.client;
|
||||||
window.startMeeting = startMeeting;
|
window.startMeeting = startMeeting;
|
||||||
|
|
||||||
const checkMediaVideoRef = ref(null);
|
|
||||||
const checkMediaWrapRef = ref(null);
|
|
||||||
const showCheckMediaVideo = ref(true);
|
|
||||||
const isVideoAvailable = ref(undefined);
|
|
||||||
const isAudioAvailable = ref(undefined);
|
|
||||||
// TODO:可能有摄像头占用问题。
|
|
||||||
onMounted(() => {
|
|
||||||
nextTick(() => {
|
|
||||||
const videoWidth = checkMediaWrapRef.value.offsetWidth;
|
|
||||||
const videoHeight = checkMediaWrapRef.value.offsetHeight;
|
|
||||||
console.log(checkMediaVideoRef.value);
|
|
||||||
navigator.mediaDevices
|
|
||||||
.getUserMedia({ video: { width: videoWidth, height: videoHeight } })
|
|
||||||
.then((stream) => {
|
|
||||||
checkMediaVideoRef.value.srcObject = stream;
|
|
||||||
checkMediaVideoRef.value.play();
|
|
||||||
isVideoAvailable.value = true;
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
console.log(err);
|
|
||||||
isVideoAvailable.value = false;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
navigator.mediaDevices
|
|
||||||
.getUserMedia({ audio: true })
|
|
||||||
.then((stream) => {
|
|
||||||
console.log(stream);
|
|
||||||
isAudioAvailable.value = true;
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
console.log(err);
|
|
||||||
isAudioAvailable.value = false;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// 设置文本标签
|
// 设置文本标签
|
||||||
const setTextLabel = () => {
|
const setTextLabel = () => {
|
||||||
document.querySelectorAll(".text-tag").forEach((el) => {
|
document.querySelectorAll(".text-tag").forEach((el) => {
|
||||||
@ -544,7 +455,7 @@ const setFullScreen = async () => {
|
|||||||
|
|
||||||
isFullScreen.value = true;
|
isFullScreen.value = true;
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
meetingWidth.value = meetingContainerRef.value.offsetWidth * 0.9;
|
meetingWidth.value = meetingContainerRef.value.offsetWidth * 0.96;
|
||||||
meetingHeight.value = (meetingWidth.value * 9) / 16;
|
meetingHeight.value = (meetingWidth.value * 9) / 16;
|
||||||
// 当手机竖屏时
|
// 当手机竖屏时
|
||||||
if (window.orientation === 0) {
|
if (window.orientation === 0) {
|
||||||
@ -747,7 +658,7 @@ const getStageQuestionnaireAnswer = (val) => {
|
|||||||
const isFullScreen = ref(false);
|
const isFullScreen = ref(false);
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
meetingContainerRef.value.style.background = ` url(${store.getters.templateBackgroundPic}) 0% 0% / cover no-repeat`;
|
meetingContainerRef.value.style.background = ` url(${store.getters.templateBackgroundPic}) 0% 0% / cover no-repeat`;
|
||||||
meetingWidth.value = meetingContainerRef.value.offsetWidth * 0.9;
|
meetingWidth.value = meetingContainerRef.value.offsetWidth * 0.96;
|
||||||
meetingHeight.value = (meetingWidth.value * 9) / 16;
|
meetingHeight.value = (meetingWidth.value * 9) / 16;
|
||||||
|
|
||||||
videoElementRef.value.style.height = `${meetingHeight.value + 42}px`;
|
videoElementRef.value.style.height = `${meetingHeight.value + 42}px`;
|
||||||
@ -759,7 +670,7 @@ onMounted(() => {
|
|||||||
isFullScreen.value = false;
|
isFullScreen.value = false;
|
||||||
isVerticalFullScreen.value = false;
|
isVerticalFullScreen.value = false;
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
meetingWidth.value = meetingContainerRef.value.offsetWidth * 0.9;
|
meetingWidth.value = meetingContainerRef.value.offsetWidth * 0.96;
|
||||||
meetingHeight.value = (meetingWidth.value * 9) / 16;
|
meetingHeight.value = (meetingWidth.value * 9) / 16;
|
||||||
// videoElementRef.value.style.width = `${meetingWidth.value}px`;
|
// videoElementRef.value.style.width = `${meetingWidth.value}px`;
|
||||||
videoElementRef.value.style.height = `${meetingHeight.value + 42}px`;
|
videoElementRef.value.style.height = `${meetingHeight.value + 42}px`;
|
||||||
@ -790,7 +701,7 @@ onMounted(() => {
|
|||||||
// 当修改窗口尺寸时
|
// 当修改窗口尺寸时
|
||||||
window.addEventListener("resize", (e) => {
|
window.addEventListener("resize", (e) => {
|
||||||
console.log(e);
|
console.log(e);
|
||||||
meetingWidth.value = meetingContainerRef.value.offsetWidth * 0.9;
|
meetingWidth.value = meetingContainerRef.value.offsetWidth * 0.96;
|
||||||
meetingHeight.value = (meetingWidth.value * 9) / 16;
|
meetingHeight.value = (meetingWidth.value * 9) / 16;
|
||||||
if (!document.fullscreenElement) {
|
if (!document.fullscreenElement) {
|
||||||
videoElementRef.value.style.height = `${meetingHeight.value + 42}px`;
|
videoElementRef.value.style.height = `${meetingHeight.value + 42}px`;
|
||||||
@ -832,7 +743,7 @@ onUnmounted(() => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
$meetingComponentWitdh: 80vw * 0.9;
|
$meetingComponentWitdh: 80vw * 0.96;
|
||||||
$meetingComponentHeight: $meetingComponentWitdh * 9 / 16;
|
$meetingComponentHeight: $meetingComponentWitdh * 9 / 16;
|
||||||
|
|
||||||
:deep(#right-chat .chat-container .message-list) {
|
:deep(#right-chat .chat-container .message-list) {
|
||||||
@ -852,7 +763,7 @@ $meetingComponentHeight: $meetingComponentWitdh * 9 / 16;
|
|||||||
width: 80vw;
|
width: 80vw;
|
||||||
position: relative;
|
position: relative;
|
||||||
#video-element {
|
#video-element {
|
||||||
width: 72vw;
|
width: $meetingComponentWitdh;
|
||||||
:deep(div[id*="suspension-view-tabpanel"]
|
:deep(div[id*="suspension-view-tabpanel"]
|
||||||
> .zmwebsdk-MuiBox-root.zmwebsdk-MuiBox-root
|
> .zmwebsdk-MuiBox-root.zmwebsdk-MuiBox-root
|
||||||
> div[class*="inSharing"]
|
> div[class*="inSharing"]
|
||||||
@ -947,7 +858,7 @@ $meetingComponentHeight: $meetingComponentWitdh * 9 / 16;
|
|||||||
> div
|
> div
|
||||||
> .zmwebsdk-MuiPaper-root
|
> .zmwebsdk-MuiPaper-root
|
||||||
> .zmwebsdk-MuiPaper-root:nth-child(1)) {
|
> .zmwebsdk-MuiPaper-root:nth-child(1)) {
|
||||||
width: calc(80vw * 0.9);
|
width: $meetingComponentWitdh;
|
||||||
}
|
}
|
||||||
:deep(#video-element
|
:deep(#video-element
|
||||||
> div
|
> div
|
||||||
@ -958,7 +869,7 @@ $meetingComponentHeight: $meetingComponentWitdh * 9 / 16;
|
|||||||
}
|
}
|
||||||
:deep(div[id*="suspension-view-tabpanel"]
|
:deep(div[id*="suspension-view-tabpanel"]
|
||||||
> .zmwebsdk-MuiBox-root.zmwebsdk-MuiBox-root) {
|
> .zmwebsdk-MuiBox-root.zmwebsdk-MuiBox-root) {
|
||||||
height: calc(80vw * 0.9 * 9 / 16);
|
height: $meetingComponentWitdh * 9 / 16;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.zmwebsdk-MuiPaper-root) {
|
:deep(.zmwebsdk-MuiPaper-root) {
|
||||||
@ -971,44 +882,10 @@ $meetingComponentHeight: $meetingComponentWitdh * 9 / 16;
|
|||||||
> .zmwebsdk-MuiBox-root) {
|
> .zmwebsdk-MuiBox-root) {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
:deep(.check-media) {
|
|
||||||
.el-dialog__body {
|
|
||||||
text-align: center;
|
|
||||||
#check-media-wrap {
|
|
||||||
display: inline-block;
|
|
||||||
margin: 1vw auto;
|
|
||||||
border-radius: 1vw;
|
|
||||||
overflow: hidden;
|
|
||||||
width: 36vw;
|
|
||||||
height: 24vw;
|
|
||||||
background-color: #000;
|
|
||||||
position: relative;
|
|
||||||
// #check-media-video {
|
|
||||||
// width: 100%;
|
|
||||||
// height: 100%;
|
|
||||||
// }
|
|
||||||
svg {
|
|
||||||
position: absolute;
|
|
||||||
left: 1vw;
|
|
||||||
bottom: 1vw;
|
|
||||||
width: 3vw;
|
|
||||||
height: 3vw;
|
|
||||||
}
|
|
||||||
.microphone-loading {
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
.microphone-on {
|
|
||||||
color: springgreen;
|
|
||||||
}
|
|
||||||
.microphone-off {
|
|
||||||
color: indianred;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 适配移动端 屏幕宽度小于 900px
|
// 适配移动端 屏幕宽度小于 900px
|
||||||
@media screen and (max-width: 900px) {
|
@media screen and (max-width: 900px) {
|
||||||
$meetingComponentWitdh: 90vw;
|
$meetingComponentWitdh: 96vw;
|
||||||
// $meetingComponentHeight: $meetingComponentWitdh * 9 / 16;
|
// $meetingComponentHeight: $meetingComponentWitdh * 9 / 16;
|
||||||
|
|
||||||
#app-container {
|
#app-container {
|
||||||
@ -1017,7 +894,7 @@ $meetingComponentHeight: $meetingComponentWitdh * 9 / 16;
|
|||||||
padding-top: calc(100vw * 0.08);
|
padding-top: calc(100vw * 0.08);
|
||||||
width: 100vw;
|
width: 100vw;
|
||||||
#video-element {
|
#video-element {
|
||||||
width: 90vw;
|
width: 96vw;
|
||||||
:deep(div[id*="suspension-view-tabpanel"]
|
:deep(div[id*="suspension-view-tabpanel"]
|
||||||
> .zmwebsdk-MuiBox-root.zmwebsdk-MuiBox-root
|
> .zmwebsdk-MuiBox-root.zmwebsdk-MuiBox-root
|
||||||
> div[class*="inSharing"]
|
> div[class*="inSharing"]
|
||||||
@ -1036,14 +913,14 @@ $meetingComponentHeight: $meetingComponentWitdh * 9 / 16;
|
|||||||
}
|
}
|
||||||
:deep(div[id*="suspension-view-tabpanel"]
|
:deep(div[id*="suspension-view-tabpanel"]
|
||||||
> .zmwebsdk-MuiBox-root.zmwebsdk-MuiBox-root) {
|
> .zmwebsdk-MuiBox-root.zmwebsdk-MuiBox-root) {
|
||||||
height: calc(100vw * 0.9 * 9 / 16);
|
height: $meetingComponentWitdh * 9 / 16;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
:deep(#video-element
|
:deep(#video-element
|
||||||
> div
|
> div
|
||||||
> .zmwebsdk-MuiPaper-root
|
> .zmwebsdk-MuiPaper-root
|
||||||
> .zmwebsdk-MuiPaper-root:nth-child(1)) {
|
> .zmwebsdk-MuiPaper-root:nth-child(1)) {
|
||||||
width: 90vw;
|
width: $meetingComponentWitdh;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1051,20 +928,6 @@ $meetingComponentHeight: $meetingComponentWitdh * 9 / 16;
|
|||||||
#right-chat {
|
#right-chat {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.check-media) {
|
|
||||||
.el-dialog__body {
|
|
||||||
#check-media-wrap {
|
|
||||||
margin: 2vw auto;
|
|
||||||
width: 80vw;
|
|
||||||
height: 54vw;
|
|
||||||
svg {
|
|
||||||
width: 6vw;
|
|
||||||
height: 6vw;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 全屏样式
|
// 全屏样式
|
||||||
@ -1074,12 +937,15 @@ $meetingComponentHeight: $meetingComponentWitdh * 9 / 16;
|
|||||||
width: 80vw;
|
width: 80vw;
|
||||||
|
|
||||||
#meeting-container.fullscreen {
|
#meeting-container.fullscreen {
|
||||||
$meetingComponentWitdh: 90vw;
|
$meetingComponentWitdh: 96vw;
|
||||||
padding-top: calc(80vw * 0.08);
|
padding-top: calc(100vw * 0.08);
|
||||||
|
@media screen and (max-width: 900px) {
|
||||||
|
padding-top: calc(80vw * 0.08);
|
||||||
|
}
|
||||||
width: 100vw;
|
width: 100vw;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
#video-element {
|
#video-element {
|
||||||
width: 90vw;
|
width: $meetingComponentWitdh;
|
||||||
}
|
}
|
||||||
.layout-template-1 {
|
.layout-template-1 {
|
||||||
:deep(div[id*="suspension-view-tabpanel"]
|
:deep(div[id*="suspension-view-tabpanel"]
|
||||||
@ -1123,7 +989,7 @@ $meetingComponentHeight: $meetingComponentWitdh * 9 / 16;
|
|||||||
> div
|
> div
|
||||||
> .zmwebsdk-MuiPaper-root
|
> .zmwebsdk-MuiPaper-root
|
||||||
> .zmwebsdk-MuiPaper-root:nth-child(1)) {
|
> .zmwebsdk-MuiPaper-root:nth-child(1)) {
|
||||||
width: 90vw;
|
width: 96vw;
|
||||||
}
|
}
|
||||||
:deep(#video-element
|
:deep(#video-element
|
||||||
> div
|
> div
|
||||||
@ -1138,7 +1004,7 @@ $meetingComponentHeight: $meetingComponentWitdh * 9 / 16;
|
|||||||
}
|
}
|
||||||
|
|
||||||
#meeting-container.verticalFullScreen {
|
#meeting-container.verticalFullScreen {
|
||||||
$meetingComponentWitdh: 90vh;
|
$meetingComponentWitdh: 96vh;
|
||||||
$meetingComponentHeight: $meetingComponentWitdh * 9 / 16;
|
$meetingComponentHeight: $meetingComponentWitdh * 9 / 16;
|
||||||
padding-top: calc(80vh * 0.08);
|
padding-top: calc(80vh * 0.08);
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@ -1149,7 +1015,7 @@ $meetingComponentHeight: $meetingComponentWitdh * 9 / 16;
|
|||||||
width: 100vh !important;
|
width: 100vh !important;
|
||||||
height: 100vw;
|
height: 100vw;
|
||||||
#video-element {
|
#video-element {
|
||||||
width: 90vh;
|
width: $meetingComponentWitdh;
|
||||||
:deep(div[id*="suspension-view-tabpanel"]
|
:deep(div[id*="suspension-view-tabpanel"]
|
||||||
> .zmwebsdk-MuiBox-root.zmwebsdk-MuiBox-root
|
> .zmwebsdk-MuiBox-root.zmwebsdk-MuiBox-root
|
||||||
> div[class*="inSharing"]
|
> div[class*="inSharing"]
|
||||||
@ -1169,7 +1035,7 @@ $meetingComponentHeight: $meetingComponentWitdh * 9 / 16;
|
|||||||
> .zmwebsdk-MuiPaper-root
|
> .zmwebsdk-MuiPaper-root
|
||||||
> .zmwebsdk-MuiPaper-root:nth-child(1)) {
|
> .zmwebsdk-MuiPaper-root:nth-child(1)) {
|
||||||
// width: calc(100vh * 0.9) !important;
|
// width: calc(100vh * 0.9) !important;
|
||||||
width: 90vh;
|
width: $meetingComponentWitdh;
|
||||||
}
|
}
|
||||||
:deep(#video-element
|
:deep(#video-element
|
||||||
> div
|
> div
|
||||||
|
@ -1,643 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div id="app-container" v-loading="isMeetingLoading" ref="appContainer">
|
|
||||||
<div class="row">
|
|
||||||
<div class="meeting-container" ref="meetingContainerRef">
|
|
||||||
<div id="video-element" ref="videoElementRef"></div>
|
|
||||||
<el-button @click="setFullScreen" class="fullscreen-btn">
|
|
||||||
全屏</el-button
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
<div class="chat-right" ref="chatRightRef">
|
|
||||||
<messageList :messageList="messages"></messageList>
|
|
||||||
<div class="option-bar">
|
|
||||||
<el-input
|
|
||||||
v-model="editingMessage"
|
|
||||||
style="margin-right: 15px"
|
|
||||||
></el-input>
|
|
||||||
<el-button
|
|
||||||
type="success"
|
|
||||||
@click="sendMessage"
|
|
||||||
:disabled="editingMessage.length === 0"
|
|
||||||
>发送</el-button
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<el-tabs class="tabs" type="border-card">
|
|
||||||
<el-tab-pane label="会议介绍">
|
|
||||||
<div class="meeting-info meeting-note" v-html="meetingNote"></div>
|
|
||||||
</el-tab-pane>
|
|
||||||
<el-tab-pane label="会议日程"
|
|
||||||
><div
|
|
||||||
class="meeting-info meeting-schedule"
|
|
||||||
v-html="meetingSchedule"
|
|
||||||
></div
|
|
||||||
></el-tab-pane>
|
|
||||||
<el-tab-pane label="专家介绍"
|
|
||||||
><div class="meeting-info expert-info" v-html="expertInfo"></div
|
|
||||||
></el-tab-pane>
|
|
||||||
<el-tab-pane label="聊天" class="chat-pane" v-if="screenWidth < 768"
|
|
||||||
><div class="meeting-info chat">
|
|
||||||
<messageList :messageList="messages"></messageList>
|
|
||||||
<div class="option-bar">
|
|
||||||
<el-input
|
|
||||||
v-model="editingMessage"
|
|
||||||
style="margin-right: 15px"
|
|
||||||
></el-input>
|
|
||||||
<!-- <svg-icon :icon-class="search" style="height: 30px; width: 16px" /> -->
|
|
||||||
<el-button
|
|
||||||
type="success"
|
|
||||||
@click="sendMessage"
|
|
||||||
:disabled="editingMessage.length === 0"
|
|
||||||
>发送</el-button
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</div></el-tab-pane
|
|
||||||
>
|
|
||||||
</el-tabs>
|
|
||||||
|
|
||||||
<el-dialog
|
|
||||||
v-model="showSignDialog"
|
|
||||||
:close-on-click-modal="false"
|
|
||||||
title="签到"
|
|
||||||
width="30%"
|
|
||||||
>
|
|
||||||
<span>是否确认签到</span>
|
|
||||||
<template #footer>
|
|
||||||
<span class="dialog-footer">
|
|
||||||
<el-button type="primary" @click="submitSign">确定</el-button>
|
|
||||||
<el-button type="primary" @click="showSignDialog = false"
|
|
||||||
>取消</el-button
|
|
||||||
>
|
|
||||||
</span>
|
|
||||||
</template>
|
|
||||||
</el-dialog>
|
|
||||||
<questions
|
|
||||||
mode="1"
|
|
||||||
v-if="showExamDialog"
|
|
||||||
:showDialog="showExamDialog"
|
|
||||||
@close="showExamDialog = $event"
|
|
||||||
></questions>
|
|
||||||
|
|
||||||
<questions
|
|
||||||
mode="2"
|
|
||||||
v-if="showQuestionnaireDialog"
|
|
||||||
:showDialog="showQuestionnaireDialog"
|
|
||||||
@close="showQuestionnaireDialog = $event"
|
|
||||||
></questions>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<script setup>
|
|
||||||
import {
|
|
||||||
computed,
|
|
||||||
reactive,
|
|
||||||
watch,
|
|
||||||
ref,
|
|
||||||
onMounted,
|
|
||||||
onUnmounted,
|
|
||||||
nextTick,
|
|
||||||
} from "vue";
|
|
||||||
import { useStore } from "vuex";
|
|
||||||
|
|
||||||
import { signMeeting, generateSignature } from "@/api/meeting";
|
|
||||||
import questions from "@/components/questions";
|
|
||||||
import messageList from "@/components/messageList";
|
|
||||||
import dayjs from "dayjs";
|
|
||||||
import ZoomMtgEmbedded from "@zoomus/websdk/embedded";
|
|
||||||
import _ from "lodash";
|
|
||||||
|
|
||||||
import ReconnectingWebSocket from "reconnecting-websocket";
|
|
||||||
import { ElMessage, ElMessageBox } from "element-plus";
|
|
||||||
import { useRoute } from "vue-router";
|
|
||||||
const route = useRoute();
|
|
||||||
const store = useStore();
|
|
||||||
|
|
||||||
const meetingNote = computed(() => store.getters.meetingNote); // 会议介绍
|
|
||||||
const meetingSchedule = computed(() => store.getters.meetingSchedule); // 会议日程
|
|
||||||
const expertInfo = computed(() => store.getters.expertInfo); // 专家信息
|
|
||||||
const isMeetingLoading = ref(true); // 视频课件是否正在加载
|
|
||||||
const templateId = ref(6); // 布局 id
|
|
||||||
if (store.getters.templateId) {
|
|
||||||
templateId.value = store.getters.templateId;
|
|
||||||
}
|
|
||||||
const joinAccount = ref(""); // 参会账号
|
|
||||||
const joinName = ref(""); // 参会名称
|
|
||||||
const screenWidth = ref(0); // 屏幕宽度
|
|
||||||
const meetingWidth = ref(0); // 视频课件容器元素宽度
|
|
||||||
const meetingHeight = ref(0); // 视频课件容器元素高度
|
|
||||||
screenWidth.value = document.body.offsetWidth;
|
|
||||||
|
|
||||||
joinAccount.value =
|
|
||||||
store.getters.icCard || store.getters.phone || store.getters.nickname;
|
|
||||||
joinName.value =
|
|
||||||
store.getters.nickname || store.getters.phone || store.getters.icCard;
|
|
||||||
|
|
||||||
// 设置会议背景大小、图片
|
|
||||||
const meetingContainerRef = ref(null); // 会议容器元素(包含背景)
|
|
||||||
const videoElementRef = ref(null); // 视频课件容器元素
|
|
||||||
const chatRightRef = ref(null); // 右侧聊天元素
|
|
||||||
onMounted(() => {
|
|
||||||
meetingContainerRef.value.style.background = `url(${store.getters.templateBackgroundPic}) 0% 0% / cover no-repeat`; // 设置背景图片
|
|
||||||
setTextLabel();
|
|
||||||
meetingWidth.value = meetingContainerRef.value.offsetWidth * 0.95; // zoom 会议组件宽度是父元素宽度的 95%
|
|
||||||
meetingHeight.value = (meetingWidth.value * 9) / 16; // 根据 zoom 会议组件长宽比 16:9 计算出会议组件高度
|
|
||||||
meetingContainerRef.value.style.paddingTop = `${meetingHeight.value * 0.2}px`; // 父元素顶部padding 是会议组件高度的20%
|
|
||||||
videoElementRef.value.style.width = meetingWidth.value + "px";
|
|
||||||
videoElementRef.value.style.height = meetingHeight.value + "px";
|
|
||||||
});
|
|
||||||
|
|
||||||
// 设置文本标签
|
|
||||||
const setTextLabel = () => {
|
|
||||||
document.querySelectorAll(".text-tag").forEach((el) => {
|
|
||||||
el.remove();
|
|
||||||
});
|
|
||||||
store.getters.textLabelList.forEach((item) => {
|
|
||||||
const labelObj = JSON.parse(item.textLabel);
|
|
||||||
const textEl = document.createElement("div");
|
|
||||||
textEl.id = `${_.uniqueId("tag-")}`;
|
|
||||||
textEl.innerHTML = labelObj.content;
|
|
||||||
textEl.className = "text-tag";
|
|
||||||
textEl.style.backgroundColor = labelObj.backgroundColor;
|
|
||||||
textEl.style.visibility =
|
|
||||||
labelObj.visibility === "1" ? "visible" : "hidden";
|
|
||||||
textEl.style.left = `${labelObj.x}`;
|
|
||||||
textEl.style.top = `${labelObj.y}`;
|
|
||||||
meetingContainerRef.value.appendChild(textEl);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// 会议配置
|
|
||||||
const meetingConfig = reactive({
|
|
||||||
client: ZoomMtgEmbedded.createClient(),
|
|
||||||
// This Sample App has been updated to use SDK App type credentials https://marketplace.zoom.us/docs/guides/build/sdk-app
|
|
||||||
sdkKey: "99Spa64AWHYVZD95imUpVyMD0KF9CpEIrIb1",
|
|
||||||
// meetingNumber: store.getters.meetingNumber,
|
|
||||||
meetingNumber: store.getters.meetingNumber,
|
|
||||||
passWord: store.state.password,
|
|
||||||
// sdkKey: "99Spa64AWHYVZD95imUpVyMD0KF9CpEIrIb1",
|
|
||||||
// meetingNumber: "97097842319",
|
|
||||||
// passWord: "111916",
|
|
||||||
// role: route.name === "Host" ? 1 : 0,
|
|
||||||
role: 0,
|
|
||||||
// signatureEndpoint: "http://120.26.107.74:4000",
|
|
||||||
userEmail: store.getters.email,
|
|
||||||
// userEmail: "934510341@qq.com",
|
|
||||||
userName: joinName.value,
|
|
||||||
// userName: "afasde1",
|
|
||||||
// pass in the registrant's token if your meeting or webinar requires registration. More info here:
|
|
||||||
// Meetings: https://marketplace.zoom.us/docs/sdk/native-sdks/web/component-view/meetings#join-registered
|
|
||||||
// Webinars: https://marketplace.zoom.us/docs/sdk/native-sdks/web/component-view/webinars#join-registered
|
|
||||||
// registrantToken:
|
|
||||||
// "Xhi0bKUzyNBnJwe2EJWZ0JZ3IYqtPgyyWE1CXW3z2X4.DQMAAAAWm3t-jxZqSS14R0dCSFRDNkFibVQwakpHbFd3AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
|
|
||||||
registrantToken: store.getters.token,
|
|
||||||
});
|
|
||||||
console.log(meetingConfig);
|
|
||||||
|
|
||||||
// 生成签名
|
|
||||||
const getSignature = async () => {
|
|
||||||
const { sign } = await generateSignature({
|
|
||||||
meetingNumber: store.getters.meetingNumber,
|
|
||||||
role: meetingConfig.role,
|
|
||||||
});
|
|
||||||
console.log(sign);
|
|
||||||
startMeeting(sign);
|
|
||||||
// axios
|
|
||||||
// .post("http://localhost:4000", {
|
|
||||||
// meetingNumber: store.getters.meetingNumber,
|
|
||||||
// role: meetingConfig.role,
|
|
||||||
// })
|
|
||||||
// .then((res) => {
|
|
||||||
// console.log(res.data.signature);
|
|
||||||
// startMeeting(res.data.signature);
|
|
||||||
// })
|
|
||||||
// .catch((error) => {
|
|
||||||
// console.log(error);
|
|
||||||
// });
|
|
||||||
};
|
|
||||||
|
|
||||||
// 开始会议
|
|
||||||
const startMeeting = async (signature) => {
|
|
||||||
let meetingSDKElement = document.getElementById("video-element");
|
|
||||||
try {
|
|
||||||
meetingConfig.client.init({
|
|
||||||
debug: true,
|
|
||||||
zoomAppRoot: meetingSDKElement,
|
|
||||||
language: "zh-CN",
|
|
||||||
customize: {
|
|
||||||
video: {
|
|
||||||
isResizable: false,
|
|
||||||
popper: {
|
|
||||||
disableDraggable: true,
|
|
||||||
},
|
|
||||||
viewSizes: {
|
|
||||||
default: {
|
|
||||||
width: meetingWidth.value,
|
|
||||||
height: meetingHeight.value - 45,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
meetingInfo: [
|
|
||||||
"topic",
|
|
||||||
"host",
|
|
||||||
"mn",
|
|
||||||
"pwd",
|
|
||||||
"telPwd",
|
|
||||||
"invite",
|
|
||||||
"participant",
|
|
||||||
"dc",
|
|
||||||
"enctype",
|
|
||||||
],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
await meetingConfig.client.join({
|
|
||||||
sdkKey: meetingConfig.sdkKey,
|
|
||||||
signature: signature,
|
|
||||||
meetingNumber: meetingConfig.meetingNumber,
|
|
||||||
password: meetingConfig.passWord,
|
|
||||||
userName: meetingConfig.userName,
|
|
||||||
userEmail: meetingConfig.userEmail,
|
|
||||||
tk: meetingConfig.registrantToken,
|
|
||||||
});
|
|
||||||
document.querySelector("#suspension-view-tab-thumbnail-gallery").click();
|
|
||||||
initDesktopLayout();
|
|
||||||
isMeetingLoading.value = false;
|
|
||||||
} catch (error) {
|
|
||||||
isMeetingLoading.value = false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const setFullScreen = () => {
|
|
||||||
videoElementRef.value.requestFullscreen();
|
|
||||||
};
|
|
||||||
onMounted(() => {
|
|
||||||
getSignature();
|
|
||||||
});
|
|
||||||
// 初始化
|
|
||||||
const initDesktopLayout = () => {
|
|
||||||
// 初始化高度
|
|
||||||
|
|
||||||
const heightEl = document.querySelectorAll(
|
|
||||||
".zmwebsdk-MuiBox-root.zmwebsdk-MuiBox-root"
|
|
||||||
)[2];
|
|
||||||
heightEl.style.height = `${meetingHeight.value - 45}px`;
|
|
||||||
console.log(meetingHeight.value);
|
|
||||||
|
|
||||||
// 初始化宽度
|
|
||||||
document.querySelectorAll(
|
|
||||||
".zmwebsdk-MuiPaper-root.zmwebsdk-MuiPaper-elevation1.zmwebsdk-MuiPaper-rounded"
|
|
||||||
)[1].style.width = `${meetingWidth.value - 4}px`;
|
|
||||||
// 加载完成显示 video element
|
|
||||||
document.querySelector("#video-element").style.visibility = "visible";
|
|
||||||
};
|
|
||||||
// 检测屏幕共享开启状态变化
|
|
||||||
const inSharing = ref(false); // 是否开启屏幕共享
|
|
||||||
setInterval(() => {
|
|
||||||
const elSharing = document.querySelector(
|
|
||||||
"div[class*=zmwebsdk-makeStyles-inSharing]"
|
|
||||||
);
|
|
||||||
if (elSharing) {
|
|
||||||
inSharing.value = true;
|
|
||||||
} else {
|
|
||||||
inSharing.value = false;
|
|
||||||
}
|
|
||||||
}, 500);
|
|
||||||
|
|
||||||
const avatarList = ref("");
|
|
||||||
setInterval(() => {
|
|
||||||
const avatarElList = document.querySelectorAll(
|
|
||||||
'ul[class^="zmwebsdk-makeStyles-avatarList"] li'
|
|
||||||
);
|
|
||||||
const arr = [];
|
|
||||||
avatarElList.forEach((el) => {
|
|
||||||
arr.push({
|
|
||||||
videoOn: el.className.includes("videoOn"),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
avatarList.value = arr
|
|
||||||
.map((el) => {
|
|
||||||
return el.videoOn ? "on" : "off";
|
|
||||||
})
|
|
||||||
.join(",");
|
|
||||||
// console.log(avatarList.value);
|
|
||||||
}, 500);
|
|
||||||
watch(avatarList, (val) => {
|
|
||||||
if (inSharing.value) {
|
|
||||||
document
|
|
||||||
.querySelectorAll(`ul[class^="zmwebsdk-makeStyles-avatarList"] li`)
|
|
||||||
.forEach((el) => {
|
|
||||||
if (el.className.includes("videoOn")) {
|
|
||||||
el.style.visibility = "visible";
|
|
||||||
} else {
|
|
||||||
el.style.visibility = "hidden";
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
// 当观众被设为嘉宾时,摄图会自动改为 ribbon,通过setInterval 监听变化,将其重新设为gallery view
|
|
||||||
const isRibbon = ref(null);
|
|
||||||
setInterval(() => {
|
|
||||||
isRibbon.value = document.querySelector(
|
|
||||||
"#suspension-view-tab-thumbnail-ribbon.zmwebsdk-MuiTab-selected"
|
|
||||||
);
|
|
||||||
}, 500);
|
|
||||||
|
|
||||||
watch(isRibbon, (val) => {
|
|
||||||
if (val) {
|
|
||||||
const galleryViewButton = document.querySelector(
|
|
||||||
"#suspension-view-tab-thumbnail-gallery"
|
|
||||||
);
|
|
||||||
if (galleryViewButton) galleryViewButton.click();
|
|
||||||
initDesktopLayout();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 当被设为嘉宾时自动点击同意按钮
|
|
||||||
setInterval(() => {
|
|
||||||
const isSetAsGuest = document.querySelector(
|
|
||||||
".zmwebsdk-MuiButtonBase-root.zmwebsdk-MuiButton-root.zmwebsdk-MuiButton-contained.zmwebsdk-MuiButton-containedPrimary.zmwebsdk-MuiButton-containedSizeSmall.zmwebsdk-MuiButton-sizeSmall.zmwebsdk-MuiButton-disableElevation"
|
|
||||||
);
|
|
||||||
if (isSetAsGuest) {
|
|
||||||
isSetAsGuest.click();
|
|
||||||
}
|
|
||||||
}, 500);
|
|
||||||
|
|
||||||
// 根据id设置布局
|
|
||||||
const setLayout = (templateId) => {
|
|
||||||
console.log(templateId);
|
|
||||||
const v_s_wrap_el = document.querySelectorAll(
|
|
||||||
".zmwebsdk-MuiBox-root.zmwebsdk-MuiBox-root"
|
|
||||||
)[2]; // 包含视频和课件的容器
|
|
||||||
v_s_wrap_el.style.flexDirection = "";
|
|
||||||
|
|
||||||
const v_wrap_el = v_s_wrap_el.lastChild;
|
|
||||||
if (!inSharing.value) return;
|
|
||||||
|
|
||||||
const s_wrap_el = document.querySelector(
|
|
||||||
`div[class*="zmwebsdk-makeStyles-inSharing"]`
|
|
||||||
);
|
|
||||||
if (templateId === "1") {
|
|
||||||
// 课件|视频 对半分
|
|
||||||
v_wrap_el.style.width = `${meetingWidth.value / 2 - 2}px`;
|
|
||||||
} else if (templateId === "2") {
|
|
||||||
// 视频|课件 对半分
|
|
||||||
v_wrap_el.style.width = `${meetingWidth.value / 2 - 2}px`;
|
|
||||||
v_s_wrap_el.style.flexDirection = "row-reverse";
|
|
||||||
} else if (templateId === "3") {
|
|
||||||
// 课件|视频 左4/5 | 右边1/5
|
|
||||||
v_wrap_el.style.width = `${(meetingWidth.value - 4) / 5}px`;
|
|
||||||
} else if (templateId === "4") {
|
|
||||||
// 视频|课件 左1/4 | 右边3/4
|
|
||||||
v_wrap_el.style.width = `${(meetingWidth.value - 4) / 5}px`;
|
|
||||||
v_s_wrap_el.style.flexDirection = "row-reverse";
|
|
||||||
} else if (templateId === "5") {
|
|
||||||
// 只显示课件
|
|
||||||
v_wrap_el.style.display = `none`;
|
|
||||||
} else if (templateId === "6") {
|
|
||||||
// 只显示视频
|
|
||||||
s_wrap_el.style.display = "none";
|
|
||||||
v_wrap_el.style.width = `${meetingWidth.value - 4}px`;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
watch(inSharing, (newVal) => {
|
|
||||||
console.log(newVal);
|
|
||||||
if (newVal) {
|
|
||||||
setLayout(templateId.value);
|
|
||||||
} else {
|
|
||||||
const galleryViewButton = document.querySelector(
|
|
||||||
"#suspension-view-tab-thumbnail-gallery"
|
|
||||||
);
|
|
||||||
if (galleryViewButton) galleryViewButton.click();
|
|
||||||
const v_s_wrap_el = document.querySelectorAll(
|
|
||||||
".zmwebsdk-MuiBox-root.zmwebsdk-MuiBox-root"
|
|
||||||
)[2];
|
|
||||||
if (!v_s_wrap_el) return;
|
|
||||||
const v_wrap_el = v_s_wrap_el.lastChild;
|
|
||||||
v_wrap_el.style.width = "";
|
|
||||||
v_s_wrap_el.style.flexDirection = "";
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
let socket = reactive({});
|
|
||||||
const initWebSocket = () => {
|
|
||||||
// 建立websocket连接
|
|
||||||
socket = new ReconnectingWebSocket(
|
|
||||||
`wss://meeting.chuhuankj.com/wss/websocket/meeting/${store.getters.meetingId}/${joinAccount.value}`
|
|
||||||
);
|
|
||||||
socket.addEventListener("open", () => {
|
|
||||||
// socket.send("Hello Server!");
|
|
||||||
console.log("websocket,已连接");
|
|
||||||
});
|
|
||||||
|
|
||||||
// 监听websocket消息
|
|
||||||
socket.addEventListener("message", async (event) => {
|
|
||||||
const data = JSON.parse(JSON.parse(event.data));
|
|
||||||
console.log(data);
|
|
||||||
// 会议信息更新时
|
|
||||||
if (data.type === "isRefreshMeeting") {
|
|
||||||
await store.dispatch("getMeetingInfo", store.getters.meetingId);
|
|
||||||
meetingContainerRef.value.style.background = ` url(${store.getters.templateBackgroundPic}) 0% 0% / cover no-repeat`;
|
|
||||||
templateId.value = store.getters.templateId;
|
|
||||||
setLayout(templateId.value);
|
|
||||||
setTextLabel();
|
|
||||||
}
|
|
||||||
// 开始签到时
|
|
||||||
else if (data.type === "isStartSign") {
|
|
||||||
console.log(data);
|
|
||||||
showSignDialog.value = true;
|
|
||||||
}
|
|
||||||
// 签到结束时
|
|
||||||
else if (data.type === "isEndSign") {
|
|
||||||
showSignDialog.value = false;
|
|
||||||
}
|
|
||||||
// 收到聊天消息时
|
|
||||||
else if (data.type === "isChat") {
|
|
||||||
console.log(JSON.parse(JSON.parse(JSON.parse(data.content).msg)));
|
|
||||||
messages.value.push({
|
|
||||||
...JSON.parse(JSON.parse(JSON.parse(data.content).msg)),
|
|
||||||
id: _.uniqueId(),
|
|
||||||
time: dayjs().format("YYYY-MM-DD HH:mm:ss"),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
// 开始考试时
|
|
||||||
else if (data.type === "isStartExam") {
|
|
||||||
showExamDialog.value = true;
|
|
||||||
}
|
|
||||||
// 开始问卷时
|
|
||||||
else if (data.type === "isStartQuestionnaire") {
|
|
||||||
showQuestionnaireDialog.value = true;
|
|
||||||
}
|
|
||||||
// 会议结束时
|
|
||||||
else if (data.type === "isCloseMeeting") {
|
|
||||||
showExamDialog.value = false;
|
|
||||||
showQuestionnaireDialog.value = false;
|
|
||||||
ElMessageBox.alert("会议已结束");
|
|
||||||
socket.close();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
socket.addEventListener("close", (event) => {
|
|
||||||
console.log(event, "close");
|
|
||||||
});
|
|
||||||
socket.addEventListener("error", (event) => {
|
|
||||||
console.log(event, "error");
|
|
||||||
});
|
|
||||||
};
|
|
||||||
initWebSocket();
|
|
||||||
/* 签到功能 */
|
|
||||||
// 提交签到
|
|
||||||
const showSignDialog = ref(false); //是否显示签到窗口
|
|
||||||
const submitSign = async () => {
|
|
||||||
await signMeeting({
|
|
||||||
meetingId: store.getters.meetingId,
|
|
||||||
signTime: dayjs().format("YYYY-MM-DD HH:mm:ss"),
|
|
||||||
account: joinAccount.value,
|
|
||||||
});
|
|
||||||
showSignDialog.value = false;
|
|
||||||
ElMessage.success("签到成功");
|
|
||||||
};
|
|
||||||
|
|
||||||
/* 聊天功能 */
|
|
||||||
const messages = ref([]); // 消息列表
|
|
||||||
const editingMessage = ref(""); // 正在编辑的内容
|
|
||||||
// 发送消息
|
|
||||||
const sendMessage = () => {
|
|
||||||
console.log(
|
|
||||||
JSON.stringify(
|
|
||||||
JSON.stringify({
|
|
||||||
account: joinName.value,
|
|
||||||
msg: editingMessage.value,
|
|
||||||
})
|
|
||||||
)
|
|
||||||
);
|
|
||||||
socket.send(
|
|
||||||
JSON.stringify(
|
|
||||||
JSON.stringify({
|
|
||||||
account: joinName.value,
|
|
||||||
msg: editingMessage.value,
|
|
||||||
})
|
|
||||||
)
|
|
||||||
);
|
|
||||||
// socket.send(editingMessage.value);
|
|
||||||
messages.value.push({
|
|
||||||
id: _.uniqueId(),
|
|
||||||
account: joinName.value,
|
|
||||||
msg: editingMessage.value,
|
|
||||||
isMe: true,
|
|
||||||
time: dayjs().format("YYYY-MM-DD HH:mm:ss"),
|
|
||||||
});
|
|
||||||
editingMessage.value = "";
|
|
||||||
};
|
|
||||||
|
|
||||||
const leaveConference = () => {
|
|
||||||
meetingConfig.client.leaveMeeting();
|
|
||||||
};
|
|
||||||
window.addEventListener("beforeunload", leaveConference);
|
|
||||||
onUnmounted(() => {
|
|
||||||
window.removeEventListener("beforeunload", leaveConference);
|
|
||||||
});
|
|
||||||
|
|
||||||
// 是否显示考试和问卷弹窗
|
|
||||||
const showExamDialog = ref(false);
|
|
||||||
const showQuestionnaireDialog = ref(false);
|
|
||||||
</script>
|
|
||||||
<style scoped lang="scss">
|
|
||||||
#app-container {
|
|
||||||
width: 100%;
|
|
||||||
margin: 0 auto;
|
|
||||||
:deep(.text-tag) {
|
|
||||||
position: absolute;
|
|
||||||
z-index: 999;
|
|
||||||
border-radius: 4px;
|
|
||||||
padding: 5px;
|
|
||||||
background-color: #fff;
|
|
||||||
* {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.row {
|
|
||||||
width: 100%;
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
.meeting-container {
|
|
||||||
position: relative;
|
|
||||||
width: 80%;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.chat-right {
|
|
||||||
width: 20%;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: space-between;
|
|
||||||
.option-bar {
|
|
||||||
margin-top: 0;
|
|
||||||
margin: 5px;
|
|
||||||
}
|
|
||||||
:deep(.message-list) {
|
|
||||||
overflow-y: scroll;
|
|
||||||
margin-bottom: 0;
|
|
||||||
// height: 80%;
|
|
||||||
flex: 1;
|
|
||||||
p {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (max-width: 768px) {
|
|
||||||
.meeting-container {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
.chat-right {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#video-element {
|
|
||||||
// visibility: hidden;
|
|
||||||
// height: 600px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tabs {
|
|
||||||
width: 100%;
|
|
||||||
.meeting-info {
|
|
||||||
:deep(p) {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// :deep(.el-tabs__content) {
|
|
||||||
// .chat {
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
.option-bar {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-around;
|
|
||||||
}
|
|
||||||
:deep(.zmwebsdk-MuiPaper-root) {
|
|
||||||
background: transparent;
|
|
||||||
box-shadow: 0 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.zmwebsdk-MuiToolbar-root) {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
:deep(.zmwebsdk-makeStyles-singleView-7) {
|
|
||||||
// background-color: transparent;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
:deep(.zmwebsdk-MuiPaper-root.zmwebsdk-makeStyles-root-50.zmwebsdk-MuiPaper-elevation1.zmwebsdk-MuiPaper-rounded) {
|
|
||||||
width: 20%;
|
|
||||||
background: #ccc;
|
|
||||||
}
|
|
||||||
.fullscreen-btn {
|
|
||||||
position: absolute;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
}
|
|
||||||
</style>
|
|
Reference in New Issue
Block a user