黄琪勇 1 سال پیش
والد
کامیت
d43018c8ef
95فایلهای تغییر یافته به همراه996 افزوده شده و 0 حذف شده
  1. 1 0
      package.json
  2. BIN
      src/img/coursewarePlay/back.png
  3. BIN
      src/img/coursewarePlay/baiban.png
  4. BIN
      src/img/coursewarePlay/close.png
  5. 0 0
      src/img/coursewarePlay/icon-load.gif
  6. BIN
      src/img/coursewarePlay/jieshu.png
  7. BIN
      src/img/coursewarePlay/kcml.png
  8. BIN
      src/img/coursewarePlay/pizhu.png
  9. BIN
      src/img/coursewarePlay/shang.png
  10. BIN
      src/img/coursewarePlay/xia.png
  11. BIN
      src/img/coursewarePlay/zhishidian.png
  12. 2 0
      src/shims-vue.d.ts
  13. 235 0
      src/views/coursewarePlay/components/courseCollapse/courseCollapse.vue
  14. 2 0
      src/views/coursewarePlay/components/courseCollapse/index.ts
  15. 2 0
      src/views/coursewarePlay/components/pen/index.ts
  16. 62 0
      src/views/coursewarePlay/components/pen/pen.vue
  17. 356 0
      src/views/coursewarePlay/coursewarePlay.vue
  18. 2 0
      src/views/coursewarePlay/index.ts
  19. BIN
      src/views/coursewarePlay/videoPlay/img/bg.png
  20. BIN
      src/views/coursewarePlay/videoPlay/img/iconLoop.png
  21. BIN
      src/views/coursewarePlay/videoPlay/img/iconLoopActive.png
  22. BIN
      src/views/coursewarePlay/videoPlay/img/iconPause.png
  23. BIN
      src/views/coursewarePlay/videoPlay/img/iconPlay.png
  24. BIN
      src/views/coursewarePlay/videoPlay/img/iconSpeed.png
  25. BIN
      src/views/coursewarePlay/videoPlay/img/jia.png
  26. BIN
      src/views/coursewarePlay/videoPlay/img/jian.png
  27. 2 0
      src/views/coursewarePlay/videoPlay/index.ts
  28. 19 0
      src/views/coursewarePlay/videoPlay/tools.ts
  29. 313 0
      src/views/coursewarePlay/videoPlay/videoPlay.vue
  30. 0 0
      src/views/coursewarePlayOld/component/musicScore.module.scss
  31. 0 0
      src/views/coursewarePlayOld/component/musicScore.tsx
  32. 0 0
      src/views/coursewarePlayOld/component/point.module.scss
  33. 0 0
      src/views/coursewarePlayOld/component/points.tsx
  34. 0 0
      src/views/coursewarePlayOld/component/tool.module.scss
  35. 0 0
      src/views/coursewarePlayOld/component/tool.tsx
  36. 0 0
      src/views/coursewarePlayOld/component/tools/pen.module.scss
  37. 0 0
      src/views/coursewarePlayOld/component/tools/pen.tsx
  38. 0 0
      src/views/coursewarePlayOld/component/video-item/index.module.scss
  39. 0 0
      src/views/coursewarePlayOld/component/video-item/index.tsx
  40. 0 0
      src/views/coursewarePlayOld/component/video-item/video-play.tsx
  41. 0 0
      src/views/coursewarePlayOld/component/video-play.tsx
  42. 0 0
      src/views/coursewarePlayOld/component/video.module.scss
  43. 0 0
      src/views/coursewarePlayOld/datas/data.json
  44. 0 0
      src/views/coursewarePlayOld/helpers/helpState.ts
  45. 0 0
      src/views/coursewarePlayOld/helpers/native-message.ts
  46. 0 0
      src/views/coursewarePlayOld/helpers/utils.ts
  47. 0 0
      src/views/coursewarePlayOld/image/back.svg
  48. 0 0
      src/views/coursewarePlayOld/image/bb.png
  49. 0 0
      src/views/coursewarePlayOld/image/icon-arrow.svg
  50. 0 0
      src/views/coursewarePlayOld/image/icon-dian.svg
  51. 0 0
      src/views/coursewarePlayOld/image/icon-down.svg
  52. 0 0
      src/views/coursewarePlayOld/image/icon-image-active.svg
  53. 0 0
      src/views/coursewarePlayOld/image/icon-image.svg
  54. BIN
      src/views/coursewarePlayOld/image/icon-load.gif
  55. 0 0
      src/views/coursewarePlayOld/image/icon-loop-active.svg
  56. 0 0
      src/views/coursewarePlayOld/image/icon-loop.svg
  57. 0 0
      src/views/coursewarePlayOld/image/icon-menu.svg
  58. 0 0
      src/views/coursewarePlayOld/image/icon-more.png
  59. 0 0
      src/views/coursewarePlayOld/image/icon-mulv.svg
  60. 0 0
      src/views/coursewarePlayOld/image/icon-pause.svg
  61. 0 0
      src/views/coursewarePlayOld/image/icon-pen.png
  62. 0 0
      src/views/coursewarePlayOld/image/icon-play.svg
  63. 0 0
      src/views/coursewarePlayOld/image/icon-point.svg
  64. 0 0
      src/views/coursewarePlayOld/image/icon-song-active.svg
  65. 0 0
      src/views/coursewarePlayOld/image/icon-song.svg
  66. 0 0
      src/views/coursewarePlayOld/image/icon-speed-add.png
  67. 0 0
      src/views/coursewarePlayOld/image/icon-speed-bg.png
  68. 0 0
      src/views/coursewarePlayOld/image/icon-speed-cut.png
  69. 0 0
      src/views/coursewarePlayOld/image/icon-start.svg
  70. 0 0
      src/views/coursewarePlayOld/image/icon-touping.svg
  71. 0 0
      src/views/coursewarePlayOld/image/icon-up.svg
  72. 0 0
      src/views/coursewarePlayOld/image/icon-video-active.svg
  73. 0 0
      src/views/coursewarePlayOld/image/icon-video.svg
  74. 0 0
      src/views/coursewarePlayOld/image/icon-videobg.png
  75. 0 0
      src/views/coursewarePlayOld/image/icon-zhibo.svg
  76. 0 0
      src/views/coursewarePlayOld/image/iconImageActive.png
  77. 0 0
      src/views/coursewarePlayOld/image/iconLoop.png
  78. 0 0
      src/views/coursewarePlayOld/image/iconLoopActive.png
  79. 0 0
      src/views/coursewarePlayOld/image/iconPause.png
  80. 0 0
      src/views/coursewarePlayOld/image/iconPlay.png
  81. 0 0
      src/views/coursewarePlayOld/image/iconSongActive.png
  82. 0 0
      src/views/coursewarePlayOld/image/iconSpeed.png
  83. 0 0
      src/views/coursewarePlayOld/image/iconVideoActive.png
  84. 0 0
      src/views/coursewarePlayOld/image/icons.json
  85. 0 0
      src/views/coursewarePlayOld/image/jia.png
  86. 0 0
      src/views/coursewarePlayOld/image/jian.png
  87. 0 0
      src/views/coursewarePlayOld/image/js.png
  88. 0 0
      src/views/coursewarePlayOld/image/kcml.png
  89. 0 0
      src/views/coursewarePlayOld/image/pz.png
  90. 0 0
      src/views/coursewarePlayOld/image/video-speed.png
  91. 0 0
      src/views/coursewarePlayOld/image/zzd.png
  92. 0 0
      src/views/coursewarePlayOld/index.module.scss
  93. 0 0
      src/views/coursewarePlayOld/index.tsx
  94. 0 0
      src/views/coursewarePlayOld/playRecordTime.tsx
  95. 0 0
      src/views/coursewarePlayOld/shims-vue.d.ts

