mobile layout

This commit is contained in:
cxc
2022-05-30 17:33:59 +08:00
parent 8afe370c1b
commit e683474b1d
6 changed files with 1165 additions and 988 deletions

View File

@ -30,8 +30,15 @@
@click="openRichMessageEditor"
v-html="editingMessage"
class="display-message"
:style="`border-color: ${
showRichEditor === true ? '#409eff' : '#c0c4cc'
}`"
></div>
<el-input v-else v-model="editingMessage"></el-input>
<el-input
v-else
v-model="editingMessage"
class="message-input"
></el-input>
<el-button @click="sendMessage">发送</el-button>
</div>
@ -162,10 +169,20 @@ const sendMessage = () => {
.input-bar {
display: flex;
margin: 10px 0;
.message-input {
margin-right: 5px;
}
.display-message {
border: 1px solid #666;
width: 80%;
min-height: 100%;
box-sizing: border-box;
display: flex;
width: 100%;
align-items: center;
padding: 0 11px;
font-size: 14px;
color: #606266;
border: 1px solid;
// width: 80%;
min-height: 32px;
border-radius: 5px;
margin-right: 5px;
word-wrap: break-word;

View File

@ -28,6 +28,7 @@
<el-radio-group
v-if="item.type === `1`"
v-model="item.choiceAnswerIdStr[0]"
@change="stageAnswer"
>
<el-radio
v-for="el in item.questionChoiceList"
@ -41,7 +42,11 @@
</el-radio>
</el-radio-group>
<el-checkbox-group v-else v-model="item.choiceAnswerIdStr">
<el-checkbox-group
v-else
v-model="item.choiceAnswerIdStr"
@change="stageAnswer"
>
<el-checkbox
v-for="el in item.questionChoiceList"
:key="el.id"
@ -65,7 +70,6 @@
<script setup>
import { toRefs, ref } from "vue";
import { ElMessage } from "element-plus";
import {
getQuestionsList,
@ -88,7 +92,7 @@ const props = defineProps({
});
const { showDialog, mode } = toRefs(props);
const emit = defineEmits(["close"]);
const emit = defineEmits(["close", "stage"]);
const joinAccount = ref("");
joinAccount.value =
store.getters.icCard || store.getters.phone || store.getters.nickname;
@ -109,6 +113,7 @@ const loadQuestionsList = async () => {
choiceAnswerIdStr: [],
};
});
stageAnswer();
};
const questionsRef = ref(null);
@ -126,6 +131,10 @@ const submit = async (questionsRef) => {
};
}),
});
store.commit("setJoinUser", {
...store.state.joinUser,
examSubmited: true,
});
ElMessage.success("提交考试成功");
} else if (mode.value == "2") {
await commitQuestionnaire({
@ -138,10 +147,18 @@ const submit = async (questionsRef) => {
};
}),
});
store.commit("setJoinUser", {
...store.state.joinUser,
questionnaireSubmited: true,
});
ElMessage.success("提交问卷成功");
}
closeDialog();
};
const stageAnswer = () => {
emit("stage", form.value.questionsList);
};
loadQuestionsList();
const closeDialog = () => {
emit("close", false);

View File

@ -30,7 +30,7 @@ const router = createRouter({
{
path: "/meeting/:meetingId",
name: "Meeting",
component: () => import("@/views/meeting_new.vue"),
component: () => import("@/views/meeting.vue"),
},
{
path: "/panelist/:meetingId",
@ -45,7 +45,7 @@ const router = createRouter({
{
path: "/host/:meetingId/:createEmail/:pwd/:username/:nickname",
name: "Host",
component: () => import("@/views/host_new.vue"),
component: () => import("@/views/meeting.vue"),
},
{
path: "/:pathMatch(.*)*",

File diff suppressed because it is too large Load Diff

View File

@ -1,577 +0,0 @@
<template>
<div id="app-container" ref="appContainerRef">
<!-- zoom 会议组件 -->
<div id="meeting-chat-row">
<div id="meeting-container" ref="meetingContainerRef">
<div
id="video-element"
class="layout-template-1"
ref="videoElementRef"
></div>
</div>
<div id="right-chat">
<Chat
:is-rich="route.name === 'Host'"
place="right"
:account="joinName"
:message-list="messages"
@send="sendMessage"
/>
</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"></el-tab-pane>
</el-tabs>
<questions
mode="1"
v-if="showExamDialog && route.name !== 'Host'"
:showDialog="showExamDialog"
@close="showExamDialog = $event"
></questions>
<questions
mode="2"
v-if="showQuestionnaireDialog && route.name !== 'Host'"
:showDialog="showQuestionnaireDialog"
@close="showQuestionnaireDialog = $event"
></questions>
</div>
</template>
<script setup>
import {
computed,
nextTick,
onMounted,
onUnmounted,
reactive,
ref,
watch,
} from "vue";
import { useStore } from "vuex";
import { useRoute } from "vue-router";
import dayjs from "dayjs";
import { uniqueId } from "lodash";
import ZoomMtgEmbedded from "@zoomus/websdk/embedded";
import ReconnectingWebSocket from "reconnecting-websocket";
import { signMeeting, generateSignature } from "@/api/meeting";
import Chat from "@/components/chat";
const store = useStore();
const route = useRoute();
const meetingNote = computed(() => store.getters.meetingNote); // 会议介绍
const meetingSchedule = computed(() => store.getters.meetingSchedule); // 会议日程
const expertInfo = computed(() => store.getters.expertInfo); // 专家信息
const joinName = ref(""); // 显示昵称
const joinAccount = ref(""); // 用于连接 websocket提交考试
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); // zoom会议组件
const meetingWidth = ref(0); // 视频和共享屏幕宽度
const meetingHeight = ref(0); // 视频和共享屏幕高度
const isMeetingLoading = ref(false);
const templateId = ref(1);
/* 会议配置 */
const meetingConfig = reactive({
client: ZoomMtgEmbedded.createClient(),
sdkKey: "99Spa64AWHYVZD95imUpVyMD0KF9CpEIrIb1",
meetingNumber: store.getters.meetingNumber,
passWord: store.state.password,
role: route.name === "Host" ? 1 : 0,
userEmail: store.getters.email,
userName: joinName.value,
registrantToken: store.getters.token,
});
/* 初始化尺寸 */
const initDesktopLayout = () => {
// 初始化高度
document.querySelectorAll(
".zmwebsdk-MuiBox-root.zmwebsdk-MuiBox-root"
)[2].style.height = `${meetingHeight.value}px`;
// 初始化宽度
document.querySelectorAll(
".zmwebsdk-MuiPaper-root.zmwebsdk-MuiPaper-elevation1.zmwebsdk-MuiPaper-rounded"
)[1].style.width = `${meetingWidth.value}px`;
};
// 开始会议
const startMeeting = async () => {
const { sign } = await generateSignature({
meetingNumber: store.getters.meetingNumber,
role: meetingConfig.role,
});
let meetingSDKElement = document.getElementById("video-element");
try {
meetingConfig.client.init({
debug: true,
zoomAppRoot: meetingSDKElement,
language: "zh-CN",
customize: {
video: {
isResizable: true,
popper: {
disableDraggable: true,
},
viewSizes: {
default: {
width: meetingWidth.value,
height: meetingHeight.value, // 不包括顶部toolbar和底部按钮高度
},
},
},
meetingInfo: [
"topic",
"host",
"mn",
"pwd",
"telPwd",
"invite",
"participant",
"dc",
"enctype",
],
},
});
await meetingConfig.client.join({
sdkKey: meetingConfig.sdkKey,
signature: sign,
meetingNumber: meetingConfig.meetingNumber,
password: meetingConfig.passWord,
userName: meetingConfig.userName,
userEmail: meetingConfig.userEmail,
tk: meetingConfig.registrantToken,
});
console.log(meetingConfig.client.getAttendeeslist());
document.querySelector("#suspension-view-tab-thumbnail-gallery").click();
// initDesktopLayout();
isMeetingLoading.value = false;
} catch (error) {
isMeetingLoading.value = false;
}
};
// 根据id设置布局
const setLayout = (templateId) => {
console.log(templateId);
const videoScreenWrapEl = document.querySelectorAll(
".zmwebsdk-MuiBox-root.zmwebsdk-MuiBox-root"
)[2]; // 包含视频和课件的容器
videoScreenWrapEl.style.flexDirection = "";
const videoWrapEl = videoScreenWrapEl.lastChild;
if (!inSharing.value) return;
const screenWrapEl = document.querySelector(
`div[class*="zmwebsdk-makeStyles-inSharing"]`
);
if (templateId === "1") {
// 课件|视频 对半分
videoWrapEl.style.width = `${meetingWidth.value / 2 - 2}px`;
} else if (templateId === "2") {
// 视频|课件 对半分
videoWrapEl.style.width = `${meetingWidth.value / 2 - 2}px`;
videoScreenWrapEl.style.flexDirection = "row-reverse";
} else if (templateId === "3") {
// 课件|视频 左4/5 | 右边1/5
videoWrapEl.style.width = `${(meetingWidth.value - 4) / 5}px`;
} else if (templateId === "4") {
// 视频|课件 左1/4 | 右边3/4
videoWrapEl.style.width = `${(meetingWidth.value - 4) / 5}px`;
videoScreenWrapEl.style.flexDirection = "row-reverse";
} else if (templateId === "5") {
// 只显示课件
videoWrapEl.style.display = `none`;
} else if (templateId === "6") {
// 只显示视频
screenWrapEl.style.display = "none";
videoWrapEl.style.width = `${meetingWidth.value - 4}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 switchToVideoOn = () => {
const prevBtn = document.querySelector(
"#suspension-view-tabpanel-ribbon>div>div:last-child>.zmwebsdk-MuiBox-root>button:first-child"
);
const nextBtn = document.querySelector(
"#suspension-view-tabpanel-ribbon>div>div:last-child>.zmwebsdk-MuiBox-root>button:last-child"
);
let isNextBtnHidden = nextBtn.className.includes(
"zmwebsdk-makeStyles-hidePaginationIcon"
);
let isPrevBtnHidden = prevBtn.className.includes(
"zmwebsdk-makeStyles-hidePaginationIcon"
);
while (!isPrevBtnHidden) {
prevBtn.click();
isPrevBtnHidden = prevBtn.className.includes(
"zmwebsdk-makeStyles-hidePaginationIcon"
);
}
let timer = setInterval(() => {
const displayLiEl = document.querySelector(
'ul[class^="zmwebsdk-makeStyles-avatarList"]>li'
);
console.log(displayLiEl);
if (displayLiEl.className.includes("videoOn")) {
console.log("开启了视频");
clearInterval(timer);
return;
}
nextBtn.click();
isNextBtnHidden = nextBtn.className.includes(
"zmwebsdk-makeStyles-hidePaginationIcon"
);
if (isNextBtnHidden) {
setTimeout(() => {
const displayLiEl = document.querySelector(
'ul[class^="zmwebsdk-makeStyles-avatarList"]>li'
);
console.log(displayLiEl);
}, 100);
clearInterval(timer);
}
}, 100);
// setTimeout(() => {
// const displayLiEl = document.querySelector(
// 'ul[class^="zmwebsdk-makeStyles-avatarList"]>li'
// );
// console.log(displayLiEl);
// }, 0);
// const displayLiEl = document.querySelector(
// 'ul[class^="zmwebsdk-makeStyles-avatarList"]>li'
// );
// console.log(displayLiEl);
// while (!isNextBtnHidden) {
// console.log(isNextBtnHidden);
// // const displayLiEl = document.querySelector(
// // 'ul[class^="zmwebsdk-makeStyles-avatarList"]>li'
// // );
// // console.log(displayLiEl);
// nextBtn.click();
// isNextBtnHidden = nextBtn.className.includes(
// "zmwebsdk-makeStyles-hidePaginationIcon"
// );
// // nextTick(() => {
// const displayLiEl = document.querySelector(
// 'ul[class^="zmwebsdk-makeStyles-avatarList"]>li'
// );
// console.log(displayLiEl);
// // });
// }
console.log(isNextBtnHidden);
// while (!isPrevBtnHidden) {
// nextTick(() => {
// const displayLiEl = document.querySelector(
// 'ul[class^="zmwebsdk-makeStyles-avatarList"]>li'
// );
// console.log(displayLiEl);
// // attenList.unshift({
// // videoOn: displayLiEl.className.includes("videoOn"),
// // displayName: displayLiEl.firstElementChild.innerHTML,
// // });
// attenList.unshift(displayLiEl);
// });
// console.log(isPrevBtnHidden);
// prevBtn.click();
// nextTick(() => {
// isPrevBtnHidden = prevBtn.className.includes(
// "zmwebsdk-makeStyles-hidePaginationIcon"
// );
// });
// }
// const displayLiEl = document.querySelector(
// 'ul[class^="zmwebsdk-makeStyles-avatarList"]>li'
// );
// console.log(displayLiEl);
// attenList.unshift({
// videoOn: displayLiEl.className.includes("videoOn"),
// displayName: displayLiEl.firstElementChild.innerHTML,
// });
// while (!isPrevBtnHidden) {}
// console.log(attenList);
};
// const attendeeslist = computed(() => {
// return meetingConfig.client.getAttendeeslist();
// });
const inSharing = ref(false); // 共享屏幕是否开启
const attendeeslist = ref("");
setInterval(() => {
// 共享屏幕状态变化
const screenShareEl = document.querySelector(
`div[class*="zmwebsdk-makeStyles-inSharing"]`
);
inSharing.value = screenShareEl ? true : false;
// ------
// 检测是否是ribbon视图, 如果是, 则切换到gallery view, 当观众被设为嘉宾或嘉宾被设为观众时需要切换视图.
const isRibbon = document.querySelector(
"#suspension-view-tab-thumbnail-ribbon.zmwebsdk-MuiTab-selected"
);
if (isRibbon && !inSharing.value) {
const galleryViewButton = document.querySelector(
"#suspension-view-tab-thumbnail-gallery"
);
if (galleryViewButton) galleryViewButton.click();
}
// ------
// 检测是否存在"同意被设为嘉宾按钮",存在则自动点击
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();
}
// -------
// attendeeslist.value.splice(0, attendeeslist.value.length);
attendeeslist.value = meetingConfig.client
.getAttendeeslist()
.map((el) => JSON.stringify(el))
.join("$");
}, 500);
// 监听共享屏幕状态
watch(inSharing, (val) => {
console.log(val);
if (val) {
setLayout(templateId.value);
} else {
const galleryViewButton = document.querySelector(
"#suspension-view-tab-thumbnail-gallery"
);
if (galleryViewButton) galleryViewButton.click();
const videoScreenWrapEl = document.querySelectorAll(
".zmwebsdk-MuiBox-root.zmwebsdk-MuiBox-root"
)[2];
if (!videoScreenWrapEl) return;
const videoWrapEl = videoScreenWrapEl.lastChild;
videoWrapEl.style.width = "";
videoScreenWrapEl.style.flexDirection = "";
}
});
watch(attendeeslist, (val) => {
console.log(val);
if (inSharing.value) {
switchToVideoOn();
}
});
// 是否显示考试和问卷弹窗
const showExamDialog = ref(false);
const showQuestionnaireDialog = ref(false);
let socket = reactive({});
const initWebSocket = () => {
// 建立websocket连接
socket = new ReconnectingWebSocket(
`wss://meeting.chuhuankj.com/wss/websocket/meeting/${store.getters.meetingId}/${joinAccount.value}`
);
socket.addEventListener("open", () => {
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 === "isStartSign" && route.name !== "Host") {
showSignDialog.value = true;
}
// 签到结束时
else if (data.type === "isEndSign" && route.name !== "Host") {
showSignDialog.value = false;
}
// 开始考试时
else if (data.type === "isStartExam" && route.name !== "Host") {
showExamDialog.value = true;
}
// 开始问卷时
else if (data.type === "isStartQuestionnaire" && route.name !== "Host") {
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 messages = ref([]);
const sendMessage = (msgObj) => {
console.log(JSON.stringify(JSON.stringify(msgObj)));
socket.send(
JSON.stringify(
JSON.stringify({
account: joinName.value,
msg: msgObj.msg,
})
)
);
messages.value.push(msgObj);
};
onMounted(() => {
meetingWidth.value = meetingContainerRef.value.offsetWidth * 0.9;
meetingHeight.value = (meetingWidth.value * 9) / 16;
videoElementRef.value.style.width = `${meetingWidth.value}px`;
videoElementRef.value.style.height = `${meetingHeight.value + 42}px`;
setTextLabel();
startMeeting();
});
window.getCurrentUser = meetingConfig.client.getCurrentUser;
window.getAttendeeslist = meetingConfig.client.getAttendeeslist;
window.getCurrentMeetingInfo = meetingConfig.client.getCurrentMeetingInfo;
const leaveConference = () => {
meetingConfig.client.leaveMeeting();
};
window.addEventListener("beforeunload", leaveConference);
onUnmounted(() => {
window.removeEventListener("beforeunload", leaveConference);
});
</script>
<style lang="scss" scoped>
// $videoELementWidth: 80% * 0.9;
// $videoElementHeight: calc($videoELementWidth * 9 / 16 + 42px);
$videoWidth: 240px;
$videoHeight: $videoWidth * 9 / 16;
#app-container {
#meeting-chat-row {
display: flex;
justify-content: space-between;
#meeting-container {
padding-top: calc(80vw * 0.08);
display: flex;
justify-content: center;
width: 80%;
position: relative;
#video-element {
// width: $videoELementWidth;
// height: $videoElementHeight;
background-color: indianred;
}
:deep(.text-tag) {
position: absolute;
z-index: 999;
border-radius: 4px;
padding: 5px;
background-color: #fff;
* {
margin: 0;
padding: 0;
}
}
}
#right-chat {
width: 20%;
}
}
}
.layout-template-1 {
:deep(#suspension-view-tabpanel-ribbon
> .zmwebsdk-MuiBox-root.zmwebsdk-MuiBox-root
> div:last-child) {
width: $videoWidth;
height: $videoHeight;
}
}
:deep(.zmwebsdk-MuiToolbar-root.zmwebsdk-MuiToolbar-regular) {
display: none; // 隐藏顶部 toolbar
}
:deep(div[class*="zmwebsdk-makeStyles-singleView"]) {
padding: 0; // 去掉会议组件边距
}
:deep(#video-element
> div
> .zmwebsdk-MuiPaper-root
> .zmwebsdk-MuiPaper-root) {
width: calc(80vw * 0.9);
}
:deep(#suspension-view-tabpanel-ribbon
> .zmwebsdk-MuiBox-root.zmwebsdk-MuiBox-root),
:deep(#suspension-view-tabpanel-gallery
> .zmwebsdk-MuiBox-root.zmwebsdk-MuiBox-root) {
height: calc(80vw * 0.9 * 9 / 16);
}
:deep(#suspension-view-tabpanel-ribbon
> .zmwebsdk-MuiBox-root.zmwebsdk-MuiBox-root
> div:last-child) {
// height: calc(240px * 9 / 16);
align-self: center;
}
// :deep(.zmwebsdk-MuiPaper-root) {
// background: transparent;
// box-shadow: 0 0;
// }
</style>

643
src/views/meeting_old.vue Normal file
View File

@ -0,0 +1,643 @@
<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>