|
@@ -0,0 +1,356 @@
|
|
|
+<!--
|
|
|
+* @FileDescription: 教程播放
|
|
|
+* @Author: 黄琪勇
|
|
|
+* @Date:2024-04-03 17:31:41
|
|
|
+-->
|
|
|
+<template>
|
|
|
+ <div class="coursewarePlay">
|
|
|
+ <videoPlay ref="videoPlayDom" @ready="handleVideoReady" @keydown="handleVideoKeydown">
|
|
|
+ <div class="leftTools posTools">
|
|
|
+ <div v-if="activeCoursewareIndex > 0" class="posBtn" @click="handleChangeCourseware(-1)">
|
|
|
+ <img src="@/img/coursewarePlay/shang.png" />
|
|
|
+ <div>上一个</div>
|
|
|
+ </div>
|
|
|
+ <div v-if="activeCoursewareIndex < flattenCoursewareList.length - 1" class="posBtn" @click="handleChangeCourseware(1)">
|
|
|
+ <img src="@/img/coursewarePlay/xia.png" />
|
|
|
+ <div>下一个</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="rightTools posTools">
|
|
|
+ <div class="posBtn" @click="whitePenShow = true">
|
|
|
+ <img src="@/img/coursewarePlay/baiban.png" />
|
|
|
+ <div>白板</div>
|
|
|
+ </div>
|
|
|
+ <div class="posBtn" @click="penShow = true">
|
|
|
+ <img src="@/img/coursewarePlay/pizhu.png" />
|
|
|
+ <div>批注</div>
|
|
|
+ </div>
|
|
|
+ <div class="posBtn" @click="drawer = true">
|
|
|
+ <img src="@/img/coursewarePlay/zhishidian.png" />
|
|
|
+ <div>知识点</div>
|
|
|
+ </div>
|
|
|
+ <div class="posBtn" @click="handleGoBack">
|
|
|
+ <img src="@/img/coursewarePlay/jieshu.png" />
|
|
|
+ <div>结束</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="topTools">
|
|
|
+ <div class="leftMenu">
|
|
|
+ <img @click="handleGoBack" class="backImg" src="@/img/coursewarePlay/back.png" />
|
|
|
+ </div>
|
|
|
+ <div class="midMenu">{{ activeCourseware?.parentData.name || "" }}</div>
|
|
|
+ <div class="rightMenu"></div>
|
|
|
+ </div>
|
|
|
+ <div class="activeName">
|
|
|
+ <div>{{ activeCourseware?.name || "" }}</div>
|
|
|
+ </div>
|
|
|
+ </videoPlay>
|
|
|
+ <el-drawer class="elDrawer" v-model="drawer" :show-close="false">
|
|
|
+ <template #header="{ close }">
|
|
|
+ <img class="directory" src="@/img/coursewarePlay/kcml.png" />
|
|
|
+ <div class="tit">课程目录</div>
|
|
|
+ <img class="close" @click="close" src="@/img/coursewarePlay/close.png" />
|
|
|
+ </template>
|
|
|
+ <ElScrollbar class="elScrollbar">
|
|
|
+ <courseCollapse :activeCollapse="activeCourseware" :courseList="coursewareList" @handleClick="handleCourseClick" />
|
|
|
+ </ElScrollbar>
|
|
|
+ </el-drawer>
|
|
|
+ <pen
|
|
|
+ :close="
|
|
|
+ () => {
|
|
|
+ penShow = false
|
|
|
+ }
|
|
|
+ "
|
|
|
+ v-model="penShow"
|
|
|
+ />
|
|
|
+ <pen
|
|
|
+ :is-white="true"
|
|
|
+ :close="
|
|
|
+ () => {
|
|
|
+ whitePenShow = false
|
|
|
+ }
|
|
|
+ "
|
|
|
+ v-model="whitePenShow"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup lang="ts">
|
|
|
+import videoPlay from "./videoPlay"
|
|
|
+import { getLessonCourseDetail_gym, getLessonCoursewareDetail_gyt } from "@/api/cloudTextbooks.api"
|
|
|
+import { httpAjaxErrMsg } from "@/plugin/httpAjax"
|
|
|
+import userStore from "@/store/modules/user"
|
|
|
+import { useRoute } from "vue-router"
|
|
|
+import { shallowRef, ref, computed, watchEffect } from "vue"
|
|
|
+import { ElMessageBox } from "element-plus"
|
|
|
+import courseCollapse from "./components/courseCollapse"
|
|
|
+import pen from "./components/pen"
|
|
|
+
|
|
|
+// 批注
|
|
|
+const penShow = ref(false)
|
|
|
+// 白板
|
|
|
+const whitePenShow = ref(false)
|
|
|
+
|
|
|
+const route = useRoute()
|
|
|
+const userStoreHook = userStore()
|
|
|
+const videoPlayDom = ref<InstanceType<typeof videoPlay>>()
|
|
|
+const coursewareList = shallowRef<any[]>([]) // 知识点
|
|
|
+const flattenCoursewareList = shallowRef<any[]>([]) // 扁平化coursewareList
|
|
|
+// 选中的知识点
|
|
|
+const activeCourseware = computed<undefined | Record<string, any>>(() => {
|
|
|
+ return flattenCoursewareList.value[activeCoursewareIndex.value]
|
|
|
+})
|
|
|
+const activeCoursewareIndex = ref(0)
|
|
|
+const drawer = ref(false)
|
|
|
+
|
|
|
+watchEffect(() => {
|
|
|
+ activeCourseware.value && videoPlayDom.value?.playVideo({ src: activeCourseware.value.content })
|
|
|
+})
|
|
|
+
|
|
|
+function getCoursewareList() {
|
|
|
+ httpAjaxErrMsg(userStoreHook.roles === "GYM" ? getLessonCourseDetail_gym : getLessonCoursewareDetail_gyt, route.params.id as string).then(res => {
|
|
|
+ if (res.code === 200) {
|
|
|
+ const { lockFlag, knowledgePointList } = res.data || {}
|
|
|
+ if (lockFlag) {
|
|
|
+ ElMessageBox.alert("课件已锁定", "温馨提示", {
|
|
|
+ confirmButtonText: "退出",
|
|
|
+ type: "error"
|
|
|
+ })
|
|
|
+ .then(() => {
|
|
|
+ handleGoBack()
|
|
|
+ })
|
|
|
+ .catch(() => {
|
|
|
+ handleGoBack()
|
|
|
+ })
|
|
|
+ return
|
|
|
+ }
|
|
|
+ if ((knowledgePointList || []).length < 1) {
|
|
|
+ ElMessageBox.alert("没有找到课件", "温馨提示", {
|
|
|
+ confirmButtonText: "退出",
|
|
|
+ type: "error"
|
|
|
+ })
|
|
|
+ .then(() => {
|
|
|
+ handleGoBack()
|
|
|
+ })
|
|
|
+ .catch(() => {
|
|
|
+ handleGoBack()
|
|
|
+ })
|
|
|
+ return
|
|
|
+ }
|
|
|
+ // 处理返回的数据
|
|
|
+ handlePointList(knowledgePointList)
|
|
|
+ }
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+let flattenCoursewareListData: any = [] // 临时扁平化数据
|
|
|
+function handlePointList(pointList: any[]) {
|
|
|
+ coursewareList.value = filterPointList(pointList)
|
|
|
+ // 如果url里面有materialId 代表指定资料播放
|
|
|
+ if (route.query.materialId) {
|
|
|
+ const index = flattenCoursewareListData.findIndex((item: any) => {
|
|
|
+ return route.query.materialId === item.id + ""
|
|
|
+ })
|
|
|
+ index > -1 && (activeCoursewareIndex.value = index)
|
|
|
+ }
|
|
|
+ flattenCoursewareList.value = flattenCoursewareListData
|
|
|
+}
|
|
|
+function filterPointList(pointList: any[], parentData?: { ids: string[]; name: string }): any[] {
|
|
|
+ // 设置父级及以上id数组和父级name
|
|
|
+ return pointList.map(point => {
|
|
|
+ if (point.children) {
|
|
|
+ return Object.assign(point, {
|
|
|
+ children: filterPointList(point.children, { ids: [...(parentData?.ids || []), point.id], name: point.name })
|
|
|
+ })
|
|
|
+ } else {
|
|
|
+ return Object.assign(point, {
|
|
|
+ materialList: point.materialList.map((item: any) => {
|
|
|
+ item.parentData = {
|
|
|
+ ids: [...(parentData?.ids || []), point.id],
|
|
|
+ name: point.name
|
|
|
+ }
|
|
|
+ flattenCoursewareListData.push(item)
|
|
|
+ return item
|
|
|
+ })
|
|
|
+ })
|
|
|
+ }
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+function handleVideoReady() {
|
|
|
+ getCoursewareList()
|
|
|
+}
|
|
|
+
|
|
|
+function handleChangeCourseware(index: -1 | 1) {
|
|
|
+ const newIndex = index + activeCoursewareIndex.value
|
|
|
+ if (newIndex < 0 || newIndex > flattenCoursewareList.value.length - 1) {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ activeCoursewareIndex.value = newIndex
|
|
|
+}
|
|
|
+function handleCourseClick(value: any) {
|
|
|
+ activeCoursewareIndex.value = flattenCoursewareList.value.findIndex((item: any) => {
|
|
|
+ return value.id === item.id
|
|
|
+ })
|
|
|
+}
|
|
|
+function handleVideoKeydown(e: KeyboardEvent) {
|
|
|
+ const key = e.key
|
|
|
+ if (key === "ArrowDown") {
|
|
|
+ handleChangeCourseware(1)
|
|
|
+ } else if (key === "ArrowUp") {
|
|
|
+ handleChangeCourseware(-1)
|
|
|
+ }
|
|
|
+}
|
|
|
+function handleGoBack() {
|
|
|
+ window.open("about:blank", "_self")
|
|
|
+ window.close()
|
|
|
+}
|
|
|
+</script>
|
|
|
+
|
|
|
+<style lang="scss" scoped>
|
|
|
+.coursewarePlay {
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ position: relative;
|
|
|
+ .activeName {
|
|
|
+ transition: all 0.5s;
|
|
|
+ position: absolute;
|
|
|
+ height: 120px;
|
|
|
+ right: 30px;
|
|
|
+ bottom: 15px;
|
|
|
+ font-weight: 500;
|
|
|
+ font-size: 22px;
|
|
|
+ color: #ffffff;
|
|
|
+ display: flex;
|
|
|
+ align-items: flex-end;
|
|
|
+ padding-bottom: 13px;
|
|
|
+ }
|
|
|
+ .topTools {
|
|
|
+ position: absolute;
|
|
|
+ top: 0;
|
|
|
+ left: 0;
|
|
|
+ width: 100%;
|
|
|
+ background: linear-gradient(180deg, rgba(0, 0, 0, 0.6), transparent);
|
|
|
+ transition: all 0.5s;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: space-between;
|
|
|
+ padding: 20px 30px;
|
|
|
+ .leftMenu {
|
|
|
+ .backImg {
|
|
|
+ cursor: pointer;
|
|
|
+ width: 22px;
|
|
|
+ &:hover {
|
|
|
+ opacity: $opacity-hover;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ .midMenu {
|
|
|
+ font-weight: 500;
|
|
|
+ font-size: 22px;
|
|
|
+ color: #ffffff;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ .posTools {
|
|
|
+ position: absolute;
|
|
|
+ top: 50%;
|
|
|
+ transform: translateY(-50%);
|
|
|
+ transition: all 0.5s;
|
|
|
+ &.leftTools {
|
|
|
+ left: 12px;
|
|
|
+ }
|
|
|
+ &.rightTools {
|
|
|
+ right: 12px;
|
|
|
+ }
|
|
|
+ .posBtn {
|
|
|
+ background: rgba(0, 0, 0, 0.3);
|
|
|
+ border-radius: 8px;
|
|
|
+ padding: 12px 6px;
|
|
|
+ font-weight: 500;
|
|
|
+ font-size: 16px;
|
|
|
+ color: #ffffff;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: center;
|
|
|
+ cursor: pointer;
|
|
|
+ margin-bottom: 12px;
|
|
|
+ &:hover {
|
|
|
+ opacity: $opacity-hover;
|
|
|
+ }
|
|
|
+ &:last-child {
|
|
|
+ margin-bottom: 0;
|
|
|
+ }
|
|
|
+ > img {
|
|
|
+ margin-bottom: 5px;
|
|
|
+ width: 34px;
|
|
|
+ height: 34px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ &:deep(.videoPlay.isHideController) {
|
|
|
+ .leftTools {
|
|
|
+ opacity: 0;
|
|
|
+ transform: translate(-100%, -50%);
|
|
|
+ }
|
|
|
+ .rightTools {
|
|
|
+ opacity: 0;
|
|
|
+ transform: translate(100%, -50%);
|
|
|
+ }
|
|
|
+ .topTools {
|
|
|
+ opacity: 0;
|
|
|
+ transform: translateY(-100%);
|
|
|
+ }
|
|
|
+ .activeName {
|
|
|
+ opacity: 0;
|
|
|
+ transform: translateY(100%);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ &:deep(.elDrawer.el-drawer) {
|
|
|
+ width: 346px !important;
|
|
|
+ .el-drawer__header {
|
|
|
+ height: 54px;
|
|
|
+ background: #ededed;
|
|
|
+ padding: 0 20px;
|
|
|
+ margin-bottom: 0;
|
|
|
+ .directory {
|
|
|
+ flex-grow: 0;
|
|
|
+ flex-shrink: 0;
|
|
|
+ width: 24px;
|
|
|
+ height: 24px;
|
|
|
+ }
|
|
|
+ .tit {
|
|
|
+ flex-grow: 1;
|
|
|
+ margin-left: 10px;
|
|
|
+ font-weight: 600;
|
|
|
+ font-size: 18px;
|
|
|
+ color: #333333;
|
|
|
+ }
|
|
|
+ .close {
|
|
|
+ cursor: pointer;
|
|
|
+ width: 14px;
|
|
|
+ flex-shrink: 0;
|
|
|
+ &:hover {
|
|
|
+ opacity: $opacity-hover;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ .el-drawer__body {
|
|
|
+ padding: 0;
|
|
|
+ overflow: hidden;
|
|
|
+ & > .elScrollbar {
|
|
|
+ .el-scrollbar__view {
|
|
|
+ padding: 0 22px;
|
|
|
+ width: 100%;
|
|
|
+ }
|
|
|
+ .el-scrollbar__wrap {
|
|
|
+ overflow-x: hidden;
|
|
|
+ }
|
|
|
+ .el-scrollbar__bar.is-vertical {
|
|
|
+ width: 4px;
|
|
|
+ right: 0;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+</style>
|