+ 1 - 0
package.json

@@ -22,6 +22,7 @@
       "plyr": "^3.7.8",
       "qrcode.vue": "^3.4.1",
       "screenfull": "^6.0.2",
+      "tcplayer.js": "^4.8.0",
       "vant": "^4.8.7",
       "vue": "^3.2.13",
       "vue-router": "^4.0.3",

BIN
src/img/coursewarePlay/back.png


BIN
src/img/coursewarePlay/baiban.png


BIN
src/img/coursewarePlay/close.png


+ 0 - 0
src/views/coursewarePlay/image/icon-load.gif → src/img/coursewarePlay/icon-load.gif


BIN
src/img/coursewarePlay/jieshu.png


BIN
src/img/coursewarePlay/kcml.png


BIN
src/img/coursewarePlay/pizhu.png


BIN
src/img/coursewarePlay/shang.png


BIN
src/img/coursewarePlay/xia.png


BIN
src/img/coursewarePlay/zhishidian.png


+ 2 - 0
src/shims-vue.d.ts

@@ -18,3 +18,5 @@ declare module "@/assets/export.module.scss" {
    }
    export default exportCss
 }
+
+declare module "tcplayer.js"

+ 235 - 0
src/views/coursewarePlay/components/courseCollapse/courseCollapse.vue

