466 lines
13 KiB
Vue
466 lines
13 KiB
Vue
|
<template>
|
||
|
<div id="app-container" ref="appContainer">
|
||
|
<div class="meeting-container" ref="meetingContainerRef">
|
||
|
<div id="video-element"></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="聊天"
|
||
|
><div class="meeting-info chat">
|
||
|
<messageList :messageList="messages"></messageList>
|
||
|
<div class="option-bar">
|
||
|
<wangEditor
|
||
|
v-model="editingMessage"
|
||
|
height="100px"
|
||
|
width="80%"
|
||
|
></wangEditor>
|
||
|
<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"
|
||
|
:showDialog="showExamDialog"
|
||
|
@close="showExamDialog = $event"
|
||
|
></questions>
|
||
|
|
||
|
<questions
|
||
|
mode="2"
|
||
|
:showDialog="showQuestionnaireDialog"
|
||
|
@close="showQuestionnaireDialog = $event"
|
||
|
></questions>
|
||
|
</div>
|
||
|
</template>
|
||
|
<script setup>
|
||
|
import { computed, reactive, watch, ref, onMounted } from "vue";
|
||
|
import { useStore } from "vuex";
|
||
|
import { signMeeting } from "@/api/meeting";
|
||
|
import wangEditor from "@/components/wangEditor";
|
||
|
import questions from "@/components/questions";
|
||
|
import messageList from "@/components/messageList";
|
||
|
import dayjs from "dayjs";
|
||
|
import ZoomMtgEmbedded from "@zoomus/websdk/embedded";
|
||
|
import axios from "axios";
|
||
|
import _ from "lodash";
|
||
|
|
||
|
import { ElMessage, ElMessageBox } from "element-plus";
|
||
|
|
||
|
const store = useStore();
|
||
|
|
||
|
const meetingNote = computed(() => store.getters.meetingNote);
|
||
|
const meetingSchedule = computed(() => store.getters.meetingSchedule);
|
||
|
const expertInfo = computed(() => store.getters.expertInfo);
|
||
|
|
||
|
const templateId = ref(1);
|
||
|
if (store.getters.templateId) {
|
||
|
templateId.value = store.getters.templateId;
|
||
|
}
|
||
|
const joinAccount = ref("");
|
||
|
joinAccount.value =
|
||
|
store.getters.nickname || store.getters.phone || store.getters.icCard;
|
||
|
const meetingWidth = ref(0);
|
||
|
const meetingHeight = ref(0);
|
||
|
// 设置会议背景大小、图片
|
||
|
const meetingContainerRef = ref(null);
|
||
|
onMounted(() => {
|
||
|
meetingContainerRef.value.style.background = `url(${store.getters.templateBackgroundPic}) 0% 0% / cover no-repeat`;
|
||
|
setTextLabel();
|
||
|
console.log(meetingContainerRef.value.offsetWidth);
|
||
|
meetingWidth.value = meetingContainerRef.value.offsetWidth * 0.8;
|
||
|
meetingHeight.value = (meetingWidth.value * 3) / 4;
|
||
|
document.querySelector("#video-element").style.width =
|
||
|
meetingWidth.value + "px";
|
||
|
document.querySelector("#video-element").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";
|
||
|
console.log(labelObj);
|
||
|
textEl.style.left = `${labelObj.x}`;
|
||
|
textEl.style.top = `${labelObj.y}`;
|
||
|
document.querySelector(".meeting-container").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: "TUgbQFx4RhPHDPa0OQAKVgPL2dPGQoVqZIuW",
|
||
|
meetingNumber: store.getters.meetingNumber,
|
||
|
passWord: store.state.password,
|
||
|
role: 0,
|
||
|
signatureEndpoint: "http://120.26.107.74:4000",
|
||
|
userEmail: "",
|
||
|
userName: joinAccount.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: "",
|
||
|
});
|
||
|
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 res = await checkMedia();
|
||
|
if (!res.audio || !res.video) {
|
||
|
return ElMessageBox.alert(
|
||
|
`摄像头状态 : ${res.video ? "可用" : "禁用"}\n麦克风状态 : ${
|
||
|
res.audio ? "可用" : "禁用"
|
||
|
}`
|
||
|
);
|
||
|
}
|
||
|
console.log(res);
|
||
|
axios
|
||
|
.post(meetingConfig.signatureEndpoint, {
|
||
|
meetingNumber: meetingConfig.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");
|
||
|
|
||
|
console.log(meetingConfig);
|
||
|
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,
|
||
|
},
|
||
|
},
|
||
|
},
|
||
|
|
||
|
meetingInfo: [
|
||
|
"topic",
|
||
|
"host",
|
||
|
"mn",
|
||
|
"pwd",
|
||
|
"telPwd",
|
||
|
"invite",
|
||
|
"participant",
|
||
|
"dc",
|
||
|
"enctype",
|
||
|
],
|
||
|
// toolbar: {
|
||
|
// buttons: [
|
||
|
// {
|
||
|
// text: "Custom Button",
|
||
|
// className: "CustomButton",
|
||
|
// onClick: () => {
|
||
|
// console.log("custom button");
|
||
|
// },
|
||
|
// },
|
||
|
// ],
|
||
|
// },
|
||
|
},
|
||
|
});
|
||
|
await meetingConfig.client.join({
|
||
|
sdkKey: meetingConfig.sdkKey,
|
||
|
signature: signature,
|
||
|
meetingNumber: meetingConfig.meetingNumber,
|
||
|
password: meetingConfig.passWord,
|
||
|
userName: meetingConfig.userName,
|
||
|
userEmail: meetingConfig.userEmail,
|
||
|
tk: meetingConfig.registrantToken,
|
||
|
});
|
||
|
// console.log(meetingConfig.client.checkSystemRequirements());
|
||
|
initDesktopLayout();
|
||
|
// document.querySelector("#suspension-view-tab-thumbnail-speaker").click();
|
||
|
};
|
||
|
// 初始化
|
||
|
const initDesktopLayout = () => {
|
||
|
console.log(meetingHeight.value);
|
||
|
// 切换到 gallery view
|
||
|
document.querySelector("#suspension-view-tab-thumbnail-gallery").click();
|
||
|
// 初始化高度
|
||
|
document.querySelector(".MuiBox-root.jss167.jss161.jss165").style.height = `${
|
||
|
meetingHeight.value - 45
|
||
|
}px`;
|
||
|
// 初始化宽度
|
||
|
document.querySelector(
|
||
|
".MuiPaper-root.jss25.MuiPaper-elevation1.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(".jss51");
|
||
|
if (elSharing) {
|
||
|
if (elSharing.className.includes("in-sharing")) {
|
||
|
inSharing.value = true;
|
||
|
} else {
|
||
|
inSharing.value = false;
|
||
|
}
|
||
|
}
|
||
|
}, 500);
|
||
|
|
||
|
// 根据id设置布局
|
||
|
const setLayout = (templateId) => {
|
||
|
console.log(templateId);
|
||
|
document.querySelector(
|
||
|
".MuiBox-root.jss167.jss161.jss165"
|
||
|
).style.flexDirection = "";
|
||
|
if (templateId === "1" && inSharing.value) {
|
||
|
document.querySelector(".jss162.jss166").style.width = `${
|
||
|
meetingWidth.value / 2 - 2
|
||
|
}px`;
|
||
|
} else if (templateId === "2" && inSharing.value) {
|
||
|
document.querySelector(".jss162.jss166").style.width = `${
|
||
|
meetingWidth.value / 2 - 2
|
||
|
}px`;
|
||
|
document.querySelector(
|
||
|
".MuiBox-root.jss167.jss161.jss165"
|
||
|
).style.flexDirection = "row-reverse";
|
||
|
} else if (templateId === "3" && inSharing.value) {
|
||
|
document.querySelector(".jss162.jss166").style.width = `${
|
||
|
(meetingWidth.value - 4) / 4
|
||
|
}px`;
|
||
|
} else if (templateId === "4" && inSharing.value) {
|
||
|
document.querySelector(".jss162.jss166").style.width = `${
|
||
|
(meetingWidth.value - 4) / 4
|
||
|
}px`;
|
||
|
document.querySelector(
|
||
|
".MuiBox-root.jss167.jss161.jss165"
|
||
|
).style.flexDirection = "row-reverse";
|
||
|
}
|
||
|
};
|
||
|
watch(inSharing, (newVal) => {
|
||
|
console.log(newVal);
|
||
|
if (newVal) {
|
||
|
setLayout(templateId.value);
|
||
|
// document.querySelector("#suspension-view-tab-thumbnail-speaker").click();
|
||
|
} else {
|
||
|
// document.querySelector("#suspension-view-tab-thumbnail-speaker").click();
|
||
|
const galleryViewButton = document.querySelector(
|
||
|
"#suspension-view-tab-thumbnail-gallery"
|
||
|
);
|
||
|
galleryViewButton.click();
|
||
|
document.querySelector(".jss162.jss166").style.width = "";
|
||
|
document.querySelector(
|
||
|
".MuiBox-root.jss167.jss161.jss165"
|
||
|
).style.flexDirection = "";
|
||
|
}
|
||
|
});
|
||
|
getSignature();
|
||
|
|
||
|
// 建立websocket连接
|
||
|
const socket = new WebSocket(
|
||
|
`ws://118.195.192.58:1618/websocket/meeting/${store.getters.meetingId}/${joinAccount.value}`
|
||
|
);
|
||
|
socket.addEventListener("open", () => {
|
||
|
// socket.send("Hello Server!");
|
||
|
console.log("websocket,已连接");
|
||
|
});
|
||
|
|
||
|
/* 签到功能 */
|
||
|
// 提交签到
|
||
|
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("签到成功");
|
||
|
};
|
||
|
|
||
|
// 监听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(data.content));
|
||
|
messages.value.push({
|
||
|
...JSON.parse(data.content),
|
||
|
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("会议已结束");
|
||
|
}
|
||
|
});
|
||
|
|
||
|
/* 聊天功能 */
|
||
|
const messages = ref([]); // 消息列表
|
||
|
const editingMessage = ref(""); // 正在编辑的内容
|
||
|
// 发送消息
|
||
|
const sendMessage = () => {
|
||
|
console.log(socket);
|
||
|
socket.send(editingMessage.value);
|
||
|
messages.value.push({
|
||
|
id: _.uniqueId(),
|
||
|
account: joinAccount.value,
|
||
|
msg: editingMessage.value,
|
||
|
isMe: true,
|
||
|
time: dayjs().format("YYYY-MM-DD HH:mm:ss"),
|
||
|
});
|
||
|
editingMessage.value = "";
|
||
|
};
|
||
|
|
||
|
// 是否显示考试和问卷弹窗
|
||
|
const showExamDialog = ref(false);
|
||
|
const showQuestionnaireDialog = ref(false);
|
||
|
</script>
|
||
|
<style scoped lang="scss">
|
||
|
#app-container {
|
||
|
width: 85%;
|
||
|
margin: 0 auto;
|
||
|
:deep(.text-tag) {
|
||
|
position: absolute;
|
||
|
z-index: 999;
|
||
|
border-radius: 4px;
|
||
|
padding: 5px;
|
||
|
background-color: #fff;
|
||
|
* {
|
||
|
margin: 0;
|
||
|
padding: 0;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
.meeting-container {
|
||
|
position: relative;
|
||
|
width: 100%;
|
||
|
display: flex;
|
||
|
justify-content: center;
|
||
|
}
|
||
|
#video-element {
|
||
|
visibility: hidden;
|
||
|
}
|
||
|
|
||
|
.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(.MuiPaper-root) {
|
||
|
background: transparent;
|
||
|
box-shadow: 0 0;
|
||
|
}
|
||
|
:deep(.MuiToolbar-root) {
|
||
|
display: none;
|
||
|
}
|
||
|
:deep(.MuiPaper-root.jss2.jss9.MuiPaper-elevation1.MuiPaper-rounded) {
|
||
|
padding: 0;
|
||
|
}
|
||
|
</style>
|