浏览代码

更新云教程

黄琪勇 1 年之前
父节点
当前提交
2d50c1f97b
共有 97 个文件被更改,包括 8605 次插入2 次删除
  1. 21 0
      src/router/routes-tenant.ts
  2. 二进制
      src/tenant/music/courseList/image/bg.png
  3. 二进制
      src/tenant/music/courseList/image/icon-cache-point.png
  4. 二进制
      src/tenant/music/courseList/image/icon-close.png
  5. 二进制
      src/tenant/music/courseList/image/icon-course-lock.png
  6. 二进制
      src/tenant/music/courseList/image/icon-course.png
  7. 二进制
      src/tenant/music/courseList/image/icon-list.png
  8. 二进制
      src/tenant/music/courseList/image/iconTip.png
  9. 13 0
      src/tenant/music/courseList/image/look.svg
  10. 332 0
      src/tenant/music/courseList/index.module.less
  11. 450 0
      src/tenant/music/courseList/index.tsx
  12. 83 0
      src/tenant/music/coursewarePlay/component/musicScore.module.less
  13. 129 0
      src/tenant/music/coursewarePlay/component/musicScore.tsx
  14. 78 0
      src/tenant/music/coursewarePlay/component/o-guide/guide/components/andoird-guide.module.less
  15. 188 0
      src/tenant/music/coursewarePlay/component/o-guide/guide/components/android-guide.tsx
  16. 84 0
      src/tenant/music/coursewarePlay/component/o-guide/guide/components/ios-guide.module.less
  17. 119 0
      src/tenant/music/coursewarePlay/component/o-guide/guide/components/ios-guide.tsx
  18. 17 0
      src/tenant/music/coursewarePlay/component/o-guide/guide/guide.tsx
  19. 二进制
      src/tenant/music/coursewarePlay/component/o-guide/guide/images/1.png
  20. 二进制
      src/tenant/music/coursewarePlay/component/o-guide/guide/images/10.png
  21. 二进制
      src/tenant/music/coursewarePlay/component/o-guide/guide/images/11.png
  22. 二进制
      src/tenant/music/coursewarePlay/component/o-guide/guide/images/12.png
  23. 二进制
      src/tenant/music/coursewarePlay/component/o-guide/guide/images/13.png
  24. 二进制
      src/tenant/music/coursewarePlay/component/o-guide/guide/images/14.png
  25. 二进制
      src/tenant/music/coursewarePlay/component/o-guide/guide/images/2.png
  26. 二进制
      src/tenant/music/coursewarePlay/component/o-guide/guide/images/3.png
  27. 二进制
      src/tenant/music/coursewarePlay/component/o-guide/guide/images/4.png
  28. 二进制
      src/tenant/music/coursewarePlay/component/o-guide/guide/images/5.png
  29. 二进制
      src/tenant/music/coursewarePlay/component/o-guide/guide/images/6.png
  30. 二进制
      src/tenant/music/coursewarePlay/component/o-guide/guide/images/7.png
  31. 二进制
      src/tenant/music/coursewarePlay/component/o-guide/guide/images/8.png
  32. 二进制
      src/tenant/music/coursewarePlay/component/o-guide/guide/images/9.png
  33. 二进制
      src/tenant/music/coursewarePlay/component/o-guide/guide/images/guide.png
  34. 二进制
      src/tenant/music/coursewarePlay/component/o-guide/guide/images/icon-img.png
  35. 二进制
      src/tenant/music/coursewarePlay/component/o-guide/guide/images/icon-music.png
  36. 二进制
      src/tenant/music/coursewarePlay/component/o-guide/guide/images/icon-video.png
  37. 6 0
      src/tenant/music/coursewarePlay/component/o-guide/guide/images/index.ts
  38. 二进制
      src/tenant/music/coursewarePlay/component/o-guide/image/1.png
  39. 二进制
      src/tenant/music/coursewarePlay/component/o-guide/image/2.png
  40. 二进制
      src/tenant/music/coursewarePlay/component/o-guide/image/3.png
  41. 43 0
      src/tenant/music/coursewarePlay/component/o-guide/index.module.less
  42. 65 0
      src/tenant/music/coursewarePlay/component/o-guide/index.tsx
  43. 47 0
      src/tenant/music/coursewarePlay/component/play-loading/index.module.less
  44. 16 0
      src/tenant/music/coursewarePlay/component/play-loading/index.tsx
  45. 168 0
      src/tenant/music/coursewarePlay/component/point.module.less
  46. 216 0
      src/tenant/music/coursewarePlay/component/points.tsx
  47. 32 0
      src/tenant/music/coursewarePlay/component/tool.module.less
  48. 39 0
      src/tenant/music/coursewarePlay/component/tool.tsx
  49. 43 0
      src/tenant/music/coursewarePlay/component/tools/pen.module.less
  50. 145 0
      src/tenant/music/coursewarePlay/component/tools/pen.tsx
  51. 216 0
      src/tenant/music/coursewarePlay/component/video-item/index.module.less
  52. 341 0
      src/tenant/music/coursewarePlay/component/video-item/index.tsx
  53. 177 0
      src/tenant/music/coursewarePlay/component/video-item/video-play.tsx
  54. 479 0
      src/tenant/music/coursewarePlay/component/video-play.tsx
  55. 367 0
      src/tenant/music/coursewarePlay/component/video.module.less
  56. 0 0
      src/tenant/music/coursewarePlay/datas/data.json
  57. 11 0
      src/tenant/music/coursewarePlay/image/back.svg
  58. 15 0
      src/tenant/music/coursewarePlay/image/icon-arrow.svg
  59. 24 0
      src/tenant/music/coursewarePlay/image/icon-dian.svg
  60. 22 0
      src/tenant/music/coursewarePlay/image/icon-down.svg
  61. 19 0
      src/tenant/music/coursewarePlay/image/icon-image-active.svg
  62. 19 0
      src/tenant/music/coursewarePlay/image/icon-image.svg
  63. 二进制
      src/tenant/music/coursewarePlay/image/icon-load.gif
  64. 23 0
      src/tenant/music/coursewarePlay/image/icon-loop-active.svg
  65. 20 0
      src/tenant/music/coursewarePlay/image/icon-loop.svg
  66. 28 0
      src/tenant/music/coursewarePlay/image/icon-menu.svg
  67. 二进制
      src/tenant/music/coursewarePlay/image/icon-more.png
  68. 18 0
      src/tenant/music/coursewarePlay/image/icon-mulv.svg
  69. 32 0
      src/tenant/music/coursewarePlay/image/icon-pause.svg
  70. 二进制
      src/tenant/music/coursewarePlay/image/icon-pen.png
  71. 22 0
      src/tenant/music/coursewarePlay/image/icon-play.svg
  72. 24 0
      src/tenant/music/coursewarePlay/image/icon-point.svg
  73. 20 0
      src/tenant/music/coursewarePlay/image/icon-song-active.svg
  74. 20 0
      src/tenant/music/coursewarePlay/image/icon-song.svg
  75. 二进制
      src/tenant/music/coursewarePlay/image/icon-speed-add.png
  76. 二进制
      src/tenant/music/coursewarePlay/image/icon-speed-bg.png
  77. 二进制
      src/tenant/music/coursewarePlay/image/icon-speed-cut.png
  78. 45 0
      src/tenant/music/coursewarePlay/image/icon-start.svg
  79. 4 0
      src/tenant/music/coursewarePlay/image/icon-touping.svg
  80. 22 0
      src/tenant/music/coursewarePlay/image/icon-up.svg
  81. 9 0
      src/tenant/music/coursewarePlay/image/icon-video-active.svg
  82. 9 0
      src/tenant/music/coursewarePlay/image/icon-video.svg
  83. 二进制
      src/tenant/music/coursewarePlay/image/icon-videobg.png
  84. 1 0
      src/tenant/music/coursewarePlay/image/icon-zhibo.svg
  85. 1 0
      src/tenant/music/coursewarePlay/image/icons.json
  86. 二进制
      src/tenant/music/coursewarePlay/image/video-speed.png
  87. 374 0
      src/tenant/music/coursewarePlay/index.module.less
  88. 3255 0
      src/tenant/music/coursewarePlay/index.tsx
  89. 128 0
      src/tenant/music/coursewarePlay/playRecordTime.tsx
  90. 二进制
      src/tenant/music/lessonCourseware/component/CourseItem/image/icon-lock.png
  91. 二进制
      src/tenant/music/lessonCourseware/component/CourseItem/image/icon-top.png
  92. 138 0
      src/tenant/music/lessonCourseware/component/CourseItem/index.module.less
  93. 33 0
      src/tenant/music/lessonCourseware/component/CourseItem/index.tsx
  94. 13 0
      src/tenant/music/lessonCourseware/image/look.svg
  95. 116 0
      src/tenant/music/lessonCourseware/index.module.less
  96. 194 0
      src/tenant/music/lessonCourseware/index.tsx
  97. 32 2
      src/tenant/music/train-tool/index.tsx

+ 21 - 0
src/router/routes-tenant.ts

@@ -124,6 +124,27 @@ export default [
         }
       },
       {
+        path: '/lessonCourseware',
+        component: () => import('@/tenant/music/lessonCourseware'),
+        meta: {
+          title: '云教程'
+        }
+      },
+      {
+        path: '/courseList',
+        component: () => import('@/tenant/music/courseList'),
+        meta: {
+          title: '教程详情'
+        }
+      },
+      {
+        path: '/coursewarePlay',
+        component: () => import('@/tenant/music/coursewarePlay'),
+        meta: {
+          title: '教程播放'
+        }
+      },
+      {
         path: '/music-detail',
         component: () => import('@/tenant/music/music-detail/new-index'),
         meta: {

二进制
src/tenant/music/courseList/image/bg.png


二进制
src/tenant/music/courseList/image/icon-cache-point.png


二进制
src/tenant/music/courseList/image/icon-close.png


二进制
src/tenant/music/courseList/image/icon-course-lock.png


二进制
src/tenant/music/courseList/image/icon-course.png


二进制
src/tenant/music/courseList/image/icon-list.png


二进制
src/tenant/music/courseList/image/iconTip.png


+ 13 - 0
src/tenant/music/courseList/image/look.svg

@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="13px" height="13px" viewBox="0 0 13 13" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <title>切片</title>
+    <g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="选择课件" transform="translate(-282.000000, -159.000000)">
+            <g id="锁备份" transform="translate(282.000000, 159.000000)">
+                <circle id="椭圆形" fill="#FFFFFF" cx="6.5" cy="8" r="1"></circle>
+                <rect id="矩形" stroke="#FFFFFF" stroke-width="1.2" x="1.6" y="4.1" width="9.8" height="7.8" rx="2"></rect>
+                <path d="M4.5,4 L4.5,3 C4.5,1.8954305 5.3954305,1 6.5,1 C7.6045695,1 8.5,1.8954305 8.5,3 L8.5,4 L8.5,4" id="路径" stroke="#FFFFFF" stroke-width="1.2"></path>
+            </g>
+        </g>
+    </g>
+</svg>

+ 332 - 0
src/tenant/music/courseList/index.module.less

@@ -0,0 +1,332 @@
+.courseList {
+  height: 100vh;
+  background-image: url('./image/bg.png');
+  background-size: 100% 214px;
+  background-repeat: no-repeat;
+  box-sizing: border-box;
+  overflow: hidden;
+}
+
+.periodContent {
+  display: flex;
+  padding: 20px;
+
+  .cover {
+    position: relative;
+    width: 107px;
+    height: 130px;
+    margin-right: 30px;
+    border-radius: 2px;
+    overflow: hidden;
+    box-shadow: 0px 2px 6px 0px rgba(221, 168, 133, 0.67);
+    overflow: hidden;
+    background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFoAAABaCAMAAAAPdrEwAAABOFBMVEUAAAC0tLTT09PU1NTt7e3////T09P////T09P////T09P////X19f////U1NTU1NTT09PT09PU1NTU1NTV1dXT09PU1NTU1NTV1dXZ2dn+/v7R0dH////////KysrIyMjT09O0tLTU1NS0tLTU1NS0tLTT09PT09PT09PU1NTU1NTU1NTT09O2trbU1NTV1dXV1dXW1tbV1dXW1tbV1dXJycm4uLj///+0tLT///+0tLT////p6em0tLT4+Pj///+2trb///+1tbX///+0tLT////T09P5+fn+/v63t7e8vLz8/Pzl5eXW1tbBwcH39/fw8PDs7Ozb29v7+/vg4ODOzs65ubny8vLJycnp6enGxsbFxcW+vr62trb09PTY2NjLy8vCwsLn5+fi4uLS0tLu7u7Q0NDd3d02Mu/gAAAARHRSTlMAnPwvBfv07ODdw5AbGezmyrirl5B4aT0kDZyclTwsIPj48+Ta1tPS0LGhiIaEcGJgV05FNhMS7++8vKyslZRzc1ZWJRSpe/cAAALfSURBVFjD7djXcuIwGAVg1pTQAiT0Ekiy6b1v7whsA6b3EGAJu+//BstaiWNiCYGlXGTG55KBDzH6j2xjMmJET5ZdDivQFavDtTxLXlsCFFlam7HmiUxl49ftApRxYWkHLe3A0lZa2oqlAXUMGk/fHG9vpCnSk/pZNJ18m6aOWELRN1CmtbMI+jjNJH0Evc2GlhD0Bhu6h6DTjPIq6Pqonb8XXoCWmjKSHTCnpccp5ges6bHCZAW2dBc8JcOWLqjoPFu6oqLLr2bVQxX9h/GElBWmKDCmxSKAyXWYt1GE677rLtLGejsLcuOCQMI7lVFlsMjxJJUeW3bP+OSTskBJqcuSFppAFT4vsqMrYDrVAitazMHV8kBJs8WGzsPmNoRCFSj5fcuA7vJKvcQ8UJKrCNT0WH0mdEpASbFOSdfhyClvzagGsSzR0D14LHTSSho1/mkQaw39dB9u2vThCU8KQj1JtFiVt+z5ONSLmnoSaeszuia/+lfzAaGS09STQDum6SH/cOXX5rY9Xz0Rj6Sqy0YmjUzrbp56ah6kIT2AH8Pe4M5TT83jP6ThsnALmq+eiD8tlIt/G8POWU8FVNMNuXc8rnH4epJpOHg1jIivJ5mWcvLg4ZqMryeZhnOLm1hyPfF0C95R9PAcvp4jcSYNL7UDHEWuJ56GO46FyPXE0+/+/7Lhgs+ffU093yPoDwCAEcki1/Mjgv4BQFUkSeR6/jQh8gn0SQy5np9NqFi+CgSDWE9rwmJCJ/X9yxt9iSdisVji2y+TESNGXjiW6LU7FYkkzQ9JRiIp93XUoodyX12cBg/9qyvOHbvXxmUw4Wxe+45zZdV/GDy9uHJbZqPhk7jTjrTI4ezO+EkY8wUBW4Y6tgCSPvNxtDLnOzOhEw2HAvt7Hj2oZ28/EApHyRtpvjwPBY/8B5Ot9O1u2b2eTds6x8nr4tZtmx6vfWvXN9nCA/9RMHR+aZ5sohEji+YfnN0/51L6d+cAAAAASUVORK5CYII=');
+    background-repeat: no-repeat;
+    background-position: center;
+    background-size: 50%;
+    flex-shrink: 0;
+
+    & > img {
+      display: block;
+      width: 100%;
+      height: 100%;
+      opacity: 0;
+      transition: opacity 0.3s;
+      object-fit: cover;
+    }
+
+    :global {
+      .van-image__loading {
+        position: relative;
+        height: 100%;
+        animation: van-skeleton-blink var(--van-skeleton-duration) ease-in-out
+          infinite;
+      }
+    }
+
+    &::before {
+      content: '';
+      position: absolute;
+      left: 5px;
+      width: 5px;
+      height: 100%;
+      background: linear-gradient(
+        270deg,
+        rgba(0, 0, 0, 0.25) 0%,
+        rgba(0, 0, 0, 0.03) 100%
+      );
+      box-shadow: 0px 2px 6px 0px rgba(0, 0, 0, 0.2);
+      z-index: 1;
+    }
+  }
+
+  .contentTitle {
+    font-weight: 600;
+    font-size: 16px;
+    color: #131415;
+    line-height: 22px;
+    padding-bottom: 8px;
+  }
+
+  .contentLabel {
+    font-weight: 400;
+    font-size: 12px;
+    color: #000000;
+    line-height: 20px;
+  }
+}
+
+.periodTitle {
+  display: flex;
+  align-items: center;
+  padding: 20px 20px 0;
+
+  .pIcon {
+    width: 20px;
+    height: 20px;
+    margin-right: 6px;
+  }
+
+  .pTitle {
+    font-size: 16px;
+    font-weight: 600;
+    color: #2d3131;
+    margin-right: 8px;
+  }
+
+  .pNum {
+    font-size: 12px;
+    font-weight: 400;
+    color: rgba(0, 0, 0, 0.5);
+  }
+}
+
+.periodList {
+  :global {
+    .van-cell-group--inset {
+      margin: 0;
+    }
+
+    .van-cell-group,
+    .van-cell {
+      background: transparent;
+    }
+
+    .van-cell {
+      padding: 18px 20px;
+
+      &::after {
+        left: 20px;
+        right: 20px;
+        border-color: rgba(242, 242, 242, 1);
+        transform: none;
+      }
+
+      .van-cell__title {
+        padding-right: 8px;
+
+        span {
+          font-size: 15px;
+          font-weight: 600;
+          color: #333333;
+          line-height: 21px;
+          word-break: break-all;
+        }
+
+        .van-cell__label {
+          font-size: 12px;
+          font-weight: 400;
+          color: #aaaaaa;
+          line-height: 17px;
+          margin: 0;
+        }
+      }
+
+      .van-cell__value {
+        flex: inherit;
+        flex-shrink: 0;
+      }
+    }
+  }
+
+  .baseBtn {
+    width: 73px;
+    height: 26px;
+    line-height: 1;
+    color: #fff;
+    font-size: 13px;
+    font-weight: 500;
+    border: 0;
+    border-radius: 13px;
+    flex-shrink: 0;
+
+    :global {
+      .van-button__text {
+        white-space: nowrap;
+      }
+    }
+
+    &.disabled {
+      opacity: 0.6 !important;
+    }
+
+    &.look {
+      background: linear-gradient(270deg, #4be0a1 0%, #01c1b5 100%);
+    }
+
+    &.down {
+      background: linear-gradient(270deg, #4be0a1 0%, #01c1b5 100%);
+    }
+
+    &.downing {
+      background: linear-gradient(270deg, #47cfe5 0%, #2fb3ff 100%);
+    }
+
+    &.disable {
+      opacity: 1;
+      background: linear-gradient(180deg, #d3d3d3 0%, #8f8f8f 100%);
+    }
+  }
+}
+
+.periodItem {
+  width: 36px;
+  height: 40px;
+  margin-right: 8px;
+  flex-shrink: 0;
+
+  img {
+    width: 100%;
+    height: 100%;
+    display: block;
+    object-fit: cover;
+  }
+}
+
+.courseDialog {
+  width: 315px;
+
+  :global {
+    .van-dialog__header {
+      // padding-top: 0;
+    }
+  }
+
+  .iconCross {
+    position: absolute;
+    top: 10px;
+    right: 10px;
+    font-size: 22px;
+    color: #ccc;
+    z-index: 99;
+  }
+}
+
+.periodItemModel {
+  position: relative;
+
+  .iconCachePoint {
+    position: absolute;
+    right: 0;
+    bottom: 0;
+    display: block;
+    object-fit: contain;
+    width: 13px;
+    height: 13px;
+  }
+
+  .periodTip {
+    position: absolute;
+    left: -7px;
+    top: 0;
+    max-height: 15px;
+    display: block;
+    object-fit: contain;
+  }
+
+  .downloading {
+    position: absolute;
+    left: 0;
+    right: 0;
+    bottom: 0;
+    top: 0;
+    background: rgba(0, 0, 0, 0.4);
+    font-size: 11px;
+    color: #fff;
+    border-radius: 9px;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+  }
+}
+
+.btnGroup {
+  padding: 20px 25px calc(20px + env(safe-area-inset-bottom));
+  border-top: 1px solid #f2f2f2;
+  background-color: #fff;
+}
+
+.courseDialog {
+  padding: 20px;
+
+  // :global {
+  //   .van-popup__close-icon {
+  //     color: #333;
+  //     top: 24px;
+  //   }
+  // }
+  .iconClose {
+    width: 18px;
+    height: 19px;
+    position: absolute;
+    top: 24px;
+    right: 23px;
+    z-index: 9;
+    background: url('./image/icon-close.png') no-repeat center;
+    background-size: contain;
+  }
+
+  .title {
+    position: relative;
+    font-size: 18px;
+    font-weight: 600;
+    color: #131415;
+    line-height: 25px;
+    text-align: center;
+  }
+
+  .content {
+    padding: 20px 0 25px;
+    font-size: 16px;
+    color: #666666;
+    line-height: 24px;
+  }
+
+  .popupBtnGroup {
+    display: flex;
+    align-items: center;
+
+    & > button {
+      flex: 1;
+      font-weight: 400;
+      font-size: 16px !important;
+
+      &:last-child {
+        margin-left: 15px;
+      }
+    }
+  }
+}
+
+.finch {
+  width: 150px;
+  margin: 80px auto 0;
+}
+
+.finchLoad {
+  text-align: center;
+  color: #333;
+  font-size: 15px;
+  margin-top: 4px;
+  margin-bottom: 120px;
+}

+ 450 - 0
src/tenant/music/courseList/index.tsx

@@ -0,0 +1,450 @@
+import request from '@/helpers/request'
+import { state } from '@/state'
+import { Button, Cell, CellGroup, Popup, Dialog, Toast } from 'vant'
+import { Vue3Lottie } from 'vue3-lottie'
+import AstronautJSON from '../music-detail/animate/bigLoad.json'
+import {
+  defineComponent,
+  onMounted,
+  reactive,
+  onUnmounted,
+  TransitionGroup,
+  ref
+} from 'vue'
+import styles from './index.module.less'
+import { useRoute, useRouter } from 'vue-router'
+import {
+  listenerMessage,
+  postMessage,
+  removeListenerMessage
+} from '@/helpers/native-message'
+// import iconLook from './image/look.svg'
+import iconCourse from './image/icon-course.png'
+import iconCachePoint from './image/icon-cache-point.png'
+// import iconCourseLock from './image/icon-course-lock.png';
+// import iconTip from './image/iconTip.png';
+import { browser } from '@/helpers/utils'
+import ColResult from '@/components/col-result'
+function hasVip() {
+  return false
+}
+function handleCheckVip() {
+  return false
+}
+import TheSticky from '@/components/the-sticky'
+import ColHeader from '@/components/col-header'
+import iconList from './image/icon-list.png'
+// import OSticky from '@/components/o-sticky';
+import { useEventListener } from '@vant/use'
+export default defineComponent({
+  name: 'courseList',
+  setup() {
+    const route = useRoute()
+    const router = useRouter()
+    const browserInfo = browser()
+    const data = reactive({
+      titleOpacity: 0,
+      catchStatus: false,
+      catchItem: {} as any,
+      loading: true,
+      detail: {
+        cover: '',
+        name: '',
+        des: ''
+      },
+      list: [] as any,
+      isDownloading: false // 是否在下载资源
+    })
+
+    /** 获取课件详情 */
+    const getDetail = async () => {
+      const res: any = await request.get(
+        `${state.platformApi}/lessonCourseware/getLessonCoursewareDetail/${route.query.id}`
+      )
+      if (res?.data) {
+        data.detail.cover = res.data.coverImg
+        data.detail.name = res.data.name
+        data.detail.des = res.data.lessonTargetDesc
+      }
+    }
+    const getList = async () => {
+      data.loading = true
+      try {
+        const res: any = await request.get(
+          state.platformApi +
+            '/lessonCourseware/getLessonCoursewareCourseList/' +
+            route.query.id
+        )
+        if (Array.isArray(res?.data)) {
+          data.list = res.data
+          res.data.forEach((item: any) => {
+            const { knowledgePointList, ...res } = item
+            const tempK = knowledgePointList || []
+            tempK.forEach((child: any) => {
+              child.materialList = [
+                ...(child.materialList || []),
+                ...getKnowledgeMaterials(child.children || [])
+              ]
+              child.children = null
+            })
+          })
+
+          // 由于ios没有对应api
+          const _list = await checkCoursewareCache(res.data)
+          data.list = browserInfo.isApp
+            ? res.data.map((item: any) => {
+                const _item = _list.find(
+                  (n: any) =>
+                    n.lessonCoursewareDetailId == item.lessonCoursewareDetailId
+                )
+                const n = {
+                  ...item
+                }
+                if (_item) {
+                  n.hasCache = _item.hasCache
+                }
+                return n
+              })
+            : res.data
+        }
+      } catch (error) {
+        //
+      }
+      data.loading = false
+    }
+    // 获取子节点数据
+    const getKnowledgeMaterials = (list: any = []) => {
+      const tempList: any = []
+      list.forEach((item: any) => {
+        if (item.materialList && item.materialList.length > 0) {
+          tempList.push(...(item.materialList || []))
+        }
+
+        if (item.children && item.children.length > 0) {
+          tempList.push(...getKnowledgeMaterials(item.children || []))
+        }
+      })
+      return tempList
+    }
+    onMounted(() => {
+      getDetail()
+      getList()
+      listenerMessage('downloadCoursewareToCache', getProgress)
+    })
+    onUnmounted(() => {
+      removeListenerMessage('downloadCoursewareToCache', getProgress)
+    })
+
+    const handleClick = async (item: any) => {
+      if (!item.knowledgePointList) {
+        Dialog.confirm({
+          message: '该课件暂无知识点'
+        })
+        return
+      }
+
+      const isVip = handleCheckVip()
+      if (!isVip) return
+
+      if (!item.hasCache) {
+        // const hasFree = String(item.accessScope) === '0';
+        // if (!hasFree) {
+
+        // 下载中不提示
+        if (item.downloadStatus == 1) {
+          // 取消下载
+          postMessage({ api: 'cancelDownloadCourseware' })
+          setTimeout(() => {
+            postMessage({ api: 'cancelDownloadCourseware' })
+            item.downloadStatus = 0
+            data.isDownloading = false
+          }, 1000)
+          Toast.loading({
+            message: '取消中...',
+            forbidClick: false,
+            loadingType: 'spinner',
+            duration: 1000
+          })
+          return
+        }
+        // 重新下载
+        if (item.downloadStatus == 3) {
+          downCatch(item)
+          return
+        }
+        data.catchStatus = true
+        data.catchItem = item
+        return
+      }
+
+      gotoPlay(item)
+    }
+    // 去课件播放
+    const gotoPlay = (item: any) => {
+      data.catchStatus = false
+      if (browser().isApp) {
+        postMessage({
+          api: 'openWebView',
+          content: {
+            url: `${location.origin}${location.pathname}#/coursewarePlay?id=${item.coursewareDetailId}&source=my-course`,
+            orientation: 0,
+            isHideTitle: true,
+            statusBarTextColor: false,
+            isOpenLight: true,
+            showLoadingAnim: true
+          }
+        })
+      } else {
+        router.push({
+          path: '/coursewarePlay',
+          query: {
+            id: item.coursewareDetailId,
+            source: 'my-course'
+          }
+        })
+      }
+    }
+    // 检查数据的缓存状态
+    const checkCoursewareCache = (list: []): Promise<any[]> => {
+      if (!browser().isApp) {
+        return Promise.resolve(list)
+      }
+      return new Promise(resolve => {
+        postMessage(
+          {
+            api: 'checkCoursewareCache',
+            content: {
+              data: list
+            }
+          },
+          res => {
+            if (res?.content?.data) {
+              resolve(res.content.data)
+              return
+            }
+            return []
+          }
+        )
+      })
+    }
+    // 下载缓存
+    const downCatch = async (item: any) => {
+      if (browserInfo.isApp) {
+        data.catchStatus = false
+        data.isDownloading = true
+        const res = await postMessage({
+          api: 'downloadCoursewareToCache',
+          content: {
+            data: item
+          }
+        })
+        return res
+      }
+
+      return true
+    }
+    // 下载缓存进度
+    const getProgress = (res: any) => {
+      // console.log('🚀 ~ res', res)
+      if (!data.isDownloading) {
+        return
+      }
+      if (res?.content?.lessonCoursewareDetailId) {
+        const { lessonCoursewareDetailId, downloadStatus, progress } =
+          res.content
+        const course = data.list.find(
+          (n: any) => n.lessonCoursewareDetailId == lessonCoursewareDetailId
+        )
+        if (course) {
+          course.downloadStatus = downloadStatus
+          course.progress = progress
+          if (downloadStatus == 2) {
+            course.hasCache = 1
+            course.progress = 100
+            // 下载完成
+            data.isDownloading = false
+          }
+        }
+      }
+    }
+    useEventListener('scroll', () => {
+      const height =
+        window.scrollY ||
+        window.pageYOffset ||
+        document.documentElement.scrollTop
+      data.titleOpacity = height > 100 ? 1 : height / 100
+    })
+    return () => (
+      <div class={styles.courseList}>
+        <TheSticky position="top">
+          <ColHeader
+            hideHeader={false}
+            background="transparent"
+            isFixed={false}
+            border={false}
+            title={'教程详情'}
+            color="#131415"
+          />
+        </TheSticky>
+        <div class={styles.periodContent}>
+          <div class={styles.cover}>
+            <img
+              src={data.detail.cover}
+              onLoad={(e: Event) => {
+                if (e.target) {
+                  ;(e.target as any).style.opacity = 1
+                }
+              }}
+            />
+          </div>
+          <div>
+            <div class={styles.contentTitle}>{data.detail.name}</div>
+            <div class={styles.contentLabel}>教学目标:{data.detail.des}</div>
+          </div>
+        </div>
+        <TransitionGroup name="van-fade">
+          {!data.loading && (
+            <>
+              <div key="periodTitle" class={styles.periodTitle}>
+                <img class={styles.pIcon} src={iconList} />
+                <div class={styles.pTitle}>课程列表</div>
+                <div class={styles.pNum}>共{data.list.length}课</div>
+              </div>
+
+              <div key="list" class={styles.periodList}>
+                <CellGroup inset>
+                  {data.list.map((item: any) => {
+                    // const isLock =
+                    //   item.lockFlag ||
+                    //   ((route.query.code == 'select' ||
+                    //     state.platformType == 'STUDENT') &&
+                    //     !item.unlock);
+                    // const isSelect = route.query.code === 'select';
+                    return (
+                      <Cell
+                        border
+                        center
+                        title={item.coursewareDetailName}
+                        // label={
+                        //   !browserInfo.isStudent
+                        //     ? `已使用${item.useNum || 0}次`
+                        //     : ''
+                        // }
+                        onClick={() => handleClick(item)}
+                      >
+                        {{
+                          icon: () => (
+                            <div class={styles.periodItem}>
+                              <div class={styles.periodItemModel}>
+                                <img src={iconCourse} />
+                                {item.hasCache ? (
+                                  <img
+                                    class={styles.iconCachePoint}
+                                    src={iconCachePoint}
+                                  />
+                                ) : (
+                                  ''
+                                )}
+                                {item.downloadStatus === 1 && (
+                                  <div class={styles.downloading}>{`${
+                                    item.progress || 0
+                                  }%`}</div>
+                                )}
+                              </div>
+                            </div>
+                          ),
+                          value: () => (
+                            <>
+                              {item.knowledgePointList ? (
+                                <>
+                                  {item.hasCache ? (
+                                    <Button
+                                      class={[
+                                        styles.baseBtn,
+                                        styles.look,
+                                        state.platformType === 'STUDENT' &&
+                                        !hasVip()
+                                          ? styles.disabled
+                                          : ''
+                                      ]}
+                                    >
+                                      查看
+                                    </Button>
+                                  ) : (
+                                    <Button
+                                      class={[
+                                        styles.baseBtn,
+                                        styles.down,
+                                        state.platformType === 'STUDENT' &&
+                                        !hasVip()
+                                          ? styles.disabled
+                                          : '',
+                                        item.downloadStatus == 1
+                                          ? styles.downing
+                                          : ''
+                                      ]}
+                                    >
+                                      {item.downloadStatus === 1
+                                        ? `取消下载`
+                                        : '查看'}
+                                    </Button>
+                                  )}
+                                </>
+                              ) : (
+                                ''
+                              )}
+                            </>
+                          )
+                        }}
+                      </Cell>
+                    )
+                  })}
+                </CellGroup>
+              </div>
+            </>
+          )}
+        </TransitionGroup>
+        {data.loading && (
+          <div>
+            <Vue3Lottie
+              animationData={AstronautJSON}
+              class={styles.finch}
+            ></Vue3Lottie>
+            <p class={styles.finchLoad}>加载中...</p>
+          </div>
+        )}
+        {!data.loading && !data.list.length && (
+          <ColResult tips="暂无内容" classImgSize="SMALL" btnStatus={false} />
+        )}
+        <Popup
+          v-model:show={data.catchStatus}
+          round
+          class={styles.courseDialog}
+        >
+          <i
+            class={styles.iconClose}
+            onClick={() => (data.catchStatus = false)}
+          ></i>
+          <div class={styles.title}>下载提醒</div>
+
+          <div class={styles.content}>
+            您尚未下载课件内容,为了更加流畅的学习体验,推荐您下载后观看课件。
+          </div>
+
+          <div class={styles.popupBtnGroup}>
+            <Button round onClick={() => gotoPlay(data.catchItem)}>
+              直接观看
+            </Button>
+            <Button
+              round
+              type="primary"
+              onClick={() => downCatch(data.catchItem)}
+            >
+              下载课件
+            </Button>
+          </div>
+        </Popup>
+      </div>
+    )
+  }
+})

+ 83 - 0
src/tenant/music/coursewarePlay/component/musicScore.module.less

@@ -0,0 +1,83 @@
+.musicScore {
+  position: relative;
+  width: 100%;
+  height: 100%;
+  -webkit-overflow-scrolling: touch;
+  overflow: scroll;
+  .container {
+    position: relative;
+    display: block;
+    border: none;
+    width: 100%;
+    height: 100%;
+    z-index: 10;
+  }
+  .musicModel {
+    position: absolute;
+    left: 0;
+    top: 0;
+    right: 0;
+    bottom: 0;
+  }
+}
+.errorModel {
+  position: absolute;
+  left: 0;
+  top: 0;
+  right: 0;
+  bottom: 0;
+  background: #000;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  overflow: hidden;
+}
+
+.startBtn{
+  position: absolute;
+  left: 50%;
+  bottom: 6vh;
+  transform: translateX(-50%);
+  z-index: 11;
+  &:active{
+    opacity: .8;
+  }
+}
+.loading{
+  position: absolute;
+  left: 4%;
+  top: 50%;
+  margin-top: -15Px;
+}
+.skeletonWrap{
+  position: absolute;
+  left: 0;
+  top: 0;
+  right: 0;
+  height: 100%;
+  z-index: 1;
+  padding-top: 1.2rem;
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
+  overflow: hidden;
+  background: #fff;
+  pointer-events: none;
+}
+.skeleton {
+  --van-skeleton-paragraph-height: 24px;
+  :global{
+    .van-skeleton__content{
+      .van-skeleton-paragraph{
+        margin: 12px auto;
+        width: 80% !important;
+        &:first-child{
+          width: 60% !important;
+        }
+        &:last-child{
+          width: 100% !important;
+        }
+      }
+    }
+  }
+}

+ 129 - 0
src/tenant/music/coursewarePlay/component/musicScore.tsx

@@ -0,0 +1,129 @@
+import { defineComponent, ref, watch } from 'vue'
+import styles from './musicScore.module.less'
+import qs from 'query-string'
+import iconStart from '../image/icon-start.svg'
+import { listenerMessage, postMessage } from '@/helpers/native-message'
+import { Skeleton } from 'vant'
+import { usePageVisibility } from '@vant/use'
+import { useRoute } from 'vue-router'
+import { browser } from '@/helpers/utils'
+
+export default defineComponent({
+  name: 'musicScore',
+  props: {
+    music: {
+      type: Object,
+      default: () => ({})
+    },
+    activeModel: {
+      type: Boolean
+    }
+  },
+  emits: ['setIframe'],
+  setup(props, { emit }) {
+    const browserInfo = browser()
+    const route = useRoute()
+    const isLoading = ref(false)
+    const pageVisibility = usePageVisibility()
+    /** 页面显示和隐藏 */
+    watch(pageVisibility, (value) => {
+      console.log("🚀 ~ value:", value)
+      if (value == 'hidden') {
+        isLoading.value = false
+      }
+    })
+    const iframeRef = ref()
+    const isLoaded = ref(false)
+    const renderError = ref(false)
+    const renderSuccess = ref(false)
+    const Authorization = sessionStorage.getItem('Authorization') || ''
+    const origin = /(localhost|192)/.test(location.host)
+      ? 'https://test.lexiaoya.cn'
+      : location.origin
+    const query = qs.stringify({
+      id: props.music.content,
+      modelType: 'practice',
+      headerHeight: 32,
+      Authorization: Authorization
+    })
+    const src = `${origin}/orchestra-music-score/?` + query
+    const checkView = () => {
+      fetch(src)
+        .then(() => {
+          renderSuccess.value = true
+          renderError.value = false
+        })
+        .catch(() => {
+          renderError.value = true
+        })
+    }
+    watch(props.music, () => {
+      if (renderSuccess.value) return
+      renderError.value = false
+      if (props.music.display) {
+        checkView()
+      }
+    })
+
+    // 去云教练完整版
+    const gotoAccomany = () => {
+      if (isLoading.value) return
+      if (!browserInfo.ios){
+        isLoading.value = true
+      }
+      const parmas = qs.stringify({
+        id: props.music.content
+      })
+      const src = `${location.origin}/orchestra-music-score/?` + parmas
+      postMessage({
+        api: 'openAccompanyWebView',
+        content: {
+          url: src,
+          orientation: 0,
+          isHideTitle: true,
+          statusBarTextColor: false,
+          isOpenLight: true
+        }
+      }, () => {
+        if (browserInfo.ios){
+          isLoading.value = true
+        }
+      })
+    }
+    listenerMessage('webViewOnResume', () => {
+      isLoading.value = false
+    })
+
+    return () => (
+      <div class={styles.musicScore}>
+        <iframe
+          ref={iframeRef}
+          onLoad={() => {
+            emit('setIframe', iframeRef.value)
+            isLoaded.value = true
+          }}
+          class={[styles.container, 'musicIframe']}
+          frameborder="0"
+          src={src}
+        ></iframe>
+        {route.query.source == 'my-course' && isLoaded.value && (
+          <div
+            style={{
+              display: props.activeModel ? '' : 'none'
+            }}
+            class={styles.startBtn}
+            onClick={(e: Event) => {
+              e.stopPropagation()
+              gotoAccomany()
+            }}
+          >
+            <img src={iconStart} />
+          </div>
+        )}
+        <div class={styles.skeletonWrap}>
+          <Skeleton class={styles.skeleton} row={8} />
+        </div>
+      </div>
+    )
+  }
+})

+ 78 - 0
src/tenant/music/coursewarePlay/component/o-guide/guide/components/andoird-guide.module.less

@@ -0,0 +1,78 @@
+.topTitle {
+  position: relative;
+  .title {
+    color: #fff;
+    line-height: 18px;
+    font-size: 16Px;
+    padding-left: 22px;
+    color: #fff;
+    position: relative;
+    z-index: 20;
+  }
+}
+.wrap {
+  box-sizing: border-box;
+  padding: 18px 0;
+  background-color: transparent;
+  color: #fff;
+
+  .wrapInfo {
+    padding: 0 22px;
+    section {
+      margin-top: 20px;
+      margin-bottom: 20px;
+      .bigP {
+        font-weight: bold;
+        color: #fff;
+        font-size: 16px;
+        line-height: 20px;
+      }
+      p {
+        font-size: 12px;
+        line-height: 20px;
+        margin-bottom: 20px;
+      }
+    }
+    h3 {
+      font-weight: 400;
+      color: #fff;
+      font-size: 14px;
+      line-height: 20px;
+    }
+    .blod {
+      font-weight: bold;
+    }
+    .red {
+      color: #ff8057;
+    }
+  }
+}
+
+.dot {
+  position: absolute;
+  width: 10px;
+  height: 17px;
+  background: #00c2b5;
+  opacity: 0.53;
+  border-radius: 1px;
+  top: -7px;
+  left: 0;
+}
+.little {
+  display: inline-block;
+  width: 4px;
+  height: 4px;
+  background: #00c2b5;
+  opacity: 0.53;
+  right: 0;
+}
+.imgWrap {
+  display: flex;
+  flex-direction: row;
+  justify-content: space-around;
+  align-items: center;
+  .img {
+    margin-bottom: 20px;
+    width: 190px;
+  }
+}

+ 188 - 0
src/tenant/music/coursewarePlay/component/o-guide/guide/components/android-guide.tsx

@@ -0,0 +1,188 @@
+import { defineComponent } from 'vue';
+import { getImage } from '../images';
+import styles from './andoird-guide.module.less';
+
+export const getAssetsHomeFile = getImage;
+
+const infoMsg = {
+  meizu: {
+    img1: getAssetsHomeFile('4.png'),
+    img2: getAssetsHomeFile('5.png'),
+    img3: getAssetsHomeFile('6.png'),
+    title1: '打开“设置”页面,点击“更多连接方式”按钮:',
+    title2: '点击“投射屏幕”',
+    title3:
+      '打开“投射屏幕”开关,即可看到可以投屏的设备列表,选择您的设备进行连接。'
+  },
+  xiaomi: {
+    img1: getAssetsHomeFile('7.png'),
+    img2: getAssetsHomeFile('8.png'),
+    img3: getAssetsHomeFile('9.png'),
+    title1: '打开“设置”页面,点击“连接与共享”按钮:',
+    title2: '点击“投屏”:',
+    title3:
+      '打开“打开投屏”开关,即可看到可以投屏的设备列表,选择您的设备进行连接。'
+  },
+  vivo: {
+    img1: getAssetsHomeFile('12.png'),
+    img2: getAssetsHomeFile('13.png'),
+    img3: getAssetsHomeFile('14.png'),
+    title1: '打开“设置”页面,点击“其他网络与连接”按钮:',
+    title2: '点击“手机投屏”:',
+    title3:
+      '打开“手机投屏”开关,即可看到可以投屏的设备列表,选择您的设备进行连接。'
+  },
+  huawei: {
+    img1: getAssetsHomeFile('1.png'),
+    img2: getAssetsHomeFile('2.png'),
+    img3: getAssetsHomeFile('3.png'),
+    title1: '打开“设置”页面,点击“更多连接”按钮:',
+    title2: '点击“手机投屏”:',
+    title3:
+      '打开“无线投屏”开关,即可看到可以投屏的设备列表,选择您的设备进行连接。'
+  }
+} as any;
+export default defineComponent({
+  name: 'adnroid-guide',
+  data() {
+    return {
+      brand: 'huawei'
+    };
+  },
+  mounted() {
+    const ua = navigator.userAgent.split('(')[1].split(')')[0];
+    this.brand = '';
+    const phone = [/MZ/gi, /mi/gi, /vivo/gi];
+    if (phone[0].test(ua)) {
+      this.brand = 'meizu';
+    } else if (phone[1].test(ua)) {
+      this.brand = 'xiaomi';
+    } else if (phone[2].test(ua)) {
+      this.brand = 'vivo';
+    } else {
+      this.brand = 'huawei';
+    }
+  },
+  render() {
+    return (
+      <div class={styles.wrap}>
+        <div class={styles.topTitle}>
+          <div class={styles.title}>通过镜像方式显示手机或平板上的内容</div>
+        </div>
+        <div class={styles.wrapInfo}>
+          <section>
+            <img
+              style={{ width: '100%', marginBottom: '20px' }}
+              src={getAssetsHomeFile('guide.png')}
+            />
+            <h3>第1步</h3>
+            <p>将您的手机或平板连接到您智能电视机所在的同一无线局域网。</p>
+            <h3>第2步</h3>
+            <p>{infoMsg[this.brand]['title1']}</p>
+            <div class={styles.imgWrap}>
+              <img
+                class={styles.img}
+                src={infoMsg[this.brand]['img1']}
+                alt=""
+              />
+            </div>
+
+            <h3>第3步</h3>
+            <p>{infoMsg[this.brand]['title2']}</p>
+            <div class={styles.imgWrap}>
+              <img
+                class={styles.img}
+                src={infoMsg[this.brand]['img2']}
+                alt=""
+              />
+            </div>
+
+            <h3>第4步</h3>
+            <p>{infoMsg[this.brand]['title3']}</p>
+            <div class={styles.imgWrap}>
+              <img
+                class={styles.img}
+                src={infoMsg[this.brand]['img3']}
+                alt=""
+              />
+            </div>
+          </section>
+          <section>
+            <p class={[styles.blod, styles.bigP]}>如果音乐意外停止:</p>
+            <p>
+              如果在这台设备上使用语音助手或进行其他任务,则可能会导致所有音频设备停止播放音乐
+            </p>
+            <p class={[styles.blod, styles.bigP]}>
+              如果您看到视频但听不到声音:
+            </p>
+            <p>
+              如果您听不到任何声音,则请确保手机设备和电视机/听筒的音量都已调高,而且没有静音。
+            </p>
+            <p>
+              请检查响铃/静音开关。如果开关设为静音,您会看到一条橙色的线。切换开关以开启响铃。
+            </p>
+            <p class={[styles.blod, styles.bigP]}>如果内容中断或网络卡顿:</p>
+            <p>
+              如果 Wi-Fi
+              信号欠佳或受到附近设备(例如,微波炉或婴儿监视器)的干扰尝试完成以下步骤:
+            </p>
+            <p>移开或关闭其他可能造成干扰的设备。</p>
+            <p>
+              如果您正尝试使用“隔空播放”将内容流化到智能电视,请尝试使用以太网线缆(而不是通过
+              Wi-Fi)将智能电视直接连接到路由器。
+            </p>
+            <p class={(styles.blod, styles.red)}>
+              *当您锁定设备、将其置于睡眠模式或切换到其他应用时,“隔空播放”连接可能会中断。
+            </p>
+          </section>
+          <section>
+            <p class={[styles.blod, styles.bigP]}>
+              如果“隔空播放”或屏幕镜像在您的设备上无法使用:
+            </p>
+            <p>1.确保您的设备都已开机且彼此距离较近。</p>
+            <p>2.请重新启动您要与“隔空播放”或屏幕镜像配合使用的设备。</p>
+            <p>3.以上方式尝试了仍无法搜到要使用的智能电视:</p>
+            <p class={(styles.blod, styles.red)}>
+              将您的设备连接到同一个 Wi-Fi 网络
+            </p>
+            <p>
+              首先需要确认电视与手机是否连接了同一个Wi-Fi
+              ,电视可以插网线,但必须是同一个路由器的,如果不确认,就把电视网线拔了改为连接Wi-Fi
+              。
+            </p>
+            <p class={[styles.blod, styles.bigP]}>
+              如果同一个Wi-Fi 也无法搜索到需要投屏的设备:
+            </p>
+            <p>
+              原因1·可能电视本身没有投屏功能(如果以前投屏过,也是可以判断为电视是支持投屏的。)
+            </p>
+            <p>A.是不是智能电视?</p>
+            <p>B.能不能自己安装软件?</p>
+            <p>C.是不是安卓系统?</p>
+            <p>D.能不能连接WiFi?</p>
+            <p>请确认以上4点,通常2016年以后购买的智能电视都支持投屏功能</p>
+          </section>
+          <section>
+            <p class={[styles.blod, styles.bigP]}>
+              如果是智能电视或者智能投影仪,但是没有投屏功能怎么办?
+            </p>
+            <p>
+              解决办法:自己安装一个投屏软件进去(幕享、傲软投屏、乐播投屏......)。相当于更新了电视投屏功能。也能解决这个问题。
+            </p>
+            <p>如果是老电视,老投影仪,老机顶盒怎么办?</p>
+            <p>这个也不是没有办法,电视最值钱的就是屏幕了。</p>
+            <p>
+              所以屏幕我们不要浪费了,继续使用,此时我们通过互联网机顶盒,从机顶盒应用商店安装投屏软件,也同样可以
+              进行投屏。
+            </p>
+            <p>a.是不是智能机顶盒?</p>
+            <p>b.能不能自己安装软件?</p>
+            <p>
+              c.是不是安卓系统?买回来之后利用HDMI线连接老电视,再从机顶盒应用商店下载投屏软件,就可以使用投屏了。
+            </p>
+          </section>
+        </div>
+      </div>
+    );
+  }
+});

+ 84 - 0
src/tenant/music/coursewarePlay/component/o-guide/guide/components/ios-guide.module.less

@@ -0,0 +1,84 @@
+.marginB33 {
+  margin-bottom: 33px;
+  h2 {
+    padding-left: 0 !important;
+  }
+}
+.wrap {
+  box-sizing: border-box;
+  padding: 18px 0;
+  background-color: transparent;
+  color: #fff;
+  .topTitle {
+    position: relative;
+    h2 {
+      font-weight: 400;
+      color: #fff;
+      line-height: 18px;
+      font-size: 16Px;
+      padding-left: 22px;
+      color: #fff;
+      position: relative;
+      z-index: 20;
+    }
+  }
+  .wrapInfo {
+    padding: 0 22px;
+    section {
+      // margin-top: 0.3rem;
+      margin-bottom: 45px;
+      .bigP {
+        font-weight: bold;
+        color: #fff;
+        font-size: 16px;
+        line-height: 20px;
+      }
+      p {
+        font-size: 12px;
+        line-height: 20px;
+        margin-bottom: 20px;
+      }
+    }
+    h3 {
+      font-weight: 400;
+      color: #fff;
+      font-size: 14px;
+      line-height: 20px;
+    }
+    .blod {
+      font-weight: bold;
+    }
+    .red {
+      color: #ff8057;
+    }
+  }
+}
+
+.dot {
+  position: absolute;
+  width: 10px;
+  height: 17px;
+  background: #00c2b5;
+  opacity: 0.53;
+  border-radius: 1px;
+  top: -7px;
+  left: 0;
+}
+.little {
+  display: inline-block;
+  width: 4px;
+  height: 4px;
+  background: #00c2b5;
+  opacity: 0.53;
+  right: 0;
+}
+.imgWrap {
+  display: flex;
+  flex-direction: row;
+  justify-content: space-around;
+  align-items: center;
+  .img {
+    margin-bottom: 20px;
+    width: 190px;
+  }
+}

+ 119 - 0
src/tenant/music/coursewarePlay/component/o-guide/guide/components/ios-guide.tsx

@@ -0,0 +1,119 @@
+import { defineComponent } from 'vue'
+import { getImage } from '../images'
+import styles from './ios-guide.module.less'
+
+export const getAssetsHomeFile = getImage
+
+export default defineComponent({
+  name: 'ios-guide',
+  render() {
+    return (
+      <div class={styles.wrap}>
+        <div class={styles.topTitle}>
+          <h2>通过镜像方式显示 iPhone或iPad上的内容</h2>
+        </div>
+
+        <div class={styles.wrapInfo}>
+          <img style={{ width: '100%', margin: '20px 0' }} src={getAssetsHomeFile('guide.png')} />
+          <section>
+            <h3>第1步</h3>
+            <p>
+              通过镜像方式显示 iPhone或iPad上的内容将您的 iPhone或iPad 连接到您的 Apple TV
+              或兼容“隔空播放 2”的智能电视机所在的同一无线局域网。
+            </p>
+            <h3>第2步</h3>
+            <p>
+              打开“控制中心”:
+              <br />
+              在 iPhone X 或更新机型或者装有 iPadOS 13 或更高版本的 iPad 上:从屏幕右上角向下轻扫。
+              <br />在 iPhone 8 或更早机型或者 iOS 11 或更低版本上:从屏幕底部边缘向上轻扫。
+            </p>
+            <h3>第3步</h3>
+            <p>轻点 “屏幕镜像”。(iOS 11 之前版本:AirPlay 镜像)</p>
+            <h3>第4步</h3>
+            <p>从列表中选择您的 Apple TV 或兼容“隔空播放 2”的智能电视机</p>
+            <h3>第5步</h3>
+            <p>如果电视机屏幕上出现“隔空播放”密码,请在 iOS 或 iPadOS 设备上输入这个密码。</p>
+            <h3>第6步</h3>
+            <p>
+              要停止镜像您的 iOS 或 iPadOS
+              设备,请打开“控制中心”,轻点“屏幕镜像”,然后轻点“停止镜像”。
+            </p>
+          </section>
+          <div class={[styles.topTitle, styles.marginB33]}>
+            <h2>iOS 10控制中心图片:</h2>
+          </div>
+          <section>
+            <img src={getAssetsHomeFile('11.png')} style={{ width: '100%' }} alt="" />
+          </section>
+          <div class={[styles.topTitle, styles.marginB33]}>
+            <h2>iOS 10之后版本控制中心图片:</h2>
+          </div>
+          <section>
+            <img src={getAssetsHomeFile('10.png')} style={{ width: '100%' }} alt="" />
+          </section>
+          <section>
+            <p class={[styles.blod, styles.bigP]}>如果音乐意外停止:</p>
+            <p>如果在这台设备上使用 Siri 或进行其他任务,则可能会导致所有音频设备停止播放音乐</p>
+            <p class={[styles.blod, styles.bigP]}>如果您看到视频但听不到声音:</p>
+            <p>
+              如果您听不到任何声音,则请确保 iOS 设备和电视机/听筒的音量都已调高,而且没有静音。
+            </p>
+            <p>请检查响铃/静音开关。如果开关设为静音,您会看到一条橙色的线。切换开关以开启响铃。</p>
+            <p class={[styles.blod, styles.bigP]}>如果内容中断或网络卡顿:</p>
+            <p>
+              如果 Wi-Fi 信号欠佳或受到附近设备(例如,微波炉或婴儿监视器)的干扰尝试完成以下步骤:
+            </p>
+            <p>移开或关闭其他可能造成干扰的设备。</p>
+            <p>
+              如果您正尝试使用“隔空播放”将内容流化到智能电视,请尝试使用以太网线缆(而不是通过
+              Wi-Fi)将智能电视直接连接到路由器。
+            </p>
+            <p class={[styles.blod, styles.red]}>
+              *当您锁定设备、将其置于睡眠模式或切换到其他应用时,“隔空播放”连接可能会中断。
+            </p>
+          </section>
+          <section>
+            <p class={[styles.blod, styles.bigP]}>如果“隔空播放”或屏幕镜像在您的设备上无法使用:</p>
+            <p>1.确保您的设备都已开机且彼此距离较近。</p>
+            <p>2.请重新启动您要与“隔空播放”或屏幕镜像配合使用的设备。</p>
+            <p>3.以上方式尝试了仍无法搜到要使用的智能电视:</p>
+            <p class={[styles.blod, styles.red]}>将您的设备连接到同一个 Wi-Fi 网络</p>
+            <p>
+              首先需要确认电视与手机是否连接了同一个Wi-Fi
+              ,电视可以插网线,但必须是同一个路由器的,如果不确认,就把电视网线拔了改为连接Wi-Fi 。
+            </p>
+            <p class={[styles.blod, styles.bigP]}>如果同一个Wi-Fi 也无法搜索到需要投屏的设备:</p>
+            <p>
+              原因1·可能电视本身没有投屏功能(如果以前投屏过,也是可以判断为电视是支持投屏的。)
+            </p>
+            <p>A.是不是智能电视?</p>
+            <p>B.能不能自己安装软件?</p>
+            <p>C.是不是安卓系统?</p>
+            <p>D.能不能连接WiFi?</p>
+            <p>请确认以上4点,通常2016年以后购买的智能电视都支持投屏功能</p>
+          </section>
+          <section>
+            <p class={[styles.blod, styles.bigP]}>
+              如果是智能电视或者智能投影仪,但是没有投屏功能怎么办?
+            </p>
+            <p>
+              解决办法:自己安装一个投屏软件进去(幕享、傲软投屏、乐播投屏......)。相当于更新了电视投屏功能。也能解决这个问题。
+            </p>
+            <p>如果是老电视,老投影仪,老机顶盒怎么办?</p>
+            <p>这个也不是没有办法,电视最值钱的就是屏幕了。</p>
+            <p>
+              所以屏幕我们不要浪费了,继续使用,此时我们通过互联网机顶盒,从机顶盒应用商店安装投屏软件,也同样可以
+              进行投屏。
+            </p>
+            <p>a.是不是智能机顶盒?</p>
+            <p>b.能不能自己安装软件?</p>
+            <p>
+              c.是不是安卓系统?买回来之后利用HDMI线连接老电视,再从机顶盒应用商店下载投屏软件,就可以使用投屏了。
+            </p>
+          </section>
+        </div>
+      </div>
+    )
+  }
+})

+ 17 - 0
src/tenant/music/coursewarePlay/component/o-guide/guide/guide.tsx

@@ -0,0 +1,17 @@
+import { defineComponent } from 'vue';
+import IosGuide from './components/ios-guide';
+import AndroidGuide from './components/android-guide';
+import { browser } from '@/helpers/utils';
+
+export default defineComponent({
+  name: 'ios-guide',
+  data() {
+    return {
+      client: 'ios'
+    };
+  },
+  render() {
+    const browserInfo = browser();
+    return <>{browserInfo.ios ? <IosGuide /> : <AndroidGuide />}</>;
+  }
+});

二进制
src/tenant/music/coursewarePlay/component/o-guide/guide/images/1.png


二进制
src/tenant/music/coursewarePlay/component/o-guide/guide/images/10.png


二进制
src/tenant/music/coursewarePlay/component/o-guide/guide/images/11.png


二进制
src/tenant/music/coursewarePlay/component/o-guide/guide/images/12.png


二进制
src/tenant/music/coursewarePlay/component/o-guide/guide/images/13.png


二进制
src/tenant/music/coursewarePlay/component/o-guide/guide/images/14.png


二进制
src/tenant/music/coursewarePlay/component/o-guide/guide/images/2.png


二进制
src/tenant/music/coursewarePlay/component/o-guide/guide/images/3.png


二进制
src/tenant/music/coursewarePlay/component/o-guide/guide/images/4.png


二进制
src/tenant/music/coursewarePlay/component/o-guide/guide/images/5.png


二进制
src/tenant/music/coursewarePlay/component/o-guide/guide/images/6.png


二进制
src/tenant/music/coursewarePlay/component/o-guide/guide/images/7.png


二进制
src/tenant/music/coursewarePlay/component/o-guide/guide/images/8.png


二进制
src/tenant/music/coursewarePlay/component/o-guide/guide/images/9.png


二进制
src/tenant/music/coursewarePlay/component/o-guide/guide/images/guide.png


二进制
src/tenant/music/coursewarePlay/component/o-guide/guide/images/icon-img.png


二进制
src/tenant/music/coursewarePlay/component/o-guide/guide/images/icon-music.png


二进制
src/tenant/music/coursewarePlay/component/o-guide/guide/images/icon-video.png


+ 6 - 0
src/tenant/music/coursewarePlay/component/o-guide/guide/images/index.ts

@@ -0,0 +1,6 @@
+const modules: any = import.meta.globEager(`../images/**`);
+export const getImage = (name: string) => {
+  // console.log("🚀 ~ modules", modules[`../images/${name}`]?.default)
+  const module: any = modules[`../images/${name}`];
+  return module?.default || '';
+};

二进制
src/tenant/music/coursewarePlay/component/o-guide/image/1.png


二进制
src/tenant/music/coursewarePlay/component/o-guide/image/2.png


二进制
src/tenant/music/coursewarePlay/component/o-guide/image/3.png


+ 43 - 0
src/tenant/music/coursewarePlay/component/o-guide/index.module.less

@@ -0,0 +1,43 @@
+.guideWrap {
+  width: 40vw;
+  height: 100vh;
+  :global {
+    .van-tabs__nav {
+      background-color: transparent;
+      .van-tab {
+        color: #fff;
+        font-size: 16px;
+      }
+    }
+    .van-tab__panel {
+      height: calc(100vh - var(--van-tabs-line-height));
+      overflow: hidden;
+      overflow-y: auto;
+      box-sizing: border-box;
+      &::-webkit-scrollbar {
+        width: 0;
+      }
+    }
+    .van-tabs__line{
+      bottom: .5rem;
+    }
+  }
+  .content {
+    padding: 18px 24px 0 24px;
+    color: #fff;
+    font-size: 12px;
+    line-height: 20px;
+    box-sizing: border-box;
+
+    img {
+      width: 100%;
+    }
+    .item {
+      margin-bottom: 20px;
+    }
+    .title {
+      font-size: 14px;
+      font-weight: 500;
+    }
+  }
+}

+ 65 - 0
src/tenant/music/coursewarePlay/component/o-guide/index.tsx

@@ -0,0 +1,65 @@
+import { Tab, Tabs } from 'vant';
+import { defineComponent, ref } from 'vue';
+import styles from './index.module.less';
+import icon1 from './image/1.png';
+import icon2 from './image/2.png';
+import icon3 from './image/3.png';
+import Guide from './guide/guide';
+
+export default defineComponent({
+  name: 'o-guide',
+  setup(props, ctx) {
+    const active = ref('tv');
+    return () => (
+      <div class={styles.guideWrap}>
+        <Tabs animated swipeable v-model:active={active.value}>
+          <Tab title="电视投屏" name="tv">
+            <Guide class={styles.tv} />
+          </Tab>
+          <Tab title="电脑投屏" name="computer">
+            <div class={styles.content}>
+              <div style={{ fontSize: '16px', marginBottom: '20px' }}>
+                通过爱思投屏助手将手机投屏至电脑
+              </div>
+              <div class={styles.item}>
+                <div class={styles.title}>第1步</div>
+                <div>在需要投屏的电脑上打开以下链接</div>
+                <a href="http://pc.i4.cn/pro_screen.html" target="_blank">
+                  <div style={{ color: '#33BDFF' }}>
+                    http://pc.i4.cn/pro_screen.html
+                  </div>
+                </a>
+              </div>
+
+              <div class={styles.item}>
+                <div class={styles.title}>第2步</div>
+                <div>下载并安装【爱思投屏助手】</div>
+                <div style={{ padding: '20px 0', textAlign: 'center' }}>
+                  <img src={icon1} />
+                </div>
+              </div>
+
+              <div class={styles.item}>
+                <div class={styles.title}>第3步</div>
+                <div>打开【爱思投屏助手】</div>
+                <div style={{ padding: '20px 0', textAlign: 'center' }}>
+                  <img src={icon2} />
+                </div>
+              </div>
+
+              <div class={styles.item}>
+                <div class={styles.title}>第4步</div>
+                <div>
+                  使用数据线将手机与电脑连接,出现投屏准备就绪提示后,点击【开始投屏】
+                </div>
+                <div style={{ padding: '20px 0', textAlign: 'center' }}>
+                  <img style={{ width: '50%' }} src={icon3} />
+                </div>
+              </div>
+            </div>
+          </Tab>
+        </Tabs>
+      </div>
+    );
+  }
+});

+ 47 - 0
src/tenant/music/coursewarePlay/component/play-loading/index.module.less

@@ -0,0 +1,47 @@
+.audioAnimate {
+  // position: absolute;
+  // left: 0;
+  // right: 0;
+  // top: 0;
+  // bottom: 0;
+  // background-color: rgba(0, 0, 0, .6);
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  padding-bottom: 30%;
+
+  div {
+    width: 2px;
+    height: 10px;
+    background: linear-gradient(135deg, #FF6E8E; 0%, #ff6e8e 100%);
+    transform-origin: bottom;
+    border-radius: 5px;
+    margin: 0 1px 0;
+  }
+
+  & div:nth-child(1) {
+    animation: musicWave 0.5s infinite linear both alternate;
+  }
+
+  & div:nth-child(2) {
+    animation: musicWave 0.2s infinite linear both alternate;
+  }
+
+  & div:nth-child(3) {
+    animation: musicWave 0.6s infinite linear both alternate;
+  }
+
+  & div:nth-child(4) {
+    animation: musicWave 0.3s infinite linear both alternate;
+  }
+}
+
+@keyframes musicWave {
+  0% {
+    height: 3px;
+  }
+
+  100% {
+    height: 10px;
+  }
+}

+ 16 - 0
src/tenant/music/coursewarePlay/component/play-loading/index.tsx

@@ -0,0 +1,16 @@
+import { defineComponent } from 'vue';
+import styles from './index.module.less';
+
+export default defineComponent({
+  name: 'playLoading',
+  setup() {
+    return () => (
+      <div class={styles.audioAnimate}>
+        <div></div>
+        <div></div>
+        <div></div>
+        <div></div>
+      </div>
+    );
+  }
+});

+ 168 - 0
src/tenant/music/coursewarePlay/component/point.module.less

@@ -0,0 +1,168 @@
+.container {
+  display: flex;
+  flex-direction: column;
+  min-width: 288px;
+  max-width: 288px;
+  height: 100vh;
+  color: #fff;
+  font-size: 12px;
+  box-sizing: border-box;
+}
+
+.pointHead {
+  display: flex;
+  align-items: center;
+  padding: 13px 10px 15px 15px;
+  flex-shrink: 0;
+  font-size: 14px;
+
+  img {
+    width: 16px;
+    height: 16px;
+    margin-right: 7px;
+  }
+}
+
+.content {
+  flex: 1;
+  overflow-y: auto;
+  padding: 0 20px;
+}
+
+.collapse {
+  &.childActive {
+    :global {
+      .van-cell {
+        color: #fff;
+      }
+    }
+
+    .arrow {
+      opacity: 1;
+    }
+  }
+
+  &:after {
+    display: none;
+    border-width: 0;
+  }
+
+  .borderTop {
+    border-top: 1px solid rgba(255, 255, 255, 0.2);
+  }
+
+  :global {
+    .van-cell {
+      background: transparent;
+      font-size: 14px;
+      color: rgba(255, 255, 255, 0.5);
+      padding: 6px 0;
+    }
+
+    .van-cell__title {
+      font-weight: 600;
+    }
+
+    .van-collapse-item__title--expanded {
+      color: #fff;
+    }
+
+    .van-collapse-item--border:after {
+      display: none;
+    }
+
+    .van-collapse-item__content {
+      background: transparent;
+      color: #fff;
+      font-size: 10px;
+      padding: 0 0 6px;
+    }
+
+    // .van-hairline--top:after {
+    //   border-color: var(--van-border-color) !important;
+    // }
+
+    // .van-collapse-item {
+    //   border-top: 1px solid rgba(255, 255, 255, 0.2);
+    // }
+  }
+
+  .childCollapseItem {
+    padding-left: 14px;
+    border-top: 0;
+
+    :global {
+      .van-cell {
+        padding-left: 0;
+        font-size: 14px;
+      }
+
+      .van-collapse-item__content {
+        padding-left: 0px;
+      }
+    }
+  }
+
+  .arrow {
+    display: block;
+    width: 12px;
+    height: 12px;
+    margin-right: 6px;
+    transition: all 0.3s;
+    opacity: 0.5;
+  }
+
+  :global(.van-collapse-item__title--expanded) {
+    .arrow {
+      transform: rotate(90deg);
+      opacity: 1;
+    }
+  }
+}
+
+.item {
+  display: flex;
+  align-items: center;
+  justify-content: flex-start;
+  padding: 5px 5px 5px 0;
+  border-radius: 6px;
+  font-size: 12px;
+  position: relative;
+
+  :global {
+    .van-icon {
+      display: none;
+    }
+
+    .van-image {
+      margin-left: 16px;
+      margin-right: 6px;
+      margin-top: -1px;
+      width: 15px;
+      height: 16px;
+    }
+  }
+}
+
+.itemActive {
+  background: rgba(255, 255, 225, 0.08);
+  color: #ff6e8e;
+
+  :global {
+    .van-icon {
+      display: block;
+    }
+  }
+
+  .playLoading {
+    display: block;
+  }
+}
+
+.playLoading {
+  display: none;
+  font-size: 12px;
+  position: absolute;
+  right: 4px;
+  top: 9px;
+}

+ 216 - 0
src/tenant/music/coursewarePlay/component/points.tsx

@@ -0,0 +1,216 @@
+import { defineComponent, reactive, watch } from 'vue'
+import styles from './point.module.less'
+import { iconMulv, iconArrow } from '../image/icons.json'
+// import iconZhibo from '../image/icon-load.gif';
+import {
+  iconImage,
+  iconImageActive,
+  iconVideo,
+  iconVideoActive,
+  iconSong,
+  iconSongActive
+} from '../image/icons.json'
+import { Collapse, CollapseItem, Icon, Image } from 'vant'
+import PlayLoading from './play-loading'
+export default defineComponent({
+  name: 'points-list',
+  props: {
+    data: {
+      type: Array,
+      default: () => []
+    },
+    tabActive: {
+      type: String,
+      default: ''
+    },
+    itemActive: {
+      type: String,
+      default: ''
+    }
+  },
+  emits: ['handleSelect'],
+  setup(props, { emit }) {
+    const pointData = reactive({
+      active: props.tabActive[0] || '',
+      childActive: props.tabActive[1] || ''
+    })
+    watch(
+      () => props.tabActive,
+      () => {
+        pointData.active = props.tabActive[0] || ''
+        pointData.childActive = props.tabActive[1] || ''
+      }
+    )
+
+    console.log(
+      pointData.active,
+      'pointData.active',
+      props.data,
+      props.tabActive
+    )
+
+    // 获取对应图片
+    const getImage = (item: any) => {
+      if (item.type === 'VIDEO') {
+        return props.itemActive == item.id ? iconVideoActive : iconVideo
+      } else if (['IMAGE', 'IMG'].includes(item.type)) {
+        return props.itemActive == item.id ? iconImageActive : iconImage
+      } else if (item.type === 'SONG') {
+        return props.itemActive == item.id ? iconSongActive : iconSong
+      } else {
+        return props.itemActive == item.id ? iconVideoActive : iconVideo
+      }
+    }
+    return () => (
+      <div class={styles.container}>
+        <div class={styles.pointHead}>
+          <img src={iconMulv} />
+          课程目录
+        </div>
+        <div class={styles.content}>
+          <Collapse
+            class={styles.collapse}
+            modelValue={pointData.active}
+            onUpdate:modelValue={(val: any) => {
+              pointData.active = val
+            }}
+            accordion
+          >
+            {props.data.map((item: any, index: number) => {
+              return (
+                <CollapseItem
+                  center
+                  border={false}
+                  class={index > 0 ? styles.borderTop : ''}
+                  isLink={false}
+                  title={item.name}
+                  name={item.id}
+                >
+                  {{
+                    default: () => (
+                      <>
+                        {Array.isArray(item?.materialList) &&
+                          item.materialList.map((n: any) => {
+                            return (
+                              <div
+                                class={[
+                                  styles.item,
+                                  props.itemActive == n.id
+                                    ? styles.itemActive
+                                    : ''
+                                ]}
+                                onClick={() => {
+                                  emit('handleSelect', {
+                                    itemActive: n.id,
+                                    tabActive: item.id,
+                                    tabName: item.name
+                                  })
+                                }}
+                              >
+                                <Image
+                                  src={getImage(n)}
+                                  class={styles.itemImage}
+                                />
+                                <span
+                                  style={{ width: '80%' }}
+                                  class="van-ellipsis"
+                                >
+                                  {n.name}
+                                </span>
+                                {/* <Icon name={iconZhibo} /> */}
+                                <div class={styles.playLoading}>
+                                  <PlayLoading />
+                                </div>
+                              </div>
+                            )
+                          })}
+
+                        {Array.isArray(item?.children) && (
+                          <Collapse
+                            class={[
+                              styles.collapse,
+                              pointData.active === item.id
+                                ? styles.childActive
+                                : ''
+                            ]}
+                            modelValue={pointData.childActive}
+                            onUpdate:modelValue={(val: any) => {
+                              pointData.childActive = val
+                            }}
+                            accordion
+                          >
+                            {item?.children.map((child: any) => {
+                              return (
+                                <CollapseItem
+                                  center
+                                  border={false}
+                                  isLink={false}
+                                  title={child.name}
+                                  name={child.id}
+                                  class={styles.childCollapseItem}
+                                >
+                                  {{
+                                    default: () => (
+                                      <>
+                                        {Array.isArray(child?.materialList) &&
+                                          child.materialList.map((n: any) => {
+                                            return (
+                                              <div
+                                                class={[
+                                                  styles.item,
+                                                  props.itemActive == n.id
+                                                    ? styles.itemActive
+                                                    : ''
+                                                ]}
+                                                onClick={() => {
+                                                  emit('handleSelect', {
+                                                    itemActive: n.id,
+                                                    tabActive: child.id,
+                                                    tabName: child.name
+                                                  })
+                                                }}
+                                              >
+                                                <Image
+                                                  src={getImage(n)}
+                                                  class={styles.itemImage}
+                                                />
+                                                <span
+                                                  style={{ width: '73%' }}
+                                                  class="van-ellipsis"
+                                                >
+                                                  {n.name}
+                                                </span>
+                                                {/* <Icon name={iconZhibo} /> */}
+                                                <div class={styles.playLoading}>
+                                                  <PlayLoading />
+                                                </div>
+                                              </div>
+                                            )
+                                          })}
+                                      </>
+                                    ),
+                                    icon: () => (
+                                      <img
+                                        class={styles.arrow}
+                                        src={iconArrow}
+                                      />
+                                    )
+                                  }}
+                                </CollapseItem>
+                              )
+                            })}
+                          </Collapse>
+                        )}
+                      </>
+                    ),
+                    icon: () => <img class={styles.arrow} src={iconArrow} />
+                  }}
+                </CollapseItem>
+              )
+            })}
+          </Collapse>
+        </div>
+      </div>
+    )
+  }
+})

+ 32 - 0
src/tenant/music/coursewarePlay/component/tool.module.less

@@ -0,0 +1,32 @@
+.tool {
+    position: relative;
+    width: 220px;
+    box-sizing: border-box;
+    height: 100vh;
+    color: #fff;
+    font-size: 13px;
+    font-weight: 500;
+    line-height: 18px;
+    * {
+        box-sizing: border-box;
+    }
+}
+
+.title {
+    padding: 12px 18px;
+}
+.grid{
+    :global{
+        .van-grid-item__content{
+            background: transparent;
+            padding: var(--van-padding-xs) var(--van-padding-xs);
+        }
+        .van-grid-item__text{
+            color: inherit;
+            margin-top: 2px;
+        }
+        .van-grid-item__icon{
+            font-size: 22px;
+        }
+    }
+}

+ 39 - 0
src/tenant/music/coursewarePlay/component/tool.tsx

@@ -0,0 +1,39 @@
+import { Grid, GridItem } from 'vant';
+import { defineComponent } from 'vue';
+import styles from './tool.module.less';
+import iconPen from '../image/icon-pen.png';
+
+export type ToolType = 'init' | 'pen';
+
+export type ToolItem = {
+  type: ToolType;
+  name: string;
+  icon: string;
+};
+
+export default defineComponent({
+  name: 'o-tool',
+  emits: ['handleTool'],
+  setup(props, { emit }) {
+    const tool: ToolItem[] = [
+      {
+        type: 'pen',
+        icon: iconPen,
+        name: '批注'
+      }
+    ];
+    return () => (
+      <div class={styles.tool}>
+        <div class={styles.title}>教学功能</div>
+        <Grid class={styles.grid} columnNum={3} border={false}>
+          {tool.map(item => (
+            <GridItem
+              icon={item.icon}
+              text={item.name}
+              onClick={() => emit('handleTool', item)}></GridItem>
+          ))}
+        </Grid>
+      </div>
+    );
+  }
+});

+ 43 - 0
src/tenant/music/coursewarePlay/component/tools/pen.module.less

@@ -0,0 +1,43 @@
+.pen{
+    position: fixed;
+    left: 0;
+    right: 0;
+    bottom: 0;
+    top: 0;
+    z-index: 11;
+}
+.open{
+    display: block;
+}
+.hide{
+    display: none;
+}
+.iframe{
+    display: block;
+    width: 100%;
+    height: 100%;
+    border: 0;
+}
+.dely{
+    opacity: 0;
+}
+.rightItem{
+    position: absolute;
+    right: 15Px;
+    bottom: 0;
+    bottom: constant(safe-area-inset-bottom);
+    bottom: env(safe-area-inset-bottom);
+    width: 50Px;
+    height: 54Px;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+}
+.img{
+    position: absolute;
+    left: 0;
+    top: 0;
+    width: 100%;
+    height: 100%;
+    display: block;
+}

+ 145 - 0
src/tenant/music/coursewarePlay/component/tools/pen.tsx

@@ -0,0 +1,145 @@
+import { promisefiyPostMessage } from '@/helpers/native-message'
+import html2canvas from 'html2canvas'
+import { Toast } from 'vant'
+import {
+  defineComponent,
+  toRefs,
+  ref,
+  reactive,
+  onMounted,
+  onUnmounted,
+  nextTick
+} from 'vue'
+import styles from './pen.module.less'
+
+export default defineComponent({
+  name: 'tools-pen',
+  props: {
+    show: {
+      type: Boolean,
+      default: false
+    },
+    close: {
+      type: Function,
+      default: () => ({})
+    }
+  },
+  setup(props) {
+    const { show } = toRefs(props)
+    const firstRender = ref(true)
+    const src = /(localhost|192)/.test(location.host)
+      ? 'https://test.lexiaoya.cn/whiteboard-noCollab'
+      : `https://kt.colexiu.com/classroom-whiteboard`
+
+    const exportImg = (event: MessageEvent) => {
+      const data = event.data
+      // console.log('🚀 ~ event:', data)
+      if (data.api === 'excalidraw_exportImg') {
+        imgs.base64 = data.base64
+        imgs.exported = true
+        nextTick(() => {
+          onSaveImg()
+        })
+      }
+    }
+    onMounted(() => {
+      window.addEventListener('message', exportImg)
+    })
+    onUnmounted(() => {
+      window.removeEventListener('message', exportImg)
+    })
+
+    const imgs = reactive({
+      exported: false,
+      saveLoading: false,
+      base64: '',
+      image: ''
+    })
+
+    const saveImg = async () => {
+      Toast.loading({ message: '图片生成中...', forbidClick: true })
+      setTimeout(() => {
+        imgs.saveLoading = false
+      }, 100)
+      const res = await promisefiyPostMessage({
+        api: 'savePicture',
+        content: {
+          base64: imgs.image
+        }
+      })
+      if (res?.content?.status === 'success') {
+        Toast.success('保存成功')
+      } else {
+        Toast.fail('保存失败')
+      }
+      imgs.exported = false
+    }
+
+    const onSaveImg = async () => {
+      // 判断是否在保存中...
+      if (imgs.saveLoading) {
+        return
+      }
+      console.log('开始')
+      imgs.saveLoading = true
+      const container: any = document.getElementById(`app`)
+      html2canvas(container, {
+        allowTaint: true,
+        useCORS: true,
+        backgroundColor: null
+      })
+        .then(async canvas => {
+          // console.log("🚀 ~ canvas:", canvas)
+          // document.body.appendChild(canvas)
+          // const url = await canvas.toDataURL()
+          try {
+            imgs.image = canvas.toDataURL()
+          } catch (error) {
+            console.log(error)
+          }
+          console.log('🚀 ~ imgs.image:', imgs.image)
+          saveImg()
+        })
+        .catch(error => {
+          console.log('🚀 ~ error:', error)
+          Toast.clear()
+          imgs.saveLoading = false
+          imgs.exported = false
+        })
+    }
+
+    return () => (
+      <div
+        class={[
+          styles.pen,
+          firstRender.value ? styles.dely : '',
+          show.value ? styles.open : styles.hide
+        ]}
+      >
+        <iframe
+          class={styles.iframe}
+          frameborder="0"
+          width="100vw"
+          height="100vh"
+          src={src}
+          onLoad={() => {
+            firstRender.value = false
+          }}
+        ></iframe>
+        {imgs.exported ? (
+          <img crossorigin="anonymous" class={styles.img} src={imgs.base64} />
+        ) : (
+          <div class={styles.rightItem} onClick={() => props.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"
+              ></path>
+            </svg>
+          </div>
+        )}
+      </div>
+    )
+  }
+})

+ 216 - 0
src/tenant/music/coursewarePlay/component/video-item/index.module.less

@@ -0,0 +1,216 @@
+.videoWrap {
+  position: relative;
+  width: 100%;
+  height: 100%;
+
+  :global {
+    .plyr--video {
+      width: 100%;
+      height: 100%;
+    }
+
+    .plyr__time {
+      display: block !important;
+    }
+
+    .plyr__video-wrapper {
+      pointer-events: none;
+    }
+  }
+}
+
+:global(.bottomFixed).controls {
+  width: 100%;
+  background: linear-gradient(0deg, rgba(0, 0, 0, 0.5), transparent);
+  padding: 0 !important;
+  flex-direction: column;
+  transition: all 0.5s;
+
+  .time {
+    display: flex;
+    justify-content: space-between;
+    width: 100%;
+    color: #fff;
+    font-size: 10px;
+    padding: 4px 20px;
+
+    :global {
+      .plyr__time+.plyr__time:before {
+        content: '';
+      }
+    }
+  }
+
+  .slider {
+    width: 100%;
+    padding: 0 20px;
+
+    :global {
+      .van-slider__button {
+        background: var(--van-primary);
+      }
+
+      .van-loading {
+        width: 100%;
+        height: 100%;
+      }
+    }
+  }
+
+  .actions {
+    display: flex;
+    justify-content: space-between;
+    width: 100%;
+    color: #fff;
+    font-size: 12px;
+    padding: 0 20px;
+    align-items: center;
+
+    .actionWrap {
+      display: flex;
+    }
+
+    .actionBtn {
+      display: flex;
+      width: 30px;
+      height: 30px;
+      padding: 4px 0;
+      background: transparent;
+    }
+
+    .actionBtn>img {
+      width: 100%;
+      height: 100%;
+    }
+
+    :global {
+      .van-loading__circular {
+        width: 100%;
+        height: 100%;
+      }
+    }
+
+    .playIcon {
+      display: none;
+    }
+
+    .btnPlay img:nth-child(2) {
+      display: block;
+    }
+
+    .btnPause img:nth-child(3) {
+      display: block;
+    }
+
+    .btnPlay,
+    .btnPause {
+      :global {
+        .van-loading {
+          display: none;
+        }
+      }
+    }
+
+    .loopBtn {
+      :global {
+        .loop {
+          display: block;
+        }
+
+        .loopActive {
+          display: none;
+        }
+      }
+    }
+
+    .loopBtn.active {
+      :global {
+        .loop {
+          display: none;
+        }
+
+        .loopActive {
+          display: block;
+        }
+      }
+    }
+
+
+    .speedBtn {
+      position: relative;
+    }
+
+    .popoverGroup {
+      position: absolute;
+      z-index: 99;
+      left: -22px;
+      background-color: #fff;
+      border-radius: 8px;
+      bottom: 38px;
+      width: 80px;
+      color: #333;
+      padding: 4px 0;
+      display: none;
+
+      &.active {
+        display: block;
+      }
+    }
+  }
+}
+
+.sliderPopup {
+  position: absolute;
+  z-index: 9999;
+  left: 74px;
+  bottom: 46px;
+  display: flex;
+  align-items: center;
+  flex-direction: column;
+  height: 165px;
+  width: 45px;
+  padding: 10px 0 15px;
+  background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFoAAAGaCAMAAAC46aQwAAAAOVBMVEUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC8dlA9AAAAE3RSTlNaAAxUVwdPRSc7MzYsEEoXGgshjXaABwAAAZBJREFUeNrt1GtO40AQReF77X46tvPY/2IHhiCDA0Ez7pb4cb4FHLVaVSU/isM/i360S5+nnIL+Q0h5On+fXuqoQ8a6fJm+VjVQr4/pEtREKPt0VTP1UzpmNZTjh3RWU3lLn9TY6T1d1Fx5S8eg5kL8m57UwfSaHoI6CIPlSV1MlpO6SNaiThbN6mRWVSdVWZ1kJXWSFNRJEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADg9wnqJGhVJ6uyOsmq6qSqqJOiqzq5yqu6WC3P6mK2HEd1MEbLfZ49+zXtpOaS39IXNXe5p13UWPF72ic1dfKW9ql1eUu7tP2NLf3inNREOnuXflFGHTaWrSdvYsk6JJfoXXoTb1PNabwL+kEY71Ku023r7tOPYv5pFJ6Rn5qfj8KRtC+jvjGefSztYdWX1sFH0k82tUY3SPsWtDfZTdJekj4JN7dKO1Z9kBY3ST+er3x10/R2vqrdOO04BUnp4nbpTVwug30g3cwf1jMLKix6VfsAAAAASUVORK5CYII=') no-repeat top center;
+  background-size: contain;
+
+  .iconAdd,
+  .iconCut {
+    display: inline-block;
+    width: 24px;
+    height: 24px;
+    background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAMAAABg3Am1AAABGlBMVEUAAAAAraMAraEArKEArqIA18kArqIA08UA1McAq6AAraAArKAArKEArKAArKAAq6EA1MYAq6AArKIAraMA2csAr6AA28kAn58A1skA0sQA1ccA2csA1cgArZ8A3M4Ar6IA1cgA2csA3M0A1MUA3M0A39EA2swA1skA1cgA3tAA3c8A3c0A08cA2swAq58A3M4A2csA4NL ///8A3tAA1sgA2s0A3M4A18kA4tQA2MsA3c8A2cwA1McArKAA08UA5NVm6+Jm6N9x599m6eEAx7oAvLDr/PsA0MNM49oAua2j8u2H8OmH7OZM6N4As6dt5t413dII1skD1skAy74AwLRf49ta49sn2s4X2MwT18tW4tpJ4Nc33tMq288faTE+AAAAMXRSTlMAGfxfUkc/9YXw6M/FtrCSUEM0MiMjCgj4z7quqG3x7tfPg1hS+Pjr69C6lZWKbVhYjYIARgAAAqJJREFUSMelk2lD2kAQhjfhkltQ0Xpbe98xJljCRo6EgApKoa32+P9/o7NOliEboB98Pj/v7MzsLotRS63tHb/Z2Xl9vLeWqrH/kdrPuE3B+SM7+6mV+voz15336/X615MXS/XNXTfuA7ubi/3TjOpjwHEOvyzy19yF9cF3LOt53P+w3IeA9X5R/fHfe9SpHyf0Y2ecgv/zttO5kwGwZT+I/Tmynwz4HeA3lY/4tm0fzu8K9vlwC/6vcRi46fVuIj7wjvx1mPY7+P1vYf3+GdB3APIbDbpBuN878H9Iv94WgTbVBx14K/0UHnD7gD4FpI8B05Tvah/W3+50/tC8GIjUB9/cQ7+Wge3fYUPoy4Dimy9rOHJTcD+m68VAN/Qb6AvW8ZLV50ABe9Y/8hF3FH+eMkA+csIEGXo+/TZyJQJXXaDVag2lbxivhK8FM//mbCHX0jcaGgTS1E9vcaCHPmCmIZBsztpfETCQJAQS7mzcpS1J30yIQEDrEUN3ARy6hQxnB1xYIpD0RIB+o6ArAq1wneRfBEkx9LSu+laLAqiHAU8MrQ1U37bxBBOROuBrDDg6V36vbeMJMd84YoKSp/gyoPqXQYkJDgbgR38jBciHgP+JCTQ9oPbnA4p+6egae6Q4oADYwFAEhuhTwC8ypMInlvIdr3u9a9MIE6hfNnmFhRRGdeo/el3kG6MCk1R1n3zlutAHpnqVzShzD9uhAyI64PEyI7bz3BP+8voTnt9mc2zlRAJ1df1YP7fFImxkuW8tK29MeXaDKWzk+ChQXrPc54jnyKeu8pz75+gjqDs+53nsR2G7rHM+mER8IwBdL+O8caoFDgy8wGnA/7Vcz+dAocqWUynqPIJerLDVaAelrLSzpQNtlUuhdDKRSKZRfir/ACF6Xp1EeZtPAAAAAElFTkSuQmCC') no-repeat center;
+    background-size: contain;
+    flex-shrink: 0;
+
+    &.disabled {
+      opacity: 0.7;
+    }
+  }
+
+  .iconCut {
+    background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAMAAABg3Am1AAABCFBMVEUAAAAAraMAraEArKEArKEArqIArqIA2swA1cgA1sgAraAArKAArKEArKAArKAAq6EA2MoArZ8Aq6AArKIAraMAr6AA28kAn58A18oA08cA1cgA1MYA1sgA0cQA3c4A1skA08YA2swA08UA1skA2MsA2s0A18kA1sgA39AAzsIA3tAA2swA39EA39AA39EA4tMA3tAA2s0A2MoA1skA3M4A1ccA49QA3c8A2cwA4NIA4dP ///8ArKAA08Zx599x5t8A5NXi+/kA0cMAyLsAvK8Aua1J4dg13dIAzsAAvrIAs6dt5t4s29AD1chJ595h49tX49oV18sO1soAxLdm5NxS4tgd2MwAwrWCpB5XAAAAMHRSTlMAGfzvX1I/V1FH6M/FtrCShm1DNDIjCgj486iEI/jx19DPurGV+Ovr0M26uqiHSCOYWU5AAAACYklEQVRIx6WT12LaMBSGZTMS9p4hSTO6FwGMRyXj2GbTNqPj/d+kOjYCGcnkot/19/8+R5KRQDNzdnP5tt1+c3lTzjTRS2S6HaIFDCmDdjdzVD99RQjzgwDl6jRW710T0R8Yxsee3D/pMJ/C+cbdxYnMPyNPmz9CP/iUsujfks16Ys8FnwaAW7F/M5nY9kycJ2A0Kh/MTx7XNPD7u7x/RIns0euQn9SfSXwW0C++coFrMqf+gxbXDwH9A3dfhH7AXj/F9oOv9/dD0fv9ZYcnFOOPqN9/x/wMIWRuPxzth0D/CwrpwgU/Cgvv60EHPoV+83z/gMR92TyA2WQrM53CfBaA+l0gfLhlPjAI4OfnfPMzAt7LHmjIgW9eIeA8zh8d+tZr8BWXm2d2/03g/gfzLV2hgTTh5gdfTDDfMtM0kCLUfylgbgMpGkiSXX/cSMwf95MQWAgXLJwP+BC4g0DKY7rwIJjf3/pjNwVL/wVf7OcCzB97sLQylflM53yKryBKQhse8U3OtxIIKHoSX5gHcIsIqEyPz2Myf+xXEKCoLvUpsf3jEENVUEBhGnnQOiDpd5YFFFLFC+GHD2HzB2i4irbkV0PphYHPAo61yiNGTfWjvtjvOEu1hnaU8HNkX3Fhx8MltKeVw57Yb3L9C5xrIY5GFj/LL5j1ZxsoQj2BfUM8zxBriRN1dEA9i1eu9MIcbYWzzOenymHsa+KBGkuMcw0koVVSMZ4udP4DlutjrJZaSE4tjylTzx2M6G9muJ6PKfkaiqdaUHEEtVBFx1EqxQSzE8WKIgjSUDqVTKbSIP8//wBbnlQnTJqlUQAAAABJRU5ErkJggg==') no-repeat center;
+    background-size: contain;
+  }
+
+  .sliderPoint {
+    background: #FFFFFF;
+    box-shadow: 0px 2px 4px 0px rgba(102, 102, 102, 0.77);
+    border-radius: 10px;
+    font-size: 14px;
+    font-weight: 500;
+    color: rgba(1, 193, 181, 1);
+    min-width: 35px;
+    text-align: center;
+    vertical-align: text-bottom;
+
+    span {
+      font-size: 12px;
+    }
+  }
+
+  :global {
+    .van-slider {
+      margin: 7px 0;
+    }
+  }
+}

+ 341 - 0
src/tenant/music/coursewarePlay/component/video-item/index.tsx

@@ -0,0 +1,341 @@
+import {
+  defineComponent,
+  nextTick,
+  onMounted,
+  reactive,
+  render,
+  toRefs,
+  watch
+} from 'vue';
+import 'plyr/dist/plyr.css';
+import Plyr from 'plyr';
+import styles from './index.module.less';
+
+import {
+  iconVideoBg,
+  iconLoop,
+  iconLoopActive,
+  iconPlay,
+  iconPause,
+  iconSpeed
+} from '../../image/icons.json';
+import { Popup, Slider } from 'vant';
+import { useElementBounding } from '@vueuse/core';
+
+export default defineComponent({
+  name: 'video-play',
+  props: {
+    item: {
+      type: Object,
+      default: () => {
+        return {};
+      }
+    },
+    activeModel: {
+      type: Boolean,
+      default: true
+    }
+  },
+  emits: ['play', 'pause', 'ended', 'close'],
+  setup(props, { emit, expose }) {
+    const { item } = toRefs(props);
+    const data = reactive({
+      videoContianerRef: null as unknown as HTMLAudioElement,
+      videoState: 'pause' as 'init' | 'play' | 'pause',
+      animationState: 'start' as 'start' | 'end',
+      videoItem: null as unknown as Plyr,
+      speedControl: false,
+      speedStyle: {
+        left: '1px'
+      },
+      defaultSpeed: 1 // 默认速度
+    });
+    const controlID = 'v' + Date.now() + Math.floor(Math.random() * 100);
+    const playBtnId = 'play' + Date.now() + Math.floor(Math.random() * 100);
+    const loopBtnId = 'loop' + Date.now() + Math.floor(Math.random() * 100);
+    const speedBtnId = 'speed' + Date.now() + Math.floor(Math.random() * 100);
+    const speedPopoverId =
+      'popover' + Date.now() + Math.floor(Math.random() * 100);
+
+    const togglePlay = (e: Event) => {
+      e.stopPropagation();
+      data.speedControl = false;
+      if (!data.videoContianerRef.paused) {
+        data.videoItem?.pause();
+      } else {
+        data.videoContianerRef?.play();
+      }
+    };
+    const toggleLoop = () => {
+      data.speedControl = false;
+      const loopBtn = document.getElementById(loopBtnId);
+      if (!loopBtn || !data.videoItem) return;
+      const isLoop = data.videoItem.loop;
+      if (isLoop) {
+        loopBtn.classList.remove(styles.active);
+      } else {
+        loopBtn.classList.add(styles.active);
+      }
+      data.videoItem.loop = !data.videoItem.loop;
+    };
+    const onDefault = () => {
+      document
+        .getElementById(controlID)
+        ?.addEventListener('click', (e: Event) => {
+          e.stopPropagation();
+          data.speedControl = false;
+          if (data.videoContianerRef.paused) return;
+          emit('close');
+        });
+      document.getElementById(controlID)?.addEventListener('touchmove', () => {
+        data.speedControl = false;
+        if (data.videoContianerRef.paused) return;
+        emit('close');
+      });
+      document.getElementById(playBtnId)?.addEventListener('click', togglePlay);
+      document.getElementById(loopBtnId)?.addEventListener('click', toggleLoop);
+
+      document.getElementById(speedBtnId)?.addEventListener('click', e => {
+        e.stopPropagation();
+        data.speedControl = !data.speedControl;
+      });
+
+      setName();
+    };
+    const setName = () => {
+      const nameEl = document.getElementById('videoItemName');
+      if (nameEl) {
+        nameEl.innerHTML = item.value.name || '';
+      }
+    };
+
+    const changePlayBtn = (code: string) => {
+      const playBtn = document.getElementById(playBtnId);
+      if (!playBtn) return;
+      if (code == 'play') {
+        playBtn.classList.remove(styles.btnPause);
+        playBtn.classList.add(styles.btnPlay);
+      } else {
+        playBtn.classList.remove(styles.btnPlay);
+        playBtn.classList.add(styles.btnPause);
+      }
+    };
+    const controls = `
+            <div id="${controlID}" class="plyr__controls bottomFixed ${styles.controls}">
+                <div class="${styles.time}">
+                    <div class="plyr__time plyr__time--current" aria-label="Current time">00:00</div>
+                    <div class="plyr__time plyr__time--duration" aria-label="Duration">00:00</div>
+                </div>
+                <div class="${styles.slider}">
+                    <div class="plyr__progress">
+                        <input data-plyr="seek" type="range" min="0" max="100" step="0.01" value="0" aria-label="Seek">
+                        <progress class="plyr__progress__buffer" min="0" max="100" value="0">% buffered</progress>
+                        <span role="tooltip" class="plyr__tooltip">00:00</span>
+                    </div>
+                </div>
+                <div class="${styles.actions}">
+                    <div class="${styles.actionWrap}">
+                        <button id="${playBtnId}" class="${styles.actionBtn}">
+                            <div class="van-loading van-loading--circular" aria-live="polite" aria-busy="true"><span class="van-loading__spinner van-loading__spinner--circular" style="color: rgb(255, 255, 255);"><svg class="van-loading__circular" viewBox="25 25 50 50"><circle cx="50" cy="50" r="20" fill="none"></circle></svg></span></div>
+                            <img class="${styles.playIcon}" src="${iconPlay}" />
+                            <img class="${styles.playIcon}" src="${iconPause}" />
+                        </button>
+                        <button id="${loopBtnId}" class="${styles.actionBtn} ${styles.loopBtn}" style="margin:0 2px;">
+                            <img class="loop" src="${iconLoop}" />
+                            <img class="loopActive" src="${iconLoopActive}" />
+                        </button>
+                        <div style="position: relative">
+                          <button id="${speedBtnId}" class="${styles.actionBtn} ${styles.speedBtn}">
+                              <img class="loop" src="${iconSpeed}" />
+                          </button>
+                        </div>
+                    </div>
+                    <div id="videoItemName"></div>
+                </div>
+            </div>`;
+
+    onMounted(() => {
+      data.videoItem = new Plyr(data.videoContianerRef, {
+        autoplay: true,
+        controls: controls,
+        ratio: '16:9', // 强制所有视频的纵横比
+        hideControls: false, // 在 2 秒没有鼠标或焦点移动、控制元素模糊(制表符退出)、播放开始或进入全屏时自动隐藏视频控件。只要移动鼠标、聚焦控制元素或暂停播放,控件就会立即重新出现。
+        clickToPlay: false, // 单击(或点击)视频容器将切换播放/暂停
+        fullscreen: { enabled: false, fallback: false, iosNative: false } // 不适用全屏
+      });
+
+      nextTick(() => {
+        onDefault();
+      });
+    });
+
+    const toggleHideControl = (isShow: boolean) => {
+      data.videoItem?.toggleControls(isShow);
+
+      if (!isShow) {
+        data.speedControl = isShow;
+      }
+    };
+    watch(
+      () => props.activeModel,
+      () => {
+        toggleHideControl(props.activeModel);
+      }
+    );
+
+    watch(
+      () => props.item,
+      () => {
+        setName();
+        // 设置视屏播放器的默认速度
+        if (data.videoItem) data.videoItem.speed = data.defaultSpeed || 1;
+
+        // 切换的时候隐藏
+        data.speedControl = false;
+      }
+    );
+    let videoTimer = null as any;
+    const handlePlayVideo = () => {
+      clearTimeout(videoTimer);
+      nextTick(() => {
+        data.videoContianerRef.play().catch(err => {
+          console.log('🚀 ~ err:', err);
+          videoTimer = setTimeout(() => {
+            if (err?.message?.includes('play()')) {
+              emit('play');
+            }
+            handlePlayVideo();
+          }, 1000);
+        });
+      });
+    };
+
+    let videoErrorTimer = null as any;
+    let videoErrorCount = 0;
+    const handleErrorVideo = () => {
+      if (videoErrorCount > 5) {
+        return;
+      }
+      clearTimeout(videoErrorTimer);
+      nextTick(() => {
+        videoErrorTimer = setTimeout(() => {
+          data.videoContianerRef.src = props.item?.content;
+
+          emit('play');
+          data.videoContianerRef.load();
+          // eslint-disable-next-line @typescript-eslint/no-unused-vars
+          handleErrorVideo();
+        }, 1000);
+      });
+      videoErrorCount++;
+    };
+    const getVideoRef = () => {
+      return data.videoContianerRef;
+    };
+    expose({
+      getVideoRef
+    });
+
+    return () => (
+      <div class={styles.videoWrap}>
+        <video
+          ref={el =>
+            (data.videoContianerRef = el as unknown as HTMLAudioElement)
+          }
+          class={styles.itemDiv}
+          src={props.item?.content}
+          poster={iconVideoBg}
+          webkit-playsinline
+          playsinline
+          x5-video-player-type="h5"
+          onLoadedmetadata={() => {
+            data.videoState = 'pause';
+            changePlayBtn('play');
+            nextTick(() => {
+              data.videoContianerRef.currentTime = 0;
+              nextTick(handlePlayVideo);
+            });
+          }}
+          onPlay={() => {
+            videoErrorCount = 0;
+            // console.log('开始播放')
+            data.videoState = 'play';
+            changePlayBtn('pause');
+            emit('close');
+            emit('play');
+            clearTimeout(videoErrorTimer);
+          }}
+          onPause={() => {
+            // console.log('暂停播放')
+            data.videoState = 'pause';
+            changePlayBtn('play');
+            emit('pause');
+          }}
+          onEnded={() => {
+            // console.log('播放结束')
+            data.videoState = 'pause';
+            changePlayBtn('play');
+            emit('ended');
+          }}
+          onError={handleErrorVideo}></video>
+
+        <div
+          style={{
+            display: data.speedControl ? 'block' : 'none'
+          }}>
+          <div
+            class={styles.sliderPopup}
+            onClick={(e: Event) => {
+              e.stopPropagation();
+            }}>
+            <i
+              class={styles.iconAdd}
+              onClick={() => {
+                if (data.defaultSpeed >= 1.5) {
+                  return;
+                }
+
+                if (data.videoItem) {
+                  data.defaultSpeed = (data.defaultSpeed * 10 + 1) / 10;
+                  data.videoItem.speed = data.defaultSpeed;
+                }
+              }}></i>
+            <Slider
+              min={0.5}
+              max={1.5}
+              step={0.1}
+              v-model={data.defaultSpeed}
+              vertical
+              barHeight={5}
+              reverse
+              onChange={() => {
+                if (data.videoItem) {
+                  data.videoItem.speed = data.defaultSpeed;
+                }
+              }}>
+              {{
+                button: () => (
+                  <div class={styles.sliderPoint}>
+                    {data.defaultSpeed}
+                    <span>x</span>
+                  </div>
+                )
+              }}
+            </Slider>
+            <i
+              class={[styles.iconCut]}
+              onClick={() => {
+                if (data.defaultSpeed <= 0.5) {
+                  return;
+                }
+                if (data.videoItem) {
+                  data.defaultSpeed = (data.defaultSpeed * 10 - 1) / 10;
+                  data.videoItem.speed = data.defaultSpeed;
+                }
+              }}></i>
+          </div>
+        </div>
+      </div>
+    );
+  }
+});

+ 177 - 0
src/tenant/music/coursewarePlay/component/video-item/video-play.tsx

@@ -0,0 +1,177 @@
+import { defineComponent, nextTick, onMounted, toRefs } from 'vue'
+import 'plyr/dist/plyr.css'
+import Plyr from 'plyr'
+import { ref } from 'vue'
+import styles from './video.module.less'
+
+import iconLoop from '../image/icon-loop.svg'
+import iconLoopActive from '../image/icon-loop-active.svg'
+import iconplay from '../image/icon-play.svg'
+import iconpause from '../image/icon-pause.svg'
+
+export default defineComponent({
+  name: 'video-play',
+  props: {
+    item: {
+      type: Object,
+      default: () => {
+        return {}
+      }
+    },
+    isEmtry: {
+      type: Boolean,
+      default: false
+    },
+    isActive: {
+      type: Boolean,
+      default: false
+    }
+  },
+  emits: ['loadedmetadata', 'togglePlay', 'ended', 'reset', 'prepare'],
+  setup(props, { emit, expose }) {
+    const { item, isEmtry } = toRefs(props)
+    const videoRef = ref()
+    const videoItem: any = ref()
+    const controlID = 'v' + Date.now() + Math.floor(Math.random() * 100)
+    const playBtnId = 'play' + Date.now() + Math.floor(Math.random() * 100)
+    const loopBtnId = 'loop' + Date.now() + Math.floor(Math.random() * 100)
+    const toggleHideControl = (isShow: false) => {
+      videoItem.value?.toggleControls(isShow)
+    }
+    const togglePlay = (e: Event) => {
+      e.stopPropagation()
+      videoItem.value?.togglePlay()
+    }
+    const toggleLoop = () => {
+      const loopBtn = document.getElementById(loopBtnId)
+      if (!loopBtn || !videoItem.value) return
+      const isLoop = videoItem.value.loop
+      if (isLoop) {
+        loopBtn.classList.remove(styles.active)
+      } else {
+        loopBtn.classList.add(styles.active)
+      }
+      videoItem.value.loop = !videoItem.value.loop
+    }
+    const onDefault = () => {
+      document.getElementById(controlID)?.addEventListener('click', (e: Event) => {
+        e.stopPropagation()
+        emit('reset')
+      })
+      document.getElementById(playBtnId)?.addEventListener('click', togglePlay)
+      document.getElementById(loopBtnId)?.addEventListener('click', toggleLoop)
+    }
+
+    const changePlayBtn = (code: string) => {
+      const playBtn = document.getElementById(playBtnId)
+      if (!playBtn) return
+      if (code == 'play') {
+        playBtn.classList.remove(styles.btnPause)
+        playBtn.classList.add(styles.btnPlay)
+      } else {
+        playBtn.classList.remove(styles.btnPlay)
+        playBtn.classList.add(styles.btnPause)
+      }
+    }
+    const controls = `
+            <div id="${controlID}" class="plyr__controls bottomFixed ${styles.controls}">
+                <div class="${styles.time}">
+                    <div class="plyr__time plyr__time--current" aria-label="Current time">00:00</div>
+                    <div class="plyr__time plyr__time--duration" aria-label="Duration">00:00</div>
+                </div>
+                <div class="${styles.slider}">
+                    <div class="plyr__progress">
+                        <input data-plyr="seek" type="range" min="0" max="100" step="0.01" value="0" aria-label="Seek">
+                        <progress class="plyr__progress__buffer" min="0" max="100" value="0">% buffered</progress>
+                        <span role="tooltip" class="plyr__tooltip">00:00</span>
+                    </div>
+                </div>
+                <div class="${styles.actions}">
+                    <div class="${styles.actionWrap}">
+                        <button id="${playBtnId}" class="${styles.actionBtn}">
+                            <div class="van-loading van-loading--circular" aria-live="polite" aria-busy="true"><span class="van-loading__spinner van-loading__spinner--circular" style="color: rgb(255, 255, 255);"><svg class="van-loading__circular" viewBox="25 25 50 50"><circle cx="50" cy="50" r="20" fill="none"></circle></svg></span></div>
+                            <img class="${styles.playIcon}" src="${iconplay}" />
+                            <img class="${styles.playIcon}" src="${iconpause}" />
+                        </button>
+                        <button id="${loopBtnId}" class="${styles.actionBtn} ${styles.loopBtn}">
+                            <img class="loop" src="${iconLoop}" />
+                            <img class="loopActive" src="${iconLoopActive}" />
+                        </button>
+                    </div>
+                    <div>${item.value.name}</div>
+                </div>
+            </div>`
+
+    onMounted(() => {
+      emit('prepare', false)
+      videoItem.value = new Plyr(videoRef.value, {
+        autoplay: false,
+        controls: controls,
+        autopause: false, // 一次只允许
+        ratio: '16:9', // 强制所有视频的纵横比
+        hideControls: false, // 在 2 秒没有鼠标或焦点移动、控制元素模糊(制表符退出)、播放开始或进入全屏时自动隐藏视频控件。只要移动鼠标、聚焦控制元素或暂停播放,控件就会立即重新出现。
+        clickToPlay: false, // 单击(或点击)视频容器将切换播放/暂停
+        fullscreen: { enabled: false, fallback: false, iosNative: false } // 不适用全屏
+      })
+      if (videoItem.value) {
+        videoItem.value.on('play', () => {
+          if (videoItem.value && videoItem.value.muted) {
+            videoItem.value.muted = false
+            videoItem.value.volume = 1
+          }
+
+          // console.log('开始播放', item.value)
+          if (!item.value.autoPlay && !item.value.isprepare && videoItem.value) {
+            // 加载完成后,取消静音播放
+            videoItem.value.pause()
+            console.log(videoItem.value?.paused, 'video status')
+          }
+          changePlayBtn('')
+          emit('togglePlay', videoItem.value?.paused)
+        })
+        videoItem.value.on('pause', () => {
+          changePlayBtn('play')
+          emit('togglePlay', videoItem.value?.paused)
+        })
+        videoItem.value.on('ended', () => {
+          emit('ended')
+          changePlayBtn('play')
+        })
+        videoItem.value.once('loadedmetadata', () => {
+          console.log('loadedmetadata')
+          changePlayBtn('play')
+          videoItem.value.currentTime = 0
+          if (item.value.autoPlay && videoItem.value && props.isActive) {
+            videoItem.value.play()
+          }
+          emit('loadedmetadata', videoItem.value)
+        })
+        videoItem.value.on('timeupdate', () => {
+          if (!props.isActive) {
+            console.log('不是激活的视频,如果在播放,就暂停')
+            videoRef.value.pause()
+          }
+        })
+
+        nextTick(() => {
+          onDefault()
+        })
+      }
+    })
+    expose({
+      changePlayBtn,
+      toggleHideControl
+    })
+
+    return () => (
+      <div class={styles.videoWrap}>
+        <video
+          style={{ width: '100%', height: '100%' }}
+          src={isEmtry.value ? '' : item.value.content}
+          ref={videoRef}
+          playsinline="false"
+        ></video>
+      </div>
+    )
+  }
+})

+ 479 - 0
src/tenant/music/coursewarePlay/component/video-play.tsx

@@ -0,0 +1,479 @@
+import {
+  defineComponent,
+  nextTick,
+  onMounted,
+  reactive,
+  toRefs,
+  watch
+} from 'vue'
+import { ref } from 'vue'
+import styles from './video.module.less'
+
+// import iconLoop from '../image/icon-loop.svg';
+// import iconLoopActive from '../image/icon-loop-active.svg';
+// import iconplay from '../image/icon-play.svg';
+// import iconpause from '../image/icon-pause.svg';
+
+import {
+  // iconVideoBg,
+  iconLoop,
+  iconLoopActive,
+  iconPlay,
+  iconPause,
+  iconSpeed
+} from '../image/icons.json'
+
+import TCPlayer from 'tcplayer.js'
+import 'tcplayer.js/dist/tcplayer.min.css'
+import { Slider, Toast } from 'vant'
+
+// 秒转分
+export const getSecondRPM = (second: number, type?: string) => {
+  if (isNaN(second)) return '00:00'
+  const mm = Math.floor(second / 60)
+    .toString()
+    .padStart(2, '0')
+  const dd = Math.floor(second % 60)
+    .toString()
+    .padStart(2, '0')
+  if (type === 'cn') {
+    return mm + '分' + dd + '秒'
+  } else {
+    return mm + ':' + dd
+  }
+}
+
+export default defineComponent({
+  name: 'video-play',
+  props: {
+    item: {
+      type: Object,
+      default: () => {
+        return {}
+      }
+    },
+    isEmtry: {
+      type: Boolean,
+      default: false
+    },
+    isActive: {
+      type: Boolean,
+      default: false
+    },
+    activeModel: {
+      type: Boolean,
+      default: true
+    }
+  },
+  emits: [
+    'loadedmetadata',
+    'togglePlay',
+    'ended',
+    'reset',
+    'error',
+    'close',
+    'play',
+    'pause',
+    'seeked',
+    'seeking',
+    'waiting',
+    'timeupdate'
+  ],
+  setup(props, { emit, expose }) {
+    const { item } = toRefs(props)
+    const data = reactive({
+      timer: null as any,
+      currentTime: 0,
+      duration: 0.1,
+      loop: false,
+      playState: 'pause' as 'play' | 'pause',
+      vudio: null as any,
+      showBar: true,
+      speedControl: false,
+      speedStyle: {
+        left: '1px'
+      },
+      defaultSpeed: 1 // 默认速度
+    })
+    const speedBtnId = 'speed' + Date.now() + Math.floor(Math.random() * 100)
+
+    // const forms = reactive({
+    //   subjectIds: [],
+    //   orgainIds: []
+    // });
+    const videoRef = ref()
+    const videoItem = ref()
+    const videoID = 'video' + Date.now() + Math.floor(Math.random() * 100)
+    const toggleHideControl = (isShow: boolean) => {
+      data.speedControl = false
+      data.showBar = isShow
+    }
+    // const togglePlay = (e: Event) => {
+    //   e.stopPropagation()
+
+    // }
+    let playTimer = null as any
+    // 切换音频播放
+    const onToggleAudio = (state: 'play' | 'pause') => {
+      // console.log(state, 'state')
+      data.speedControl = false
+      clearTimeout(playTimer)
+      if (state === 'play') {
+        playTimer = setTimeout(() => {
+          videoItem.value?.play()
+          data.playState = 'play'
+        }, 100)
+      } else {
+        videoItem.value?.pause()
+        data.playState = 'pause'
+      }
+
+      emit('togglePlay', data.playState)
+    }
+    const toggleLoop = () => {
+      data.speedControl = false
+      if (!videoItem.value) return
+      if (data.loop) {
+        videoItem.value.loop(false)
+      } else {
+        videoItem.value.loop(true)
+      }
+      data.loop = !data.loop
+    }
+    const changePlayBtn = (code: string) => {
+      // data.speedControl = false;
+      if (code == 'play') {
+        data.playState = 'play'
+      } else {
+        data.playState = 'pause'
+      }
+    }
+
+    /** 改变播放时间 */
+    const handleChangeTime = (val: number) => {
+      data.currentTime = val
+      clearTimeout(data.timer)
+      data.timer = setTimeout(() => {
+        videoItem.value.currentTime(val)
+        data.timer = null
+      }, 300)
+    }
+
+    const __initVideo = () => {
+      if (videoItem.value && props.item.id) {
+        console.log(videoItem.value, 'videoItem.value')
+        nextTick(() => {
+          videoItem.value?.currentTime(0)
+        })
+        videoItem.value.poster(props.item.coverImg) // 封面
+        videoItem.value.src(props.item.content) // url 播放地址
+        videoItem.value.playbackRate(data.defaultSpeed)
+        data.speedControl = false
+        // 初步加载时
+        videoItem.value.on('loadedmetadata', () => {
+          videoItem.value.playbackRate(data.defaultSpeed)
+
+          // 获取时长
+          data.duration = videoItem.value.duration()
+          // 必须在当前元素
+
+          if (item.value.autoPlay && videoItem.value && props.isActive) {
+            // videoItem.value?.play()
+            nextTick(() => {
+              videoItem.value.currentTime(0)
+              nextTick(handlePlayVideo)
+            })
+          }
+          emit('loadedmetadata', videoItem.value)
+        })
+
+        // 视频播放时加载
+        videoItem.value.on('timeupdate', () => {
+          if (data.timer) return
+          data.currentTime = videoItem.value.currentTime()
+          emit('timeupdate')
+        })
+
+        // 视频播放结束
+        videoItem.value.on('ended', () => {
+          changePlayBtn('pause')
+          emit('ended')
+        })
+
+        //
+        videoItem.value.on('pause', () => {
+          data.playState = 'pause'
+          changePlayBtn('pause')
+          emit('togglePlay', true)
+          emit('pause')
+        })
+
+        videoItem.value.on('seeked', () => {
+          emit('seeked')
+        })
+
+        videoItem.value.on('seeking', () => {
+          emit('seeking')
+        })
+        videoItem.value.on('waiting', () => {
+          emit('waiting')
+        })
+
+        videoItem.value.on('play', () => {
+          // console.log(play, 'playing')
+          changePlayBtn('play')
+          if (videoItem.value) {
+            videoItem.value.muted(false)
+            videoItem.value.volume(1)
+          }
+          // if (
+          //   !item.value.autoPlay &&
+          //   !item.value.isprepare &&
+          //   videoItem.value
+          // ) {
+          //   // 加载完成后,取消静音播放
+          //   videoItem.value.pause();
+          // }
+          emit('togglePlay', videoItem.value?.paused)
+          emit('play')
+        })
+
+        // 视频播放异常
+        videoItem.value.on('error', (e: any) => {
+          handleErrorVideo()
+          emit('error')
+          console.log(e, 'error')
+        })
+      }
+    }
+
+    let videoTimer = null as any
+    let videoTimerErrorCount = 0
+    const handlePlayVideo = () => {
+      if (videoTimerErrorCount > 5) {
+        return
+      }
+      clearTimeout(videoTimer)
+      nextTick(() => {
+        videoItem.value?.play().catch((err: any) => {
+          // console.log('🚀 ~ err:', err)
+          videoTimer = setTimeout(() => {
+            if (err?.message?.includes('play()')) {
+              emit('play')
+            }
+            handlePlayVideo()
+          }, 1000)
+        })
+      })
+      videoTimerErrorCount++
+    }
+
+    let videoErrorTimer = null as any
+    let videoErrorCount = 0
+    const handleErrorVideo = () => {
+      if (videoErrorCount > 5) {
+        return
+      }
+      clearTimeout(videoErrorTimer)
+      nextTick(() => {
+        videoErrorTimer = setTimeout(() => {
+          videoItem.value.src(props.item?.content)
+          emit('play')
+          videoItem.value.load()
+          // eslint-disable-next-line @typescript-eslint/no-unused-vars
+          handleErrorVideo()
+        }, 1000)
+      })
+      videoErrorCount++
+    }
+
+    onMounted(() => {
+      videoItem.value = TCPlayer(videoID, {
+        appID: '',
+        controls: false,
+        autoplay: true
+      }) // player-container-id 为播放器容器 ID,必须与 html 中一致
+      __initVideo()
+
+      document.getElementById(speedBtnId)?.addEventListener('click', e => {
+        e.stopPropagation()
+        data.speedControl = !data.speedControl
+      })
+    })
+
+    watch(
+      () => props.activeModel,
+      () => {
+        toggleHideControl(props.activeModel)
+      }
+    )
+    watch(
+      () => props.item,
+      () => {
+        videoItem.value?.currentTime(0)
+        setTimeout(() => {
+          videoItem.value?.pause()
+          __initVideo()
+        }, 60)
+      }
+    )
+
+    const getVideoRef = () => {
+      return videoRef.value
+    }
+
+    const getPlyrRef = () => {
+      return videoItem.value
+    }
+
+    expose({
+      changePlayBtn,
+      toggleHideControl,
+      getVideoRef,
+      getPlyrRef
+    })
+
+    watch(
+      () => props.isActive,
+      val => {
+        if (!val) {
+          videoItem.value?.pause()
+        }
+      }
+    )
+
+    return () => (
+      <div
+        class={styles.videoWrap}
+        onClick={() => {
+          data.speedControl = false
+        }}
+      >
+        <video
+          style={{ width: '100%', height: '100%' }}
+          src={item.value.content}
+          ref={videoRef}
+          id={videoID}
+          preload="auto"
+          playsinline
+          webkit-playsinline
+        ></video>
+        <div class={styles.videoSection}></div>
+
+        <div
+          class={[styles.controls, data.showBar ? '' : styles.hide]}
+          onClick={(e: Event) => {
+            e.stopPropagation()
+          }}
+          // onTouchmove={(e: TouchEvent) => {
+          //   emit('close')
+          // }}
+        >
+          <div class={styles.time}>
+            <div>{getSecondRPM(data.currentTime)}</div>
+            <div>{getSecondRPM(data.duration)}</div>
+          </div>
+          <div class={styles.slider}>
+            <Slider
+              step={0.01}
+              class={styles.timeProgress}
+              v-model={data.currentTime}
+              max={data.duration}
+              onUpdate:modelValue={val => {
+                handleChangeTime(val)
+              }}
+            />
+          </div>
+          <div class={styles.actionSection}>
+            <div class={styles.actions} onClick={() => emit('close')}>
+              <div
+                class={styles.actionBtn}
+                onClick={(e: any) => {
+                  e.stopPropagation()
+                  onToggleAudio(data.playState === 'pause' ? 'play' : 'pause')
+                }}
+              >
+                <img src={data.playState === 'pause' ? iconPlay : iconPause} />
+              </div>
+              <div
+                class={styles.actionBtn}
+                onClick={() => {
+                  toggleLoop()
+                  Toast(data.loop ? '已打开循环播放' : '已关闭循环播放')
+                }}
+              >
+                <img src={data.loop ? iconLoopActive : iconLoop} />
+              </div>
+              <div class={styles.actionBtn} id={speedBtnId}>
+                <img src={iconSpeed} />
+              </div>
+            </div>
+            <div class={styles.name}>{item.value.name}</div>
+          </div>
+        </div>
+
+        <div
+          style={{
+            display: data.speedControl ? 'block' : 'none'
+          }}
+        >
+          <div
+            class={styles.sliderPopup}
+            onClick={(e: Event) => {
+              e.stopPropagation()
+            }}
+          >
+            <i
+              class={styles.iconAdd}
+              onClick={() => {
+                if (data.defaultSpeed >= 1.5) {
+                  return
+                }
+
+                if (videoItem.value) {
+                  data.defaultSpeed = (data.defaultSpeed * 10 + 1) / 10
+                  videoItem.value.playbackRate(data.defaultSpeed)
+                }
+              }}
+            ></i>
+            <Slider
+              min={0.6}
+              max={1.5}
+              step={0.1}
+              v-model={data.defaultSpeed}
+              vertical
+              barHeight={5}
+              reverse
+              onChange={() => {
+                if (videoItem.value) {
+                  videoItem.value.playbackRate(data.defaultSpeed)
+                }
+              }}
+            >
+              {{
+                button: () => (
+                  <div class={styles.sliderPoint}>
+                    {data.defaultSpeed}
+                    <span>x</span>
+                  </div>
+                )
+              }}
+            </Slider>
+            <i
+              class={[styles.iconCut]}
+              onClick={() => {
+                if (data.defaultSpeed <= 0.6) {
+                  return
+                }
+                if (videoItem.value) {
+                  data.defaultSpeed = (data.defaultSpeed * 10 - 1) / 10
+                  videoItem.value.playbackRate(data.defaultSpeed)
+                }
+              }}
+            ></i>
+          </div>
+        </div>
+      </div>
+    )
+  }
+})

+ 367 - 0
src/tenant/music/coursewarePlay/component/video.module.less

@@ -0,0 +1,367 @@
+// .videoWrap {
+//     width: 100%;
+//     height: 100%;
+
+//     :global {
+//         .plyr--video {
+//             width: 100%;
+//             height: 100%;
+//         }
+
+//         .plyr__time {
+//             display: block !important;
+//         }
+//         .plyr__video-wrapper{
+//             pointer-events: none;
+//         }
+//     }
+// }
+
+// :global(.bottomFixed).controls {
+//     width: 100%;
+//     background: linear-gradient(0deg, rgba(0, 0, 0, 0.5), transparent);
+//     padding: 0 !important;
+//     flex-direction: column;
+//     transition: all 0.5s;
+
+//     .time {
+//         display: flex;
+//         justify-content: space-between;
+//         width: 100%;
+//         color: #fff;
+//         font-size: 10px;
+//         padding: 4px 20px;
+
+//         :global {
+//             .plyr__time+.plyr__time:before {
+//                 content: '';
+//             }
+//         }
+//     }
+
+//     .slider {
+//         width: 100%;
+//         padding: 0 20px;
+
+//         :global {
+//             .van-slider__button {
+//                 background: var(--van-primary);
+//             }
+
+//             .van-loading {
+//                 width: 100%;
+//                 height: 100%;
+//             }
+//         }
+//     }
+
+//     .actions {
+//         display: flex;
+//         justify-content: space-between;
+//         width: 100%;
+//         color: #fff;
+//         font-size: 12px;
+//         padding: 0 20px;
+//         align-items: center;
+
+//         .actionWrap {
+//             display: flex;
+//         }
+
+//         .actionBtn {
+//             display: flex;
+//             width: 38px;
+//             height: 38px;
+//             padding: 4px 0;
+//             background: transparent;
+//         }
+
+//         .actionBtn>img {
+//             width: 100%;
+//             height: 100%;
+//         }
+
+//         :global {
+//             .van-loading__circular {
+//                 width: 100%;
+//                 height: 100%;
+//             }
+//         }
+
+//         .playIcon {
+//             display: none;
+//         }
+
+//         .btnPlay img:nth-child(2) {
+//             display: block;
+//         }
+
+//         .btnPause img:nth-child(3) {
+//             display: block;
+//         }
+
+//         .btnPlay,
+//         .btnPause {
+//             :global {
+//                 .van-loading {
+//                     display: none;
+//                 }
+//             }
+//         }
+//         .loopBtn{
+//             :global{
+//                 .loop{
+//                     display: block;
+//                 }
+//                 .loopActive{
+//                     display: none;
+//                 }
+//             }
+//         }
+//         .loopBtn.active{
+//             :global{
+//                 .loop{
+//                     display: none;
+//                 }
+//                 .loopActive{
+//                     display: block;
+//                 }
+//             }
+//         }
+
+//     }
+// }
+
+.videoWrap {
+  position: relative;
+  width: 100%;
+  height: 100%;
+
+  .videoSection {
+    position: absolute;
+    top: 0;
+    left: 0;
+    right: 0;
+    bottom: 0;
+    z-index: 8;
+  }
+}
+
+.content {
+  position: relative;
+  height: 100%;
+}
+
+.contentWrap {
+  height: 100%;
+
+  video {
+    width: 100%;
+    height: 100%;
+  }
+}
+
+.controls {
+  position: absolute;
+  left: 0;
+  bottom: 0;
+  right: 0;
+  height: 80px;
+  background: linear-gradient(0deg, rgba(0, 0, 0, 0.5), transparent);
+  display: flex;
+  flex-direction: column;
+  justify-content: space-between;
+  transition: all 0.5s;
+  width: 100%;
+  z-index: 9;
+
+  &.hide {
+    transform: translateY(100%);
+  }
+
+  .time {
+    display: flex;
+    justify-content: space-between;
+    // width: 100%;
+    color: #fff;
+    font-size: 10px;
+    padding: 4px 20px;
+  }
+
+  .slider {
+    // width: 100%;
+    padding: 0 20px;
+    --van-slider-button-width: 13px !important;
+    --van-slider-button-height: 13px !important;
+    --van-slider-active-background-color: #ff7c97 !important;
+
+    :global {
+      .n-slider {
+        --n-handle-size: 13px !important;
+        --n-fill-color: var(--van-primary-color) !important;
+        --n-fill-color-hover: var(--van-primary-color) !important;
+      }
+
+      .van-loading {
+        width: 100%;
+        height: 100%;
+      }
+    }
+  }
+
+  .actionSection {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    padding: 0 16px 8px 15px;
+
+    .name {
+      font-size: 14px;
+      font-weight: 500;
+      color: #ffffff;
+    }
+  }
+
+  .actions {
+    display: flex;
+    // width: 100%;
+    color: #fff;
+    font-size: 12px;
+
+    align-items: center;
+
+    .actionWrap {
+      display: flex;
+    }
+
+    .actionBtn {
+      display: flex;
+      width: 30px;
+      height: 30px;
+      padding: 4px 0;
+      background: transparent;
+
+      & + .actionBtn {
+        margin-left: 18px;
+      }
+    }
+
+    .actionBtn > img {
+      width: 100%;
+      height: 100%;
+    }
+
+    :global {
+      .van-loading__circular {
+        width: 100%;
+        height: 100%;
+      }
+    }
+
+    .playIcon {
+      display: none;
+    }
+
+    .btnPlay img:nth-child(2) {
+      display: block;
+    }
+
+    .btnPause img:nth-child(3) {
+      display: block;
+    }
+
+    .btnPlay,
+    .btnPause {
+      :global {
+        .van-loading {
+          display: none;
+        }
+      }
+    }
+
+    .loopBtn {
+      :global {
+        .loop {
+          display: block;
+        }
+
+        .loopActive {
+          display: none;
+        }
+      }
+    }
+
+    .loopBtn.active {
+      :global {
+        .loop {
+          display: none;
+        }
+
+        .loopActive {
+          display: block;
+        }
+      }
+    }
+  }
+}
+
+.sliderPopup {
+  position: absolute;
+  z-index: 9999;
+  left: 104px;
+  bottom: 46px;
+  display: flex;
+  align-items: center;
+  flex-direction: column;
+  height: 165px;
+  width: 45px;
+  padding: 10px 0 15px;
+  background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFoAAAGaCAMAAAC46aQwAAAAOVBMVEUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC8dlA9AAAAE3RSTlNaAAxUVwdPRSc7MzYsEEoXGgshjXaABwAAAZBJREFUeNrt1GtO40AQReF77X46tvPY/2IHhiCDA0Ez7pb4cb4FHLVaVSU/isM/i360S5+nnIL+Q0h5On+fXuqoQ8a6fJm+VjVQr4/pEtREKPt0VTP1UzpmNZTjh3RWU3lLn9TY6T1d1Fx5S8eg5kL8m57UwfSaHoI6CIPlSV1MlpO6SNaiThbN6mRWVSdVWZ1kJXWSFNRJEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADg9wnqJGhVJ6uyOsmq6qSqqJOiqzq5yqu6WC3P6mK2HEd1MEbLfZ49+zXtpOaS39IXNXe5p13UWPF72ic1dfKW9ql1eUu7tP2NLf3inNREOnuXflFGHTaWrSdvYsk6JJfoXXoTb1PNabwL+kEY71Ku023r7tOPYv5pFJ6Rn5qfj8KRtC+jvjGefSztYdWX1sFH0k82tUY3SPsWtDfZTdJekj4JN7dKO1Z9kBY3ST+er3x10/R2vqrdOO04BUnp4nbpTVwug30g3cwf1jMLKix6VfsAAAAASUVORK5CYII=')
+    no-repeat top center;
+  background-size: contain;
+  --van-slider-active-background-color: #ff7c97 !important;
+
+  .iconAdd,
+  .iconCut {
+    display: inline-block;
+    width: 24px;
+    height: 24px;
+    background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACwAAAAsCAMAAAApWqozAAABYlBMVEUAAAD/bJz/dKT/bJz/P2r/QGr/dKP/VoP/VoP/NmD/R3L/OGL/V4P/a5r/PW7/c6P/Y5H/QWv/P2n/ZZP/R3L/OWP/W4n/UH3/NmD/QWz/bp//c6T/YI7/S3f/daX/XYv/T3v/b5//aZj/Q27/PGX/cqP/Y5L/SHT/OWP/VYT/aJX/d6X/dKX/Xo7/THf/a5v/daj/Ol//P2n/PWj/a5r/aJj/OGL/N2H/N2H/N2D/dqb/N2D/N1//NV//////ZpT/SHP/YY//bJv/U4D/RXH/Wof/Q27/Snb/Y5L/VYL/UX3/V4T/XIr/aZj/aJf/QWz/bp7/Xoz/Tnv/THj/cKD/QGv/kqr/Pmn/PGf/cqL/OmT/OGL/la//dKT/NmH/nLj/l7L/8fX/mrX/nrr/oLz/kqz/g6D/tcv/gJ3/jKX/Zoj/WXz/TnX/wdL/q7//qb3/krL/jq7/eJb/w9T/v8//YoTsHMC2AAAAPnRSTlMA+PTr14aFg1hVR0cjCgpHR/j49vb28fHx69fQ0NC6urqxsbGxqKioqJWGWFJSUkcjI+vr19fQ0Lq6lZWDg/d34FwAAAJ4SURBVDjLfdNnW+JAFAXgS1Xp9t571+1VQCCAIAgIgjTpqNi2/P+dnmwSOF/zPueZzL0Dqrjslg3zu9HRWfO6xeaCQbHvTN7m8zfZbFKSgkHfxLatLx35VLxVWl84HF480qWnX4pam8mkVxw6tTPM3mSTSWJ9xKYvpofV9kelIqwkSQp7EQgM/W+/Uyt6g0obje4q7U9ks4+9fjYWU3SPIPt4n8s9ECtpbSgkzn06g2wO5ZeujSIbmuJ38rVS9N0j25WYfanVntPEIhzDNh5f5oco5tu4N8ls9wylzWyU2lTqkODPxeJf3Jvkw2hhXFJZ/wLZBzQMXNxjlmP+c8z6/R6Ed9AF45uQh0zwXUBhMY5sAbgm0TAecm2JWoxLBPMzhGhvJDLuBDsZXC8obCZDsehFGFuv9xgsYsgSsQLL1k9tYg82tItGsbo3kVgDs9zbzbVaJZQ/GD/V6/Uqypuf28sP8F70vpzppsPtpQnGxPLU9HHNj6wX2XMDjHHbF3OL8KwYxnOfY3B7bgKzvJRt9HN3KE/Y/K42Go1ms/mKbQLZq6s5WOc2jBeYLEQd4yofHO3FeBUs3IptZ1hjr61gU/dGYwQ3NPbaDa4J1issw2IY3BqdANvMyq+zSrEYBrWFTQCw+VSW4iaxXtFbKLgBZVFl4wyzXm7nAedIZeNvGL9GxDCILR8AyYrq1ac6tVpH1VteAhrHtMKyBea9DJeNJ8AyPMDS3vI+iAwJqx0ysVZQZFd+9ZphIPuNKtFNe+O6VvSKc0+JXtVFGPdBE8eyfu/SCejlcCGlsfMH0C+erXHlGYybbhgUp2dv7aPJYDDNrVqPnaqP/wDNG7JKX9QniQAAAABJRU5ErkJggg==)
+      no-repeat center;
+    background-size: contain;
+    flex-shrink: 0;
+
+    &.disabled {
+      opacity: 0.7;
+    }
+  }
+
+  .iconCut {
+    background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACwAAAAsCAMAAAApWqozAAABTVBMVEUAAAD/O2b/Pmn/cqL/apr/apr/VYH/VIP/cKH/Y5H/NmD/VYP/a5T/PWf/bJr/PWj/P2r/N2L/WYX/PWf/Y5H/OGH/T3v/NV//bJz/aZn/bZ3/aJf/cqP/X43/W4n/THr/N2D/Qmz/OmT/caL/YpH/R3H/N2L/VYL/OmT/daX/Nl//daX/NGD/c6L/R3L/QWz/daj/Nl//apj/RnL/RHD/W4j/WYX/QWv/S3f/SnT/daP/c6P/dKb/NWD/RW7/Q2z/////ZZP/UX3/RnL/YpH/S3f/Tnv/RHD/Qm3/XYv/VoP/QGv/WIX/Woj/YI//apn/Z5b/SnX/U3//SHT/XIn/bJv/aJf/P2n/X43/VIH/bp7/PWf/OmT/VYL/TXn/caH/cJ//kKj/N2H/7PH/kaj/NV//c6P/fZv/jqX/hZ7/WXv/ja7/cpH/ZIb/YYPuAB6lAAAAQHRSTlMA/vj0sYaDWEdHRyMKCvjr19NSUvb28fHr69fX0NC6urqxsaioqKiVhoODWFhSR0cjI/j29vHx69DQurqVlYaGVpLnlAAAAj9JREFUOMuN0udX4kAUBfAnTTqIvfeya9/edyEU6b0IQQUJirv6/3/cmckjTiaJx/v5d+55kxsQEvAebE2FbLZQePP3aQBeindvVpbliU6nmc1mL1Ifdk8s6fRUv8/Zi1RKqn6dNqXnG4pmkUrVajmx7jPao3lzm7iamxTtW6X5UDa1yWTMpbc/lYf7Xu9Rb8toY7F9XS+zg39iL9rKJdd9pEjU3jU7TaEXbTej3X0+L49ob8faZj75EG8oT7RXsxJvK8zmcmu4hdIf9Qb3Ek5Ba5lN8jZfizBMdhsNBo+kF0+QDDafr9VWqfWSKZ7I46jl7xVsOu0heI9OUc3i01Lsk1HLnkZsDm1hByAwy2bjb2C1SWYzrJfQVqsw4wevbDab0EttPO6BA9lqtgo9F3upjTtha8JqistnWyA2GnVA2HI20ZaWIcTZu9s/htz+HdvSIti4XrSCZpbid2B7tglzjLbRCMJn7l7zM9gNxBYXIKz1mk1MPhnaYrG4BJvkN0ObFG1LZ+sO+DXuvRJ606KtO+EUZxOnQBtlltJ22w2B9zrbtbZ2P8DuK+3NNgCc8H96l5tYsNduIPnC94q2odkVoIlgL9qaub0+BpZ1C1sa2xtiv4Ma35xqc0Ivb+1ngJlEm7fsHR6CFpdgo5ptq/YNcNln94q2rtrh8Afo4lI/WVqcgtbyvXj3R7W2gE9roCVvOwRDfGs4sdD77QzMElk1TrFyDFbx7Mzw1r7thpfi9zgdy4vB4MKSw+n2gz7/Aec6wbfzmWfwAAAAAElFTkSuQmCC)
+      no-repeat center;
+    background-size: contain;
+  }
+
+  .sliderPoint {
+    padding: 1px 0;
+    background: #ffffff;
+    box-shadow: 0px 2px 4px 0px rgba(102, 102, 102, 0.77);
+    border-radius: 10px;
+    font-weight: 500;
+    font-size: 14px;
+    color: #fe2451;
+    min-width: 35px;
+    text-align: center;
+    vertical-align: text-bottom;
+
+    span {
+      font-size: 12px;
+    }
+  }
+
+  :global {
+    .van-slider {
+      margin: 8px 0;
+    }
+  }
+}

文件差异内容过多而无法显示
+ 0 - 0
src/tenant/music/coursewarePlay/datas/data.json


+ 11 - 0
src/tenant/music/coursewarePlay/image/back.svg

@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="19px" height="19px" viewBox="0 0 19 19" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <title>切片</title>
+    <g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round">
+        <g id="课件播放(老师)" transform="translate(-40.000000, -13.000000)" stroke="#FFFFFF" stroke-width="2">
+            <g id="图标/通用/返回" transform="translate(40.000000, 13.000000)">
+                <polyline id="Stroke-1" points="13.5 18 5 9.5 13.5 1"></polyline>
+            </g>
+        </g>
+    </g>
+</svg>

+ 15 - 0
src/tenant/music/coursewarePlay/image/icon-arrow.svg

@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="12px" height="12px" viewBox="0 0 12 12" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <title>切片</title>
+    <g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="课件播放目录(老师)" transform="translate(-570.000000, -213.000000)" fill="#FFFFFF">
+            <g id="编组" transform="translate(548.000000, 0.000000)">
+                <g id="编组-5" transform="translate(22.000000, 209.000000)">
+                    <g id="展开更多备份" transform="translate(6.000000, 10.000000) rotate(-90.000000) translate(-6.000000, -10.000000) translate(0.000000, 4.000000)">
+                        <path d="M6.85328183,4.39627936 L10.070225,9.66036817 C10.3582139,10.1316227 10.2096477,10.7471111 9.73839317,11.0351 C9.58138526,11.1310493 9.40094791,11.1818182 9.21694316,11.1818182 L2.78305684,11.1818182 C2.23077209,11.1818182 1.78305684,10.7341029 1.78305684,10.1818182 C1.78305684,9.99781343 1.83382572,9.81737609 1.92977501,9.66036817 L5.14671817,4.39627936 C5.43470705,3.92502482 6.05019547,3.77645865 6.52145001,4.06444754 C6.6568159,4.14717114 6.77055823,4.26091347 6.85328183,4.39627936 Z" id="展开更多" transform="translate(6.000000, 7.090909) rotate(-180.000000) translate(-6.000000, -7.090909) "></path>
+                    </g>
+                </g>
+            </g>
+        </g>
+    </g>
+</svg>

+ 24 - 0
src/tenant/music/coursewarePlay/image/icon-dian.svg

@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <title>切片</title>
+    <defs>
+        <path d="M23.21608,16.0687402 L23.3033009,16.1464466 C23.6659306,16.5090763 23.6918327,17.0809217 23.3810072,17.4734394 L23.3033009,17.5606602 L17.6464466,23.2175144 C17.2838169,23.5801441 16.7119715,23.6060462 16.3194539,23.2952208 L16.232233,23.2175144 L12.6966991,19.6819805 C12.3061748,19.2914562 12.3061748,18.6582912 12.6966991,18.267767 C13.0593288,17.9051373 13.6311742,17.8792351 14.0236919,18.1900606 L14.1109127,18.267767 L16.9384817,21.0967502 L21.8890873,16.1464466 C22.251717,15.7838169 22.8235624,15.7579148 23.21608,16.0687402 Z M11,0 C14.8659932,0 18,3.13400675 18,7 C18,9.41274397 16.7793245,11.5403839 14.9218582,12.7990351 C15.9323681,13.229421 16.8689386,13.8268917 17.694678,14.5714883 C18.1048344,14.9413398 18.1375079,15.5736612 17.7676564,15.9838176 C17.3978048,16.393974 16.7654834,16.4266475 16.355327,16.0567959 C14.8954225,14.7403517 13.0066893,14 11,14 C6.581722,14 3,17.581722 3,22 C3,22.5522847 2.55228475,23 2,23 C1.44771525,23 1,22.5522847 1,22 C1,17.8693735 3.50442274,14.3236863 7.07765565,12.7985509 C5.22023262,11.5396119 4,9.41230625 4,7 C4,3.13400675 7.13400675,0 11,0 Z M11,2 C8.23857625,2 6,4.23857625 6,7 C6,9.76142375 8.23857625,12 11,12 C13.7614237,12 16,9.76142375 16,7 C16,4.23857625 13.7614237,2 11,2 Z" id="path-1"></path>
+        <filter x="-13.3%" y="-12.8%" width="126.6%" height="125.5%" filterUnits="objectBoundingBox" id="filter-2">
+            <feOffset dx="0" dy="0" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
+            <feGaussianBlur stdDeviation="1" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
+            <feColorMatrix values="0 0 0 0 0   0 0 0 0 0   0 0 0 0 0  0 0 0 0.125792177 0" type="matrix" in="shadowBlurOuter1"></feColorMatrix>
+        </filter>
+    </defs>
+    <g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="课件播放(老师)" transform="translate(-740.000000, -151.000000)" fill-rule="nonzero">
+            <g id="编组-8" transform="translate(732.000000, 66.000000)">
+                <g id="编组-7" transform="translate(0.000000, 75.000000)">
+                    <g id="形状结合" transform="translate(8.000000, 10.000000)">
+                        <use fill="black" fill-opacity="1" filter="url(#filter-2)" xlink:href="#path-1"></use>
+                        <use fill="#FFFFFF" xlink:href="#path-1"></use>
+                    </g>
+                </g>
+            </g>
+        </g>
+    </g>
+</svg>

+ 22 - 0
src/tenant/music/coursewarePlay/image/icon-down.svg

@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <title>切片</title>
+    <defs>
+        <path d="M12.619886,7.97254617 L12.7071068,8.05025253 L16.2426407,11.5857864 C16.633165,11.9763107 16.633165,12.6094757 16.2426407,13 C15.880011,13.3626297 15.3081656,13.3885318 14.9156479,13.0777064 L14.8284271,13 L12.9994661,11.1713593 L13,19 C13,19.5522847 12.5522847,20 12,20 C11.4477153,20 11,19.5522847 11,19 L10.9994661,11.1703593 L9.17157288,13 C8.80894318,13.3626297 8.23709778,13.3885318 7.84458013,13.0777064 L7.75735931,13 C7.39472961,12.6373703 7.36882749,12.0655249 7.67965295,11.6730073 L7.75735931,11.5857864 L11.2928932,8.05025253 C11.6555229,7.68762283 12.2273683,7.66172071 12.619886,7.97254617 Z M18.363961,5.63603897 C21.8786797,9.15075759 21.8786797,14.8492424 18.363961,18.363961 C17.9734367,18.7544853 17.3402718,18.7544853 16.9497475,18.363961 C16.5592232,17.9734367 16.5592232,17.3402718 16.9497475,16.9497475 C19.6834175,14.2160774 19.6834175,9.78392257 16.9497475,7.05025253 C14.2160774,4.31658249 9.78392257,4.31658249 7.05025253,7.05025253 C4.31658249,9.78392257 4.31658249,14.2160774 7.05025253,16.9497475 C7.44077682,17.3402718 7.44077682,17.9734367 7.05025253,18.363961 C6.65972824,18.7544853 6.02656326,18.7544853 5.63603897,18.363961 C2.12132034,14.8492424 2.12132034,9.15075759 5.63603897,5.63603897 C9.15075759,2.12132034 14.8492424,2.12132034 18.363961,5.63603897 Z" id="path-1"></path>
+        <filter x="-33.3%" y="-35.3%" width="166.7%" height="170.6%" filterUnits="objectBoundingBox" id="filter-2">
+            <feOffset dx="0" dy="0" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
+            <feGaussianBlur stdDeviation="2" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
+            <feColorMatrix values="0 0 0 0 0   0 0 0 0 0   0 0 0 0 0  0 0 0 0.15 0" type="matrix" in="shadowBlurOuter1"></feColorMatrix>
+        </filter>
+    </defs>
+    <g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="课件播放/全屏(伴学端)" transform="translate(-90.000000, -227.000000)" fill-rule="nonzero">
+            <g id="编组-8备份" transform="translate(82.000000, 68.000000)">
+                <g id="形状结合" transform="translate(20.000000, 171.000000) rotate(-180.000000) translate(-20.000000, -171.000000) translate(8.000000, 159.000000)">
+                    <use fill="black" fill-opacity="1" filter="url(#filter-2)" xlink:href="#path-1"></use>
+                    <use fill="#FFFFFF" xlink:href="#path-1"></use>
+                </g>
+            </g>
+        </g>
+    </g>
+</svg>

+ 19 - 0
src/tenant/music/coursewarePlay/image/icon-image-active.svg

@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="15px" height="16px" viewBox="0 0 15 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <title>切片</title>
+    <g id="页面-2" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="课件播放(三层)" transform="translate(-306.000000, -298.000000)" fill="#FF8057" fill-rule="nonzero">
+            <g id="编组" transform="translate(306.000000, 0.000000)">
+                <g id="编组-8" transform="translate(0.000000, 93.500000)">
+                    <g id="编组-12" transform="translate(0.000000, 65.000000)">
+                        <g id="知识点目录/图片备份" transform="translate(0.000000, 140.000000)">
+                            <g id="编组" transform="translate(7.500000, 7.500000) scale(-1, 1) translate(-7.500000, -7.500000) translate(0.500000, 2.000000)">
+                                <path d="M12.6293706,0 C13.3863483,-1.39054547e-16 14,0.613651672 14,1.37062937 L14,1.37062937 L14,9.62937063 C14,10.3863483 13.3863483,11 12.6293706,11 L12.6293706,11 L1.37062937,11 C0.613651672,11 9.27030316e-17,10.3863483 0,9.62937063 L0,9.62937063 L0,1.37062937 C-9.27030316e-17,0.613651672 0.613651672,1.39054547e-16 1.37062937,0 L1.37062937,0 Z M9.22142099,4.12294163 C8.89196248,3.93663301 8.47385062,4.05267893 8.287542,4.38213744 L8.287542,4.38213744 L7.04470782,6.57990105 C6.98353166,6.68808179 6.89408921,6.77759053 6.78595381,6.8388468 C6.4566334,7.02539942 6.03843569,6.90966326 5.85188307,6.58034285 L5.85188307,6.58034285 L5.64657316,6.21791028 C5.58509397,6.10938138 5.49516084,6.01968934 5.3864673,5.95850172 C5.05664729,5.77283381 4.63876177,5.88969218 4.45309386,6.21951219 L4.45309386,6.21951219 L3.17297968,8.49350383 C3.11520712,8.59613088 3.08485695,8.71191444 3.08485695,8.82968531 C3.08485695,9.20817416 3.39168279,9.515 3.77017164,9.515 L3.77017164,9.515 L11.2094939,9.515 C11.3277631,9.515 11.4440194,9.4843926 11.5469559,9.42615461 C11.8763768,9.23977951 11.9923383,8.82164423 11.8059632,8.49222333 L11.8059632,8.49222333 L9.4805487,4.38201704 C9.41928897,4.27373951 9.32971088,4.18417951 9.22142099,4.12294163 Z M3.23076923,1.5125 C2.34230769,1.5125 1.61538462,2.255 1.61538462,3.1625 C1.61538462,4.07 2.34230769,4.8125 3.23076923,4.8125 C4.11923077,4.8125 4.84615385,4.07 4.84615385,3.1625 C4.84615385,2.255 4.11923077,1.5125 3.23076923,1.5125 Z" id="形状结合"></path>
+                            </g>
+                        </g>
+                    </g>
+                </g>
+            </g>
+        </g>
+    </g>
+</svg>

+ 19 - 0
src/tenant/music/coursewarePlay/image/icon-image.svg

@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="15px" height="16px" viewBox="0 0 15 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <title>切片</title>
+    <g id="页面-2" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="课件播放(三层)" transform="translate(-306.000000, -268.000000)" fill="#FFFFFF" fill-rule="nonzero">
+            <g id="编组" transform="translate(306.000000, 0.000000)">
+                <g id="编组-8" transform="translate(0.000000, 93.500000)">
+                    <g id="编组-12" transform="translate(0.000000, 65.000000)">
+                        <g id="知识点目录/图片备份-4" transform="translate(0.000000, 110.000000)">
+                            <g id="编组" transform="translate(7.500000, 7.500000) scale(-1, 1) translate(-7.500000, -7.500000) translate(0.500000, 2.000000)">
+                                <path d="M12.6293706,0 C13.3863483,-1.39054547e-16 14,0.613651672 14,1.37062937 L14,1.37062937 L14,9.62937063 C14,10.3863483 13.3863483,11 12.6293706,11 L12.6293706,11 L1.37062937,11 C0.613651672,11 9.27030316e-17,10.3863483 0,9.62937063 L0,9.62937063 L0,1.37062937 C-9.27030316e-17,0.613651672 0.613651672,1.39054547e-16 1.37062937,0 L1.37062937,0 Z M9.22142099,4.12294163 C8.89196248,3.93663301 8.47385062,4.05267893 8.287542,4.38213744 L8.287542,4.38213744 L7.04470782,6.57990105 C6.98353166,6.68808179 6.89408921,6.77759053 6.78595381,6.8388468 C6.4566334,7.02539942 6.03843569,6.90966326 5.85188307,6.58034285 L5.85188307,6.58034285 L5.64657316,6.21791028 C5.58509397,6.10938138 5.49516084,6.01968934 5.3864673,5.95850172 C5.05664729,5.77283381 4.63876177,5.88969218 4.45309386,6.21951219 L4.45309386,6.21951219 L3.17297968,8.49350383 C3.11520712,8.59613088 3.08485695,8.71191444 3.08485695,8.82968531 C3.08485695,9.20817416 3.39168279,9.515 3.77017164,9.515 L3.77017164,9.515 L11.2094939,9.515 C11.3277631,9.515 11.4440194,9.4843926 11.5469559,9.42615461 C11.8763768,9.23977951 11.9923383,8.82164423 11.8059632,8.49222333 L11.8059632,8.49222333 L9.4805487,4.38201704 C9.41928897,4.27373951 9.32971088,4.18417951 9.22142099,4.12294163 Z M3.23076923,1.5125 C2.34230769,1.5125 1.61538462,2.255 1.61538462,3.1625 C1.61538462,4.07 2.34230769,4.8125 3.23076923,4.8125 C4.11923077,4.8125 4.84615385,4.07 4.84615385,3.1625 C4.84615385,2.255 4.11923077,1.5125 3.23076923,1.5125 Z" id="形状结合"></path>
+                            </g>
+                        </g>
+                    </g>
+                </g>
+            </g>
+        </g>
+    </g>
+</svg>

二进制
src/tenant/music/coursewarePlay/image/icon-load.gif


+ 23 - 0
src/tenant/music/coursewarePlay/image/icon-loop-active.svg

@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <title>切片</title>
+    <defs>
+        <path d="M5.00480947,15.25 C6.49624217,17.8332372 9.29235844,19.1935866 12.0802733,18.9792617 C12.1933752,19.672858 12.4261755,20.3264161 12.7562805,20.918295 C9.05647623,21.4111568 5.25493468,19.6832296 3.27275866,16.25 C2.99661629,15.7717074 3.16049144,15.160117 3.63878407,14.8839746 C4.11707669,14.6078322 4.7286671,14.7717074 5.00480947,15.25 Z M10.4132849,7.08904722 L10.5144958,7.14250707 L15.5144958,10.1425071 C16.123756,10.5080632 16.1595948,11.3606179 15.6220123,11.7834679 L15.5144958,11.8574929 L10.5144958,14.8574929 C9.88129747,15.2374119 9.08434351,14.8231057 9.00623111,14.1142925 L9,14 L9,8 C9,7.26157025 9.76529405,6.79134664 10.4132849,7.08904722 Z M19.7272413,6.75 C20.8183076,8.63978222 21.1863003,10.7486105 20.9170607,12.7561204 C20.3245684,12.4254359 19.6711964,12.1929305 18.9778301,12.0792818 C19.0927198,10.6209049 18.7822682,9.11325853 17.9951905,7.75 C15.9241227,4.16280532 11.3371947,2.93374166 7.75,5.00480947 C6.85788041,5.51987496 6.09738693,6.19866524 5.49784407,6.99904787 L7,7 C7.51283584,7 7.93550716,7.38604019 7.99327227,7.88337887 L8,8 C8,8.51283584 7.61395981,8.93550716 7.11662113,8.99327227 L7,9 L3,9 C2.48716416,9 2.06449284,8.61395981 2.00672773,8.11662113 L2,8 L2,4 C2,3.44771525 2.44771525,3 3,3 C3.51283584,3 3.93550716,3.38604019 3.99327227,3.88337887 L4,4 L3.99919549,5.66623947 C4.74283972,4.70951337 5.67119653,3.89560614 6.75,3.27275866 C11.2937799,0.649406102 17.1038888,2.20622008 19.7272413,6.75 Z M11.001,9.766 L11.001,12.233 L13.057,11 L11.001,9.766 Z" id="path-1"></path>
+        <filter x="-31.6%" y="-31.6%" width="163.2%" height="163.1%" filterUnits="objectBoundingBox" id="filter-2">
+            <feOffset dx="0" dy="0" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
+            <feGaussianBlur stdDeviation="2" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
+            <feColorMatrix values="0 0 0 0 0   0 0 0 0 0   0 0 0 0 0  0 0 0 0.15 0" type="matrix" in="shadowBlurOuter1"></feColorMatrix>
+        </filter>
+    </defs>
+    <g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="课件播放(控件)" transform="translate(-42.000000, -289.000000)" fill-rule="nonzero">
+            <g id="循环备份" transform="translate(42.000000, 289.000000)">
+                <g id="形状结合">
+                    <use fill="black" fill-opacity="1" filter="url(#filter-2)" xlink:href="#path-1"></use>
+                    <use fill="#FFFFFF" xlink:href="#path-1"></use>
+                </g>
+                <path d="M18,13 C20.7614237,13 23,15.2385763 23,18 C23,20.7614237 20.7614237,23 18,23 C15.2385763,23 13,20.7614237 13,18 C13,15.2385763 15.2385763,13 18,13 Z M20.9553274,16.1420886 C20.7524108,15.9832858 20.4506336,16.0042922 20.2548135,16.2001122 L20.2548135,16.2001122 L17.5814904,18.87328 L16.1273302,17.4183562 L16.0823515,17.3785153 C15.8794349,17.2197125 15.5776577,17.2407189 15.3818377,17.436539 C15.1709545,17.6474221 15.1628139,17.9811905 15.3636549,18.1820315 L15.3636549,18.1820315 L17.1819295,20.0003061 L17.2269082,20.0401471 C17.4298248,20.1989498 17.731602,20.1779434 17.9274221,19.9821234 L17.9274221,19.9821234 L20.9821234,16.9274221 L21.0242071,16.8804452 C21.1931032,16.669536 21.1868014,16.3684248 21.0003061,16.1819295 L21.0003061,16.1819295 Z" id="形状结合" fill="#FF8057"></path>
+            </g>
+        </g>
+    </g>
+</svg>

+ 20 - 0
src/tenant/music/coursewarePlay/image/icon-loop.svg

@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <title>切片</title>
+    <defs>
+        <path d="M19.7272413,6.75 C22.3505939,11.2937799 20.7937799,17.1038888 16.25,19.7272413 C11.7062201,22.3505939 5.89611123,20.7937799 3.27275866,16.25 C2.99661629,15.7717074 3.16049144,15.160117 3.63878407,14.8839746 C4.11707669,14.6078322 4.7286671,14.7717074 5.00480947,15.25 C7.07587728,18.8371947 11.6628053,20.0662583 15.25,17.9951905 C18.8371947,15.9241227 20.0662583,11.3371947 17.9951905,7.75 C15.9241227,4.16280532 11.3371947,2.93374166 7.75,5.00480947 C6.85788041,5.51987496 6.09738693,6.19866524 5.49784407,6.99904787 L7,7 C7.51283584,7 7.93550716,7.38604019 7.99327227,7.88337887 L8,8 C8,8.51283584 7.61395981,8.93550716 7.11662113,8.99327227 L7,9 L3,9 C2.48716416,9 2.06449284,8.61395981 2.00672773,8.11662113 L2,8 L2,4 C2,3.44771525 2.44771525,3 3,3 C3.51283584,3 3.93550716,3.38604019 3.99327227,3.88337887 L4,4 L3.99919549,5.66623947 C4.74283972,4.70951337 5.67119653,3.89560614 6.75,3.27275866 C11.2937799,0.649406102 17.1038888,2.20622008 19.7272413,6.75 Z M9,8 C9,7.22270552 9.84797124,6.74259237 10.5144958,7.14250707 L10.5144958,7.14250707 L15.5144958,10.1425071 C16.1618347,10.5309105 16.1618347,11.4690895 15.5144958,11.8574929 L15.5144958,11.8574929 L10.5144958,14.8574929 C9.84797124,15.2574076 9,14.7772945 9,14 L9,14 Z M11.001,9.766 L11.001,12.233 L13.057,11 L11.001,9.766 Z" id="path-1"></path>
+        <filter x="-31.6%" y="-31.6%" width="163.2%" height="163.1%" filterUnits="objectBoundingBox" id="filter-2">
+            <feOffset dx="0" dy="0" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
+            <feGaussianBlur stdDeviation="2" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
+            <feColorMatrix values="0 0 0 0 0   0 0 0 0 0   0 0 0 0 0  0 0 0 0.15 0" type="matrix" in="shadowBlurOuter1"></feColorMatrix>
+        </filter>
+    </defs>
+    <g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="课件播放(控件)" transform="translate(-42.000000, -246.000000)" fill-rule="nonzero">
+            <g id="形状结合" transform="translate(42.000000, 246.000000)">
+                <use fill="black" fill-opacity="1" filter="url(#filter-2)" xlink:href="#path-1"></use>
+                <use fill="#FFFFFF" xlink:href="#path-1"></use>
+            </g>
+        </g>
+    </g>
+</svg>

+ 28 - 0
src/tenant/music/coursewarePlay/image/icon-menu.svg

@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <title>切片</title>
+    <defs>
+        <path d="M12,12.5 L12,5.83333333 C13.3333333,4.64814815 14.6666667,4.05555556 16,4.05555556 C17.3333333,4.05555556 18.6666667,4.05555556 20,4.05555556 L20,19.1666667 C18.7195163,19.1666667 18.4037995,19.1666667 17.0528496,19.1666667 C15.7018997,19.1666667 13.3509499,18.7592593 12,19.9444444 C10.4052541,18.7592593 7.98478324,19.1666667 6.73858743,19.1666667 C5.49239162,19.1666667 5.24619581,19.1666667 4,19.1666667 L4,4.05555556 C5.33333333,4.05555556 6.66666667,4.05555556 8,4.05555556" id="path-1"></path>
+        <filter x="-25.0%" y="-25.2%" width="150.0%" height="150.3%" filterUnits="objectBoundingBox" id="filter-2">
+            <feMorphology radius="1" operator="dilate" in="SourceAlpha" result="shadowSpreadOuter1"></feMorphology>
+            <feOffset dx="0" dy="0" in="shadowSpreadOuter1" result="shadowOffsetOuter1"></feOffset>
+            <feMorphology radius="1" operator="erode" in="SourceAlpha" result="shadowInner"></feMorphology>
+            <feOffset dx="0" dy="0" in="shadowInner" result="shadowInner"></feOffset>
+            <feComposite in="shadowOffsetOuter1" in2="shadowInner" operator="out" result="shadowOffsetOuter1"></feComposite>
+            <feGaussianBlur stdDeviation="1" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
+            <feColorMatrix values="0 0 0 0 0   0 0 0 0 0   0 0 0 0 0  0 0 0 0.125792177 0" type="matrix" in="shadowBlurOuter1"></feColorMatrix>
+        </filter>
+    </defs>
+    <g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round">
+        <g id="课件播放(老师)" transform="translate(-740.000000, -76.000000)" fill-rule="nonzero">
+            <g id="编组-8" transform="translate(732.000000, 66.000000)">
+                <g id="编组-2" transform="translate(2.000000, 10.000000)">
+                    <g id="路径" transform="translate(6.000000, 0.000000)">
+                        <use fill="black" fill-opacity="1" filter="url(#filter-2)" xlink:href="#path-1"></use>
+                        <use stroke="#FFFFFF" stroke-width="2" xlink:href="#path-1"></use>
+                    </g>
+                </g>
+            </g>
+        </g>
+    </g>
+</svg>

二进制
src/tenant/music/coursewarePlay/image/icon-more.png


+ 18 - 0
src/tenant/music/coursewarePlay/image/icon-mulv.svg

@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <title>课程目录</title>
+    <defs>
+        <linearGradient x1="50%" y1="0%" x2="50%" y2="100%" id="linearGradient-1">
+            <stop stop-color="#FFB790" offset="0%"></stop>
+            <stop stop-color="#FF8057" offset="100%"></stop>
+        </linearGradient>
+    </defs>
+    <g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="课件播放(侧边目录)" transform="translate(-63.000000, -40.000000)" fill-rule="nonzero">
+            <g id="课程目录" transform="translate(63.000000, 40.000000)">
+                <path d="M0,12 C0.0544564416,8.0225337 0.745221648,4.72912615 2.76493864,2.76493864 C4.72912615,0.745221648 8.0225337,0.0544564416 12,0 C15.9774663,0.0544564416 19.2708739,0.745221648 21.2350614,2.76493864 C23.2547784,4.72912615 23.9455436,8.0225337 24,12 C23.9455436,15.9774663 23.2547784,19.2708739 21.2350614,21.2350614 C19.2708739,23.2547784 15.9774663,23.9455436 12,24 C8.0225337,23.9455436 4.72912615,23.2547784 2.76493864,21.2350614 C0.745221648,19.2708739 0.0544564416,15.9774663 0,12 Z" id="路径" fill="url(#linearGradient-1)"></path>
+                <path d="M16.9005371,8.99518041 L7.09972174,8.99518041 C6.61722643,8.99518041 6.22222222,8.57140376 6.22222222,8.05314576 C6.22222222,7.53516565 6.61696758,7.11111111 7.09972174,7.11111111 L16.9002783,7.11111111 C17.3827736,7.11111111 17.7777778,7.53488776 17.7777778,8.05314576 C17.7777778,8.57112587 17.3830324,8.99518041 16.9005371,8.99518041 L16.9005371,8.99518041 Z M16.9005371,13.3863402 L7.09972174,13.3863402 C6.61722643,13.3863402 6.22222222,12.9625635 6.22222222,12.4443055 C6.22222222,11.9263254 6.61696758,11.5022709 7.09972174,11.5022709 L16.9002783,11.5022709 C17.3827736,11.5022709 17.7777778,11.9260475 17.7777778,12.4443055 C17.7777778,12.9625635 17.3830324,13.3863402 16.9005371,13.3863402 L16.9005371,13.3863402 Z M10.9332816,17.7777778 L7.09972174,17.7777778 C6.61722643,17.7777778 6.22222222,17.3540011 6.22222222,16.8357431 C6.22222222,16.317763 6.61696758,15.8937085 7.09972174,15.8937085 L10.9330227,15.8937085 C11.415518,15.8937085 11.8105224,16.3174851 11.8105224,16.8357431 C11.8107811,17.3540011 11.4157769,17.7777778 10.9332816,17.7777778 L10.9332816,17.7777778 Z" id="形状" fill="#FFFFFF"></path>
+            </g>
+        </g>
+    </g>
+</svg>

+ 32 - 0
src/tenant/music/coursewarePlay/image/icon-pause.svg

@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <title>切片</title>
+    <defs>
+        <rect id="path-1" x="4" y="3" width="5" height="18" rx="2"></rect>
+        <filter x="-120.0%" y="-33.3%" width="340.0%" height="166.7%" filterUnits="objectBoundingBox" id="filter-2">
+            <feOffset dx="0" dy="0" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
+            <feGaussianBlur stdDeviation="2" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
+            <feColorMatrix values="0 0 0 0 0   0 0 0 0 0   0 0 0 0 0  0 0 0 0.15 0" type="matrix" in="shadowBlurOuter1"></feColorMatrix>
+        </filter>
+        <rect id="path-3" x="15" y="3" width="5" height="18" rx="2"></rect>
+        <filter x="-120.0%" y="-33.3%" width="340.0%" height="166.7%" filterUnits="objectBoundingBox" id="filter-4">
+            <feOffset dx="0" dy="0" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
+            <feGaussianBlur stdDeviation="2" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
+            <feColorMatrix values="0 0 0 0 0   0 0 0 0 0   0 0 0 0 0  0 0 0 0.15 0" type="matrix" in="shadowBlurOuter1"></feColorMatrix>
+        </filter>
+    </defs>
+    <g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="课件播放(控件)" transform="translate(-42.000000, -46.000000)" fill-rule="nonzero">
+            <g id="播放" transform="translate(42.000000, 46.000000)">
+                <g id="矩形">
+                    <use fill="black" fill-opacity="1" filter="url(#filter-2)" xlink:href="#path-1"></use>
+                    <use fill="#FFFFFF" xlink:href="#path-1"></use>
+                </g>
+                <g id="矩形备份">
+                    <use fill="black" fill-opacity="1" filter="url(#filter-4)" xlink:href="#path-3"></use>
+                    <use fill="#FFFFFF" xlink:href="#path-3"></use>
+                </g>
+            </g>
+        </g>
+    </g>
+</svg>

二进制
src/tenant/music/coursewarePlay/image/icon-pen.png


+ 22 - 0
src/tenant/music/coursewarePlay/image/icon-play.svg

@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <title>切片</title>
+    <defs>
+        <path d="M15.2349141,5.27497835 L22.6793243,18.2549758 C23.228861,19.2131423 22.8976011,20.4353773 21.9394345,20.9849141 C21.6365821,21.1586088 21.293537,21.25 20.9444103,21.25 L6.05558972,21.25 C4.95102022,21.25 4.05558972,20.3545695 4.05558972,19.25 C4.05558972,18.9008732 4.14698087,18.5578282 4.32067566,18.2549758 L11.7650859,5.27497835 C12.3146227,4.31681177 13.5368577,3.98555182 14.4950242,4.53508853 C14.8029434,4.7116892 15.0583134,4.96705923 15.2349141,5.27497835 Z" id="path-1"></path>
+        <filter x="-27.5%" y="-31.6%" width="155.1%" height="163.2%" filterUnits="objectBoundingBox" id="filter-2">
+            <feOffset dx="0" dy="0" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
+            <feGaussianBlur stdDeviation="2" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
+            <feColorMatrix values="0 0 0 0 0   0 0 0 0 0   0 0 0 0 0  0 0 0 0.15 0" type="matrix" in="shadowBlurOuter1"></feColorMatrix>
+        </filter>
+    </defs>
+    <g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="课件播放(控件)" transform="translate(-42.000000, -83.000000)" fill-rule="nonzero">
+            <g id="播放备份" transform="translate(42.000000, 83.000000)">
+                <g id="三角形" transform="translate(13.500000, 11.750000) rotate(-270.000000) translate(-13.500000, -11.750000) ">
+                    <use fill="black" fill-opacity="1" filter="url(#filter-2)" xlink:href="#path-1"></use>
+                    <use fill="#FFFFFF" xlink:href="#path-1"></use>
+                </g>
+            </g>
+        </g>
+    </g>
+</svg>

+ 24 - 0
src/tenant/music/coursewarePlay/image/icon-point.svg

@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <title>切片</title>
+    <defs>
+        <path d="M17.9889353,7.03683462 C15.5779696,4.12723478 11.4245096,3.37164362 8.14331066,5.24573351 C4.86211176,7.1198234 3.40277969,11.0812175 4.68401757,14.6360727 C5.96525544,18.190928 9.6162626,20.3104404 13.338596,19.6602991 C17.0609294,19.0101577 19.7773154,15.7785188 19.7776894,11.9998178 C19.7776894,11.3862113 20.2751133,10.888785 20.8887168,10.888785 C21.5023203,10.888785 21.9997442,11.3862113 21.9997442,11.9998178 C21.9997099,16.8400427 18.5329414,20.9853151 13.7690477,21.8413977 C9.00515408,22.6974802 4.31207278,20.0185514 2.62687668,15.4811663 C0.941680576,10.9437811 2.7482686,5.85083589 6.91602308,3.38965046 C11.0837776,0.928465034 16.4159405,1.80576128 19.5754824,5.47250055 L20.1087755,4.94698208 C20.545968,4.51623039 21.2495741,4.52145337 21.6803238,4.95864794 C22.1110734,5.39584251 22.1058505,6.09945198 21.668658,6.5302037 L12.3204735,15.742887 C11.8856052,16.1721277 11.185738,16.1696459 10.7539248,15.7373318 L6.69200863,11.6742852 C6.27096012,11.2383394 6.2769817,10.5453817 6.70554253,10.1168189 C7.13410335,9.688256 7.82705768,9.68223439 8.26300138,10.1032849 L11.5460874,13.3863866 L17.9889353,7.03683462 L17.9889353,7.03683462 Z" id="path-1"></path>
+        <filter x="-15.0%" y="-15.0%" width="130.0%" height="130.0%" filterUnits="objectBoundingBox" id="filter-2">
+            <feOffset dx="0" dy="0" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
+            <feGaussianBlur stdDeviation="1" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
+            <feColorMatrix values="0 0 0 0 0   0 0 0 0 0   0 0 0 0 0  0 0 0 0.125792177 0" type="matrix" in="shadowBlurOuter1"></feColorMatrix>
+        </filter>
+    </defs>
+    <g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="课件播放(老师)" transform="translate(-740.000000, -208.000000)" fill-rule="nonzero">
+            <g id="编组-8" transform="translate(732.000000, 66.000000)">
+                <g id="编组-7" transform="translate(0.000000, 75.000000)">
+                    <g id="路径" transform="translate(8.000000, 67.000000)">
+                        <use fill="black" fill-opacity="1" filter="url(#filter-2)" xlink:href="#path-1"></use>
+                        <use fill="#FFFFFF" xlink:href="#path-1"></use>
+                    </g>
+                </g>
+            </g>
+        </g>
+    </g>
+</svg>

+ 20 - 0
src/tenant/music/coursewarePlay/image/icon-song-active.svg

@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="15px" height="16px" viewBox="0 0 15 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <title>切片</title>
+    <g id="页面-2" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="课件播放(三层)" transform="translate(-364.000000, -298.000000)" fill-rule="nonzero">
+            <g id="编组" transform="translate(306.000000, 0.000000)">
+                <g id="编组-8" transform="translate(0.000000, 93.500000)">
+                    <g id="编组-12" transform="translate(0.000000, 65.000000)">
+                        <g id="知识点目录/图片备份-3" transform="translate(58.000000, 140.000000)">
+                            <g id="编组" transform="translate(1.000000, 0.000000)">
+                                <path d="M9,0 L13,4 L13,13 C13,14.1 12.1,15 11,15 L2,15 C0.900000001,15 0,14.1 0,13 L0,2 C0,0.9 0.900000001,0 2,0 L9,0 Z M9.12286624,4.39507366 L9.05045121,4.40041053 L4.62790119,5.15709075 C4.43655776,5.19167453 4.28513683,5.3491615 4.28561715,5.52434895 L4.28561715,5.52434895 L4.23182216,9.53593936 C3.89020555,9.39268515 3.46572382,9.3940618 3.05944296,9.57324696 C2.32170786,9.89861591 1.93033971,10.6831644 2.18299927,11.3317032 L2.18299927,11.3317032 L2.20168239,11.3766901 C2.48234091,12.0123255 3.29959494,12.2398786 4.02247275,11.8921219 C4.56059052,11.6329597 4.90067764,11.1236487 4.92749856,10.6174852 L4.92749856,10.6174852 L4.97723571,6.93534117 C4.98142189,6.75800316 5.13114435,6.59642647 5.32418625,6.56593241 L5.32418625,6.56593241 L8.30000288,6.10080156 C8.30466938,6.098651 8.31400237,6.09434988 8.32036733,6.09628904 C8.51680615,6.07397444 8.6721386,6.20703285 8.66965088,6.38846059 L8.66965088,6.38846059 L8.5995415,9.09592309 C8.2484902,8.94608506 7.81030549,8.94997414 7.39554263,9.14388305 C6.66756943,9.47937063 6.29062966,10.2718872 6.5572887,10.9139742 C6.82394775,11.5560613 7.63229769,11.8023353 8.36323892,11.4606074 C8.94898573,11.1906651 9.30736799,10.629963 9.28887187,10.0889393 L9.28887187,10.0889393 L9.42646417,4.69000872 C9.42773374,4.6796787 9.42603527,4.67558898 9.42730484,4.66525896 C9.4140946,4.48619318 9.2481596,4.36776591 9.05045121,4.40041053 Z" id="形状结合" fill="#FF8057"></path>
+                                <path d="M9.00000001,0 L13,4 L10,4 C9.4,4 9.00000001,3.6 9.00000001,3 L9.00000001,0 Z" id="路径" fill="#CC4419" opacity="0.506022135" style="mix-blend-mode: multiply;"></path>
+                            </g>
+                        </g>
+                    </g>
+                </g>
+            </g>
+        </g>
+    </g>
+</svg>

+ 20 - 0
src/tenant/music/coursewarePlay/image/icon-song.svg

@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="15px" height="16px" viewBox="0 0 15 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <title>切片</title>
+    <g id="页面-2" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="课件播放(三层)" transform="translate(-364.000000, -268.000000)" fill-rule="nonzero">
+            <g id="编组" transform="translate(306.000000, 0.000000)">
+                <g id="编组-8" transform="translate(0.000000, 93.500000)">
+                    <g id="编组-12" transform="translate(0.000000, 65.000000)">
+                        <g id="知识点目录/图片备份-6" transform="translate(58.000000, 110.000000)">
+                            <g id="编组" transform="translate(1.000000, 0.000000)">
+                                <path d="M9,0 L13,4 L13,13 C13,14.1 12.1,15 11,15 L2,15 C0.900000001,15 0,14.1 0,13 L0,2 C0,0.9 0.900000001,0 2,0 L9,0 Z M9.12286624,4.39507366 L9.05045121,4.40041053 L4.62790119,5.15709075 C4.43655776,5.19167453 4.28513683,5.3491615 4.28561715,5.52434895 L4.28561715,5.52434895 L4.23182216,9.53593936 C3.89020555,9.39268515 3.46572382,9.3940618 3.05944296,9.57324696 C2.32170786,9.89861591 1.93033971,10.6831644 2.18299927,11.3317032 L2.18299927,11.3317032 L2.20168239,11.3766901 C2.48234091,12.0123255 3.29959494,12.2398786 4.02247275,11.8921219 C4.56059052,11.6329597 4.90067764,11.1236487 4.92749856,10.6174852 L4.92749856,10.6174852 L4.97723571,6.93534117 C4.98142189,6.75800316 5.13114435,6.59642647 5.32418625,6.56593241 L5.32418625,6.56593241 L8.30000288,6.10080156 C8.30466938,6.098651 8.31400237,6.09434988 8.32036733,6.09628904 C8.51680615,6.07397444 8.6721386,6.20703285 8.66965088,6.38846059 L8.66965088,6.38846059 L8.5995415,9.09592309 C8.2484902,8.94608506 7.81030549,8.94997414 7.39554263,9.14388305 C6.66756943,9.47937063 6.29062966,10.2718872 6.5572887,10.9139742 C6.82394775,11.5560613 7.63229769,11.8023353 8.36323892,11.4606074 C8.94898573,11.1906651 9.30736799,10.629963 9.28887187,10.0889393 L9.28887187,10.0889393 L9.42646417,4.69000872 C9.42773374,4.6796787 9.42603527,4.67558898 9.42730484,4.66525896 C9.4140946,4.48619318 9.2481596,4.36776591 9.05045121,4.40041053 Z" id="形状结合" fill="#FFFFFF"></path>
+                                <path d="M9.00000001,0 L13,4 L10,4 C9.4,4 9.00000001,3.6 9.00000001,3 L9.00000001,0 Z" id="路径" fill="#B3B3B3" opacity="0.619"></path>
+                            </g>
+                        </g>
+                    </g>
+                </g>
+            </g>
+        </g>
+    </g>
+</svg>

二进制
src/tenant/music/coursewarePlay/image/icon-speed-add.png


二进制
src/tenant/music/coursewarePlay/image/icon-speed-bg.png


二进制
src/tenant/music/coursewarePlay/image/icon-speed-cut.png


+ 45 - 0
src/tenant/music/coursewarePlay/image/icon-start.svg

@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="121px" height="38px" viewBox="0 0 121 38" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <title>按钮/小按钮/橙</title>
+    <defs>
+        <linearGradient x1="15.3622126%" y1="46.5566937%" x2="89.7647319%" y2="53.6358332%" id="linearGradient-1">
+            <stop stop-color="#FF9C63" offset="0%"></stop>
+            <stop stop-color="#FF7144" offset="100%"></stop>
+        </linearGradient>
+        <text id="text-2" font-family="PingFangSC-Medium, PingFang SC" font-size="16" font-weight="400" fill="#FFFFFF">
+            <tspan x="49" y="24">去练习</tspan>
+        </text>
+        <filter x="-4.2%" y="-4.5%" width="108.3%" height="118.2%" filterUnits="objectBoundingBox" id="filter-3">
+            <feOffset dx="0" dy="1" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
+            <feGaussianBlur stdDeviation="0.5" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
+            <feColorMatrix values="0 0 0 0 0.941236855   0 0 0 0 0.448637032   0 0 0 0 0.244388325  0 0 0 1 0" type="matrix" in="shadowBlurOuter1"></feColorMatrix>
+        </filter>
+        <linearGradient x1="50%" y1="0%" x2="50%" y2="100%" id="linearGradient-4">
+            <stop stop-color="#FCFFFF" offset="0%"></stop>
+            <stop stop-color="#FFE4D4" offset="100%"></stop>
+        </linearGradient>
+        <path d="M10,0 C15.5228475,0 20,4.4771525 20,10 C20,15.5228475 15.5228475,20 10,20 C4.4771525,20 0,15.5228475 0,10 C0,4.4771525 4.4771525,0 10,0 Z M15.8740909,4.13727273 C15.7362351,3.65455394 15.2331964,3.37495234 14.7504546,3.51272728 L10.205,4.81181817 C9.89624241,4.89978885 9.65677854,5.14409039 9.57499999,5.45454546 L9.54545455,5.45454546 L9.54590909,11.0254545 C8.4211929,10.2420678 6.92120134,10.2669785 5.82311793,11.0872797 C4.72503453,11.907581 4.27567032,13.3388975 4.70785225,14.6396281 C5.14003419,15.9403587 6.35662277,16.8181818 7.72727272,16.8181818 L7.83636363,16.8163636 C9.5501842,16.7575695 10.9091192,15.3511924 10.9090909,13.6363636 L10.9090909,8.24636364 L15.25,7.00636364 C15.6400039,6.89481163 15.9089107,6.53837105 15.9090909,6.13272727 L15.9090909,4.38727272 C15.9090909,4.30271722 15.8973272,4.21857285 15.8740909,4.13727273 Z" id="path-5"></path>
+        <filter x="-7.5%" y="-7.5%" width="115.0%" height="115.0%" filterUnits="objectBoundingBox" id="filter-6">
+            <feOffset dx="0" dy="0" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
+            <feGaussianBlur stdDeviation="0.5" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
+            <feColorMatrix values="0 0 0 0 0.941236855   0 0 0 0 0.448637032   0 0 0 0 0.244388325  0 0 0 1 0" type="matrix" in="shadowBlurOuter1"></feColorMatrix>
+        </filter>
+    </defs>
+    <g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="画板" transform="translate(-350.000000, -321.000000)">
+            <g id="编组-2" transform="translate(350.000000, 321.000000)">
+                <rect id="button-normal" fill="url(#linearGradient-1)" x="0" y="0" width="121" height="38" rx="19"></rect>
+                <g id="确定" fill-rule="nonzero" fill="#FFFFFF" fill-opacity="1">
+                    <use filter="url(#filter-3)" xlink:href="#text-2"></use>
+                    <use xlink:href="#text-2"></use>
+                </g>
+                <g id="编组" transform="translate(24.000000, 9.000000)" fill-rule="nonzero">
+                    <g id="形状结合">
+                        <use fill="black" fill-opacity="1" filter="url(#filter-6)" xlink:href="#path-5"></use>
+                        <use fill="url(#linearGradient-4)" xlink:href="#path-5"></use>
+                    </g>
+                </g>
+            </g>
+        </g>
+    </g>
+</svg>

文件差异内容过多而无法显示
+ 4 - 0
src/tenant/music/coursewarePlay/image/icon-touping.svg


+ 22 - 0
src/tenant/music/coursewarePlay/image/icon-up.svg

@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <title>切片</title>
+    <defs>
+        <path d="M12.619886,7.97254617 L12.7071068,8.05025253 L16.2426407,11.5857864 C16.633165,11.9763107 16.633165,12.6094757 16.2426407,13 C15.880011,13.3626297 15.3081656,13.3885318 14.9156479,13.0777064 L14.8284271,13 L12.9994661,11.1713593 L13,19 C13,19.5522847 12.5522847,20 12,20 C11.4477153,20 11,19.5522847 11,19 L10.9994661,11.1703593 L9.17157288,13 C8.80894318,13.3626297 8.23709778,13.3885318 7.84458013,13.0777064 L7.75735931,13 C7.39472961,12.6373703 7.36882749,12.0655249 7.67965295,11.6730073 L7.75735931,11.5857864 L11.2928932,8.05025253 C11.6555229,7.68762283 12.2273683,7.66172071 12.619886,7.97254617 Z M18.363961,5.63603897 C21.8786797,9.15075759 21.8786797,14.8492424 18.363961,18.363961 C17.9734367,18.7544853 17.3402718,18.7544853 16.9497475,18.363961 C16.5592232,17.9734367 16.5592232,17.3402718 16.9497475,16.9497475 C19.6834175,14.2160774 19.6834175,9.78392257 16.9497475,7.05025253 C14.2160774,4.31658249 9.78392257,4.31658249 7.05025253,7.05025253 C4.31658249,9.78392257 4.31658249,14.2160774 7.05025253,16.9497475 C7.44077682,17.3402718 7.44077682,17.9734367 7.05025253,18.363961 C6.65972824,18.7544853 6.02656326,18.7544853 5.63603897,18.363961 C2.12132034,14.8492424 2.12132034,9.15075759 5.63603897,5.63603897 C9.15075759,2.12132034 14.8492424,2.12132034 18.363961,5.63603897 Z" id="path-1"></path>
+        <filter x="-33.3%" y="-35.3%" width="166.7%" height="170.6%" filterUnits="objectBoundingBox" id="filter-2">
+            <feOffset dx="0" dy="0" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
+            <feGaussianBlur stdDeviation="2" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
+            <feColorMatrix values="0 0 0 0 0   0 0 0 0 0   0 0 0 0 0  0 0 0 0.15 0" type="matrix" in="shadowBlurOuter1"></feColorMatrix>
+        </filter>
+    </defs>
+    <g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="课件播放/全屏(伴学端)" transform="translate(-90.000000, -82.000000)" fill-rule="nonzero">
+            <g id="编组-8备份" transform="translate(82.000000, 68.000000)">
+                <g id="形状结合" transform="translate(8.000000, 14.000000)">
+                    <use fill="black" fill-opacity="1" filter="url(#filter-2)" xlink:href="#path-1"></use>
+                    <use fill="#FFFFFF" xlink:href="#path-1"></use>
+                </g>
+            </g>
+        </g>
+    </g>
+</svg>

文件差异内容过多而无法显示
+ 9 - 0
src/tenant/music/coursewarePlay/image/icon-video-active.svg


文件差异内容过多而无法显示
+ 9 - 0
src/tenant/music/coursewarePlay/image/icon-video.svg


二进制
src/tenant/music/coursewarePlay/image/icon-videobg.png


+ 1 - 0
src/tenant/music/coursewarePlay/image/icon-zhibo.svg

@@ -0,0 +1 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1673454396647" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1930" width="32" height="32" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M74.666667 853.333333c-17.066667 0-32-14.933333-32-32v-426.666666c0-17.066667 14.933333-32 32-32s32 14.933333 32 32v426.666666c0 17.066667-14.933333 32-32 32zM366.933333 853.333333c-17.066667 0-32-14.933333-32-32v-618.666666c0-17.066667 14.933333-32 32-32s32 14.933333 32 32v618.666666c0 17.066667-14.933333 32-32 32zM657.066667 853.333333c-17.066667 0-32-14.933333-32-32v-341.333333c0-17.066667 14.933333-32 32-32s32 14.933333 32 32v341.333333c0 17.066667-12.8 32-32 32zM949.333333 853.333333c-17.066667 0-32-14.933333-32-32v-512c0-17.066667 14.933333-32 32-32s32 14.933333 32 32v512c0 17.066667-14.933333 32-32 32z" fill="#FF8057" p-id="1931"></path></svg>

文件差异内容过多而无法显示
+ 1 - 0
src/tenant/music/coursewarePlay/image/icons.json


二进制
src/tenant/music/coursewarePlay/image/video-speed.png


+ 374 - 0
src/tenant/music/coursewarePlay/index.module.less

@@ -0,0 +1,374 @@
+.playContent {
+  width: 100vw;
+  height: 100vh;
+  background-color: #000;
+  overflow: hidden;
+  --plyr-color-main: var(--van-primary);
+  --plyr-range-track-height: 3px;
+}
+
+.coursewarePlay {
+  position: relative;
+  height: 100vh;
+  margin: 0 auto;
+  overflow: hidden;
+}
+
+.playModel {
+  position: absolute;
+  left: 0;
+  top: 0;
+  right: 0;
+  bottom: 0;
+  box-shadow: inset 0px 0px 164px 0px rgba(0, 0, 0, 1);
+  pointer-events: none;
+}
+
+.headerContainer {
+  position: fixed;
+  top: 0;
+  left: 0;
+  right: 0;
+  z-index: 10;
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  height: 40px;
+  background: linear-gradient(180deg, rgba(0, 0, 0, 0.6), transparent);
+  transition: transform 0.5s;
+  box-sizing: border-box;
+
+  div {
+    box-sizing: border-box;
+  }
+}
+
+.backBtn {
+  font-weight: 500;
+  font-size: 16px;
+  color: #ffffff;
+  height: 100%;
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  z-index: 10;
+  padding: 0 15px;
+
+  :global {
+    .van-icon {
+      margin-right: 8px;
+    }
+  }
+}
+
+.headRight {
+  position: relative;
+  z-index: 10;
+  display: flex;
+  align-items: center;
+  margin-left: auto;
+  height: 100%;
+  padding-right: 15px;
+
+  .rightBtn {
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    height: 100%;
+    padding: 0 10px;
+
+    img {
+      width: 22px;
+      height: 22px;
+      display: block;
+    }
+  }
+}
+
+.menu {
+  position: absolute;
+  width: 100%;
+  height: 100%;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  font-weight: 500;
+  font-size: 16px;
+  color: #ffffff;
+}
+
+.tabsContent {
+  width: 100vw;
+  height: 100vh;
+
+  :global {
+    .van-tabs__wrap {
+      display: none !important;
+    }
+
+    .van-tabs__content {
+      width: 100%;
+      height: 100%;
+    }
+  }
+}
+
+.wraps {
+  width: 100%;
+  height: 100%;
+  transform-style: preserve-3d;
+  perspective: (32rem);
+  transition-timing-function: initial;
+}
+
+.itemDiv {
+  position: absolute;
+  left: 0;
+  top: 0;
+  right: 0;
+  bottom: 0;
+  width: 100%;
+  height: 100%;
+  background-color: #000;
+  // transition-duration: .8s;
+  transform-style: preserve-3d;
+  transition-property: transform, opacity, height;
+  backface-visibility: hidden;
+  overflow: hidden;
+  z-index: 1;
+
+  &.itemActive {
+    z-index: 10;
+  }
+
+  &.acitveAnimation {
+    transition-duration: 0.8s;
+  }
+
+  &.show {
+    display: block;
+  }
+
+  &.hide {
+    display: none;
+  }
+
+  video {
+    width: 100%;
+    height: 100%;
+  }
+
+  img {
+    display: block;
+    width: 100%;
+    height: 100%;
+    object-fit: contain;
+  }
+}
+
+.fullBtn {
+  width: 38px;
+  height: 50px;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  color: #fff;
+  justify-content: space-evenly;
+  overflow: hidden;
+  white-space: nowrap;
+
+  &:active {
+    background: rgba(255, 255, 255, 0.2);
+  }
+}
+
+.rightFixedBtns {
+  position: absolute;
+  top: 50%;
+  transform: translateY(-50%);
+  right: 12px;
+  z-index: 10;
+
+  .btnsBottom {
+    margin-top: 10px;
+  }
+}
+
+.leftFixedBtns {
+  position: absolute;
+  top: 50%;
+  transform: translateY(-50%);
+  left: 12px;
+  z-index: 10;
+
+  .prePoint {
+    margin-bottom: 8px;
+  }
+}
+
+.btnsWrap {
+  background: rgba(51, 51, 51, 0.4);
+  border-radius: 6px;
+  overflow: hidden;
+}
+
+.bottomFixedContainer {
+  position: absolute;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  z-index: 10;
+  background: linear-gradient(0deg, rgba(0, 0, 0, 0.5), transparent);
+  transition: transform 0.5s;
+
+  .time {
+    display: flex;
+    justify-content: space-between;
+    color: #fff;
+    font-size: 10px;
+    padding: 4px 20px;
+  }
+
+  .slider {
+    padding: 8px 20px;
+
+    :global {
+      .van-slider__button {
+        background: var(--van-primary);
+      }
+    }
+  }
+
+  .actions {
+    display: flex;
+    justify-content: space-between;
+    color: #fff;
+    font-size: 12px;
+    padding: 0 10px 4px 20px;
+    align-items: center;
+
+    .actionBtn {
+      display: flex;
+    }
+
+    .actionBtn > img {
+      width: 30px;
+      height: 30px;
+      display: block;
+      padding: 4px 10px 4px 4px;
+    }
+  }
+}
+
+.popup {
+  background: rgba(0, 0, 0, 0.5);
+}
+
+.overlayClass {
+  --van-overlay-background: transparent;
+}
+
+:global {
+  .top-enter-active,
+  .top-leave-active {
+    transition: transform 0.5s;
+  }
+
+  .top-enter-from,
+  .top-leave-to {
+    transform: translateY(-100%);
+  }
+
+  .left-enter-active,
+  .left-leave-active {
+    transition: all 0.5s;
+  }
+
+  .left-enter-from,
+  .left-leave-to {
+    left: -60px;
+    opacity: 0;
+  }
+
+  .right-enter-active,
+  .right-leave-active {
+    transition: all 0.5s;
+  }
+
+  .right-enter-from,
+  .right-leave-to {
+    right: -60px;
+    opacity: 0;
+  }
+
+  .bottom-enter-active,
+  .bottom-leave-active {
+    transition: transform 0.5s;
+  }
+
+  .bottom-enter-from,
+  .bottom-leave-to {
+    transform: translateY(100%);
+  }
+}
+
+.loadWrap {
+  position: absolute;
+  left: -6px;
+  top: 0;
+  right: -6px;
+  bottom: 0;
+  background: linear-gradient(45deg, #21232a, #111218);
+  display: flex;
+  justify-content: center;
+  align-items: center;
+}
+
+.playRecordTime {
+  width: 90px;
+  margin-right: 10px;
+  background: rgba(0, 0, 0, 0.4);
+  border-radius: 20px;
+  font-size: 12px;
+  padding: 6px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  color: #fff;
+
+  .timeLoad {
+    width: 5px;
+    height: 5px;
+    background: #ff4e19;
+    border: 0.5px solid #ffffff;
+    border-radius: 50%;
+    margin-right: 3px;
+    animation: loadFade 1s ease-in-out infinite;
+  }
+}
+
+@keyframes loadFade {
+  0% {
+    opacity: 0;
+  }
+
+  50% {
+    opacity: 0.5;
+  }
+
+  100% {
+    opacity: 1;
+  }
+}
+
+.swiperContainer {
+  :global {
+    .swiper-wrapper {
+      height: 100vh;
+    }
+  }
+}
+
+.popupMore {
+  background: rgba(0, 0, 0, 0.8);
+}

+ 3255 - 0
src/tenant/music/coursewarePlay/index.tsx

@@ -0,0 +1,3255 @@
+import { Toast, Icon, Popup, Dialog } from 'vant'
+import {
+  defineComponent,
+  onMounted,
+  reactive,
+  nextTick,
+  onUnmounted,
+  ref,
+  watch,
+  Transition,
+  computed
+} from 'vue'
+import iconBack from './image/back.svg'
+import styles from './index.module.less'
+import 'plyr/dist/plyr.css'
+import request from '@/helpers/request'
+import { state } from '@/state'
+import { useRoute } from 'vue-router'
+import { postMessage, promisefiyPostMessage } from '@/helpers/native-message'
+import MusicScore from './component/musicScore'
+import iconDian from './image/icon-dian.svg'
+import iconPoint from './image/icon-point.svg'
+import {
+  iconUp,
+  iconDown,
+  // iconPen,
+  iconTouping,
+  iconMenu
+} from './image/icons.json'
+import Points from './component/points'
+import { browser } from '@/helpers/utils'
+import { Vue3Lottie } from 'vue3-lottie'
+import playLoadData from './datas/data.json'
+import { usePageVisibility } from '@vant/use'
+import PlayRecordTime from './playRecordTime'
+// import { handleCheckVip } from '../hook/useFee'
+import OGuide from './component/o-guide'
+import Tool, { ToolItem, ToolType } from './component/tool'
+import Pen from './component/tools/pen'
+// import VideoItem from './component/video-item';
+import VideoPlay from './component/video-play'
+
+export default defineComponent({
+  name: 'CoursewarePlay',
+  setup() {
+    const pageVisibility = usePageVisibility()
+    /** 页面显示和隐藏 */
+    watch(
+      () => pageVisibility.value,
+      value => {
+        if (value == 'hidden') {
+          handleStop()
+        }
+      }
+    )
+    /** 设置播放容器 16:9 */
+    const parentContainer = reactive({
+      width: '100vw'
+    })
+    const setContainer = () => {
+      const min = Math.min(screen.width, screen.height)
+      const max = Math.max(screen.width, screen.height)
+      const width = min * (16 / 9)
+      if (width > max) {
+        parentContainer.width = '100vw'
+        return
+      } else {
+        parentContainer.width = width + 'px'
+      }
+    }
+    const handleInit = (type = 0) => {
+      //设置容器16:9
+      setContainer()
+      // 横屏
+      postMessage(
+        {
+          api: 'setRequestedOrientation',
+          content: {
+            orientation: type
+          }
+        },
+        () => {
+          // console.log(234);
+        }
+      )
+      // 头,包括返回箭头
+      // postMessage({
+      //   api: 'setTitleBarVisibility',
+      //   content: {
+      //     status: type
+      //   }
+      // })
+      // 安卓的状态栏
+      postMessage({
+        api: 'setStatusBarVisibility',
+        content: {
+          isVisibility: type
+        }
+      })
+      // 进入页面设置常量
+      postMessage({
+        api: 'keepScreenLongLight',
+        content: {
+          isOpenLight: type ? true : false
+        }
+      })
+    }
+    handleInit()
+    onUnmounted(() => {
+      handleInit(1)
+      window.removeEventListener('message', iframeHandle)
+    })
+
+    const route = useRoute()
+    const headeRef = ref()
+    const data = reactive({
+      detail: null as any,
+      knowledgePointList: [] as any,
+      itemList: [] as any,
+      showHead: true,
+      isCourse: false,
+      isRecordPlay: false,
+      videoRefs: {},
+
+      videoState: 'init' as 'init' | 'play',
+      videoItemRef: null as any,
+      animationState: 'start' as 'start' | 'end'
+    })
+    const activeData = reactive({
+      isAutoPlay: true, // 是否自动播放
+      nowTime: 0,
+      model: true, // 遮罩
+      isAnimation: true, // 是否动画
+      videoBtns: true, // 视频
+      currentTime: 0,
+      duration: 0,
+      timer: null as any,
+      item: null as any
+    })
+    // 获取缓存路径
+    const getCacheFilePath = async (material: any) => {
+      const res = await promisefiyPostMessage({
+        api: 'getCourseFilePath',
+        content: {
+          url: material.content,
+          localPath: '',
+          materialId: material.materialId,
+          updateTime: material.updateTime,
+          type: material.typeCode // SONG VIDEO IMAGE
+        }
+      })
+      // console.log('缓存路径返回', res)
+      return res
+    }
+    // 获取当前课程是否签退
+    // const getCourseSchedule = async () => {
+    //   if (!route.query.courseId) return;
+    //   try {
+    //     const res = await request.get(
+    //       `${state.platformApi}/courseSchedule/detail/${route.query.courseId}`,
+    //       {
+    //         hideLoading: true
+    //       }
+    //     );
+    //     if (res?.data) {
+    //       data.isCourse =
+    //         res.data.status === 'ING' && state.platformType == 'TEACHER'
+    //           ? true
+    //           : false;
+    //       // data.isRecordPlay = Date.now() > dayjs(res.data.startTime).valueOf()
+    //     }
+    //   } catch (e) {
+    //     console.log(e);
+    //   }
+    // };
+    const getTempList = async (materialList: any, name: any) => {
+      const list: any = []
+      const browserInfo = browser()
+      for (let j = 0; j < materialList.length; j++) {
+        const material = materialList[j]
+        //请求本地缓存
+        if (browserInfo.isApp && ['VIDEO', 'IMG'].includes(material.typeCode)) {
+          const localData: any = await getCacheFilePath(material)
+
+          if (localData?.content?.localPath) {
+            material.url = material.content
+            material.content = localData.content.localPath
+          } else {
+            material.url = material.content + '?t=' + +new Date()
+            material.content = material.content + '?t=' + +new Date()
+          }
+        }
+
+        list.push({
+          ...material,
+          iframeRef: null,
+          videoEle: null,
+          tabName: name,
+          autoPlay: false, //加载完成是否自动播放
+          isprepare: false, // 视频是否加载完成
+          isRender: false // 是否渲染了
+        })
+      }
+      return list
+    }
+    const getItemList = async () => {
+      const list: any = []
+
+      for (let i = 0; i < data.knowledgePointList.length; i++) {
+        const item = data.knowledgePointList[i]
+        if (item.materialList && item.materialList.length > 0) {
+          const tempList = await getTempList(item.materialList, item.name)
+          list.push(...tempList)
+        }
+
+        // 第二层级
+        if (item.children && item.children.length > 0) {
+          const childrenList = item.children || []
+          for (let j = 0; j < childrenList.length; j++) {
+            const childItem = childrenList[j]
+            const tempList = await getTempList(
+              childItem.materialList,
+              childItem.name
+            )
+            list.push(...tempList)
+          }
+        }
+      }
+
+      // console.log(list, 'list')
+
+      let _firstIndex = list.findIndex(
+        (n: any) =>
+          n.knowledgePointMaterialRelationId == route.query.kId ||
+          n.materialId == route.query.kId
+      )
+      _firstIndex = _firstIndex > -1 ? _firstIndex : 0
+      const item = list[_firstIndex]
+
+      // console.log(_firstIndex, '_firstIndex', route.query.kId, 'route.query.kId', item)
+      // 是否自动播放
+      if (activeData.isAutoPlay) {
+        item.autoPlay = true
+      }
+      popupData.activeIndex = _firstIndex
+      popupData.playIndex = _firstIndex
+      popupData.tabName = item.tabName
+      popupData.tabActive = item.knowledgePointId
+      popupData.itemActive = item.id
+      popupData.itemName = item.name
+      nextTick(() => {
+        data.itemList = list
+        checkedAnimation(popupData.activeIndex)
+        postMessage({
+          api: 'courseLoading',
+          content: {
+            show: false,
+            type: 'fullscreen'
+          }
+        })
+        setTimeout(() => {
+          data.animationState = 'end'
+        }, 500)
+      })
+    }
+    const getDetail = async () => {
+      try {
+        // const res: any = await request.get(
+        //   state.platformApi +
+        //     `/lessonCourseware/getLessonCourseDetail/${route.query.id}`,
+        //   {
+        //     hideLoading: true
+        //   }
+        // )
+        const res = {
+          status: true,
+          data: {
+            updateTime: '2023-09-22 22:17:43',
+            createTime: '2023-09-11 15:43:18',
+            lockFlag: false,
+            lessonCoursewareId: 1000101,
+            lessonTrainingId: 0,
+            lessonTrainingName: '',
+            lessonDurationSecond: 0,
+            lessonOrder: 1,
+            lockEnable: false,
+            knowledgePointIds: '',
+            accessScope: 0,
+            knowledgePointList: [
+              {
+                children: null,
+                materialList: [
+                  {
+                    updateTime: '2023-09-11 15:43:18',
+                    sn: '小军鼓乐器安装前言-打击乐- L1-1-1-讲话',
+                    knowledgePointMaterialRelationId: 1701139355188412400,
+                    adviseStudyTimeSecond: 0,
+                    knowledgePointId: 1030622,
+                    courseTypeCode: 'PERCUSSION_SINGLE',
+                    name: '安装前言',
+                    id: 1081414,
+                    type: 0,
+                    content:
+                      'https://courseware.lexiaoya.cn/3小军鼓乐器安装前言-打击乐-L1-1-1-讲话.mp4',
+                    typeCode: 'VIDEO'
+                  }
+                ],
+                sortNo: 1,
+                totalMaterialTimeSecond: 0,
+                name: '安装前言',
+                id: 1030622
+              },
+              {
+                children: null,
+                materialList: [
+                  {
+                    updateTime: '2023-09-11 15:43:18',
+                    sn: '了解小军鼓的二个部件-打击乐- L1-1-2-讲话',
+                    knowledgePointMaterialRelationId: 1701139355192606700,
+                    adviseStudyTimeSecond: 0,
+                    knowledgePointId: 1030623,
+                    courseTypeCode: 'PERCUSSION_SINGLE',
+                    name: '了解小军鼓的二个部件',
+                    id: 1081415,
+                    type: 0,
+                    content:
+                      'https://courseware.lexiaoya.cn/4了解小军鼓的二个部件-打击乐-L1-1-2-讲话.mp4',
+                    typeCode: 'VIDEO'
+                  }
+                ],
+                sortNo: 2,
+                totalMaterialTimeSecond: 0,
+                name: '了解小军鼓的二个部件',
+                id: 1030623
+              },
+              {
+                children: null,
+                materialList: [
+                  {
+                    updateTime: '2023-09-11 15:43:18',
+                    sn: '鼓架组装-打击乐- L1-1-3-A1-讲话',
+                    knowledgePointMaterialRelationId: 1701139355192606700,
+                    adviseStudyTimeSecond: 0,
+                    knowledgePointId: 1030624,
+                    courseTypeCode: 'PERCUSSION_SINGLE',
+                    name: '小军鼓的组装1',
+                    id: 1081416,
+                    type: 0,
+                    content:
+                      'https://courseware.lexiaoya.cn/5鼓架组装-打击乐-L1-1-3-A1-讲话.mp4',
+                    typeCode: 'VIDEO'
+                  },
+                  {
+                    updateTime: '2023-09-11 15:43:18',
+                    sn: '鼓架组装-打击乐- L1-1-3-A2-讲话',
+                    knowledgePointMaterialRelationId: 1701139355192606700,
+                    adviseStudyTimeSecond: 0,
+                    knowledgePointId: 1030624,
+                    courseTypeCode: 'PERCUSSION_SINGLE',
+                    name: '小军鼓的组装2',
+                    id: 1081417,
+                    type: 0,
+                    content:
+                      'https://courseware.lexiaoya.cn/6鼓架组装-打击乐-L1-1-3-A2-讲话.mp4',
+                    typeCode: 'VIDEO'
+                  }
+                ],
+                sortNo: 3,
+                totalMaterialTimeSecond: 0,
+                name: '鼓架组装',
+                id: 1030624
+              },
+              {
+                children: null,
+                materialList: [
+                  {
+                    updateTime: '2023-09-11 15:43:18',
+                    sn: '小军鼓放置-打击乐- L1-1-4-A1-讲话',
+                    knowledgePointMaterialRelationId: 1701139355192606700,
+                    adviseStudyTimeSecond: 0,
+                    knowledgePointId: 1030625,
+                    courseTypeCode: 'PERCUSSION_SINGLE',
+                    name: '小军鼓放置1',
+                    id: 1081418,
+                    type: 0,
+                    content:
+                      'https://courseware.lexiaoya.cn/9小军鼓放置-打击乐-L1-1-4-A1-讲话.mp4',
+                    typeCode: 'VIDEO'
+                  },
+                  {
+                    updateTime: '2023-09-11 15:43:18',
+                    sn: '小军鼓放置-打击乐- L1-1-4-A2-讲话',
+                    knowledgePointMaterialRelationId: 1701139355196801000,
+                    adviseStudyTimeSecond: 0,
+                    knowledgePointId: 1030625,
+                    courseTypeCode: 'PERCUSSION_SINGLE',
+                    name: '小军鼓放置2',
+                    id: 1081419,
+                    type: 0,
+                    content:
+                      'https://courseware.lexiaoya.cn/11小军鼓放置-打击乐-L1-1-4-A2-讲话.mp4',
+                    typeCode: 'VIDEO'
+                  }
+                ],
+                sortNo: 4,
+                totalMaterialTimeSecond: 0,
+                name: '小军鼓放置',
+                id: 1030625
+              },
+              {
+                children: null,
+                materialList: [
+                  {
+                    updateTime: '2023-09-11 15:43:18',
+                    sn: '小军鼓放置-打击乐- L1-1-5-A1-讲话',
+                    knowledgePointMaterialRelationId: 1701139355200995300,
+                    adviseStudyTimeSecond: 0,
+                    knowledgePointId: 1030626,
+                    courseTypeCode: 'PERCUSSION_SINGLE',
+                    name: '小军鼓收纳1',
+                    id: 1081420,
+                    type: 0,
+                    content:
+                      'https://courseware.lexiaoya.cn/12小军鼓放置-打击乐-L1-1-5-A1-讲话.mp4',
+                    typeCode: 'VIDEO'
+                  },
+                  {
+                    updateTime: '2023-09-11 15:43:18',
+                    sn: '小军鼓放置-打击乐- L1-1-5-A2-讲话',
+                    knowledgePointMaterialRelationId: 1701139355200995300,
+                    adviseStudyTimeSecond: 0,
+                    knowledgePointId: 1030626,
+                    courseTypeCode: 'PERCUSSION_SINGLE',
+                    name: '小军鼓收纳2',
+                    id: 1081421,
+                    type: 0,
+                    content:
+                      'https://courseware.lexiaoya.cn/13小军鼓放置-打击乐-L1-1-5-A2-讲话.mp4',
+                    typeCode: 'VIDEO'
+                  }
+                ],
+                sortNo: 5,
+                totalMaterialTimeSecond: 0,
+                name: '小军鼓收纳',
+                id: 1030626
+              },
+              {
+                children: null,
+                materialList: [
+                  {
+                    updateTime: '2023-09-11 15:43:18',
+                    sn: '结尾-打击乐-L1-1-7-讲话',
+                    knowledgePointMaterialRelationId: 1701139355200995300,
+                    adviseStudyTimeSecond: 0,
+                    knowledgePointId: 1030627,
+                    courseTypeCode: 'PERCUSSION_SINGLE',
+                    name: '安装结束语',
+                    id: 1081422,
+                    type: 0,
+                    content:
+                      'https://courseware.lexiaoya.cn/191结尾-打击乐-L1-1-7-讲话.mp4',
+                    typeCode: 'VIDEO'
+                  }
+                ],
+                sortNo: 6,
+                totalMaterialTimeSecond: 0,
+                name: '安装结束语',
+                id: 1030627
+              },
+              {
+                children: null,
+                materialList: [
+                  {
+                    updateTime: '2023-09-11 15:43:18',
+                    sn: '小军鼓演奏姿势前言-打击乐-L1-1-1-讲话',
+                    knowledgePointMaterialRelationId: 1701139355200995300,
+                    adviseStudyTimeSecond: 0,
+                    knowledgePointId: 1030628,
+                    courseTypeCode: 'PERCUSSION_SINGLE',
+                    name: '演奏姿势前言',
+                    id: 1081423,
+                    type: 0,
+                    content:
+                      'https://courseware.lexiaoya.cn/25小军鼓演奏姿势前言-打击乐-L1-1-1-讲话.mp4',
+                    typeCode: 'VIDEO'
+                  }
+                ],
+                sortNo: 7,
+                totalMaterialTimeSecond: 0,
+                name: '演奏姿势前言',
+                id: 1030628
+              },
+              {
+                children: null,
+                materialList: [
+                  {
+                    updateTime: '2023-09-11 15:43:18',
+                    sn: '小军鼓的演奏姿势-打击乐- L1-1-2-A1讲话',
+                    knowledgePointMaterialRelationId: 1701139355200995300,
+                    adviseStudyTimeSecond: 0,
+                    knowledgePointId: 1030629,
+                    courseTypeCode: 'PERCUSSION_SINGLE',
+                    name: '调节小军鼓高度',
+                    id: 1081424,
+                    type: 0,
+                    content:
+                      'https://courseware.lexiaoya.cn/26小军鼓的演奏姿势-打击乐-L1-1-2-A1讲话.mp4',
+                    typeCode: 'VIDEO'
+                  },
+                  {
+                    updateTime: '2023-09-11 15:43:18',
+                    sn: '小军鼓的演奏姿势-打击乐- L1-1-2-A2讲话',
+                    knowledgePointMaterialRelationId: 1701139355200995300,
+                    adviseStudyTimeSecond: 0,
+                    knowledgePointId: 1030629,
+                    courseTypeCode: 'PERCUSSION_SINGLE',
+                    name: '演奏姿势:放松姿势与准备姿势介绍',
+                    id: 1081425,
+                    type: 0,
+                    content:
+                      'https://courseware.lexiaoya.cn/27小军鼓的演奏姿势-打击乐-L1-1-2-A2讲话.mp4',
+                    typeCode: 'VIDEO'
+                  },
+                  {
+                    updateTime: '2023-09-11 15:43:18',
+                    sn: '小军鼓的演奏姿势-打击乐- L1-1-2-A3讲话',
+                    knowledgePointMaterialRelationId: 1701139355200995300,
+                    adviseStudyTimeSecond: 0,
+                    knowledgePointId: 1030629,
+                    courseTypeCode: 'PERCUSSION_SINGLE',
+                    name: '准备姿势:站姿',
+                    id: 1081426,
+                    type: 0,
+                    content:
+                      'https://courseware.lexiaoya.cn/28小军鼓的演奏姿势-打击乐-L1-1-2-A3讲话.mp4',
+                    typeCode: 'VIDEO'
+                  },
+                  {
+                    updateTime: '2023-09-11 15:43:18',
+                    sn: '小军鼓的演奏姿势-打击乐- L1-1-2-A4讲话',
+                    knowledgePointMaterialRelationId: 1701139355200995300,
+                    adviseStudyTimeSecond: 0,
+                    knowledgePointId: 1030629,
+                    courseTypeCode: 'PERCUSSION_SINGLE',
+                    name: '准备姿势:鼓棒握姿',
+                    id: 1081427,
+                    type: 0,
+                    content:
+                      'https://courseware.lexiaoya.cn/29小军鼓的演奏姿势-打击乐-L1-1-2-A4讲话.mp4',
+                    typeCode: 'VIDEO'
+                  },
+                  {
+                    updateTime: '2023-09-11 15:43:18',
+                    sn: '小军鼓的演奏姿势-打击乐- L1-1-2-A5讲话',
+                    knowledgePointMaterialRelationId: 1701139355200995300,
+                    adviseStudyTimeSecond: 0,
+                    knowledgePointId: 1030629,
+                    courseTypeCode: 'PERCUSSION_SINGLE',
+                    name: '准备姿势:鼓棒与小军鼓距离',
+                    id: 1081428,
+                    type: 0,
+                    content:
+                      'https://courseware.lexiaoya.cn/30小军鼓的演奏姿势-打击乐-L1-1-2-A5讲话.mp4',
+                    typeCode: 'VIDEO'
+                  },
+                  {
+                    updateTime: '2023-09-11 15:43:18',
+                    sn: '小军鼓的演奏姿势-打击乐- L1-1-2-A6讲话',
+                    knowledgePointMaterialRelationId: 1701139355200995300,
+                    adviseStudyTimeSecond: 0,
+                    knowledgePointId: 1030629,
+                    courseTypeCode: 'PERCUSSION_SINGLE',
+                    name: '注意事项',
+                    id: 1081429,
+                    type: 0,
+                    content:
+                      'https://courseware.lexiaoya.cn/31小军鼓的演奏姿势-打击乐-L1-1-2-A6讲话.mp4',
+                    typeCode: 'VIDEO'
+                  }
+                ],
+                sortNo: 8,
+                totalMaterialTimeSecond: 0,
+                name: '小军鼓的演奏姿势',
+                id: 1030629
+              },
+              {
+                children: null,
+                materialList: [
+                  {
+                    updateTime: '2023-09-11 15:43:18',
+                    sn: '小军鼓的演奏姿势-打击乐- L1-1-3-A-结尾',
+                    knowledgePointMaterialRelationId: 1701139355200995300,
+                    adviseStudyTimeSecond: 0,
+                    knowledgePointId: 1030630,
+                    courseTypeCode: 'PERCUSSION_SINGLE',
+                    name: '演奏姿势结束语',
+                    id: 1081430,
+                    type: 0,
+                    content:
+                      'https://courseware.lexiaoya.cn/32小军鼓的演奏姿势-打击乐-L1-1-3-A-结尾.mp4',
+                    typeCode: 'VIDEO'
+                  }
+                ],
+                sortNo: 9,
+                totalMaterialTimeSecond: 0,
+                name: '演奏姿势结束语',
+                id: 1030630
+              },
+              {
+                children: null,
+                materialList: [
+                  {
+                    updateTime: '2023-09-11 15:43:18',
+                    sn: '小军鼓乐器保养前言-打击乐-L1-1-1-讲话',
+                    knowledgePointMaterialRelationId: 1701139355200995300,
+                    adviseStudyTimeSecond: 0,
+                    knowledgePointId: 1030631,
+                    courseTypeCode: 'PERCUSSION_SINGLE',
+                    name: '保养前言',
+                    id: 1081431,
+                    type: 0,
+                    content:
+                      'https://courseware.lexiaoya.cn/18小军鼓乐器保养前言-打击乐-L1-1-1-讲话.mp4',
+                    typeCode: 'VIDEO'
+                  }
+                ],
+                sortNo: 10,
+                totalMaterialTimeSecond: 0,
+                name: '保养前言',
+                id: 1030631
+              },
+              {
+                children: null,
+                materialList: [
+                  {
+                    updateTime: '2023-09-11 15:43:18',
+                    sn: '小军鼓的保养-打击乐- L1-1-2-A1讲话',
+                    knowledgePointMaterialRelationId: 1701139355200995300,
+                    adviseStudyTimeSecond: 0,
+                    knowledgePointId: 1030632,
+                    courseTypeCode: 'PERCUSSION_SINGLE',
+                    name: '保养介绍:小军鼓清洁',
+                    id: 1081432,
+                    type: 0,
+                    content:
+                      'https://courseware.lexiaoya.cn/19小军鼓的保养-打击乐-L1-1-2-A1讲话.mp4',
+                    typeCode: 'VIDEO'
+                  },
+                  {
+                    updateTime: '2023-09-11 15:43:18',
+                    sn: '小军鼓的保养-打击乐- L1-1-2-A2讲话',
+                    knowledgePointMaterialRelationId: 1701139355209384000,
+                    adviseStudyTimeSecond: 0,
+                    knowledgePointId: 1030632,
+                    courseTypeCode: 'PERCUSSION_SINGLE',
+                    name: '保养介绍:金属带开关调节',
+                    id: 1081433,
+                    type: 0,
+                    content:
+                      'https://courseware.lexiaoya.cn/20小军鼓的保养-打击乐-L1-1-2-A2讲话.mp4',
+                    typeCode: 'VIDEO'
+                  }
+                ],
+                sortNo: 11,
+                totalMaterialTimeSecond: 0,
+                name: '小军鼓的保养',
+                id: 1030632
+              },
+              {
+                children: null,
+                materialList: [
+                  {
+                    updateTime: '2023-09-11 15:43:18',
+                    sn: '小军鼓的保养-打击乐- L1-1-3-A-结尾(20秒)',
+                    knowledgePointMaterialRelationId: 1701139355209384000,
+                    adviseStudyTimeSecond: 0,
+                    knowledgePointId: 1030633,
+                    courseTypeCode: 'PERCUSSION_SINGLE',
+                    name: '保养结束语',
+                    id: 1081434,
+                    type: 0,
+                    content:
+                      'https://courseware.lexiaoya.cn/21小军鼓的保养-打击乐-L1-1-3-A-结尾.mp4',
+                    typeCode: 'VIDEO'
+                  }
+                ],
+                sortNo: 12,
+                totalMaterialTimeSecond: 0,
+                name: '结束语',
+                id: 1030633
+              },
+              {
+                children: [
+                  {
+                    children: null,
+                    materialList: [
+                      {
+                        updateTime: '2023-09-11 15:43:18',
+                        sn: '手腕热身前言-打击乐- L1-1-讲话',
+                        knowledgePointMaterialRelationId: 1701139355209384000,
+                        adviseStudyTimeSecond: 0,
+                        knowledgePointId: 1030635,
+                        courseTypeCode: 'PERCUSSION_SINGLE',
+                        name: '手腕热身练习-前言',
+                        id: 1081435,
+                        type: 0,
+                        content:
+                          'https://courseware.lexiaoya.cn/36手腕热身前言-打击乐-L1-1-讲话.mp4',
+                        typeCode: 'VIDEO'
+                      }
+                    ],
+                    sortNo: 13,
+                    totalMaterialTimeSecond: 0,
+                    name: '前言',
+                    id: 1030635
+                  },
+                  {
+                    children: null,
+                    materialList: [
+                      {
+                        updateTime: '2023-09-11 15:43:18',
+                        sn: '手腕热身-打击乐- L1-1-2-A1-讲话',
+                        knowledgePointMaterialRelationId: 1701139355209384000,
+                        adviseStudyTimeSecond: 0,
+                        knowledgePointId: 1030636,
+                        courseTypeCode: 'PERCUSSION_SINGLE',
+                        name: '手腕热身练习-讲话',
+                        id: 1081436,
+                        type: 0,
+                        content:
+                          'https://courseware.lexiaoya.cn/156手腕热身-打击乐-L1-1-2-A1-讲话.mp4',
+                        typeCode: 'VIDEO'
+                      },
+                      {
+                        updateTime: '2023-09-11 15:43:18',
+                        sn: '手腕热身一(举高高)-打击乐- L1-1-2-B1-讲话',
+                        knowledgePointMaterialRelationId: 1701139355209384000,
+                        adviseStudyTimeSecond: 0,
+                        knowledgePointId: 1030636,
+                        courseTypeCode: 'PERCUSSION_SINGLE',
+                        name: '手腕热身练习1-6个动作-讲话1',
+                        id: 1081437,
+                        type: 0,
+                        content:
+                          'https://courseware.lexiaoya.cn/157手腕热身一(举高高)-打击乐-L1-1-2-B1-讲话.mp4',
+                        typeCode: 'VIDEO'
+                      },
+                      {
+                        updateTime: '2023-09-11 15:43:18',
+                        sn: '手腕热身一(举高高)-打击乐- L1-1-2-B2-讲话1',
+                        knowledgePointMaterialRelationId: 1701139355209384000,
+                        adviseStudyTimeSecond: 0,
+                        knowledgePointId: 1030636,
+                        courseTypeCode: 'PERCUSSION_SINGLE',
+                        name: '手腕热身练习1-举高高-讲话2',
+                        id: 1081438,
+                        type: 0,
+                        content:
+                          'https://courseware.lexiaoya.cn/158手腕热身一(举高高)-打击乐-L1-1-2-B2-讲话1.mp4',
+                        typeCode: 'VIDEO'
+                      },
+                      {
+                        updateTime: '2023-09-11 15:43:18',
+                        sn: '手腕热身一(举高高)-打击乐- L1-1-2-B2-示范',
+                        knowledgePointMaterialRelationId: 1701139355209384000,
+                        adviseStudyTimeSecond: 0,
+                        knowledgePointId: 1030636,
+                        courseTypeCode: 'PERCUSSION_SINGLE',
+                        name: '手腕热身练习1-举高高-第一次练习',
+                        id: 1081439,
+                        type: 0,
+                        content:
+                          'https://courseware.lexiaoya.cn/159手腕热身一(举高高)-打击乐-L1-1-2-B2-示范.mp4',
+                        typeCode: 'VIDEO'
+                      },
+                      {
+                        updateTime: '2023-09-11 15:43:18',
+                        sn: '手腕热身一(举高高)-打击乐- L1-1-2-B2-讲话2',
+                        knowledgePointMaterialRelationId: 1701139355209384000,
+                        adviseStudyTimeSecond: 0,
+                        knowledgePointId: 1030636,
+                        courseTypeCode: 'PERCUSSION_SINGLE',
+                        name: '手腕热身练习1-举高高-讲话3',
+                        id: 1081440,
+                        type: 0,
+                        content:
+                          'https://courseware.lexiaoya.cn/160手腕热身一(举高高)-打击乐-L1-1-2-B2-讲话2.mp4',
+                        typeCode: 'VIDEO'
+                      },
+                      {
+                        updateTime: '2023-09-11 15:43:18',
+                        sn: '手腕热身一(举高高)-打击乐- L1-1-2-B2-示范',
+                        knowledgePointMaterialRelationId: 1701139355209384000,
+                        adviseStudyTimeSecond: 0,
+                        knowledgePointId: 1030636,
+                        courseTypeCode: 'PERCUSSION_SINGLE',
+                        name: '手腕热身练习1-举高高-第二次练习',
+                        id: 1081441,
+                        type: 0,
+                        content:
+                          'https://courseware.lexiaoya.cn/159手腕热身一(举高高)-打击乐-L1-1-2-B2-示范.mp4',
+                        typeCode: 'VIDEO'
+                      }
+                    ],
+                    sortNo: 14,
+                    totalMaterialTimeSecond: 0,
+                    name: '手腕热身一(举高高)',
+                    id: 1030636
+                  },
+                  {
+                    children: null,
+                    materialList: [
+                      {
+                        updateTime: '2023-09-11 15:43:18',
+                        sn: '手腕热身二(推手手)-打击乐- L1-1-2-C1-讲话',
+                        knowledgePointMaterialRelationId: 1701139355209384000,
+                        adviseStudyTimeSecond: 0,
+                        knowledgePointId: 1030637,
+                        courseTypeCode: 'PERCUSSION_SINGLE',
+                        name: '手腕热身练习2-推手手-讲话1',
+                        id: 1081442,
+                        type: 0,
+                        content:
+                          'https://courseware.lexiaoya.cn/162手腕热身二(推手手)-打击乐-L1-1-2-C1-讲话.mp4',
+                        typeCode: 'VIDEO'
+                      },
+                      {
+                        updateTime: '2023-09-11 15:43:18',
+                        sn: '手腕热身二(推手手)-打击乐- L1-1-2-C2-讲话1',
+                        knowledgePointMaterialRelationId: 1701139355213578200,
+                        adviseStudyTimeSecond: 0,
+                        knowledgePointId: 1030637,
+                        courseTypeCode: 'PERCUSSION_SINGLE',
+                        name: '手腕热身练习2-推手手-讲话2',
+                        id: 1081443,
+                        type: 0,
+                        content:
+                          'https://courseware.lexiaoya.cn/163手腕热身二(推手手)-打击乐-L1-1-2-C2-讲话1.mp4',
+                        typeCode: 'VIDEO'
+                      },
+                      {
+                        updateTime: '2023-09-11 15:43:18',
+                        sn: '手腕热身二(推手手)-打击乐- L1-1-2-C2-示范',
+                        knowledgePointMaterialRelationId: 1701139355213578200,
+                        adviseStudyTimeSecond: 0,
+                        knowledgePointId: 1030637,
+                        courseTypeCode: 'PERCUSSION_SINGLE',
+                        name: '手腕热身练习2-推手手-练习',
+                        id: 1081444,
+                        type: 0,
+                        content:
+                          'https://courseware.lexiaoya.cn/164手腕热身二(推手手)-打击乐-L1-1-2-C2-示范.mp4',
+                        typeCode: 'VIDEO'
+                      },
+                      {
+                        updateTime: '2023-09-11 15:43:18',
+                        sn: '手腕热身二(推手手)-打击乐- L1-1-2-C2-讲话2',
+                        knowledgePointMaterialRelationId: 1701139355213578200,
+                        adviseStudyTimeSecond: 0,
+                        knowledgePointId: 1030637,
+                        courseTypeCode: 'PERCUSSION_SINGLE',
+                        name: '手腕热身练习2-推手手-讲话3',
+                        id: 1081445,
+                        type: 0,
+                        content:
+                          'https://courseware.lexiaoya.cn/160手腕热身一(举高高)-打击乐-L1-1-2-B2-讲话2.mp4',
+                        typeCode: 'VIDEO'
+                      },
+                      {
+                        updateTime: '2023-09-11 15:43:18',
+                        sn: '手腕热身二(推手手)-打击乐- L1-1-2-C2-示范',
+                        knowledgePointMaterialRelationId: 1701139355213578200,
+                        adviseStudyTimeSecond: 0,
+                        knowledgePointId: 1030637,
+                        courseTypeCode: 'PERCUSSION_SINGLE',
+                        name: '手腕热身练习2-推手手-第二次练习',
+                        id: 1081446,
+                        type: 0,
+                        content:
+                          'https://courseware.lexiaoya.cn/164手腕热身二(推手手)-打击乐-L1-1-2-C2-示范.mp4',
+                        typeCode: 'VIDEO'
+                      }
+                    ],
+                    sortNo: 15,
+                    totalMaterialTimeSecond: 0,
+                    name: '手腕热身二(推手手)',
+                    id: 1030637
+                  },
+                  {
+                    children: null,
+                    materialList: [
+                      {
+                        updateTime: '2023-09-11 15:43:18',
+                        sn: '手腕热身三(转圈圈)-打击乐- L1-3-2-B1-讲话',
+                        knowledgePointMaterialRelationId: 1701139355213578200,
+                        adviseStudyTimeSecond: 0,
+                        knowledgePointId: 1030638,
+                        courseTypeCode: 'PERCUSSION_SINGLE',
+                        name: '讲解2个步骤',
+                        id: 1081447,
+                        type: 0,
+                        content:
+                          'https://courseware.lexiaoya.cn/255手腕热身三(转圈圈)-打击乐-L1-3-2-B1-讲话.mp4',
+                        typeCode: 'VIDEO'
+                      },
+                      {
+                        updateTime: '2023-09-11 15:43:18',
+                        sn: '手腕热身三(转圈圈)-打击乐- L1-3-2-B2-讲话1',
+                        knowledgePointMaterialRelationId: 1701139355213578200,
+                        adviseStudyTimeSecond: 0,
+                        knowledgePointId: 1030638,
+                        courseTypeCode: 'PERCUSSION_SINGLE',
+                        name: '手腕热身练习3-转圈圈-讲话1',
+                        id: 1081448,
+                        type: 0,
+                        content:
+                          'https://courseware.lexiaoya.cn/256手腕热身三(转圈圈)-打击乐-L1-3-2-B2-讲话1.mp4',
+                        typeCode: 'VIDEO'
+                      },
+                      {
+                        updateTime: '2023-09-11 15:43:18',
+                        sn: '手腕热身三(转圈圈)-打击乐- L1-3-2-B2-示范',
+                        knowledgePointMaterialRelationId: 1701139355213578200,
+                        adviseStudyTimeSecond: 0,
+                        knowledgePointId: 1030638,
+                        courseTypeCode: 'PERCUSSION_SINGLE',
+                        name: '手腕热身练习3-转圈圈-第一次练习',
+                        id: 1081449,
+                        type: 0,
+                        content:
+                          'https://courseware.lexiaoya.cn/257手腕热身三(转圈圈)-打击乐-L1-3-2-B2-示范.mp4',
+                        typeCode: 'VIDEO'
+                      },
+                      {
+                        updateTime: '2023-09-11 15:43:18',
+                        sn: '手腕热身三(转圈圈)-打击乐- L1-3-2-B2-讲话2',
+                        knowledgePointMaterialRelationId: 1701139355213578200,
+                        adviseStudyTimeSecond: 0,
+                        knowledgePointId: 1030638,
+                        courseTypeCode: 'PERCUSSION_SINGLE',
+                        name: '手腕热身练习3-转圈圈-讲话2',
+                        id: 1081450,
+                        type: 0,
+                        content:
+                          'https://courseware.lexiaoya.cn/258手腕热身三(转圈圈)-打击乐-L1-3-2-B2-讲话2.mp4',
+                        typeCode: 'VIDEO'
+                      },
+                      {
+                        updateTime: '2023-09-11 15:43:18',
+                        sn: '手腕热身三(转圈圈)-打击乐- L1-3-2-B2-示范',
+                        knowledgePointMaterialRelationId: 1701139355213578200,
+                        adviseStudyTimeSecond: 0,
+                        knowledgePointId: 1030638,
+                        courseTypeCode: 'PERCUSSION_SINGLE',
+                        name: '手腕热身练习3-转圈圈-第二次练习',
+                        id: 1081451,
+                        type: 0,
+                        content:
+                          'https://courseware.lexiaoya.cn/257手腕热身三(转圈圈)-打击乐-L1-3-2-B2-示范.mp4',
+                        typeCode: 'VIDEO'
+                      }
+                    ],
+                    sortNo: 16,
+                    totalMaterialTimeSecond: 0,
+                    name: '手腕热身三(转圈圈)',
+                    id: 1030638
+                  },
+                  {
+                    children: null,
+                    materialList: [
+                      {
+                        updateTime: '2023-09-11 15:43:18',
+                        sn: '手腕热身四(一个巴掌拍的响)-打击乐-L1-5-2-B1-讲话',
+                        knowledgePointMaterialRelationId: 1701139355213578200,
+                        adviseStudyTimeSecond: 0,
+                        knowledgePointId: 1030639,
+                        courseTypeCode: 'PERCUSSION_SINGLE',
+                        name: '手腕热身练习4-一个巴掌拍的响-前言',
+                        id: 1081452,
+                        type: 0,
+                        content:
+                          'https://courseware.lexiaoya.cn/379手腕热身四(一个巴掌拍的响)-打击乐-L1-5-2-B1-讲话.mp4',
+                        typeCode: 'VIDEO'
+                      },
+                      {
+                        updateTime: '2023-09-11 15:43:18',
+                        sn: '手腕热身四(一个巴掌拍的响)-打击乐-L1-5-2-B2-讲话',
+                        knowledgePointMaterialRelationId: 1701139355217772500,
+                        adviseStudyTimeSecond: 0,
+                        knowledgePointId: 1030639,
+                        courseTypeCode: 'PERCUSSION_SINGLE',
+                        name: '讲解两个步骤',
+                        id: 1081453,
+                        type: 0,
+                        content:
+                          'https://courseware.lexiaoya.cn/380手腕热身四(一个巴掌拍的响)-打击乐-L1-5-2-B2-讲话.mp4',
+                        typeCode: 'VIDEO'
+                      },
+                      {
+                        updateTime: '2023-09-11 15:43:18',
+                        sn: '手腕热身四(一个巴掌拍的响1拍教学)-打击乐-L1-5-2-B3-讲话1',
+                        knowledgePointMaterialRelationId: 1701139355217772500,
+                        adviseStudyTimeSecond: 0,
+                        knowledgePointId: 1030639,
+                        courseTypeCode: 'PERCUSSION_SINGLE',
+                        name: '手腕热身练习4-一个巴掌拍的响- 练习一拍1次',
+                        id: 1081454,
+                        type: 0,
+                        content:
+                          'https://courseware.lexiaoya.cn/381手腕热身四(一个巴掌拍的响1拍教学)-打击乐-L1-5-2-B3-讲话1.mp4',
+                        typeCode: 'VIDEO'
+                      },
+                      {
+                        updateTime: '2023-09-11 15:43:18',
+                        sn: '手腕热身四(一个巴掌拍的响半拍教学)-打击乐-L1-5-2-B3-讲话2',
+                        knowledgePointMaterialRelationId: 1701139355217772500,
+                        adviseStudyTimeSecond: 0,
+                        knowledgePointId: 1030639,
+                        courseTypeCode: 'PERCUSSION_SINGLE',
+                        name: '手腕热身练习4-一个巴掌拍的响-练习一拍2次',
+                        id: 1081455,
+                        type: 0,
+                        content:
+                          'https://courseware.lexiaoya.cn/382手腕热身四(一个巴掌拍的响半拍教学)-打击乐-L1-5-2-B3-讲话2.mp4',
+                        typeCode: 'VIDEO'
+                      },
+                      {
+                        updateTime: '2023-09-11 15:43:18',
+                        sn: '手腕热身四(一个巴掌拍的响4遍)-打击乐-L1-5-2-B3-讲话3',
+                        knowledgePointMaterialRelationId: 1701139355217772500,
+                        adviseStudyTimeSecond: 0,
+                        knowledgePointId: 1030639,
+                        courseTypeCode: 'PERCUSSION_SINGLE',
+                        name: '手腕热身练习4-一个巴掌拍的响-完整练习-讲话',
+                        id: 1081456,
+                        type: 0,
+                        content:
+                          'https://courseware.lexiaoya.cn/383手腕热身四(一个巴掌拍的响4遍)-打击乐-L1-5-2-B3-讲话3.mp4',
+                        typeCode: 'VIDEO'
+                      },
+                      {
+                        updateTime: '2023-09-11 15:43:18',
+                        sn: '手腕热身四(一个巴掌拍的响4遍)-打击乐-L1-5-2-B3-示范1',
+                        knowledgePointMaterialRelationId: 1701139355217772500,
+                        adviseStudyTimeSecond: 0,
+                        knowledgePointId: 1030639,
+                        courseTypeCode: 'PERCUSSION_SINGLE',
+                        name: '手腕热身练习4-一个巴掌拍的响- 完整练习-练习',
+                        id: 1081457,
+                        type: 0,
+                        content:
+                          'https://courseware.lexiaoya.cn/384手腕热身四(一个巴掌拍的响4遍)-打击乐-L1-5-2-B3-示范1.mp4',
+                        typeCode: 'VIDEO'
+                      },
+                      {
+                        updateTime: '2023-09-11 15:43:18',
+                        sn: '手腕热身四(一个巴掌拍的响放松)-打击乐-L1-5-2-B3-讲话4',
+                        knowledgePointMaterialRelationId: 1701139355217772500,
+                        adviseStudyTimeSecond: 0,
+                        knowledgePointId: 1030639,
+                        courseTypeCode: 'PERCUSSION_SINGLE',
+                        name: '手腕热身练习4-一个巴掌拍的响-放松练习-讲话',
+                        id: 1081458,
+                        type: 0,
+                        content:
+                          'https://courseware.lexiaoya.cn/385手腕热身四(一个巴掌拍的响放松)-打击乐-L1-5-2-B3-讲话4.mp4',
+                        typeCode: 'VIDEO'
+                      },
+                      {
+                        updateTime: '2023-09-11 15:43:18',
+                        sn: '手腕热身四(一个巴掌拍的响放松)-打击乐-L1-5-2-B3-示范2',
+                        knowledgePointMaterialRelationId: 1701139355217772500,
+                        adviseStudyTimeSecond: 0,
+                        knowledgePointId: 1030639,
+                        courseTypeCode: 'PERCUSSION_SINGLE',
+                        name: '手腕热身练习4-一个巴掌拍的响-放松练习-练习',
+                        id: 1081459,
+                        type: 0,
+                        content:
+                          'https://courseware.lexiaoya.cn/386手腕热身四(一个巴掌拍的响放松)-打击乐-L1-5-2-B3-示范2.mp4',
+                        typeCode: 'VIDEO'
+                      }
+                    ],
+                    sortNo: 17,
+                    totalMaterialTimeSecond: 0,
+                    name: '手腕热身四(一个巴掌拍的响)',
+                    id: 1030639
+                  },
+                  {
+                    children: null,
+                    materialList: [
+                      {
+                        updateTime: '2023-09-11 15:43:18',
+                        sn: '手腕热身(连贯)-打击乐-L1-1-6-A1-讲话',
+                        knowledgePointMaterialRelationId: 1701139355217772500,
+                        adviseStudyTimeSecond: 0,
+                        knowledgePointId: 1030640,
+                        courseTypeCode: 'PERCUSSION_SINGLE',
+                        name: '完整手腕热身/讲话',
+                        id: 1081460,
+                        type: 0,
+                        content:
+                          'https://courseware.lexiaoya.cn/61手腕热身(连贯)-打击乐-L1-1-6-A1-讲话.mp4',
+                        typeCode: 'VIDEO'
+                      },
+                      {
+                        updateTime: '2023-09-11 15:43:18',
+                        sn: '手腕热身(连贯)-打击乐-L1-6-2-A1-示范',
+                        knowledgePointMaterialRelationId: 1701139355217772500,
+                        adviseStudyTimeSecond: 0,
+                        knowledgePointId: 1030640,
+                        courseTypeCode: 'PERCUSSION_SINGLE',
+                        name: '完整手腕热身/练习',
+                        id: 1081461,
+                        type: 0,
+                        content:
+                          'https://courseware.lexiaoya.cn/426手腕热身(连贯)-打击乐-L1-6-2-A1-示范.mp4',
+                        typeCode: 'VIDEO'
+                      }
+                    ],
+                    sortNo: 18,
+                    totalMaterialTimeSecond: 0,
+                    name: '完整手腕热身',
+                    id: 1030640
+                  },
+                  {
+                    children: null,
+                    materialList: [
+                      {
+                        updateTime: '2023-09-11 15:43:18',
+                        sn: '结尾-打击乐-L1-1-7-讲话',
+                        knowledgePointMaterialRelationId: 1701139355217772500,
+                        adviseStudyTimeSecond: 0,
+                        knowledgePointId: 1030641,
+                        courseTypeCode: 'PERCUSSION_SINGLE',
+                        name: '结束语',
+                        id: 1081462,
+                        type: 0,
+                        content:
+                          'https://courseware.lexiaoya.cn/191结尾-打击乐-L1-1-7-讲话.mp4',
+                        typeCode: 'VIDEO'
+                      }
+                    ],
+                    sortNo: 19,
+                    totalMaterialTimeSecond: 0,
+                    name: '结束语',
+                    id: 1030641
+                  }
+                ],
+                materialList: null,
+                sortNo: 13,
+                totalMaterialTimeSecond: 0,
+                name: '手腕热身练习',
+                id: 1030634
+              },
+              {
+                children: [
+                  {
+                    children: null,
+                    materialList: [
+                      {
+                        updateTime: '2023-09-11 15:43:18',
+                        sn: '鼓棒控制前言-打击乐- L1-1-1-讲话',
+                        knowledgePointMaterialRelationId: 1701139355217772500,
+                        adviseStudyTimeSecond: 0,
+                        knowledgePointId: 1030643,
+                        courseTypeCode: 'PERCUSSION_SINGLE',
+                        name: '鼓棒控制练习1-前言',
+                        id: 1081463,
+                        type: 0,
+                        content:
+                          'https://courseware.lexiaoya.cn/86鼓棒控制前言-打击乐-L1-1-1-讲话.mp4',
+                        typeCode: 'VIDEO'
+                      }
+                    ],
+                    sortNo: 20,
+                    totalMaterialTimeSecond: 0,
+                    name: '前言',
+                    id: 1030643
+                  },
+                  {
+                    children: null,
+                    materialList: [
+                      {
+                        updateTime: '2023-09-11 15:43:18',
+                        sn: '鼓棒控制练习1打击乐- L1-1-2-A1-讲话1',
+                        knowledgePointMaterialRelationId: 1701139355221966800,
+                        adviseStudyTimeSecond: 0,
+                        knowledgePointId: 1030644,
+                        courseTypeCode: 'PERCUSSION_SINGLE',
+                        name: '鼓棒控制练习1-讲解1',
+                        id: 1081464,
+                        type: 0,
+                        content:
+                          'https://courseware.lexiaoya.cn/87鼓棒控制练习1打击乐-L1-1-2-A1-讲话1.mp4',
+                        typeCode: 'VIDEO'
+                      },
+                      {
+                        updateTime: '2023-09-11 15:43:18',
+                        sn: '鼓棒控制练习一打击乐- L1-1-2-A1-讲话2',
+                        knowledgePointMaterialRelationId: 1701139355221966800,
+                        adviseStudyTimeSecond: 0,
+                        knowledgePointId: 1030644,
+                        courseTypeCode: 'PERCUSSION_SINGLE',
+                        name: '鼓棒控制练习1-讲解2',
+                        id: 1081465,
+                        type: 0,
+                        content:
+                          'https://courseware.lexiaoya.cn/88鼓棒控制练习一打击乐-L1-1-2-A1-讲话2.mp4',
+                        typeCode: 'VIDEO'
+                      },
+                      {
+                        updateTime: '2023-09-11 15:43:18',
+                        sn: '鼓棒控制练习一-打击乐- L1-1-3-A2-讲话-第一步',
+                        knowledgePointMaterialRelationId: 1701139355221966800,
+                        adviseStudyTimeSecond: 0,
+                        knowledgePointId: 1030644,
+                        courseTypeCode: 'PERCUSSION_SINGLE',
+                        name: '第1个动作-鼓棒持拿-指导',
+                        id: 1081466,
+                        type: 0,
+                        content:
+                          'https://courseware.lexiaoya.cn/166鼓棒控制练习一-打击乐-L1-1-3-A2-讲话-第一步.mp4',
+                        typeCode: 'VIDEO'
+                      },
+                      {
+                        updateTime: '2023-09-11 15:43:18',
+                        sn: '鼓棒控制练习一-打击乐- L1-1-3-A2-讲话-第二步',
+                        knowledgePointMaterialRelationId: 1701139355221966800,
+                        adviseStudyTimeSecond: 0,
+                        knowledgePointId: 1030644,
+                        courseTypeCode: 'PERCUSSION_SINGLE',
+                        name: '第2个动作-鼓棒旋转-指导',
+                        id: 1081467,
+                        type: 0,
+                        content:
+                          'https://courseware.lexiaoya.cn/167鼓棒控制练习一-打击乐-L1-1-3-A2-讲话-第二步.mp4',
+                        typeCode: 'VIDEO'
+                      },
+                      {
+                        updateTime: '2023-09-11 15:43:18',
+                        sn: '鼓棒控制练习一-打击乐- L1-1-3-A2-讲话-第三步',
+                        knowledgePointMaterialRelationId: 1701139355221966800,
+                        adviseStudyTimeSecond: 0,
+                        knowledgePointId: 1030644,
+                        courseTypeCode: 'PERCUSSION_SINGLE',
+                        name: '第3个动作-自然反弹-指导',
+                        id: 1081468,
+                        type: 0,
+                        content:
+                          'https://courseware.lexiaoya.cn/168鼓棒控制练习一-打击乐-L1-1-3-A2-讲话-第三步.mp4',
+                        typeCode: 'VIDEO'
+                      },
+                      {
+                        updateTime: '2023-09-11 15:43:18',
+                        sn: '鼓棒控制练习一-打击乐- L1-1-3-A2-讲话-第四步',
+                        knowledgePointMaterialRelationId: 1701139355221966800,
+                        adviseStudyTimeSecond: 0,
+                        knowledgePointId: 1030644,
+                        courseTypeCode: 'PERCUSSION_SINGLE',
+                        name: '第4个动作-反弹到虎口-指导',
+                        id: 1081469,
+                        type: 0,
+                        content:
+                          'https://courseware.lexiaoya.cn/169鼓棒控制练习一-打击乐-L1-1-3-A2-讲话-第四步.mp4',
+                        typeCode: 'VIDEO'
+                      },
+                      {
+                        updateTime: '2023-09-11 15:43:18',
+                        sn: '鼓棒控制练习一-打击乐- L1-1-3-A2-讲话-第五步',
+                        knowledgePointMaterialRelationId: 1701139355221966800,
+                        adviseStudyTimeSecond: 0,
+                        knowledgePointId: 1030644,
+                        courseTypeCode: 'PERCUSSION_SINGLE',
+                        name: '第5个动作-完整动作-指导',
+                        id: 1081470,
+                        type: 0,
+                        content:
+                          'https://courseware.lexiaoya.cn/170鼓棒控制练习一-打击乐-L1-1-3-A2-讲话-第五步.mp4',
+                        typeCode: 'VIDEO'
+                      },
+                      {
+                        updateTime: '2023-09-11 15:43:18',
+                        sn: '鼓棒控制练习一-打击乐- L1-1-3-A3-讲话',
+                        knowledgePointMaterialRelationId: 1701139355221966800,
+                        adviseStudyTimeSecond: 0,
+                        knowledgePointId: 1030644,
+                        courseTypeCode: 'PERCUSSION_SINGLE',
+                        name: '过度话术',
+                        id: 1081471,
+                        type: 0,
+                        content:
+                          'https://courseware.lexiaoya.cn/172鼓棒控制练习一-打击乐-L1-1-3-A3-讲话.mp4',
+                        typeCode: 'VIDEO'
+                      },
+                      {
+                        updateTime: '2023-09-11 15:43:18',
+                        sn: '鼓棒控制练习一-打击乐- L1-1-3-A4-讲话',
+                        knowledgePointMaterialRelationId: 1701139355221966800,
+                        adviseStudyTimeSecond: 0,
+                        knowledgePointId: 1030644,
+                        courseTypeCode: 'PERCUSSION_SINGLE',
+                        name: '第1个动作-鼓棒持拿-讲话',
+                        id: 1081472,
+                        type: 0,
+                        content:
+                          'https://courseware.lexiaoya.cn/173鼓棒控制练习一-打击乐-L1-1-3-A4-讲话.mp4',
+                        typeCode: 'VIDEO'
+                      },
+                      {
+                        updateTime: '2023-09-11 15:43:18',
+                        sn: '鼓棒控制练习一-打击乐- L1-1-3-A4-示范',
+                        knowledgePointMaterialRelationId: 1701139355221966800,
+                        adviseStudyTimeSecond: 0,
+                        knowledgePointId: 1030644,
+                        courseTypeCode: 'PERCUSSION_SINGLE',
+                        name: '第1个动作-鼓棒持拿-练习',
+                        id: 1081473,
+                        type: 0,
+                        content:
+                          'https://courseware.lexiaoya.cn/174鼓棒控制练习一-打击乐-L1-1-3-A4-示范.mp4',
+                        typeCode: 'VIDEO'
+                      },
+                      {
+                        updateTime: '2023-09-11 15:43:18',
+                        sn: '鼓棒控制练习一-打击乐- L1-1-3-A5-讲话',
+                        knowledgePointMaterialRelationId: 1701139355221966800,
+                        adviseStudyTimeSecond: 0,
+                        knowledgePointId: 1030644,
+                        courseTypeCode: 'PERCUSSION_SINGLE',
+                        name: '第2个动作-鼓棒旋转-讲话',
+                        id: 1081474,
+                        type: 0,
+                        content:
+                          'https://courseware.lexiaoya.cn/175鼓棒控制练习一-打击乐-L1-1-3-A5-讲话.mp4',
+                        typeCode: 'VIDEO'
+                      },
+                      {
+                        updateTime: '2023-09-11 15:43:18',
+                        sn: '鼓棒控制练习一-打击乐- L1-1-3-A5-示范',
+                        knowledgePointMaterialRelationId: 1701139355221966800,
+                        adviseStudyTimeSecond: 0,
+                        knowledgePointId: 1030644,
+                        courseTypeCode: 'PERCUSSION_SINGLE',
+                        name: '第2个动作-鼓棒旋转-练习',
+                        id: 1081475,
+                        type: 0,
+                        content:
+                          'https://courseware.lexiaoya.cn/176鼓棒控制练习一-打击乐-L1-1-3-A5-示范.mp4',
+                        typeCode: 'VIDEO'
+                      },
+                      {
+                        updateTime: '2023-09-11 15:43:18',
+                        sn: '鼓棒控制练习一-打击乐- L1-1-3-A6-讲话',
+                        knowledgePointMaterialRelationId: 1701139355221966800,
+                        adviseStudyTimeSecond: 0,
+                        knowledgePointId: 1030644,
+                        courseTypeCode: 'PERCUSSION_SINGLE',
+                        name: '第3个动作-自然反弹-讲话',
+                        id: 1081476,
+                        type: 0,
+                        content:
+                          'https://courseware.lexiaoya.cn/177鼓棒控制练习一-打击乐-L1-1-3-A6-讲话.mp4',
+                        typeCode: 'VIDEO'
+                      },
+                      {
+                        updateTime: '2023-09-11 15:43:18',
+                        sn: '鼓棒控制练习一-打击乐- L1-1-3-A6-示范',
+                        knowledgePointMaterialRelationId: 1701139355221966800,
+                        adviseStudyTimeSecond: 0,
+                        knowledgePointId: 1030644,
+                        courseTypeCode: 'PERCUSSION_SINGLE',
+                        name: '第3个动作-自然反弹-练习',
+                        id: 1081477,
+                        type: 0,
+                        content:
+                          'https://courseware.lexiaoya.cn/178鼓棒控制练习一-打击乐-L1-1-3-A6-示范.mp4',
+                        typeCode: 'VIDEO'
+                      },
+                      {
+                        updateTime: '2023-09-11 15:43:18',
+                        sn: '鼓棒控制练习一-打击乐- L1-1-3-A7-讲话',
+                        knowledgePointMaterialRelationId: 1701139355221966800,
+                        adviseStudyTimeSecond: 0,
+                        knowledgePointId: 1030644,
+                        courseTypeCode: 'PERCUSSION_SINGLE',
+                        name: '第4个动作-反弹到虎口-讲话',
+                        id: 1081478,
+                        type: 0,
+                        content:
+                          'https://courseware.lexiaoya.cn/179鼓棒控制练习一-打击乐-L1-1-3-A7-讲话.mp4',
+                        typeCode: 'VIDEO'
+                      },
+                      {
+                        updateTime: '2023-09-11 15:43:18',
+                        sn: '鼓棒控制练习一-打击乐- L1-1-3-A7-示范',
+                        knowledgePointMaterialRelationId: 1701139355221966800,
+                        adviseStudyTimeSecond: 0,
+                        knowledgePointId: 1030644,
+                        courseTypeCode: 'PERCUSSION_SINGLE',
+                        name: '第4个动作-反弹到虎口-练习',
+                        id: 1081479,
+                        type: 0,
+                        content:
+                          'https://courseware.lexiaoya.cn/180鼓棒控制练习一-打击乐-L1-1-3-A7-示范.mp4',
+                        typeCode: 'VIDEO'
+                      },
+                      {
+                        updateTime: '2023-09-11 15:43:18',
+                        sn: '鼓棒控制练习一-打击乐- L1-1-3-A8-讲话',
+                        knowledgePointMaterialRelationId: 1701139355221966800,
+                        adviseStudyTimeSecond: 0,
+                        knowledgePointId: 1030644,
+                        courseTypeCode: 'PERCUSSION_SINGLE',
+                        name: '第5个动作-完整动作-讲话',
+                        id: 1081480,
+                        type: 0,
+                        content:
+                          'https://courseware.lexiaoya.cn/181鼓棒控制练习一-打击乐-L1-1-3-A8-讲话.mp4',
+                        typeCode: 'VIDEO'
+                      },
+                      {
+                        updateTime: '2023-09-11 15:43:18',
+                        sn: '鼓棒控制练习一-打击乐- L1-1-3-A8-示范',
+                        knowledgePointMaterialRelationId: 1701139355221966800,
+                        adviseStudyTimeSecond: 0,
+                        knowledgePointId: 1030644,
+                        courseTypeCode: 'PERCUSSION_SINGLE',
+                        name: '第5个动作-完整动作-练习',
+                        id: 1081481,
+                        type: 0,
+                        content:
+                          'https://courseware.lexiaoya.cn/182鼓棒控制练习一-打击乐-L1-1-3-A8-示范.mp4',
+                        typeCode: 'VIDEO'
+                      }
+                    ],
+                    sortNo: 21,
+                    totalMaterialTimeSecond: 0,
+                    name: '鼓棒控制练习一',
+                    id: 1030644
+                  },
+                  {
+                    children: null,
+                    materialList: [
+                      {
+                        updateTime: '2023-09-11 15:43:18',
+                        sn: '鼓棒控制练习二-打击乐-1-3-A1-讲话',
+                        knowledgePointMaterialRelationId: 1701139355221966800,
+                        adviseStudyTimeSecond: 0,
+                        knowledgePointId: 1030645,
+                        courseTypeCode: 'PERCUSSION_SINGLE',
+                        name: '鼓棒控制练习2-前言',
+                        id: 1081482,
+                        type: 0,
+                        content:
+                          'https://courseware.lexiaoya.cn/101鼓棒控制练习二-打击乐-1-3-A1-讲话.mp4',
+                        typeCode: 'VIDEO'
+                      },
+                      {
+                        updateTime: '2023-09-11 15:43:18',
+                        sn: '鼓棒控制练习二-打击乐-1-3-A2-讲话',
+                        knowledgePointMaterialRelationId: 1701139355221966800,
+                        adviseStudyTimeSecond: 0,
+                        knowledgePointId: 1030645,
+                        courseTypeCode: 'PERCUSSION_SINGLE',
+                        name: '鼓棒控制练习2-讲话1',
+                        id: 1081483,
+                        type: 0,
+                        content:
+                          'https://courseware.lexiaoya.cn/102鼓棒控制练习二-打击乐-1-3-A2-讲话.mp4',
+                        typeCode: 'VIDEO'
+                      },
+                      {
+                        updateTime: '2023-09-11 15:43:18',
+                        sn: '鼓棒控制练习二-打击乐-1-3-A3-讲话',
+                        knowledgePointMaterialRelationId: 1701139355221966800,
+                        adviseStudyTimeSecond: 0,
+                        knowledgePointId: 1030645,
+                        courseTypeCode: 'PERCUSSION_SINGLE',
+                        name: '鼓棒控制练习2-讲话2',
+                        id: 1081484,
+                        type: 0,
+                        content:
+                          'https://courseware.lexiaoya.cn/103鼓棒控制练习二-打击乐-1-3-A3-讲话.mp4',
+                        typeCode: 'VIDEO'
+                      },
+                      {
+                        updateTime: '2023-09-11 15:43:18',
+                        sn: '鼓棒控制练习二-打击乐- L1-2-3-B3-示范',
+                        knowledgePointMaterialRelationId: 1701139355221966800,
+                        adviseStudyTimeSecond: 0,
+                        knowledgePointId: 1030645,
+                        courseTypeCode: 'PERCUSSION_SINGLE',
+                        name: '鼓棒控制练习2-第3拍抓握-练习',
+                        id: 1081485,
+                        type: 0,
+                        content:
+                          'https://courseware.lexiaoya.cn/216鼓棒控制练习二-打击乐-L1-2-3-B3-示范.mp4',
+                        typeCode: 'VIDEO'
+                      },
+                      {
+                        updateTime: '2023-09-11 15:43:18',
+                        sn: '鼓棒控制练习二-打击乐- L1-2-3-B4-讲话',
+                        knowledgePointMaterialRelationId: 1701139355221966800,
+                        adviseStudyTimeSecond: 0,
+                        knowledgePointId: 1030645,
+                        courseTypeCode: 'PERCUSSION_SINGLE',
+                        name: '鼓棒控制练习2.1-第2拍抓握-讲话1',
+                        id: 1081486,
+                        type: 0,
+                        content:
+                          'https://courseware.lexiaoya.cn/217鼓棒控制练习二-打击乐-L1-2-3-B4-讲话.mp4',
+                        typeCode: 'VIDEO'
+                      },
+                      {
+                        updateTime: '2023-09-11 15:43:18',
+                        sn: '鼓棒控制练习二-打击乐- L1-2-3-B5-讲话',
+                        knowledgePointMaterialRelationId: 1701139355230355500,
+                        adviseStudyTimeSecond: 0,
+                        knowledgePointId: 1030645,
+                        courseTypeCode: 'PERCUSSION_SINGLE',
+                        name: '鼓棒控制练习2.1-第2拍抓握-讲话3',
+                        id: 1081487,
+                        type: 0,
+                        content:
+                          'https://courseware.lexiaoya.cn/219鼓棒控制练习二-打击乐-L1-2-3-B5-讲话.mp4',
+                        typeCode: 'VIDEO'
+                      },
+                      {
+                        updateTime: '2023-09-11 15:43:18',
+                        sn: '鼓棒控制练习二-打击乐- L1-2-3-B5-示范',
+                        knowledgePointMaterialRelationId: 1701139355230355500,
+                        adviseStudyTimeSecond: 0,
+                        knowledgePointId: 1030645,
+                        courseTypeCode: 'PERCUSSION_SINGLE',
+                        name: '鼓棒控制练习2.1-第2拍抓握-练习',
+                        id: 1081488,
+                        type: 0,
+                        content:
+                          'https://courseware.lexiaoya.cn/220鼓棒控制练习二-打击乐-L1-2-3-B5-示范.mp4',
+                        typeCode: 'VIDEO'
+                      },
+                      {
+                        updateTime: '2023-09-11 15:43:18',
+                        sn: '鼓棒控制练习二-打击乐- L1-2-3-B6-讲话',
+                        knowledgePointMaterialRelationId: 1701139355230355500,
+                        adviseStudyTimeSecond: 0,
+                        knowledgePointId: 1030645,
+                        courseTypeCode: 'PERCUSSION_SINGLE',
+                        name: '鼓棒控制练习2.2-第1拍后半拍抓握-讲话1',
+                        id: 1081489,
+                        type: 0,
+                        content:
+                          'https://courseware.lexiaoya.cn/221鼓棒控制练习二-打击乐-L1-2-3-B6-讲话.mp4',
+                        typeCode: 'VIDEO'
+                      },
+                      {
+                        updateTime: '2023-09-11 15:43:18',
+                        sn: '鼓棒控制练习二-打击乐- L1-2-3-B7-讲话',
+                        knowledgePointMaterialRelationId: 1701139355230355500,
+                        adviseStudyTimeSecond: 0,
+                        knowledgePointId: 1030645,
+                        courseTypeCode: 'PERCUSSION_SINGLE',
+                        name: '鼓棒控制练习2.2-第1拍后半拍抓握-讲话2',
+                        id: 1081490,
+                        type: 0,
+                        content:
+                          'https://courseware.lexiaoya.cn/222鼓棒控制练习二-打击乐-L1-2-3-B7-讲话.mp4',
+                        typeCode: 'VIDEO'
+                      },
+                      {
+                        updateTime: '2023-09-11 15:43:18',
+                        sn: '鼓棒控制练习二-打击乐- L1-2-3-B7-示范',
+                        knowledgePointMaterialRelationId: 1701139355230355500,
+                        adviseStudyTimeSecond: 0,
+                        knowledgePointId: 1030645,
+                        courseTypeCode: 'PERCUSSION_SINGLE',
+                        name: '鼓棒控制练习2.2-第1拍后半拍抓握 练习',
+                        id: 1081491,
+                        type: 0,
+                        content:
+                          'https://courseware.lexiaoya.cn/223鼓棒控制练习二-打击乐-L1-2-3-B7-示范.mp4',
+                        typeCode: 'VIDEO'
+                      },
+                      {
+                        updateTime: '2023-09-11 15:43:18',
+                        sn: '鼓棒控制练习二-打击乐- L1-2-3-B8-讲话',
+                        knowledgePointMaterialRelationId: 1701139355230355500,
+                        adviseStudyTimeSecond: 0,
+                        knowledgePointId: 1030645,
+                        courseTypeCode: 'PERCUSSION_SINGLE',
+                        name: '鼓棒控制练习2.3-e拍抓握-讲话1',
+                        id: 1081492,
+                        type: 0,
+                        content:
+                          'https://courseware.lexiaoya.cn/224鼓棒控制练习二-打击乐-L1-2-3-B8-讲话.mp4',
+                        typeCode: 'VIDEO'
+                      },
+                      {
+                        updateTime: '2023-09-11 15:43:18',
+                        sn: '鼓棒控制练习二-打击乐- L1-2-3-B9-讲话',
+                        knowledgePointMaterialRelationId: 1701139355230355500,
+                        adviseStudyTimeSecond: 0,
+                        knowledgePointId: 1030645,
+                        courseTypeCode: 'PERCUSSION_SINGLE',
+                        name: '鼓棒控制练习2.3-e拍抓握-讲话3',
+                        id: 1081493,
+                        type: 0,
+                        content:
+                          'https://courseware.lexiaoya.cn/226鼓棒控制练习二-打击乐-L1-2-3-B9-讲话.mp4',
+                        typeCode: 'VIDEO'
+                      },
+                      {
+                        updateTime: '2023-09-11 15:43:18',
+                        sn: '鼓棒控制练习二-打击乐- L1-2-3-B9-示范',
+                        knowledgePointMaterialRelationId: 1701139355234549800,
+                        adviseStudyTimeSecond: 0,
+                        knowledgePointId: 1030645,
+                        courseTypeCode: 'PERCUSSION_SINGLE',
+                        name: '鼓棒控制练习2.3-e拍抓握 练习',
+                        id: 1081494,
+                        type: 0,
+                        content:
+                          'https://courseware.lexiaoya.cn/227鼓棒控制练习二-打击乐-L1-2-3-B9-示范.mp4',
+                        typeCode: 'VIDEO'
+                      },
+                      {
+                        updateTime: '2023-09-11 15:43:18',
+                        sn: '鼓棒控制练习二-打击乐- L1-2-3-B10-讲话',
+                        knowledgePointMaterialRelationId: 1701139355234549800,
+                        adviseStudyTimeSecond: 0,
+                        knowledgePointId: 1030645,
+                        courseTypeCode: 'PERCUSSION_SINGLE',
+                        name: '鼓棒控制练习2.4-击打同时抓握 讲解',
+                        id: 1081495,
+                        type: 0,
+                        content:
+                          'https://courseware.lexiaoya.cn/228鼓棒控制练习二-打击乐-L1-2-3-B10-讲话.mp4',
+                        typeCode: 'VIDEO'
+                      },
+                      {
+                        updateTime: '2023-09-11 15:43:18',
+                        sn: '鼓棒控制练习二-打击乐- L1-2-3-B11-讲话1',
+                        knowledgePointMaterialRelationId: 1701139355234549800,
+                        adviseStudyTimeSecond: 0,
+                        knowledgePointId: 1030645,
+                        courseTypeCode: 'PERCUSSION_SINGLE',
+                        name: '鼓棒控制练习2.4-击打同时抓握 讲解',
+                        id: 1081496,
+                        type: 0,
+                        content:
+                          'https://courseware.lexiaoya.cn/230鼓棒控制练习二-打击乐-L1-2-3-B11-讲话1.mp4',
+                        typeCode: 'VIDEO'
+                      },
+                      {
+                        updateTime: '2023-09-11 15:43:18',
+                        sn: '鼓棒控制练习二-打击乐- L1-2-3-B11-示范',
+                        knowledgePointMaterialRelationId: 1701139355234549800,
+                        adviseStudyTimeSecond: 0,
+                        knowledgePointId: 1030645,
+                        courseTypeCode: 'PERCUSSION_SINGLE',
+                        name: '鼓棒控制练习2.4-击打同时抓握 练习',
+                        id: 1081497,
+                        type: 0,
+                        content:
+                          'https://courseware.lexiaoya.cn/231鼓棒控制练习二-打击乐-L1-2-3-B11-示范.mp4',
+                        typeCode: 'VIDEO'
+                      }
+                    ],
+                    sortNo: 22,
+                    totalMaterialTimeSecond: 0,
+                    name: '鼓棒控制练习二',
+                    id: 1030645
+                  },
+                  {
+                    children: null,
+                    materialList: [
+                      {
+                        updateTime: '2023-09-11 15:43:18',
+                        sn: '鼓棒控制练习三-打击乐-L1-1-3-C1-讲话',
+                        knowledgePointMaterialRelationId: 1701139355234549800,
+                        adviseStudyTimeSecond: 0,
+                        knowledgePointId: 1030646,
+                        courseTypeCode: 'PERCUSSION_SINGLE',
+                        name: '鼓棒控制练习3-手腕单手练习-前言',
+                        id: 1081498,
+                        type: 0,
+                        content:
+                          'https://courseware.lexiaoya.cn/117鼓棒控制练习三-打击乐-L1-1-3-C1-讲话.mp4',
+                        typeCode: 'VIDEO'
+                      },
+                      {
+                        updateTime: '2023-09-11 15:43:18',
+                        sn: '鼓棒控制练习三-打击乐-L1-3-3-B1-讲话1',
+                        knowledgePointMaterialRelationId: 1701139355234549800,
+                        adviseStudyTimeSecond: 0,
+                        knowledgePointId: 1030646,
+                        courseTypeCode: 'PERCUSSION_SINGLE',
+                        name: '鼓棒控制练习3-手腕单手练习-讲话1',
+                        id: 1081499,
+                        type: 0,
+                        content:
+                          'https://courseware.lexiaoya.cn/284鼓棒控制练习三-打击乐-L1-3-3-B2-讲话1.mp4',
+                        typeCode: 'VIDEO'
+                      },
+                      {
+                        updateTime: '2023-09-11 15:43:18',
+                        sn: '鼓棒控制练习三-打击乐-L1-3-3-B2-示范',
+                        knowledgePointMaterialRelationId: 1701139355234549800,
+                        adviseStudyTimeSecond: 0,
+                        knowledgePointId: 1030646,
+                        courseTypeCode: 'PERCUSSION_SINGLE',
+                        name: '鼓棒控制练习3-手腕单手练习-练习',
+                        id: 1081500,
+                        type: 0,
+                        content:
+                          'https://courseware.lexiaoya.cn/285鼓棒控制练习三-打击乐-L1-3-3-B2-示范.mp4',
+                        typeCode: 'VIDEO'
+                      },
+                      {
+                        updateTime: '2023-09-11 15:43:18',
+                        sn: '鼓棒控制练习三-打击乐-L1-3-3-C1-讲话',
+                        knowledgePointMaterialRelationId: 1701139355234549800,
+                        adviseStudyTimeSecond: 0,
+                        knowledgePointId: 1030646,
+                        courseTypeCode: 'PERCUSSION_SINGLE',
+                        name: '鼓棒控制练习3.1-四拍连贯练习-讲话',
+                        id: 1081501,
+                        type: 0,
+                        content:
+                          'https://courseware.lexiaoya.cn/288鼓棒控制练习三-打击乐-L1-3-3-C1-讲话.mp4',
+                        typeCode: 'VIDEO'
+                      },
+                      {
+                        updateTime: '2023-09-11 15:43:18',
+                        sn: '鼓棒控制练习三-打击乐-L1-3-3-C2-讲话',
+                        knowledgePointMaterialRelationId: 1701139355234549800,
+                        adviseStudyTimeSecond: 0,
+                        knowledgePointId: 1030646,
+                        courseTypeCode: 'PERCUSSION_SINGLE',
+                        name: '鼓棒控制练习3.1-四拍连贯练习-讲话2',
+                        id: 1081502,
+                        type: 0,
+                        content:
+                          'https://courseware.lexiaoya.cn/289鼓棒控制练习三-打击乐-L1-3-3-C2-讲话.mp4',
+                        typeCode: 'VIDEO'
+                      },
+                      {
+                        updateTime: '2023-09-11 15:43:18',
+                        sn: '鼓棒控制练习三-打击乐-L1-3-3-C2-示范',
+                        knowledgePointMaterialRelationId: 1701139355234549800,
+                        adviseStudyTimeSecond: 0,
+                        knowledgePointId: 1030646,
+                        courseTypeCode: 'PERCUSSION_SINGLE',
+                        name: '鼓棒控制练习3.1-四拍连贯练习-练习',
+                        id: 1081503,
+                        type: 0,
+                        content:
+                          'https://courseware.lexiaoya.cn/290鼓棒控制练习三-打击乐-L1-3-3-C2-示范.mp4',
+                        typeCode: 'VIDEO'
+                      },
+                      {
+                        updateTime: '2023-09-11 15:43:18',
+                        sn: '鼓棒控制练习三-打击乐-1-4-C1-讲话',
+                        knowledgePointMaterialRelationId: 1701139355234549800,
+                        adviseStudyTimeSecond: 0,
+                        knowledgePointId: 1030646,
+                        courseTypeCode: 'PERCUSSION_SINGLE',
+                        name: '鼓棒控制练习3.2 -手腕四拍连贯练习讲解',
+                        id: 1081504,
+                        type: 0,
+                        content:
+                          'https://courseware.lexiaoya.cn/123鼓棒控制练习三-打击乐-1-4-C1-讲话.mp4',
+                        typeCode: 'VIDEO'
+                      },
+                      {
+                        updateTime: '2023-09-11 15:43:18',
+                        sn: '鼓棒控制练习三-打击乐-L1-4-3-B2-讲话1',
+                        knowledgePointMaterialRelationId: 1701139355234549800,
+                        adviseStudyTimeSecond: 0,
+                        knowledgePointId: 1030646,
+                        courseTypeCode: 'PERCUSSION_SINGLE',
+                        name: '鼓棒控制练习3.2 -手腕四拍连贯练习讲话',
+                        id: 1081505,
+                        type: 0,
+                        content:
+                          'https://courseware.lexiaoya.cn/345鼓棒控制练习三-打击乐-L1-4-3-B2-讲话1.mp4',
+                        typeCode: 'VIDEO'
+                      },
+                      {
+                        updateTime: '2023-09-11 15:43:18',
+                        sn: '鼓棒控制练习三-打击乐-L1-4-3-B2-示范',
+                        knowledgePointMaterialRelationId: 1701139355234549800,
+                        adviseStudyTimeSecond: 0,
+                        knowledgePointId: 1030646,
+                        courseTypeCode: 'PERCUSSION_SINGLE',
+                        name: '鼓棒控制练习3.2 -手腕四拍连贯练习练习',
+                        id: 1081506,
+                        type: 0,
+                        content:
+                          'https://courseware.lexiaoya.cn/346鼓棒控制练习三-打击乐-L1-4-3-B2-示范.mp4',
+                        typeCode: 'VIDEO'
+                      },
+                      {
+                        updateTime: '2023-09-11 15:43:18',
+                        sn: '鼓棒控制练习三(一拍)-打击乐-L1-1-4-D2-讲话',
+                        knowledgePointMaterialRelationId: 1701139355234549800,
+                        adviseStudyTimeSecond: 0,
+                        knowledgePointId: 1030646,
+                        courseTypeCode: 'PERCUSSION_SINGLE',
+                        name: '鼓棒控制练习3.3-手腕一拍连贯练习 讲解(速度70)',
+                        id: 1081507,
+                        type: 0,
+                        content:
+                          'https://courseware.lexiaoya.cn/126鼓棒控制练习三(一拍)-打击乐-L1-1-4-D2-讲话.mp4',
+                        typeCode: 'VIDEO'
+                      },
+                      {
+                        updateTime: '2023-09-11 15:43:18',
+                        sn: '鼓棒控制练习三(一拍)-打击乐-L1-1-4-D2-示范',
+                        knowledgePointMaterialRelationId: 1701139355234549800,
+                        adviseStudyTimeSecond: 0,
+                        knowledgePointId: 1030646,
+                        courseTypeCode: 'PERCUSSION_SINGLE',
+                        name: '鼓棒控制练习3.3-手腕一拍连贯练习 示范(速度70)',
+                        id: 1081508,
+                        type: 0,
+                        content:
+                          'https://courseware.lexiaoya.cn/127鼓棒控制练习三(一拍)-打击乐-L1-1-4-D2-示范.mp4',
+                        typeCode: 'VIDEO'
+                      },
+                      {
+                        updateTime: '2023-09-11 15:43:18',
+                        sn: '鼓棒控制练习三(一拍)-打击乐-L1-1-4-D3-讲话',
+                        knowledgePointMaterialRelationId: 1701139355234549800,
+                        adviseStudyTimeSecond: 0,
+                        knowledgePointId: 1030646,
+                        courseTypeCode: 'PERCUSSION_SINGLE',
+                        name: '鼓棒控制练习3.3-手腕一拍连贯练习 讲解(速度90)',
+                        id: 1081509,
+                        type: 0,
+                        content:
+                          'https://courseware.lexiaoya.cn/128鼓棒控制练习三(一拍)-打击乐-L1-1-4-D3-讲话.mp4',
+                        typeCode: 'VIDEO'
+                      },
+                      {
+                        updateTime: '2023-09-11 15:43:18',
+                        sn: '鼓棒控制练习三(一拍)-打击乐-L1-5-3-B2-示范',
+                        knowledgePointMaterialRelationId: 1701139355234549800,
+                        adviseStudyTimeSecond: 0,
+                        knowledgePointId: 1030646,
+                        courseTypeCode: 'PERCUSSION_SINGLE',
+                        name: '鼓棒控制练习3.3-手腕一拍连贯练习 示范(速度90)',
+                        id: 1081510,
+                        type: 0,
+                        content:
+                          'https://courseware.lexiaoya.cn/398鼓棒控制练习三(一拍)-打击乐-L1-5-3-B2-示范.mp4',
+                        typeCode: 'VIDEO'
+                      },
+                      {
+                        updateTime: '2023-09-11 15:43:18',
+                        sn: '鼓棒控制练习三(半拍)-打击乐-L1-1-4-D1-讲话',
+                        knowledgePointMaterialRelationId: 1701139355234549800,
+                        adviseStudyTimeSecond: 0,
+                        knowledgePointId: 1030646,
+                        courseTypeCode: 'PERCUSSION_SINGLE',
+                        name: '鼓棒控制练习3.4-手腕一拍连贯练习 讲解',
+                        id: 1081511,
+                        type: 0,
+                        content:
+                          'https://courseware.lexiaoya.cn/130鼓棒控制练习三(半拍)-打击乐-L1-1-4-D1-讲话.mp4',
+                        typeCode: 'VIDEO'
+                      },
+                      {
+                        updateTime: '2023-09-11 15:43:18',
+                        sn: '鼓棒控制练习三(半拍)-打击乐-L1-5-3-C2-示范',
+                        knowledgePointMaterialRelationId: 1701139355234549800,
+                        adviseStudyTimeSecond: 0,
+                        knowledgePointId: 1030646,
+                        courseTypeCode: 'PERCUSSION_SINGLE',
+                        name: '鼓棒控制练习3.4-手腕一拍连贯练习 示范',
+                        id: 1081512,
+                        type: 0,
+                        content:
+                          'https://courseware.lexiaoya.cn/402鼓棒控制练习三(半拍)-打击乐-L1-5-3-C2-示范.mp4',
+                        typeCode: 'VIDEO'
+                      },
+                      {
+                        updateTime: '2023-09-11 15:43:18',
+                        sn: '鼓棒控制练习三(连贯)-打击乐-L1-1-4-E1-讲话',
+                        knowledgePointMaterialRelationId: 1701139355234549800,
+                        adviseStudyTimeSecond: 0,
+                        knowledgePointId: 1030646,
+                        courseTypeCode: 'PERCUSSION_SINGLE',
+                        name: '鼓棒控制练习3所有内容连贯 讲解',
+                        id: 1081513,
+                        type: 0,
+                        content:
+                          'https://courseware.lexiaoya.cn/132鼓棒控制练习三(连贯)-打击乐-L1-1-4-E1-讲话.mp4',
+                        typeCode: 'VIDEO'
+                      },
+                      {
+                        updateTime: '2023-09-11 15:43:18',
+                        sn: '鼓棒控制练习三(完整90速度)-打击乐-L1-7-3-C1-示范',
+                        knowledgePointMaterialRelationId: 1701139355234549800,
+                        adviseStudyTimeSecond: 0,
+                        knowledgePointId: 1030646,
+                        courseTypeCode: 'PERCUSSION_SINGLE',
+                        name: '鼓棒控制练习3所有内容连贯 练习',
+                        id: 1081514,
+                        type: 0,
+                        content:
+                          'https://courseware.lexiaoya.cn/481鼓棒控制练习三(完整90速度)-打击乐-L1-7-3-C1-示范.mp4',
+                        typeCode: 'VIDEO'
+                      },
+                      {
+                        updateTime: '2023-09-11 15:43:18',
+                        sn: '鼓棒控制练习三(连贯)-打击乐-L1-1-4-E2-讲话',
+                        knowledgePointMaterialRelationId: 1701139355234549800,
+                        adviseStudyTimeSecond: 0,
+                        knowledgePointId: 1030646,
+                        courseTypeCode: 'PERCUSSION_SINGLE',
+                        name: '鼓棒控制练习3注意事项',
+                        id: 1081515,
+                        type: 0,
+                        content:
+                          'https://courseware.lexiaoya.cn/134鼓棒控制练习三(连贯)-打击乐-L1-1-4-E2-讲话.mp4',
+                        typeCode: 'VIDEO'
+                      }
+                    ],
+                    sortNo: 23,
+                    totalMaterialTimeSecond: 0,
+                    name: '鼓棒控制练习三',
+                    id: 1030646
+                  },
+                  {
+                    children: null,
+                    materialList: [
+                      {
+                        updateTime: '2023-09-11 15:43:18',
+                        sn: '鼓棒控制练习三(十六分音符,一拍)-打击乐-L1-1-4-F1-讲话',
+                        knowledgePointMaterialRelationId: 1701139355234549800,
+                        adviseStudyTimeSecond: 0,
+                        knowledgePointId: 1030647,
+                        courseTypeCode: 'PERCUSSION_SINGLE',
+                        name: '鼓棒控制练习3 EX1 讲解',
+                        id: 1081516,
+                        type: 0,
+                        content:
+                          'https://courseware.lexiaoya.cn/135鼓棒控制练习三(十六分音符,一拍)-打击乐-L1-1-4-F1-讲话.mp4',
+                        typeCode: 'VIDEO'
+                      },
+                      {
+                        updateTime: '2023-09-11 15:43:18',
+                        sn: '鼓棒控制练习三(十六分音符,一拍)-打击乐-L1-9-3-A5-示范',
+                        knowledgePointMaterialRelationId: 1701139355234549800,
+                        adviseStudyTimeSecond: 0,
+                        knowledgePointId: 1030647,
+                        courseTypeCode: 'PERCUSSION_SINGLE',
+                        name: '鼓棒控制练习3 EX1 练习',
+                        id: 1081517,
+                        type: 0,
+                        content:
+                          'https://courseware.lexiaoya.cn/572鼓棒控制练习三(十六分音符,一拍)-打击乐-L1-9-3-A5-示范.mp4',
+                        typeCode: 'VIDEO'
+                      }
+                    ],
+                    sortNo: 24,
+                    totalMaterialTimeSecond: 0,
+                    name: '十六分音符-1拍',
+                    id: 1030647
+                  },
+                  {
+                    children: null,
+                    materialList: [
+                      {
+                        updateTime: '2023-09-11 15:43:18',
+                        sn: '鼓棒控制练习三(十六分音符,二拍)-打击乐- 1-4-G1-讲话',
+                        knowledgePointMaterialRelationId: 1701139355234549800,
+                        adviseStudyTimeSecond: 0,
+                        knowledgePointId: 1030648,
+                        courseTypeCode: 'PERCUSSION_SINGLE',
+                        name: '鼓棒控制练习3 EX2 讲解',
+                        id: 1081518,
+                        type: 0,
+                        content:
+                          'https://courseware.lexiaoya.cn/137鼓棒控制练习三(十六分音符,二拍)-打击乐-1-4-G1-讲话.mp4',
+                        typeCode: 'VIDEO'
+                      },
+                      {
+                        updateTime: '2023-09-11 15:43:18',
+                        sn: '鼓棒控制练习三(十六分音符,二拍)-打击乐- L1-11-3-A5-示范',
+                        knowledgePointMaterialRelationId: 1701139355234549800,
+                        adviseStudyTimeSecond: 0,
+                        knowledgePointId: 1030648,
+                        courseTypeCode: 'PERCUSSION_SINGLE',
+                        name: '鼓棒控制练习3 EX2 练习',
+                        id: 1081519,
+                        type: 0,
+                        content:
+                          'https://courseware.lexiaoya.cn/666鼓棒控制练习三(十六分音符,二拍)-打击乐-L1-11-3-A5-示范.mp4',
+                        typeCode: 'VIDEO'
+                      }
+                    ],
+                    sortNo: 25,
+                    totalMaterialTimeSecond: 0,
+                    name: '十六分音符-2拍',
+                    id: 1030648
+                  },
+                  {
+                    children: null,
+                    materialList: [
+                      {
+                        updateTime: '2023-09-11 15:43:18',
+                        sn: '鼓棒控制练习三(十六分音符,三拍)-打击乐- L1-1-4-H1-讲话',
+                        knowledgePointMaterialRelationId: 1701139355234549800,
+                        adviseStudyTimeSecond: 0,
+                        knowledgePointId: 1030649,
+                        courseTypeCode: 'PERCUSSION_SINGLE',
+                        name: '鼓棒控制练习3 EX3 讲解',
+                        id: 1081520,
+                        type: 0,
+                        content:
+                          'https://courseware.lexiaoya.cn/139鼓棒控制练习三(十六分音符,三拍)-打击乐-L1-1-4-H1-讲话.mp4',
+                        typeCode: 'VIDEO'
+                      },
+                      {
+                        updateTime: '2023-09-11 15:43:18',
+                        sn: '鼓棒控制练习三(十六分音符,三拍)-打击乐- L1-13-3-A5-示范',
+                        knowledgePointMaterialRelationId: 1701139355234549800,
+                        adviseStudyTimeSecond: 0,
+                        knowledgePointId: 1030649,
+                        courseTypeCode: 'PERCUSSION_SINGLE',
+                        name: '鼓棒控制练习3 EX3 练习',
+                        id: 1081521,
+                        type: 0,
+                        content:
+                          'https://courseware.lexiaoya.cn/766鼓棒控制练习三(十六分音符,三拍)-打击乐-L1-13-3-A5-示范.mp4',
+                        typeCode: 'VIDEO'
+                      }
+                    ],
+                    sortNo: 26,
+                    totalMaterialTimeSecond: 0,
+                    name: '十六分音符-3拍',
+                    id: 1030649
+                  },
+                  {
+                    children: null,
+                    materialList: [
+                      {
+                        updateTime: '2023-09-11 15:43:18',
+                        sn: '鼓棒控制练习三(十六分音符,四拍)-打击乐- L1-1-4-I1-讲话',
+                        knowledgePointMaterialRelationId: 1701139355234549800,
+                        adviseStudyTimeSecond: 0,
+                        knowledgePointId: 1030650,
+                        courseTypeCode: 'PERCUSSION_SINGLE',
+                        name: '鼓棒控制练习3 EX4 讲解',
+                        id: 1081522,
+                        type: 0,
+                        content:
+                          'https://courseware.lexiaoya.cn/141鼓棒控制练习三(十六分音符,四拍)-打击乐-L1-1-4-I1-讲话.mp4',
+                        typeCode: 'VIDEO'
+                      },
+                      {
+                        updateTime: '2023-09-11 15:43:18',
+                        sn: '鼓棒控制练习三(十六分音符,四拍)-打击乐- L1-15-3-A5-示范',
+                        knowledgePointMaterialRelationId: 1701139355242938400,
+                        adviseStudyTimeSecond: 0,
+                        knowledgePointId: 1030650,
+                        courseTypeCode: 'PERCUSSION_SINGLE',
+                        name: '鼓棒控制练习3 EX4 练习',
+                        id: 1081523,
+                        type: 0,
+                        content:
+                          'https://courseware.lexiaoya.cn/866鼓棒控制练习三(十六分音符,四拍)-打击乐-L1-15-3-A5-示范.mp4',
+                        typeCode: 'VIDEO'
+                      }
+                    ],
+                    sortNo: 27,
+                    totalMaterialTimeSecond: 0,
+                    name: '十六分音符-4拍',
+                    id: 1030650
+                  },
+                  {
+                    children: null,
+                    materialList: [
+                      {
+                        updateTime: '2023-09-11 15:43:18',
+                        sn: '鼓棒控制练习三(十六分音符,连贯)-打击乐- L1-1-4-J1-讲话',
+                        knowledgePointMaterialRelationId: 1701139355242938400,
+                        adviseStudyTimeSecond: 0,
+                        knowledgePointId: 1030651,
+                        courseTypeCode: 'PERCUSSION_SINGLE',
+                        name: '鼓棒控制练习3 EX4 练习',
+                        id: 1081524,
+                        type: 0,
+                        content:
+                          'https://courseware.lexiaoya.cn/143鼓棒控制练习三(十六分音符,连贯)-打击乐-L1-1-4-J1-讲话.mp4',
+                        typeCode: 'VIDEO'
+                      },
+                      {
+                        updateTime: '2023-09-11 15:43:18',
+                        sn: '鼓棒控制练习三(十六分音符,连贯)-打击乐- L1-1-4-J1-示范(节拍器80)',
+                        knowledgePointMaterialRelationId: 1701139355242938400,
+                        adviseStudyTimeSecond: 0,
+                        knowledgePointId: 1030651,
+                        courseTypeCode: 'PERCUSSION_SINGLE',
+                        name: '鼓棒控制练习3 EX1-Ex4 完整讲解',
+                        id: 1081525,
+                        type: 0,
+                        content:
+                          'https://courseware.lexiaoya.cn/144鼓棒控制练习三(十六分音符,连贯)-打击乐-L1-1-4-J1-示范(节拍器80).mp4',
+                        typeCode: 'VIDEO'
+                      },
+                      {
+                        updateTime: '2023-09-11 15:43:18',
+                        sn: '鼓棒控制练习三(十六分音符,连贯)-打击乐- L1-1-4-J2-示范(节拍器90)',
+                        knowledgePointMaterialRelationId: 1701139355242938400,
+                        adviseStudyTimeSecond: 0,
+                        knowledgePointId: 1030651,
+                        courseTypeCode: 'PERCUSSION_SINGLE',
+                        name: '鼓棒控制练习3 EX1-Ex4 完整练习速度90',
+                        id: 1081526,
+                        type: 0,
+                        content:
+                          'https://courseware.lexiaoya.cn/145鼓棒控制练习三(十六分音符,连贯)-打击乐-L1-1-4-J2-示范(节拍器90).mp4',
+                        typeCode: 'VIDEO'
+                      },
+                      {
+                        updateTime: '2023-09-11 15:43:18',
+                        sn: '鼓棒控制练习三(十六分音符,连贯)-打击乐- L1-1-4-J3-示范(节拍器100)',
+                        knowledgePointMaterialRelationId: 1701139355242938400,
+                        adviseStudyTimeSecond: 0,
+                        knowledgePointId: 1030651,
+                        courseTypeCode: 'PERCUSSION_SINGLE',
+                        name: '鼓棒控制练习3 EX1-Ex4 完整练习速度10',
+                        id: 1081527,
+                        type: 0,
+                        content:
+                          'https://courseware.lexiaoya.cn/146鼓棒控制练习三(十六分音符,连贯)-打击乐-L1-1-4-J3-示范(节拍器100).mp4',
+                        typeCode: 'VIDEO'
+                      },
+                      {
+                        updateTime: '2023-09-11 15:43:18',
+                        sn: '鼓棒控制练习三(十六分音符,连贯)-打击乐- L1-1-4-J4-示范(节拍器110)',
+                        knowledgePointMaterialRelationId: 1701139355242938400,
+                        adviseStudyTimeSecond: 0,
+                        knowledgePointId: 1030651,
+                        courseTypeCode: 'PERCUSSION_SINGLE',
+                        name: '鼓棒控制练习3 EX1-Ex4 完整练习速度110',
+                        id: 1081528,
+                        type: 0,
+                        content:
+                          'https://courseware.lexiaoya.cn/147鼓棒控制练习三(十六分音符,连贯)-打击乐-L1-1-4-J4-示范(节拍器110).mp4',
+                        typeCode: 'VIDEO'
+                      },
+                      {
+                        updateTime: '2023-09-11 15:43:18',
+                        sn: '结尾-打击乐-L1-1-7-讲话',
+                        knowledgePointMaterialRelationId: 1701139355247132700,
+                        adviseStudyTimeSecond: 0,
+                        knowledgePointId: 1030651,
+                        courseTypeCode: 'PERCUSSION_SINGLE',
+                        name: '结束语',
+                        id: 1081462,
+                        type: 0,
+                        content:
+                          'https://courseware.lexiaoya.cn/191结尾-打击乐-L1-1-7-讲话.mp4',
+                        typeCode: 'VIDEO'
+                      }
+                    ],
+                    sortNo: 28,
+                    totalMaterialTimeSecond: 0,
+                    name: '十六分音符,连贯练习',
+                    id: 1030651
+                  }
+                ],
+                materialList: null,
+                sortNo: 20,
+                totalMaterialTimeSecond: 0,
+                name: '鼓棒控制练习',
+                id: 1030642
+              },
+              {
+                children: [
+                  {
+                    children: null,
+                    materialList: [
+                      {
+                        updateTime: '2023-09-11 15:43:18',
+                        sn: '四种打法前言-打击乐1-1-讲话',
+                        knowledgePointMaterialRelationId: 1701139355247132700,
+                        adviseStudyTimeSecond: 0,
+                        knowledgePointId: 1030653,
+                        courseTypeCode: 'PERCUSSION_SINGLE',
+                        name: '四种打法-前言',
+                        id: 1081529,
+                        type: 0,
+                        content:
+                          'https://courseware.lexiaoya.cn/67四种打法前言-打击乐1-1-讲话.mp4',
+                        typeCode: 'VIDEO'
+                      }
+                    ],
+                    sortNo: 29,
+                    totalMaterialTimeSecond: 0,
+                    name: '前言',
+                    id: 1030653
+                  },
+                  {
+                    children: null,
+                    materialList: [
+                      {
+                        updateTime: '2023-09-11 15:43:18',
+                        sn: 'Piston Stroke -打击乐- L1-1-2-A1-讲话',
+                        knowledgePointMaterialRelationId: 1701139355247132700,
+                        adviseStudyTimeSecond: 0,
+                        knowledgePointId: 1030654,
+                        courseTypeCode: 'PERCUSSION_SINGLE',
+                        name: '鼓棒持拿与演奏姿势',
+                        id: 1081530,
+                        type: 0,
+                        content:
+                          'https://courseware.lexiaoya.cn/68PistonStroke-打击乐-L1-1-2-A1-讲话.mp4',
+                        typeCode: 'VIDEO'
+                      },
+                      {
+                        updateTime: '2023-09-11 15:43:18',
+                        sn: 'Piston Stroke-打击乐-L1-1-4-A2-讲话',
+                        knowledgePointMaterialRelationId: 1701139355247132700,
+                        adviseStudyTimeSecond: 0,
+                        knowledgePointId: 1030654,
+                        courseTypeCode: 'PERCUSSION_SINGLE',
+                        name: 'Piston Stroke-讲解',
+                        id: 1081531,
+                        type: 0,
+                        content:
+                          'https://courseware.lexiaoya.cn/186PistonStroke-打击乐-L1-1-4-A2-讲话.mp4',
+                        typeCode: 'VIDEO'
+                      },
+                      {
+                        updateTime: '2023-09-11 15:43:18',
+                        sn: 'Piston Stroke-打击乐-L1-1-4-A2-示范',
+                        knowledgePointMaterialRelationId: 1701139355247132700,
+                        adviseStudyTimeSecond: 0,
+                        knowledgePointId: 1030654,
+                        courseTypeCode: 'PERCUSSION_SINGLE',
+                        name: 'Piston Stroke-练习',
+                        id: 1081532,
+                        type: 0,
+                        content:
+                          'https://courseware.lexiaoya.cn/187PistonStroke-打击乐-L1-1-4-A2-示范.mp4',
+                        typeCode: 'VIDEO'
+                      }
+                    ],
+                    sortNo: 30,
+                    totalMaterialTimeSecond: 0,
+                    name: 'Piston Stroke',
+                    id: 1030654
+                  },
+                  {
+                    children: null,
+                    materialList: [
+                      {
+                        updateTime: '2023-09-11 15:43:18',
+                        sn: 'Down Stroke-打击乐-L1-1-2-B1-讲话',
+                        knowledgePointMaterialRelationId: 1701139355247132700,
+                        adviseStudyTimeSecond: 0,
+                        knowledgePointId: 1030655,
+                        courseTypeCode: 'PERCUSSION_SINGLE',
+                        name: 'Down Stroke-讲解1',
+                        id: 1081533,
+                        type: 0,
+                        content:
+                          'https://courseware.lexiaoya.cn/71DownStroke-打击乐-L1-1-2-B1-讲话.mp4',
+                        typeCode: 'VIDEO'
+                      },
+                      {
+                        updateTime: '2023-09-11 15:43:18',
+                        sn: 'Down Stroke-打击乐-L1-2-4-A4-讲话1',
+                        knowledgePointMaterialRelationId: 1701139355247132700,
+                        adviseStudyTimeSecond: 0,
+                        knowledgePointId: 1030655,
+                        courseTypeCode: 'PERCUSSION_SINGLE',
+                        name: 'Down Stroke-讲解2',
+                        id: 1081534,
+                        type: 0,
+                        content:
+                          'https://courseware.lexiaoya.cn/239DownStroke-打击乐-L1-2-4-A4-讲话1.mp4',
+                        typeCode: 'VIDEO'
+                      },
+                      {
+                        updateTime: '2023-09-11 15:43:18',
+                        sn: 'Down Stroke-打击乐-L1-2-4-A4-示范',
+                        knowledgePointMaterialRelationId: 1701139355247132700,
+                        adviseStudyTimeSecond: 0,
+                        knowledgePointId: 1030655,
+                        courseTypeCode: 'PERCUSSION_SINGLE',
+                        name: 'Down Stroke-练习',
+                        id: 1081535,
+                        type: 0,
+                        content:
+                          'https://courseware.lexiaoya.cn/240DownStroke-打击乐-L1-2-4-A4-示范.mp4',
+                        typeCode: 'VIDEO'
+                      }
+                    ],
+                    sortNo: 31,
+                    totalMaterialTimeSecond: 0,
+                    name: 'Down Stroke',
+                    id: 1030655
+                  },
+                  {
+                    children: null,
+                    materialList: [
+                      {
+                        updateTime: '2023-09-11 15:43:18',
+                        sn: 'Tap Stroke-打击乐-L1-1-2-C1-讲话',
+                        knowledgePointMaterialRelationId: 1701139355247132700,
+                        adviseStudyTimeSecond: 0,
+                        knowledgePointId: 1030656,
+                        courseTypeCode: 'PERCUSSION_SINGLE',
+                        name: 'Tap Stroke-讲解1',
+                        id: 1081536,
+                        type: 0,
+                        content:
+                          'https://courseware.lexiaoya.cn/74TapStroke-打击乐-L1-1-2-C1-讲话.mp4',
+                        typeCode: 'VIDEO'
+                      },
+                      {
+                        updateTime: '2023-09-11 15:43:18',
+                        sn: 'Tap Stroke-打击乐-L1-3-4-A5-讲话1',
+                        knowledgePointMaterialRelationId: 1701139355247132700,
+                        adviseStudyTimeSecond: 0,
+                        knowledgePointId: 1030656,
+                        courseTypeCode: 'PERCUSSION_SINGLE',
+                        name: 'Tap Stroke-讲解2',
+                        id: 1081537,
+                        type: 0,
+                        content:
+                          'https://courseware.lexiaoya.cn/301TapStroke-打击乐-L1-3-4-A5-讲话1.mp4',
+                        typeCode: 'VIDEO'
+                      },
+                      {
+                        updateTime: '2023-09-11 15:43:18',
+                        sn: 'Tap Stroke-打击乐-L1-3-4-A5-示范',
+                        knowledgePointMaterialRelationId: 1701139355247132700,
+                        adviseStudyTimeSecond: 0,
+                        knowledgePointId: 1030656,
+                        courseTypeCode: 'PERCUSSION_SINGLE',
+                        name: 'Tap Stroke-练习',
+                        id: 1081538,
+                        type: 0,
+                        content:
+                          'https://courseware.lexiaoya.cn/302TapStroke-打击乐-L1-3-4-A5-示范.mp4',
+                        typeCode: 'VIDEO'
+                      }
+                    ],
+                    sortNo: 32,
+                    totalMaterialTimeSecond: 0,
+                    name: 'Tap Stroke',
+                    id: 1030656
+                  },
+                  {
+                    children: null,
+                    materialList: [
+                      {
+                        updateTime: '2023-09-11 15:43:18',
+                        sn: 'Up Stroke-打击乐-L1-1-2-D1-讲话',
+                        knowledgePointMaterialRelationId: 1701139355247132700,
+                        adviseStudyTimeSecond: 0,
+                        knowledgePointId: 1030657,
+                        courseTypeCode: 'PERCUSSION_SINGLE',
+                        name: 'Up  Stroke-讲解1',
+                        id: 1081539,
+                        type: 0,
+                        content:
+                          'https://courseware.lexiaoya.cn/77UpStroke-打击乐-L1-1-2-D1-讲话.mp4',
+                        typeCode: 'VIDEO'
+                      },
+                      {
+                        updateTime: '2023-09-11 15:43:18',
+                        sn: 'Up Stroke-打击乐-L1-4-4-A6-讲话1',
+                        knowledgePointMaterialRelationId: 1701139355251327000,
+                        adviseStudyTimeSecond: 0,
+                        knowledgePointId: 1030657,
+                        courseTypeCode: 'PERCUSSION_SINGLE',
+                        name: 'Up Stroke-讲解2',
+                        id: 1081540,
+                        type: 0,
+                        content:
+                          'https://courseware.lexiaoya.cn/363Upstroke-打击乐-L1-4-4-A6-讲话1.mp4',
+                        typeCode: 'VIDEO'
+                      },
+                      {
+                        updateTime: '2023-09-11 15:43:18',
+                        sn: 'Up Stroke-打击乐-L1-4-4-A6-示范',
+                        knowledgePointMaterialRelationId: 1701139355251327000,
+                        adviseStudyTimeSecond: 0,
+                        knowledgePointId: 1030657,
+                        courseTypeCode: 'PERCUSSION_SINGLE',
+                        name: 'Up Stroke-练习',
+                        id: 1081541,
+                        type: 0,
+                        content:
+                          'https://courseware.lexiaoya.cn/364Upstroke-打击乐-L1-4-4-A6-示范.mp4',
+                        typeCode: 'VIDEO'
+                      }
+                    ],
+                    sortNo: 33,
+                    totalMaterialTimeSecond: 0,
+                    name: 'Up Stroke',
+                    id: 1030657
+                  },
+                  {
+                    children: null,
+                    materialList: [
+                      {
+                        updateTime: '2023-09-11 15:43:18',
+                        sn: '四种打法(连贯)-打击乐-L1-1-3-A1-讲话',
+                        knowledgePointMaterialRelationId: 1701139355251327000,
+                        adviseStudyTimeSecond: 0,
+                        knowledgePointId: 1030658,
+                        courseTypeCode: 'PERCUSSION_SINGLE',
+                        name: '四种打法连贯-讲解',
+                        id: 1081542,
+                        type: 0,
+                        content:
+                          'https://courseware.lexiaoya.cn/80四种打法(连贯)-打击乐-L1-1-3-A1-讲话.mp4',
+                        typeCode: 'VIDEO'
+                      },
+                      {
+                        updateTime: '2023-09-11 15:43:18',
+                        sn: '基本打法练习(连贯90速度)-打击乐-L1-5-4-A1-示范',
+                        knowledgePointMaterialRelationId: 1701139355251327000,
+                        adviseStudyTimeSecond: 0,
+                        knowledgePointId: 1030658,
+                        courseTypeCode: 'PERCUSSION_SINGLE',
+                        name: '四种打法连贯-练习',
+                        id: 1081543,
+                        type: 0,
+                        content:
+                          'https://courseware.lexiaoya.cn/406基本打法练习(连贯90速度)-打击乐-L1-5-4-A1-示范.mp4',
+                        typeCode: 'VIDEO'
+                      },
+                      {
+                        updateTime: '2023-09-11 15:43:18',
+                        sn: '结尾-打击乐-L1-1-7-讲话',
+                        knowledgePointMaterialRelationId: 1701139355251327000,
+                        adviseStudyTimeSecond: 0,
+                        knowledgePointId: 1030658,
+                        courseTypeCode: 'PERCUSSION_SINGLE',
+                        name: '结束语',
+                        id: 1081462,
+                        type: 0,
+                        content:
+                          'https://courseware.lexiaoya.cn/191结尾-打击乐-L1-1-7-讲话.mp4',
+                        typeCode: 'VIDEO'
+                      }
+                    ],
+                    sortNo: 34,
+                    totalMaterialTimeSecond: 0,
+                    name: '四种打法(连贯)',
+                    id: 1030658
+                  }
+                ],
+                materialList: null,
+                sortNo: 29,
+                totalMaterialTimeSecond: 0,
+                name: '四种基本打法',
+                id: 1030652
+              }
+            ],
+            lessonCoursewareDetailIds: null,
+            lessonTargetDesc:
+              '了解小军鼓的组成、组装和拆解。学习小军鼓的演奏姿势,学习小军鼓的日常保养',
+            lessonCoursewareIds: null,
+            name: '必学必看',
+            id: '1701139354735427585'
+          },
+          code: 200,
+          msg: '',
+          nowTime: '2024-04-15 10:33:15'
+        }
+        data.detail = res.data
+        if (res?.data?.lockFlag) {
+          postMessage({
+            api: 'courseLoading',
+            content: {
+              show: false,
+              type: 'fullscreen'
+            }
+          })
+          Dialog.alert({
+            title: '温馨提示',
+            message: '课件已锁定'
+          }).then(() => {
+            goback()
+          })
+          return
+        }
+        if (Array.isArray(res?.data?.knowledgePointList)) {
+          let index = 0
+          data.knowledgePointList = res.data.knowledgePointList.map(
+            (n: any) => {
+              if (Array.isArray(n.materialList)) {
+                n.materialList = n.materialList.map((item: any) => {
+                  index++
+                  return {
+                    ...item,
+                    content: item.content,
+                    knowledgePointId: [item.knowledgePointId],
+                    materialId: item.id,
+                    id: index + '',
+                    typeCode: item.typeCode || item.type
+                  }
+                })
+              }
+              if (Array.isArray(n.children)) {
+                n.children = n.children.map((cn: any) => {
+                  cn.materialList = cn.materialList.map((item: any) => {
+                    index++
+                    return {
+                      ...item,
+                      content: item.content,
+                      knowledgePointId: [n.id, item.knowledgePointId],
+                      materialId: item.id,
+                      id: index + '',
+                      typeCode: item.typeCode || item.type
+                    }
+                  })
+                  return cn
+                })
+              }
+              return n
+            }
+          )
+          getItemList()
+        }
+      } catch (error) {
+        console.log(error)
+      }
+    }
+
+    // ifram事件处理
+    const iframeHandle = (ev: MessageEvent) => {
+      if (ev.data?.api === 'headerTogge') {
+        activeData.model =
+          ev.data.show || (ev.data.playState == 'play' ? false : true)
+      }
+    }
+
+    onMounted(async () => {
+      await getDetail()
+      // const hasFree = String(data.detail?.accessScope) === '0'
+      // if (!hasFree) {
+      //   if (state.platformType === 'STUDENT') {
+      //     const hasVip = handleCheckVip()
+      //     if (!hasVip) {
+      //       nextTick(() => {
+      //         postMessage({
+      //           api: 'courseLoading',
+      //           content: {
+      //             show: false,
+      //             type: 'fullscreen'
+      //           }
+      //         })
+      //       })
+      //       return
+      //     }
+      //   }
+      // }
+      // getCourseSchedule();
+      window.addEventListener('message', iframeHandle)
+    })
+
+    const playRef = ref()
+    // 返回
+    const goback = () => {
+      try {
+        playRef.value?.handleOut()
+      } catch (error) {
+        console.log(error)
+      }
+      postMessage({ api: 'back' })
+    }
+
+    const popupData = reactive({
+      open: false,
+      activeIndex: 0,
+      playIndex: 0,
+      tabActive: '',
+      tabName: '',
+      itemActive: '',
+      itemName: '',
+      guideOpen: false,
+      toolOpen: false // 工具弹窗控制
+    })
+
+    const stopVideo = (el: HTMLVideoElement) => {
+      return new Promise(resolve => {
+        if (el.paused) return resolve(true)
+        el.onpause = () => {
+          console.log('暂停')
+          resolve(true)
+        }
+        el.pause()
+      })
+    }
+
+    /**停止所有的播放 */
+    const handleStop = async () => {
+      const videos = document.querySelectorAll('video')
+      for (let i = 0; i < videos.length; i++) {
+        const videoEle = videos[i] as HTMLVideoElement
+        await stopVideo(videoEle)
+      }
+      console.log('视频暂停完成')
+      data.itemList.forEach((item: any) => {
+        if (item.typeCode === 'SONG') {
+          item.iframeRef?.contentWindow?.postMessage(
+            { api: 'setPlayState' },
+            '*'
+          )
+        }
+      })
+    }
+    // 切换素材
+    const toggleMaterial = (itemActive: any) => {
+      const index = data.itemList.findIndex((n: any) => n.id == itemActive)
+      if (index > -1) {
+        handleSwipeChange(index)
+      }
+    }
+    /** 延迟收起模态框 */
+    const setModelOpen = () => {
+      clearTimeout(activeData.timer)
+      Toast.clear()
+      activeData.timer = setTimeout(() => {
+        activeData.model = false
+      }, 4000)
+    }
+    /** 立即收起所有的模态框 */
+    const clearModel = () => {
+      clearTimeout(activeData.timer)
+      Toast.clear()
+      activeData.model = false
+    }
+    const toggleModel = (type = true) => {
+      activeData.model = type
+    }
+
+    // 去点名,签退
+    const gotoRollCall = (pageTag: string) => {
+      postMessage({
+        api: 'open_app_page',
+        content: {
+          action: 'app',
+          pageTag: pageTag,
+          url: '',
+          params: JSON.stringify({ courseId: route.query.courseId })
+        }
+      })
+    }
+
+    // 双击
+    const handleDbClick = () => {
+      if (activeVideoItem.value.typeCode === 'VIDEO') {
+        const activeVideoRef = data.videoItemRef?.getPlyrRef()
+        if (activeVideoRef) {
+          if (activeVideoRef.paused()) {
+            activeVideoRef.play()
+          } else {
+            activeVideoRef.pause()
+            Toast('已暂停')
+          }
+        }
+      }
+    }
+
+    const effectIndex = ref(0)
+    const effects = [
+      {
+        prev: {
+          transform: 'translate3d(0, 0, -800px) rotateX(180deg)'
+        },
+        next: {
+          transform: 'translate3d(0, 0, -800px) rotateX(-180deg)'
+        }
+      },
+      {
+        prev: {
+          transform: 'translate3d(-100%, 0, -800px)'
+        },
+        next: {
+          transform: 'translate3d(100%, 0, -800px)'
+        }
+      },
+      {
+        prev: {
+          transform: 'translate3d(-50%, 0, -800px) rotateY(80deg)'
+        },
+        next: {
+          transform: 'translate3d(50%, 0, -800px) rotateY(-80deg)'
+        }
+      },
+      {
+        prev: {
+          transform: 'translate3d(-100%, 0, -800px) rotateY(-120deg)'
+        },
+        next: {
+          transform: 'translate3d(100%, 0, -800px) rotateY(120deg)'
+        }
+      },
+      // 风车4
+      {
+        prev: {
+          transform: 'translate3d(-50%, 50%, -800px) rotateZ(-14deg)',
+          opacity: 0
+        },
+        next: {
+          transform: 'translate3d(50%, 50%, -800px) rotateZ(14deg)',
+          opacity: 0
+        }
+      },
+      // 翻页5
+      {
+        prev: {
+          transform: 'translateZ(-800px) rotate3d(0, -1, 0, 90deg)',
+          opacity: 0
+        },
+        next: {
+          transform: 'translateZ(-800px) rotate3d(0, 1, 0, 90deg)',
+          opacity: 0
+        },
+        current: { transitionDelay: '700ms' }
+      }
+    ]
+
+    const acitveTimer = ref()
+    // 轮播切换
+    const handleSwipeChange = async (index: number) => {
+      // 如果是当前正在播放 或者是视频最后一个
+      if (popupData.activeIndex == index) return
+      await handleStop()
+      data.animationState = 'start'
+      data.videoState = 'init'
+      clearTimeout(acitveTimer.value)
+      checkedAnimation(popupData.activeIndex, index)
+      nextTick(() => {
+        popupData.activeIndex = index
+
+        acitveTimer.value = setTimeout(
+          () => {
+            popupData.playIndex = index
+            const item = data.itemList[index]
+            if (item) {
+              popupData.tabActive = item.knowledgePointId
+              popupData.itemActive = item.id
+              popupData.itemName = item.name
+              popupData.tabName = item.tabName
+              if (item.typeCode == 'SONG') {
+                activeData.model = true
+              }
+            }
+            requestAnimationFrame(() => {
+              const _effectIndex = effectIndex.value + 1
+              effectIndex.value =
+                _effectIndex >= effects.length - 1 ? 0 : _effectIndex
+
+              if (item && item.typeCode === 'VIDEO') {
+                // 自动播放下一个视频
+                clearTimeout(activeData.timer)
+                Toast.clear()
+                item.autoPlay = true
+                data.animationState = 'end'
+              }
+            })
+          },
+          activeData.isAnimation ? 850 : 0
+        )
+      })
+    }
+
+    /** 是否有转场动画 */
+    const checkedAnimation = (index: number, nextIndex?: number) => {
+      nextIndex = nextIndex ? nextIndex : index + 1
+      const item = data.itemList[index]
+      const nextItem = data.itemList[nextIndex]
+      if (nextItem) {
+        if (nextItem.knowledgePointId != item.knowledgePointId) {
+          activeData.isAnimation = true
+          return
+        }
+        const videoEle = item.videoEle
+        const nextVideo = nextItem.videoEle
+        if (videoEle && videoEle.duration < 8 && index < nextIndex) {
+          activeData.isAnimation = false
+        } else if (nextVideo && nextVideo.duration < 8 && index > nextIndex) {
+          activeData.isAnimation = false
+        } else {
+          activeData.isAnimation = true
+        }
+      } else {
+        activeData.isAnimation = item?.adviseStudyTimeSecond < 8 ? false : true
+      }
+    }
+
+    // 上一个知识点, 下一个知识点
+    const handlePreAndNext = (type: string) => {
+      if (type === 'up') {
+        handleSwipeChange(popupData.activeIndex - 1)
+      } else {
+        handleSwipeChange(popupData.activeIndex + 1)
+      }
+    }
+
+    /** 弹窗关闭 */
+    const handleClosePopup = () => {
+      const item = data.itemList[popupData.activeIndex]
+      if (item?.typeCode == 'VIDEO' && !item.videoEle?.paused) {
+        setModelOpen()
+      }
+    }
+
+    /** 教学数据 */
+    const studyData = reactive({
+      type: '' as ToolType,
+      penShow: false
+    })
+
+    /** 打开教学工具 */
+    const openStudyTool = (item: ToolItem) => {
+      const activeItem = data.itemList[popupData.activeIndex]
+      // 暂停视频和曲谱的播放
+      if (activeItem.typeCode === 'VIDEO' && activeItem.videoEle) {
+        activeItem.videoEle.pause()
+      }
+      if (activeItem.typeCode === 'SONG') {
+        activeItem.iframeRef?.contentWindow?.postMessage(
+          { api: 'setPlayState' },
+          '*'
+        )
+      }
+      clearModel()
+      popupData.toolOpen = false
+      studyData.type = item.type
+
+      switch (item.type) {
+        case 'pen':
+          studyData.penShow = true
+          break
+      }
+    }
+
+    /** 关闭教学工具 */
+    const closeStudyTool = () => {
+      studyData.type = 'init'
+      toggleModel()
+    }
+
+    const activeVideoItem = computed(() => {
+      const item = data.itemList[popupData.activeIndex]
+      if (
+        item &&
+        item.typeCode &&
+        item.typeCode.toLocaleUpperCase() === 'VIDEO'
+      ) {
+        return item
+      }
+      return {}
+    })
+    let closeModelTimer: any = null
+    return () => (
+      <div id="playContent" class={styles.playContent}>
+        <div
+          class={styles.coursewarePlay}
+          style={{ width: parentContainer.width }}
+          onClick={() => {
+            clearTimeout(closeModelTimer)
+            clearTimeout(activeData.timer)
+            Toast.clear()
+            if (Date.now() - activeData.nowTime < 300) {
+              handleDbClick()
+              return
+            }
+            activeData.nowTime = Date.now()
+            closeModelTimer = setTimeout(() => {
+              activeData.model = !activeData.model
+            }, 300)
+          }}
+        >
+          <div class={styles.wraps}>
+            <div
+              style={
+                activeVideoItem.value.typeCode &&
+                data.animationState === 'end' &&
+                data.videoState === 'play'
+                  ? {
+                      zIndex: 15,
+                      opacity: 1
+                    }
+                  : { opacity: 0, zIndex: -1 }
+              }
+              class={styles.itemDiv}
+            >
+              {/* <VideoItem
+                ref={(el: any) => (data.videoItemRef = el)}
+                item={activeVideoItem.value}
+                activeModel={activeData.model}
+                onClose={setModelOpen}
+                onPlay={() => {
+                  data.videoState = 'play';
+                }}
+                onPause={() => {
+                  clearTimeout(activeData.timer);
+                  activeData.model = true;
+                }}
+                onEnded={() => {
+                  const _index = popupData.activeIndex + 1;
+                  if (_index < data.itemList.length) {
+                    handleSwipeChange(_index);
+                  }
+                }}
+              /> */}
+              <VideoPlay
+                ref={(el: any) => (data.videoItemRef = el)}
+                item={activeVideoItem.value}
+                activeModel={activeData.model}
+                onPlay={() => {
+                  data.videoState = 'play'
+                  data.animationState = 'end'
+                }}
+                onLoadedmetadata={(videoItem: any) => {
+                  data.videoState = 'play'
+                  activeVideoItem.value.videoEle = videoItem
+                  if (!activeVideoItem.value.isprepare) {
+                    activeVideoItem.value.isprepare = true
+                  }
+                }}
+                onPause={() => {
+                  clearTimeout(activeData.timer)
+                  activeData.model = true
+                }}
+                onEnded={async () => {
+                  const _index = popupData.activeIndex + 1
+                  if (_index < data.itemList.length) {
+                    handleSwipeChange(_index)
+                  }
+                }}
+                onError={() => {
+                  // 视屏异常
+                  activeVideoItem.value.error = true
+                }}
+              />
+            </div>
+            {data.itemList.map((m: any, mIndex: number) => {
+              const isRenderItem = Math.abs(popupData.activeIndex - mIndex) < 2
+              const isRender = Math.abs(popupData.playIndex - mIndex) < 2
+              // 判断是否是当前选中的元素
+              const activeEle = popupData.playIndex === mIndex ? true : false
+
+              return isRenderItem ? (
+                <div
+                  key={'index' + mIndex}
+                  data-id={'data' + mIndex}
+                  class={[
+                    styles.itemDiv,
+                    activeEle && styles.itemActive,
+                    activeData.isAnimation && styles.acitveAnimation,
+                    isRenderItem ? styles.show : styles.hide
+                  ]}
+                  style={
+                    mIndex < popupData.activeIndex
+                      ? effects[effectIndex.value].prev
+                      : mIndex > popupData.activeIndex
+                      ? effects[effectIndex.value].next
+                      : {}
+                  }
+                >
+                  {/* {m.type === 'VIDEO' && (
+                      <>
+                        <VideoPlay
+                          ref={(v: any) => (data.videoRefs[mIndex] = v)}
+                          item={m}
+                          isActive={activeEle}
+                          isEmtry={isEmtry}
+                          onPrepare={(val) => {
+                            m.isprepare = val
+                          }}
+                          onLoadedmetadata={(videoItem: any) => {
+                            m.videoEle = videoItem
+                          }}
+                          onTogglePlay={(paused: boolean) => {
+                            // console.log('播放切换', paused)
+                            if (!m.isprepare) {
+                              m.isprepare = true
+                            }
+                            m.autoPlay = false
+                            if (paused || popupData.open || popupData.guideOpen) {
+                              clearTimeout(activeData.timer)
+                            } else {
+                              setModelOpen()
+                            }
+                          }}
+                          onEnded={() => {
+                            const _index = popupData.activeIndex + 1
+                            if (_index < data.itemList.length) {
+                              handleSwipeChange(_index)
+                            }
+                          }}
+                          onReset={() => {
+                            if (!m.videoEle?.paused) {
+                              setModelOpen()
+                            }
+                          }}
+                        />
+                        <Transition name="van-fade">
+                          {!m.isprepare && (
+                            <div class={styles.loadWrap}>
+                              <Vue3Lottie animationData={playLoadData}></Vue3Lottie>
+                            </div>
+                          )}
+                        </Transition>
+                      </>
+                    )} */}
+                  <Transition name="van-fade">
+                    {m.typeCode === 'VIDEO' &&
+                      data.animationState !== 'end' &&
+                      data.videoState != 'play' && (
+                        <div class={styles.loadWrap}>
+                          <Vue3Lottie animationData={playLoadData}></Vue3Lottie>
+                        </div>
+                      )}
+                  </Transition>
+                  {isRender && m.typeCode === 'IMG' && <img src={m.content} />}
+                  {isRender && m.typeCode === 'SONG' && (
+                    <MusicScore
+                      activeModel={activeData.model}
+                      data-vid={m.id}
+                      music={m}
+                      onSetIframe={(el: any) => {
+                        m.iframeRef = el
+                      }}
+                    />
+                  )}
+                </div>
+              ) : (
+                ''
+              )
+            })}
+          </div>
+          <Transition name="right">
+            {activeData.model && (
+              <div
+                class={styles.rightFixedBtns}
+                onClick={(e: Event) => {
+                  e.stopPropagation()
+                  clearTimeout(activeData.timer)
+                }}
+              >
+                <div class={styles.btnsWrap}>
+                  <div
+                    class={[styles.fullBtn, styles.point]}
+                    onClick={() => (popupData.open = true)}
+                  >
+                    <img src={iconMenu} />
+                    <span>知识点</span>
+                  </div>
+                </div>
+
+                <div class={[styles.btnsWrap, styles.btnsBottom]}>
+                  <div
+                    class={styles.fullBtn}
+                    onClick={() => (popupData.guideOpen = true)}
+                  >
+                    <img src={iconTouping} />
+                    <span>投屏</span>
+                  </div>
+                  {data.isCourse && (
+                    <>
+                      <div
+                        class={styles.fullBtn}
+                        onClick={() => gotoRollCall('student_roll_call')}
+                      >
+                        <img src={iconDian} />
+                        <span>点名</span>
+                      </div>
+                      <div
+                        class={styles.fullBtn}
+                        onClick={() => gotoRollCall('sign_out')}
+                      >
+                        <img src={iconPoint} />
+                        <span>签退</span>
+                      </div>
+                    </>
+                  )}
+                </div>
+              </div>
+            )}
+          </Transition>
+
+          <Transition name="left">
+            {activeData.model && (
+              <div
+                class={styles.leftFixedBtns}
+                onClick={(e: Event) => e.stopPropagation()}
+              >
+                {popupData.activeIndex != 0 && (
+                  <div class={[styles.btnsWrap, styles.prePoint]}>
+                    <div
+                      class={styles.fullBtn}
+                      onClick={() => {
+                        // useThrottleFn(() => {
+                        //   handlePreAndNext('up')
+                        // }, 300)
+                        // onChangeSwiper('up')
+                        handlePreAndNext('up')
+                      }}
+                    >
+                      <img src={iconUp} />
+                      <span style={{ textAlign: 'center' }}>上一个</span>
+                    </div>
+                  </div>
+                )}
+                {popupData.activeIndex != data.itemList.length - 1 && (
+                  <div class={styles.btnsWrap}>
+                    <div
+                      class={styles.fullBtn}
+                      onClick={() => {
+                        // console.log('click down')
+                        // useThrottleFn(() => {
+                        //   console.log('click down pass')
+                        //   handlePreAndNext('down')
+                        // }, 300)
+                        // onChangeSwiper('down')
+                        handlePreAndNext('down')
+                      }}
+                    >
+                      <span style={{ textAlign: 'center' }}>下一个</span>
+                      <img src={iconDown} />
+                    </div>
+                  </div>
+                )}
+              </div>
+            )}
+          </Transition>
+        </div>
+
+        <div
+          style={{ transform: activeData.model ? '' : 'translateY(-100%)' }}
+          id="coursePlayHeader"
+          class={styles.headerContainer}
+          ref={headeRef}
+        >
+          <div class={styles.backBtn} onClick={() => goback()}>
+            <Icon name={iconBack} />
+            返回
+          </div>
+          {data.isCourse && (
+            <PlayRecordTime ref={playRef} list={data.knowledgePointList} />
+          )}
+          <div
+            class={styles.menu}
+            onClick={() => {
+              const _effectIndex = effectIndex.value + 1
+              effectIndex.value =
+                _effectIndex >= effects.length - 1 ? 0 : _effectIndex
+              setModelOpen()
+            }}
+          >
+            {popupData.tabName}
+          </div>
+
+          {state.platformType === 'TEACHER' && (
+            <div
+              class={styles.headRight}
+              onClick={(e: Event) => {
+                e.stopPropagation()
+                clearTimeout(activeData.timer)
+              }}
+            >
+              {/* <div
+                class={styles.rightBtn}
+                onClick={() => (popupData.guideOpen = true)}
+              >
+                <img src={iconTouping} />
+              </div> */}
+              {/* <div
+                class={styles.rightBtn}
+                onClick={() => {
+                  openStudyTool({
+                    type: 'pen',
+                    icon: iconPen,
+                    name: '批注'
+                  });
+                }}>
+                <img src={iconPen} />
+              </div> */}
+              {/* <div class={styles.rightBtn} onClick={() => (popupData.toolOpen = true)}>
+                <img src={iconMore} />
+              </div> */}
+            </div>
+          )}
+        </div>
+
+        {/* 更多弹窗 */}
+        <Popup
+          class={styles.popupMore}
+          overlayClass={styles.overlayClass}
+          position="right"
+          round
+          v-model:show={popupData.toolOpen}
+          onClose={handleClosePopup}
+        >
+          <Tool onHandleTool={openStudyTool} />
+        </Popup>
+
+        <Popup
+          class={styles.popup}
+          style={{ background: 'rgba(0,0,0, 0.7)' }}
+          overlayClass={styles.overlayClass}
+          position="right"
+          round
+          v-model:show={popupData.open}
+          onClose={handleClosePopup}
+        >
+          <Points
+            data={data.knowledgePointList}
+            tabActive={popupData.tabActive}
+            itemActive={popupData.itemActive}
+            onHandleSelect={(res: any) => {
+              // onChangeSwiper('change', res.itemActive)
+              popupData.open = false
+              toggleMaterial(res.itemActive)
+            }}
+          />
+        </Popup>
+
+        <Popup
+          class={styles.popup}
+          overlayClass={styles.overlayClass}
+          position="right"
+          round
+          v-model:show={popupData.guideOpen}
+          onClose={handleClosePopup}
+        >
+          <OGuide />
+        </Popup>
+
+        {studyData.penShow && (
+          <Pen show={studyData.type === 'pen'} close={() => closeStudyTool()} />
+        )}
+      </div>
+    )
+  }
+})

+ 128 - 0
src/tenant/music/coursewarePlay/playRecordTime.tsx

@@ -0,0 +1,128 @@
+import request from '@/helpers/request';
+import { getSecondRPM } from '@/helpers/utils';
+import { state } from '@/state';
+import { usePageVisibility } from '@vant/use';
+import {
+  computed,
+  defineComponent,
+  onMounted,
+  onUnmounted,
+  reactive,
+  ref,
+  watch
+} from 'vue';
+import { useRoute } from 'vue-router';
+import styles from './index.module.less';
+
+export default defineComponent({
+  name: 'playRecordTime',
+  props: {
+    list: {
+      type: Array,
+      default: () => []
+    }
+  },
+  setup(props, { expose }) {
+    const pageVisibility = usePageVisibility();
+
+    watch(pageVisibility, value => {
+      if (value == 'hidden') {
+        handleOut();
+      } else {
+        // 页面显示
+        handleStartInterval();
+      }
+    });
+    const handleOut = () => {
+      clearInterval(timerRecord.value);
+      handleRecord(true);
+    };
+    expose({
+      handleOut
+    });
+    const route = useRoute();
+    const saveModel = reactive({
+      loading: true,
+      /**当前时长 */
+      currentTime: 0,
+      /**记录的开始时间 */
+      startTime: 0,
+      timer: null as any,
+      /** 已经播放的时间 */
+      playTime: 0
+    });
+    /** 建议学习总时长 */
+    const total = computed(() => {
+      const _total = props.list.reduce(
+        (_total: number, item: any) =>
+          _total + (item.totalMaterialTimeSecond || 0),
+        0
+      );
+      return _total;
+    });
+
+    const getPlayTime = async () => {
+      saveModel.loading = true;
+      try {
+        const res: any = await request.post(
+          `${state.platformApi}/courseSchedule/getCoursewarePlayTime?courseScheduleId=${route.query.courseId}`
+        );
+        if (res.data) {
+          saveModel.playTime = res.data;
+        }
+      } catch (error) {
+        //
+      }
+      saveModel.loading = false;
+      handleStartInterval();
+    };
+
+    /** 记录时长 */
+    const handleRecord = (isOut = false) => {
+      saveModel.currentTime++;
+      const playTime = saveModel.currentTime - saveModel.startTime;
+      // 1分钟记录1次
+      if (playTime >= 5 || isOut) {
+        console.log('isOut', isOut);
+        saveModel.startTime = saveModel.currentTime;
+        request.post(`${state.platformApi}/courseSchedule/coursewarePlayTime`, {
+          params: {
+            courseScheduleId: route.query.courseId,
+            playTime
+          },
+          hideLoading: true
+        });
+      }
+    };
+    const timerRecord = ref();
+    const handleStartInterval = () => {
+      clearInterval(timerRecord.value);
+      timerRecord.value = setInterval(() => handleRecord(), 1000);
+    };
+    onMounted(() => {
+      getPlayTime();
+    });
+    onUnmounted(() => {
+      clearInterval(timerRecord.value);
+    });
+    return () => (
+      <div
+        class={styles.playRecordTimeWrap}
+        style={{
+          display:
+            saveModel.loading ||
+            saveModel.currentTime + saveModel.playTime > total.value
+              ? 'none'
+              : ''
+        }}>
+        <div class={styles.playRecordTime}>
+          <div class={styles.timeLoad}></div>
+          <div>
+            {getSecondRPM(saveModel.currentTime + saveModel.playTime)} /{' '}
+            {getSecondRPM(total.value)}
+          </div>
+        </div>
+      </div>
+    );
+  }
+});

二进制
src/tenant/music/lessonCourseware/component/CourseItem/image/icon-lock.png


二进制
src/tenant/music/lessonCourseware/component/CourseItem/image/icon-top.png


+ 138 - 0
src/tenant/music/lessonCourseware/component/CourseItem/index.module.less

@@ -0,0 +1,138 @@
+.wrap {
+  position: relative;
+  background: rgba(255, 255, 255, 0.6);
+  border-radius: 10px;
+  border: 2px solid #FFFFFF;
+  min-height: 200px;
+  display: flex;
+  flex-wrap: wrap;
+  // padding-top: 22px;
+  padding-bottom: 20px;
+  box-sizing: border-box;
+
+  div {
+    box-sizing: border-box;
+  }
+
+  .icon {
+    position: absolute;
+    left: 50%;
+    top: -8px;
+    width: 126px;
+    padding: 5px 10px;
+    transform: translate(-50%, 0);
+    z-index: 10;
+    background-image: url('./image/icon-top.png');
+    background-repeat: no-repeat;
+    background-size: contain;
+    text-align: center;
+    color: #fff;
+    font-size: 16px;
+    font-weight: 500;
+    line-height: 22px;
+
+    img {
+      display: block;
+      width: 100%;
+      height: 100%;
+    }
+  }
+}
+
+.item {
+  margin: 20px 0 0 0;
+  padding: 0 8px;
+  width: 33.333%;
+
+  .cover {
+    position: relative;
+    border-radius: 2px;
+    overflow: hidden;
+    margin-bottom: 8px;
+    box-shadow: 0px 2px 6px 0px rgba(0, 0, 0, 0.2);
+  }
+
+  .model {
+    position: absolute;
+    left: 0;
+    top: 0;
+    right: 0;
+    bottom: 0;
+    background-color: rgba(0, 0, 0, 0.4);
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    color: #fff;
+    font-size: 12px;
+    line-height: 18px;
+
+    img {
+      width: 12px;
+      height: 12px;
+      margin-right: 4px;
+    }
+  }
+
+  .coverNum {
+    position: absolute;
+    bottom: 12px;
+    left: 50%;
+    transform: translateX(-50%);
+    border-radius: 20px;
+    color: rgba(116, 44, 0, 1);
+    background-color: #fff;
+    padding: 4px 6px;
+    line-height: 1;
+    font-size: 12px;
+    z-index: 1;
+    white-space: nowrap;
+    word-break: break-all;
+    min-width: 50px;
+    text-align: center;
+  }
+
+  .coverImg {
+    width: 100%;
+    background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFoAAABaCAMAAAAPdrEwAAABOFBMVEUAAAC0tLTT09PU1NTt7e3////T09P////T09P////T09P////X19f////U1NTU1NTT09PT09PU1NTU1NTV1dXT09PU1NTU1NTV1dXZ2dn+/v7R0dH////////KysrIyMjT09O0tLTU1NS0tLTU1NS0tLTT09PT09PT09PU1NTU1NTU1NTT09O2trbU1NTV1dXV1dXW1tbV1dXW1tbV1dXJycm4uLj///+0tLT///+0tLT////p6em0tLT4+Pj///+2trb///+1tbX///+0tLT////T09P5+fn+/v63t7e8vLz8/Pzl5eXW1tbBwcH39/fw8PDs7Ozb29v7+/vg4ODOzs65ubny8vLJycnp6enGxsbFxcW+vr62trb09PTY2NjLy8vCwsLn5+fi4uLS0tLu7u7Q0NDd3d02Mu/gAAAARHRSTlMAnPwvBfv07ODdw5AbGezmyrirl5B4aT0kDZyclTwsIPj48+Ta1tPS0LGhiIaEcGJgV05FNhMS7++8vKyslZRzc1ZWJRSpe/cAAALfSURBVFjD7djXcuIwGAVg1pTQAiT0Ekiy6b1v7whsA6b3EGAJu+//BstaiWNiCYGlXGTG55KBDzH6j2xjMmJET5ZdDivQFavDtTxLXlsCFFlam7HmiUxl49ftApRxYWkHLe3A0lZa2oqlAXUMGk/fHG9vpCnSk/pZNJ18m6aOWELRN1CmtbMI+jjNJH0Evc2GlhD0Bhu6h6DTjPIq6Pqonb8XXoCWmjKSHTCnpccp5ges6bHCZAW2dBc8JcOWLqjoPFu6oqLLr2bVQxX9h/GElBWmKDCmxSKAyXWYt1GE677rLtLGejsLcuOCQMI7lVFlsMjxJJUeW3bP+OSTskBJqcuSFppAFT4vsqMrYDrVAitazMHV8kBJs8WGzsPmNoRCFSj5fcuA7vJKvcQ8UJKrCNT0WH0mdEpASbFOSdfhyClvzagGsSzR0D14LHTSSho1/mkQaw39dB9u2vThCU8KQj1JtFiVt+z5ONSLmnoSaeszuia/+lfzAaGS09STQDum6SH/cOXX5rY9Xz0Rj6Sqy0YmjUzrbp56ah6kIT2AH8Pe4M5TT83jP6ThsnALmq+eiD8tlIt/G8POWU8FVNMNuXc8rnH4epJpOHg1jIivJ5mWcvLg4ZqMryeZhnOLm1hyPfF0C95R9PAcvp4jcSYNL7UDHEWuJ56GO46FyPXE0+/+/7Lhgs+ffU093yPoDwCAEcki1/Mjgv4BQFUkSeR6/jQh8gn0SQy5np9NqFi+CgSDWE9rwmJCJ/X9yxt9iSdisVji2y+TESNGXjiW6LU7FYkkzQ9JRiIp93XUoodyX12cBg/9qyvOHbvXxmUw4Wxe+45zZdV/GDy9uHJbZqPhk7jTjrTI4ezO+EkY8wUBW4Y6tgCSPvNxtDLnOzOhEw2HAvt7Hj2oZ28/EApHyRtpvjwPBY/8B5Ot9O1u2b2eTds6x8nr4tZtmx6vfWvXN9nCA/9RMHR+aZ5sohEji+YfnN0/51L6d+cAAAAASUVORK5CYII=');
+    background-repeat: no-repeat;
+    background-position: center;
+    background-size: 50%;
+    border-radius: 2px;
+    overflow: hidden;
+
+    &>img {
+      display: block;
+      width: 100%;
+      height: 120px;
+      opacity: 0;
+      transition: opacity .3s;
+    }
+
+    :global {
+      .van-image__loading {
+        position: relative;
+        height: 120px;
+        animation: van-skeleton-blink var(--van-skeleton-duration) ease-in-out infinite;
+      }
+    }
+
+    &::before {
+      content: '';
+      position: absolute;
+      left: 5px;
+      width: 5px;
+      height: 100%;
+      background: linear-gradient(270deg, rgba(0, 0, 0, 0.25) 0%, rgba(0, 0, 0, 0.03) 100%);
+      box-shadow: 0px 2px 6px 0px rgba(0, 0, 0, 0.2);
+      z-index: 1;
+    }
+  }
+
+  .name {
+    width: 109%;
+    line-height: 18px;
+    font-weight: 400;
+    font-size: 13px;
+    color: #131415;
+  }
+}

文件差异内容过多而无法显示
+ 33 - 0
src/tenant/music/lessonCourseware/component/CourseItem/index.tsx


+ 13 - 0
src/tenant/music/lessonCourseware/image/look.svg

@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="13px" height="13px" viewBox="0 0 13 13" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <title>切片</title>
+    <g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="选择课件" transform="translate(-282.000000, -159.000000)">
+            <g id="锁备份" transform="translate(282.000000, 159.000000)">
+                <circle id="椭圆形" fill="#FFFFFF" cx="6.5" cy="8" r="1"></circle>
+                <rect id="矩形" stroke="#FFFFFF" stroke-width="1.2" x="1.6" y="4.1" width="9.8" height="7.8" rx="2"></rect>
+                <path d="M4.5,4 L4.5,3 C4.5,1.8954305 5.3954305,1 6.5,1 C7.6045695,1 8.5,1.8954305 8.5,3 L8.5,4 L8.5,4" id="路径" stroke="#FFFFFF" stroke-width="1.2"></path>
+            </g>
+        </g>
+    </g>
+</svg>

+ 116 - 0
src/tenant/music/lessonCourseware/index.module.less

@@ -0,0 +1,116 @@
+.sticky {
+  :global(.van-sticky--fixed) {
+    // box-shadow: 10px 10px 10px var(--box-shadow-color);
+  }
+
+  :global {
+    .van-sticky {
+      background: url('../../images/bg-image.png') no-repeat top center;
+      background-size: 100% 214px;
+    }
+
+    .van-search__content {
+      background: rgba(255, 255, 255, 0.5) !important;
+
+      input::placeholder {
+        color: rgba(0, 0, 0, 0.4) !important;
+      }
+
+      input {
+        color: rgba(0, 0, 0, 0.4) !important;
+      }
+
+      .van-field__clear {
+        color: rgba(0, 0, 0, 0.4) !important;
+      }
+    }
+
+    .van-dropdown-menu__bar {
+      background-color: transparent;
+      box-shadow: none;
+      padding-right: 15px;
+    }
+
+    .van-dropdown-menu__title {
+      padding-left: 0;
+    }
+
+    .van-dropdown-menu__title:after {
+      border-color: transparent transparent rgba(0, 0, 0, 0.4)
+        rgba(0, 0, 0, 0.4);
+    }
+
+    .van-dropdown-item__content {
+      border-radius: 0px 0px 20px 20px;
+    }
+
+    .van-haptics-feedback:active {
+      opacity: 1 !important;
+    }
+  }
+  .dropdownMenuSub {
+    :global {
+      .van-popup {
+        box-sizing: border-box;
+        padding: 12px;
+      }
+      .van-cell {
+        padding: 0;
+        height: 50px;
+        line-height: 50px;
+        color: #333333;
+        &:after {
+          display: none;
+        }
+        &.van-dropdown-item__option--active {
+          background-color: rgba(254, 36, 81, 0.08);
+          border-radius: 10px;
+          color: #fe2451;
+        }
+        .van-cell__title {
+          font-weight: 400;
+          font-size: 16px;
+          text-align: center;
+        }
+        .van-cell__value {
+          display: none;
+        }
+      }
+    }
+  }
+  .titleActive {
+    color: #fe2451;
+    :global(.van-ellipsis) {
+      max-width: 62px;
+    }
+  }
+}
+.bgImg {
+  position: fixed;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 214px;
+  // object-fit: cover;
+  z-index: -1;
+}
+
+.alumnList {
+  min-height: 40vh;
+}
+.courseItem.courseItem1 {
+  background: initial;
+  border: initial;
+  padding: 0 10px 20px;
+  :global {
+    .courseItem:nth-child(1) {
+      margin-top: 10px;
+    }
+    .courseItem:nth-child(2) {
+      margin-top: 10px;
+    }
+    .courseItem:nth-child(3) {
+      margin-top: 10px;
+    }
+  }
+}

+ 194 - 0
src/tenant/music/lessonCourseware/index.tsx

@@ -0,0 +1,194 @@
+import request from '@/helpers/request'
+import { state } from '@/state'
+import { Tab, Tabs, DropdownMenu, DropdownItem, List } from 'vant'
+import {
+  computed,
+  defineComponent,
+  onMounted,
+  reactive,
+  Transition,
+  ref
+} from 'vue'
+import styles from './index.module.less'
+import { useRouter } from 'vue-router'
+import ColHeader from '@/components/col-header'
+import TheSticky from '@/components/the-sticky'
+import bgImg from '../../images/bg-image.png'
+import Search from '@/components/col-search'
+import CourseItem from './component/CourseItem'
+import ColResult from '@/components/col-result'
+
+export default defineComponent({
+  name: 'lessonCourseware',
+  setup() {
+    const router = useRouter()
+    const data = reactive<{
+      subjectList: any[]
+    }>({
+      subjectList: [{}]
+    })
+    const loading = ref(false)
+    const finished = ref(false)
+    const isError = ref(false)
+    const subjectOptValue = ref('全部课件')
+    const subjectOpt = computed(() => {
+      const _list = data.subjectList.map((item: any) => {
+        return {
+          text: '全部全部课件11222',
+          value: '全部全部课件22222'
+        }
+      })
+      _list.unshift({
+        text: '全部课件',
+        value: '全部课件'
+      })
+      return [
+        ..._list,
+        {
+          text: '全部全部课件',
+          value: '全部全部课件'
+        },
+        {
+          text: '全2',
+          value: '全部2'
+        }
+      ]
+    })
+    function onSearch() {
+      console.log('onSearch')
+    }
+    function handleOnRefresh() {
+      console.log('handleOnRefresh')
+    }
+    function FetchList() {}
+    return () => {
+      return (
+        <>
+          <div class={styles.sticky}>
+            <TheSticky>
+              <ColHeader
+                hideHeader={false}
+                background="transparent"
+                isFixed={false}
+                border={false}
+                title={'云教程'}
+                color="#131415"
+              ></ColHeader>
+              <Search
+                onSearch={onSearch}
+                type="tenant"
+                placeholder={'请输入教材关键词'}
+                background="transparent"
+                inputBackground="transparent"
+                // leftIcon={iconSearch}
+                v-slots={{
+                  left: () => (
+                    <DropdownMenu class={styles.dropdownMenuSub}>
+                      <DropdownItem
+                        titleClass={styles.titleActive}
+                        title={subjectOptValue.value}
+                        v-model={subjectOptValue.value}
+                        options={subjectOpt.value}
+                        onChange={handleOnRefresh}
+                      >
+                        <div></div>
+                      </DropdownItem>
+                    </DropdownMenu>
+                  )
+                }}
+              />
+            </TheSticky>
+            <img class={styles.bgImg} src={bgImg} />
+          </div>
+
+          <div class={styles.alumnList}>
+            <List
+              // loading={loading.value}
+              finished={finished.value}
+              finished-text={''}
+              onLoad={FetchList}
+              error={isError.value}
+              immediateCheck={false}
+            >
+              {data.subjectList.length ? (
+                <CourseItem
+                  class={[styles.courseItem, styles.courseItem1]}
+                  list={[
+                    {
+                      coverImg:
+                        'https://oss.dayaedu.com/gyt/courseware/1709189798128.png',
+                      name: '2333333'
+                    },
+                    {
+                      coverImg:
+                        'https://oss.dayaedu.com/gyt/courseware/1709189798128.png',
+                      name: '2333333'
+                    },
+                    {
+                      coverImg:
+                        'https://oss.dayaedu.com/gyt/courseware/1709189798128.png',
+                      name: '2333333'
+                    },
+                    {
+                      coverImg:
+                        'https://oss.dayaedu.com/gyt/courseware/1709189798128.png',
+                      name: '2333333'
+                    },
+                    {
+                      coverImg:
+                        'https://oss.dayaedu.com/gyt/courseware/1709189798128.png',
+                      name: '2333333'
+                    },
+                    {
+                      coverImg:
+                        'https://oss.dayaedu.com/gyt/courseware/1709189798128.png',
+                      name: '2333333'
+                    },
+                    {
+                      coverImg:
+                        'https://oss.dayaedu.com/gyt/courseware/1709189798128.png',
+                      name: '2333333'
+                    },
+                    {
+                      coverImg:
+                        'https://oss.dayaedu.com/gyt/courseware/1709189798128.png',
+                      name: '2333333'
+                    },
+                    {
+                      coverImg:
+                        'https://oss.dayaedu.com/gyt/courseware/1709189798128.png',
+                      name: '2333333'
+                    },
+                    {
+                      coverImg:
+                        'https://oss.dayaedu.com/gyt/courseware/1709189798128.png',
+                      name: '2333333'
+                    },
+                    {
+                      coverImg:
+                        'https://oss.dayaedu.com/gyt/courseware/1709189798128.png',
+                      name: '2333333'
+                    },
+                    {
+                      coverImg: '',
+                      name: '2333333'
+                    }
+                  ]}
+                  onItemClick={row => console.log(row)}
+                />
+              ) : (
+                !loading.value && (
+                  <ColResult
+                    tips="暂无教程"
+                    classImgSize="SMALL"
+                    btnStatus={false}
+                  />
+                )
+              )}
+            </List>
+          </div>
+        </>
+      )
+    }
+  }
+})

+ 32 - 2
src/tenant/music/train-tool/index.tsx

@@ -35,6 +35,7 @@ import { Swiper, SwiperSlide } from 'swiper/vue'
 // Import Swiper styles
 import 'swiper/css'
 import 'swiper/css/pagination'
+import CourseItem from '../lessonCourseware/component/CourseItem'
 
 export default defineComponent({
   name: 'train-tool',
@@ -322,7 +323,6 @@ export default defineComponent({
         //
       }
     }
-
     return () => (
       <div class={styles.trainTool}>
         {!state.loading && !state.details.id && state.buy != '1' ? (
@@ -514,6 +514,7 @@ export default defineComponent({
                     {state.ensembleCounts && (
                       <Tab title="合奏练习" name="ENSEMBLE"></Tab>
                     )}
+                    <Tab title="云教程" name="COURSEWARE"></Tab>
                   </Tabs>
                 </Sticky>
 
@@ -546,9 +547,38 @@ export default defineComponent({
                         }}
                       />
                     ) : (
+                      // <CourseItem
+                      //   list={[
+                      //     {
+                      //       coverImg:
+                      //         'https://oss.dayaedu.com/gyt/courseware/1709189798128.png',
+                      //       name: '2333333'
+                      //     },
+                      //     {
+                      //       coverImg:
+                      //         'https://oss.dayaedu.com/gyt/courseware/1709189798128.png',
+                      //       name: '2333333'
+                      //     },
+                      //     {
+                      //       coverImg:
+                      //         'https://oss.dayaedu.com/gyt/courseware/1709189798128.png',
+                      //       name: '2333333'
+                      //     },
+                      //     {
+                      //       coverImg:
+                      //         'https://oss.dayaedu.com/gyt/courseware/1709189798128.png',
+                      //       name: '2333333'
+                      //     },
+                      //     {
+                      //       coverImg: '',
+                      //       name: '2333333'
+                      //     }
+                      //   ]}
+                      //   onItemClick={row => console.log(row)}
+                      // />
                       !state.loading && (
                         <ColResult
-                          tips="暂无曲目"
+                          tips="暂无曲目" //暂无教程
                           classImgSize="SMALL"
                           btnStatus={false}
                         />

部分文件因为文件数量过多而无法显示