@@ -0,0 +1,235 @@
+<!--
+* @FileDescription: 折叠菜单
+* @Author: 黄琪勇
+* @Date:2024-04-01 18:40:50
+-->
+<template>
+   <el-collapse class="courseCollapse" accordion v-model="activeCollapseId">
+      <el-collapse-item v-for="item in props.courseList" :key="item.id" :name="item.id">
+         <template #title>
+            <div class="courseCollapseHead">
+               <div class="courseCollapseHeadTit">
+                  <template v-if="props.titleType === 'round'">
+                     <div class="roundCon">
+                        <img src="@/img/curriculum/yuan.png" />
+                        <div class="ellipsisBox">
+                           <ellipsisScroll :title="item.name" />
+                        </div>
+                     </div>
+                  </template>
+                  <ellipsisScroll v-else :title="item.name" />
+               </div>
+               <div class="courseCollapseHeadArrow">
+                  <div class="headArrow">
+                     <div>展开</div>
+                     <img src="@/img/curriculum/xiangxia.png" />
+                  </div>
+                  <div class="headArrowActive">
+                     <div>收起</div>
+                     <img src="@/img/curriculum/xiangshang.png" />
+                  </div>
+               </div>
+            </div>
+         </template>
+         <div class="courseCollapseCon">
+            <template v-if="item.materialList">
+               <div class="courseList" v-for="i in item.materialList" :key="i.id" @click="handleClick(i)">
+                  <div class="courseTitleCon">
+                     <img :src="require(`@/img/curriculum/${i.typeCode || i.type}.png`)" />
+                     <div class="ellipsisBox">
+                        <ellipsisScroll :title="i.name" />
+                     </div>
+                  </div>
+                  <div class="iconArrow">
+                     <img v-if="activeCollapse?.id === i.id" src="@/img/coursewarePlay/icon-load.gif" />
+                  </div>
+               </div>
+            </template>
+            <courseCollapse
+               v-else
+               :courseList="item.children || []"
+               :activeCollapse="activeCollapse"
+               :titleType="'round'"
+               @handleClick="handleClick"
+            />
+         </div>
+      </el-collapse-item>
+   </el-collapse>
+</template>
+
+<script setup lang="ts">
+import ellipsisScroll from "@/components/ellipsisScroll"
+import { ref, watch } from "vue"
+
+type materialListType = {
+   id: string
+   type: string
+   typeCode?: string
+   name: string
+}
+type courseListType = {
+   id: string
+   name: string
+   materialList: materialListType[] | null
+   children: courseListType | null
+}[]
+
+const props = withDefaults(
+   defineProps<{
+      activeCollapse: undefined | Record<string, any>
+      courseList: courseListType
+      titleType?: "default" | "round"
+   }>(),
+   {
+      titleType: "default"
+   }
+)
+const emits = defineEmits<{
+   (e: "handleClick", value: any): void
+}>()
+watch(
+   () => props.activeCollapse,
+   () => {
+      activeCollapseId.value = filterActiveId()
+   }
+)
+const activeCollapseId = ref(filterActiveId())
+
+function filterActiveId() {
+   const course = props.courseList.find(item => {
+      return (props.activeCollapse?.parentData.ids || []).includes(item.id)
+   })
+   return course?.id || ""
+}
+function handleClick(value: any) {
+   emits("handleClick", value)
+}
+</script>
+
+<style lang="scss" scoped>
+.courseCollapse.el-collapse {
+   --el-collapse-border-color: #f2f2f2;
+   --el-collapse-header-height: 62px;
+   border: none;
+   & > :deep(.el-collapse-item) {
+      > .el-collapse-item__wrap > .el-collapse-item__content {
+         padding-bottom: 2px;
+      }
+      &:last-child {
+         > .el-collapse-item__wrap {
+            border-bottom: none;
+         }
+         > .el-collapse-item__header {
+            border-bottom: none;
+         }
+      }
+      .el-collapse-item__arrow {
+         display: none;
+      }
+      &.is-active > .el-collapse-item__header {
+         > .courseCollapseHead .courseCollapseHeadArrow {
+            > .headArrow {
+               display: none;
+            }
+            > .headArrowActive {
+               display: flex;
+            }
+         }
+      }
+   }
+   .courseCollapseHead {
+      width: 100%;
+      height: 100%;
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      .courseCollapseHeadTit {
+         text-align: left;
+         margin-right: 8px;
+         flex-grow: 1;
+         font-weight: 500;
+         font-size: 18px;
+         color: #333333;
+         overflow: hidden;
+         .roundCon {
+            display: flex;
+            align-items: center;
+            color: #f67146;
+            > .ellipsisBox {
+               flex-grow: 1;
+               overflow: hidden;
+            }
+            > img {
+               flex-shrink: 0;
+               width: 8px;
+               height: 8px;
+               margin-right: 10px;
+            }
+         }
+      }
+      .courseCollapseHeadArrow {
+         flex-shrink: 0;
+         .headArrow,
+         .headArrowActive {
+            display: flex;
+            align-items: center;
+            font-weight: 400;
+            font-size: 17px;
+            color: #999999;
+            > img {
+               margin-left: 5px;
+               width: 12px;
+               height: 8px;
+            }
+         }
+         .headArrowActive {
+            display: none;
+            color: #ff8057;
+         }
+      }
+   }
+   .courseCollapseCon {
+      padding-left: 20px;
+      .courseList {
+         display: flex;
+         justify-content: space-between;
+         align-items: center;
+         height: 68px;
+         border-bottom: 1px solid #f2f2f2;
+         cursor: pointer;
+         &:last-child {
+            border-bottom: initial;
+         }
+         .courseTitleCon {
+            flex-grow: 1;
+            overflow: hidden;
+            margin-right: 8px;
+            display: flex;
+            align-items: center;
+            font-weight: 400;
+            font-size: 18px;
+            color: #333333;
+            > .ellipsisBox {
+               flex-grow: 1;
+               overflow: hidden;
+            }
+            > img {
+               flex-shrink: 0;
+               width: 33px;
+               height: 33px;
+               margin-right: 10px;
+            }
+         }
+         .iconArrow {
+            flex-shrink: 0;
+            width: 35px;
+            height: 35px;
+            > img {
+               width: 100%;
+               height: 100%;
+            }
+         }
+      }
+   }
+}
+</style>

+ 2 - 0
src/views/coursewarePlay/components/courseCollapse/index.ts

@@ -0,0 +1,2 @@
+import courseCollapse from "./courseCollapse.vue"
+export default courseCollapse

+ 2 - 0
src/views/coursewarePlay/components/pen/index.ts

@@ -0,0 +1,2 @@
+import pen from "./pen.vue"
+export default pen

+ 62 - 0
src/views/coursewarePlay/components/pen/pen.vue

@@ -0,0 +1,62 @@
+<!--
+* @FileDescription: 批注
+* @Author: 黄琪勇
+* @Date:2024-04-06 18:40:23
+-->
+<template>
+   <el-dialog modal-class="penModalClass" class="penElDialog" :class="{ isWhite: isWhite }" v-bind="$attrs" :fullscreen="true" :show-close="false">
+      <iframe class="penIframe" frameborder="0" :src="URL_WHITEBOARD"></iframe>
+      <div class="closeSvg" @click="close">
+         <svg width="22px" height="20px" viewBox="0 0 22 20">
+            <path
+               transform="translate(-1.000000, -2.000000)"
+               fill="#FFFFFF"
+               d="M13,2 C13.5522847,2 14,2.44771525 14,3 C14,3.51283584 13.6139598,3.93550716 13.1166211,3.99327227 L13,4 L3,4 L3,20 L13,20 C13.5128358,20 13.9355072,20.3860402 13.9932723,20.8833789 L14,21 C14,21.5128358 13.6139598,21.9355072 13.1166211,21.9932723 L13,22 L2,22 C1.48716416,22 1.06449284,21.6139598 1.00672773,21.1166211 L1,21 L1,3 C1,2.48716416 1.38604019,2.06449284 1.88337887,2.00672773 L2,2 L13,2 Z M17.7071068,7.05025253 L21.9497475,11.2928932 L21.9497475,11.2928932 C22.3402718,11.6834175 22.3402718,12.3165825 21.9497475,12.7071068 L17.7071068,16.9497475 C17.3165825,17.3402718 16.6834175,17.3402718 16.2928932,16.9497475 C15.9023689,16.5592232 15.9023689,15.9260582 16.2928932,15.5355339 L18.828,12.999 L9.29368112,13 C8.74139637,13 8.29368112,12.5522847 8.29368112,12 C8.29368112,11.4871642 8.67972131,11.0644928 9.17706,11.0067277 L9.29368112,11 L18.827,10.999 L16.2928932,8.46446609 C15.9023689,8.0739418 15.9023689,7.44077682 16.2928932,7.05025253 C16.6834175,6.65972824 17.3165825,6.65972824 17.7071068,7.05025253 Z"
+            />
+         </svg>
+      </div>
+   </el-dialog>
+</template>
+
+<script setup lang="ts">
+import { URL_WHITEBOARD } from "@/config/index"
+defineProps<{
+   isWhite?: boolean
+   close: () => void
+}>()
+</script>
+<style lang="scss">
+.penElDialog.el-dialog {
+   padding: 0;
+   background-color: initial;
+   &.isWhite {
+      background-color: #fff;
+   }
+   .el-dialog__header {
+      padding: 0;
+   }
+   .el-dialog__body {
+      width: 100%;
+      height: 100%;
+      .penIframe {
+         display: block;
+         width: 100%;
+         height: 100%;
+      }
+   }
+}
+.penModalClass.el-overlay {
+   background-color: initial;
+   .closeSvg {
+      position: absolute;
+      right: 15px;
+      bottom: 0;
+      width: 50px;
+      height: 54px;
+      display: flex;
+      justify-content: center;
+      align-items: center;
+      z-index: 10;
+   }
+}
+</style>

+ 356 - 0
src/views/coursewarePlay/coursewarePlay.vue

@@ -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>

+ 2 - 0
src/views/coursewarePlay/index.ts

@@ -0,0 +1,2 @@
+import coursewarePlay from "./coursewarePlay.vue"
+export default coursewarePlay

BIN
src/views/coursewarePlay/videoPlay/img/bg.png


BIN
src/views/coursewarePlay/videoPlay/img/iconLoop.png


BIN
src/views/coursewarePlay/videoPlay/img/iconLoopActive.png


BIN
src/views/coursewarePlay/videoPlay/img/iconPause.png


BIN
src/views/coursewarePlay/videoPlay/img/iconPlay.png


BIN
src/views/coursewarePlay/videoPlay/img/iconSpeed.png


BIN
src/views/coursewarePlay/videoPlay/img/jia.png


BIN
src/views/coursewarePlay/videoPlay/img/jian.png


+ 2 - 0
src/views/coursewarePlay/videoPlay/index.ts

@@ -0,0 +1,2 @@
+import videoPlay from "./videoPlay.vue"
+export default videoPlay

+ 19 - 0
src/views/coursewarePlay/videoPlay/tools.ts

@@ -0,0 +1,19 @@
+function padStart(v: string | number, len = 2, str = "0"): string {
+   v = String(v)
+   if (v.length >= 2) return v
+   for (let i = 0, l = len - v.length; i < l; i++) {
+      v = str + v
+   }
+   return v
+}
+export function formatTime(seconds: number): string {
+   if (!isFinite(seconds)) return "-"
+   if (seconds <= 0) return "00:00"
+
+   seconds = Math.ceil(seconds)
+   if (seconds < 60) return `00:${padStart(seconds)}`
+   if (seconds < 3600) {
+      return `${padStart(Math.floor(seconds / 60))}:${padStart(seconds % 60)}`
+   }
+   return `${padStart(Math.floor(seconds / 3600))}:${padStart(Math.floor((seconds % 3600) / 60))}:${padStart(seconds % 60)}`
+}

+ 313 - 0
src/views/coursewarePlay/videoPlay/videoPlay.vue

@@ -0,0 +1,313 @@
+<!--
+* @FileDescription: 播放器
+* @Author: 黄琪勇
+* @Date:2024-04-03 18:30:09
+-->
+<template>
+   <div
+      @keydown="handleVideoKeydown"
+      @mousemove="handleVideoMousemove"
+      class="videoPlay"
+      :class="{ isHideController: !isShowController }"
+      tabindex="-1"
+   >
+      <video class="videoPlayBox" @click="handlePlay" :id="videoId" preload="auto" playsinline webkit-playsinline></video>
+      <div class="videoController" @click.stop>
+         <div class="timeController">{{ `${formatTime(timeController.currentTime)} / ${formatTime(timeController.duration)}` }}</div>
+         <el-slider
+            class="sliderController"
+            @change="handleTimeChange"
+            @mousedown="handleSilderMousedown"
+            v-model="timeController.currentTimeSilder"
+            :max="timeController.duration"
+            :format-tooltip="
+               (value:number) => {
+                  return formatTime(value)
+               }
+            "
+         />
+         <div class="playController">
+            <div class="leftPlayController">
+               <img @click="handlePlay" :src="require(`./img/${playController.type === 'play' ? 'iconPause' : 'iconPlay'}.png`)" />
+               <img @click="handleLoop" :src="require(`./img/${playController.loop ? 'iconLoopActive' : 'iconLoop'}.png`)" />
+               <el-popover placement="top" trigger="click" :teleported="false" popper-class="palySpeedPopover">
+                  <template #reference>
+                     <img src="./img//iconSpeed.png" />
+                  </template>
+                  <div class="sliderSpeedCon">
+                     <img @click="handlePalySpeed(playController.speedStep)" src="./img/jia.png" />
+                     <el-slider
+                        class="sliderSpeed"
+                        @change="handlePalySpeedChange"
+                        v-model="playController.palySpeed"
+                        vertical
+                        :step="playController.speedStep"
+                        :max="playController.maxSpeed"
+                        :min="playController.minSpeed"
+                     />
+                     <img @click="handlePalySpeed(-playController.speedStep)" src="./img/jian.png" />
+                  </div>
+               </el-popover>
+            </div>
+            <div class="rightPlayController"></div>
+         </div>
+      </div>
+      <slot></slot>
+   </div>
+</template>
+
+<script setup lang="ts">
+import TCPlayer from "tcplayer.js"
+import "tcplayer.js/dist/tcplayer.min.css"
+import { onMounted, onUnmounted, ref, reactive } from "vue"
+import { UUID } from "@/libs/tools"
+import { formatTime } from "./tools"
+
+const emits = defineEmits<{
+   (e: "ready"): void //播放器初始化完成
+}>()
+
+const videoId = "video" + UUID()
+let playerVm: Record<string, any>
+
+/* 时间控制器 */
+const timeController = reactive({
+   currentTime: 0, // 当前时间
+   duration: 0, // 总时长
+   currentTimeSilder: 0,
+   isDrag: false
+})
+/* 播放控制器 */
+const playController = reactive<{
+   type: "play" | "pause"
+   loop: boolean
+   minSpeed: number
+   maxSpeed: number
+   speedStep: number
+   palySpeed: number
+}>({
+   type: "pause", //play|pause
+   loop: false,
+   minSpeed: 0.5,
+   maxSpeed: 1.5,
+   speedStep: 0.1,
+   palySpeed: 1
+})
+/* 是否显示控制器 */
+const isShowController = ref(true)
+let _showTimer: any
+onMounted(() => {
+   initVideo()
+})
+onUnmounted(() => {
+   playerVm?.dispose()
+})
+/**
+ * 初始化播放器
+ */
+function initVideo() {
+   playerVm = TCPlayer(videoId, {
+      controls: false,
+      autoplay: true,
+      loop: false
+   })
+   // 初始化完成
+   playerVm.on("ready", () => {
+      console.log("播放器初始化完成")
+      emits("ready")
+   })
+   // 开始加载数据时
+   playerVm.on("loadstart", () => {
+      // 重新设置播放倍速  因为切换视频播放倍速会重置
+      playerVm.playbackRate(playController.palySpeed)
+   })
+   //总时长变化时候
+   playerVm.on("durationchange", () => {
+      //duration和currentTime 时间向上取整Math.ceil
+      timeController.duration = Math.ceil(playerVm.duration())
+   })
+   //当前播放时间变化
+   playerVm.on("timeupdate", () => {
+      //duration和currentTime 时间向上取整Math.ceil
+      timeController.currentTime = Math.ceil(playerVm.currentTime())
+      if (!timeController.isDrag) {
+         timeController.currentTimeSilder = timeController.currentTime
+      }
+   })
+   playerVm.on("play", () => {
+      playController.type = "play"
+   })
+   playerVm.on("pause", () => {
+      playController.type = "pause"
+   })
+}
+/**
+ * 播放  需要在ready之后调用
+ */
+function playVideo({ src }: { src: string }) {
+   playerVm?.src(src)
+   showController()
+}
+
+/* 时间控制器 */
+function handleTimeChange(value: number | number[]) {
+   playerVm.currentTime(value || 0)
+   timeController.isDrag = false
+}
+function handleSilderMousedown() {
+   timeController.isDrag = true
+}
+/* 播放控制器 */
+function handlePlay() {
+   playController.type === "pause" ? playerVm.play() : playerVm.pause()
+   showController()
+}
+function handleLoop() {
+   playController.loop ? playerVm.loop(false) : playerVm.loop(true)
+   playController.loop = playerVm.loop()
+}
+function handlePalySpeedChange(value: number | number[]) {
+   playerVm.playbackRate(value)
+}
+function handlePalySpeed(value: number) {
+   const palySpeed = parseFloat((playController.palySpeed + value).toFixed(1))
+   if (palySpeed > playController.maxSpeed || palySpeed < playController.minSpeed) {
+      return
+   }
+   playController.palySpeed = palySpeed
+   handlePalySpeedChange(palySpeed)
+}
+/* 是否显示控制器 */
+function handleVideoKeydown(e: KeyboardEvent) {
+   console.log("handleVideoKeydown")
+   const key = e.key
+   if (key === " ") {
+      handlePlay()
+   }
+}
+function handleVideoMousemove() {
+   console.log("handleVideoMousemove")
+   showController()
+}
+function showController() {
+   isShowController.value = true
+   _showTimer && clearTimeout(_showTimer)
+   _showTimer = setTimeout(tryHideController, 3000)
+}
+function tryHideController() {
+   if (playController.type === "play") {
+      isShowController.value = false
+   }
+}
+defineExpose({
+   playVideo
+})
+</script>
+
+<style lang="scss" scoped>
+.videoPlay {
+   width: 100%;
+   height: 100%;
+   position: relative;
+   overflow: hidden;
+   .videoPlayBox {
+      width: 100%;
+      height: 100%;
+   }
+   &.isHideController {
+      cursor: none;
+      .videoController {
+         opacity: 0;
+         transform: translateY(100%);
+      }
+   }
+   .videoController {
+      position: absolute;
+      width: 100%;
+      left: 0;
+      bottom: 0;
+      padding: 0 30px 15px;
+      background: linear-gradient(0deg, rgba(0, 0, 0, 0.5), transparent);
+      color: #fff;
+      transition: all 0.5s;
+      .timeController {
+         font-weight: 500;
+         font-size: 22px;
+         color: #ffffff;
+         line-height: 30px;
+      }
+      & > :deep(.sliderController.el-slider) {
+         --el-slider-button-wrapper-offset: -12px;
+         --el-slider-button-wrapper-size: 28px;
+         --el-slider-height: 4px;
+         --el-slider-border-radius: 2px;
+         --el-slider-main-bg-color: #ff8057;
+         --el-slider-runway-bg-color: rgba(255, 255, 255, 0.5);
+         height: 28px;
+         .el-slider__button {
+            border: none;
+            &:hover {
+               transform: scale(1.1);
+            }
+         }
+      }
+      .playController {
+         display: flex;
+         justify-content: space-between;
+         .leftPlayController {
+            margin-left: -10px;
+            display: flex;
+            & > img {
+               cursor: pointer;
+               width: 48px;
+               height: 48px;
+               margin-right: 26px;
+            }
+            & > :deep(.palySpeedPopover.el-popover.el-popper) {
+               min-width: initial;
+               width: 65px !important;
+               height: 293px;
+               background: url("./img/bg.png") no-repeat;
+               background-size: 100% 100%;
+               box-shadow: none;
+               border: none;
+               padding: 15px 0 22px;
+               .el-popper__arrow {
+                  display: none;
+               }
+               .sliderSpeedCon {
+                  width: 100%;
+                  height: 100%;
+                  display: flex;
+                  justify-content: center;
+                  align-items: center;
+                  flex-direction: column;
+                  & > img {
+                     cursor: pointer;
+                     flex-shrink: 0;
+                     width: 33px;
+                     height: 35px;
+                  }
+                  .sliderSpeed.el-slider {
+                     flex-grow: 1;
+                     padding: 14px 0;
+                     --el-slider-button-wrapper-offset: -11px;
+                     --el-slider-button-wrapper-size: 26px;
+                     --el-slider-height: 6px;
+                     --el-slider-border-radius: 4px;
+                     --el-slider-main-bg-color: #ff8057;
+                     --el-slider-runway-bg-color: rgba(255, 255, 255, 0.5);
+                     .el-slider__button {
+                        border: none;
+                        &:hover {
+                           transform: scale(1.1);
+                        }
+                     }
+                  }
+               }
+            }
+         }
+      }
+   }
+}
+</style>

+ 0 - 0
src/views/coursewarePlay/component/musicScore.module.scss → src/views/coursewarePlayOld/component/musicScore.module.scss


+ 0 - 0
src/views/coursewarePlay/component/musicScore.tsx → src/views/coursewarePlayOld/component/musicScore.tsx


+ 0 - 0
src/views/coursewarePlay/component/point.module.scss → src/views/coursewarePlayOld/component/point.module.scss


+ 0 - 0
src/views/coursewarePlay/component/points.tsx → src/views/coursewarePlayOld/component/points.tsx


+ 0 - 0
src/views/coursewarePlay/component/tool.module.scss → src/views/coursewarePlayOld/component/tool.module.scss


+ 0 - 0
src/views/coursewarePlay/component/tool.tsx → src/views/coursewarePlayOld/component/tool.tsx


+ 0 - 0
src/views/coursewarePlay/component/tools/pen.module.scss → src/views/coursewarePlayOld/component/tools/pen.module.scss


+ 0 - 0
src/views/coursewarePlay/component/tools/pen.tsx → src/views/coursewarePlayOld/component/tools/pen.tsx


+ 0 - 0
src/views/coursewarePlay/component/video-item/index.module.scss → src/views/coursewarePlayOld/component/video-item/index.module.scss


+ 0 - 0
src/views/coursewarePlay/component/video-item/index.tsx → src/views/coursewarePlayOld/component/video-item/index.tsx


+ 0 - 0
src/views/coursewarePlay/component/video-item/video-play.tsx → src/views/coursewarePlayOld/component/video-item/video-play.tsx


+ 0 - 0
src/views/coursewarePlay/component/video-play.tsx → src/views/coursewarePlayOld/component/video-play.tsx


+ 0 - 0
src/views/coursewarePlay/component/video.module.scss → src/views/coursewarePlayOld/component/video.module.scss


+ 0 - 0
src/views/coursewarePlay/datas/data.json → src/views/coursewarePlayOld/datas/data.json


+ 0 - 0
src/views/coursewarePlay/helpers/helpState.ts → src/views/coursewarePlayOld/helpers/helpState.ts


+ 0 - 0
src/views/coursewarePlay/helpers/native-message.ts → src/views/coursewarePlayOld/helpers/native-message.ts


+ 0 - 0
src/views/coursewarePlay/helpers/utils.ts → src/views/coursewarePlayOld/helpers/utils.ts


+ 0 - 0
src/views/coursewarePlay/image/back.svg → src/views/coursewarePlayOld/image/back.svg


+ 0 - 0
src/views/coursewarePlay/image/bb.png → src/views/coursewarePlayOld/image/bb.png


+ 0 - 0
src/views/coursewarePlay/image/icon-arrow.svg → src/views/coursewarePlayOld/image/icon-arrow.svg


+ 0 - 0
src/views/coursewarePlay/image/icon-dian.svg → src/views/coursewarePlayOld/image/icon-dian.svg


+ 0 - 0
src/views/coursewarePlay/image/icon-down.svg → src/views/coursewarePlayOld/image/icon-down.svg


+ 0 - 0
src/views/coursewarePlay/image/icon-image-active.svg → src/views/coursewarePlayOld/image/icon-image-active.svg


+ 0 - 0
src/views/coursewarePlay/image/icon-image.svg → src/views/coursewarePlayOld/image/icon-image.svg


BIN
src/views/coursewarePlayOld/image/icon-load.gif


+ 0 - 0
src/views/coursewarePlay/image/icon-loop-active.svg → src/views/coursewarePlayOld/image/icon-loop-active.svg


+ 0 - 0
src/views/coursewarePlay/image/icon-loop.svg → src/views/coursewarePlayOld/image/icon-loop.svg


+ 0 - 0
src/views/coursewarePlay/image/icon-menu.svg → src/views/coursewarePlayOld/image/icon-menu.svg


+ 0 - 0
src/views/coursewarePlay/image/icon-more.png → src/views/coursewarePlayOld/image/icon-more.png


+ 0 - 0
src/views/coursewarePlay/image/icon-mulv.svg → src/views/coursewarePlayOld/image/icon-mulv.svg


+ 0 - 0
src/views/coursewarePlay/image/icon-pause.svg → src/views/coursewarePlayOld/image/icon-pause.svg


+ 0 - 0
src/views/coursewarePlay/image/icon-pen.png → src/views/coursewarePlayOld/image/icon-pen.png


+ 0 - 0
src/views/coursewarePlay/image/icon-play.svg → src/views/coursewarePlayOld/image/icon-play.svg


+ 0 - 0
src/views/coursewarePlay/image/icon-point.svg → src/views/coursewarePlayOld/image/icon-point.svg


+ 0 - 0
src/views/coursewarePlay/image/icon-song-active.svg → src/views/coursewarePlayOld/image/icon-song-active.svg


+ 0 - 0
src/views/coursewarePlay/image/icon-song.svg → src/views/coursewarePlayOld/image/icon-song.svg


+ 0 - 0
src/views/coursewarePlay/image/icon-speed-add.png → src/views/coursewarePlayOld/image/icon-speed-add.png


+ 0 - 0
src/views/coursewarePlay/image/icon-speed-bg.png → src/views/coursewarePlayOld/image/icon-speed-bg.png


+ 0 - 0
src/views/coursewarePlay/image/icon-speed-cut.png → src/views/coursewarePlayOld/image/icon-speed-cut.png


+ 0 - 0
src/views/coursewarePlay/image/icon-start.svg → src/views/coursewarePlayOld/image/icon-start.svg


+ 0 - 0
src/views/coursewarePlay/image/icon-touping.svg → src/views/coursewarePlayOld/image/icon-touping.svg


+ 0 - 0
src/views/coursewarePlay/image/icon-up.svg → src/views/coursewarePlayOld/image/icon-up.svg


+ 0 - 0
src/views/coursewarePlay/image/icon-video-active.svg → src/views/coursewarePlayOld/image/icon-video-active.svg


+ 0 - 0
src/views/coursewarePlay/image/icon-video.svg → src/views/coursewarePlayOld/image/icon-video.svg


+ 0 - 0
src/views/coursewarePlay/image/icon-videobg.png → src/views/coursewarePlayOld/image/icon-videobg.png


+ 0 - 0
src/views/coursewarePlay/image/icon-zhibo.svg → src/views/coursewarePlayOld/image/icon-zhibo.svg


+ 0 - 0
src/views/coursewarePlay/image/iconImageActive.png → src/views/coursewarePlayOld/image/iconImageActive.png


+ 0 - 0
src/views/coursewarePlay/image/iconLoop.png → src/views/coursewarePlayOld/image/iconLoop.png


+ 0 - 0
src/views/coursewarePlay/image/iconLoopActive.png → src/views/coursewarePlayOld/image/iconLoopActive.png


+ 0 - 0
src/views/coursewarePlay/image/iconPause.png → src/views/coursewarePlayOld/image/iconPause.png


+ 0 - 0
src/views/coursewarePlay/image/iconPlay.png → src/views/coursewarePlayOld/image/iconPlay.png


+ 0 - 0
src/views/coursewarePlay/image/iconSongActive.png → src/views/coursewarePlayOld/image/iconSongActive.png


+ 0 - 0
src/views/coursewarePlay/image/iconSpeed.png → src/views/coursewarePlayOld/image/iconSpeed.png


+ 0 - 0
src/views/coursewarePlay/image/iconVideoActive.png → src/views/coursewarePlayOld/image/iconVideoActive.png


+ 0 - 0
src/views/coursewarePlay/image/icons.json → src/views/coursewarePlayOld/image/icons.json


+ 0 - 0
src/views/coursewarePlay/image/jia.png → src/views/coursewarePlayOld/image/jia.png


+ 0 - 0
src/views/coursewarePlay/image/jian.png → src/views/coursewarePlayOld/image/jian.png


+ 0 - 0
src/views/coursewarePlay/image/js.png → src/views/coursewarePlayOld/image/js.png


+ 0 - 0
src/views/coursewarePlay/image/kcml.png → src/views/coursewarePlayOld/image/kcml.png


+ 0 - 0
src/views/coursewarePlay/image/pz.png → src/views/coursewarePlayOld/image/pz.png


+ 0 - 0
src/views/coursewarePlay/image/video-speed.png → src/views/coursewarePlayOld/image/video-speed.png


+ 0 - 0
src/views/coursewarePlay/image/zzd.png → src/views/coursewarePlayOld/image/zzd.png


+ 0 - 0
src/views/coursewarePlay/index.module.scss → src/views/coursewarePlayOld/index.module.scss


+ 0 - 0
src/views/coursewarePlay/index.tsx → src/views/coursewarePlayOld/index.tsx


+ 0 - 0
src/views/coursewarePlay/playRecordTime.tsx → src/views/coursewarePlayOld/playRecordTime.tsx


+ 0 - 0
src/views/coursewarePlay/shims-vue.d.ts → src/views/coursewarePlayOld/shims-vue.d.ts