瀏覽代碼

Merge branch 'feature-3.8' into online

TIANYONG 1 年之前
父節點
當前提交
dfc587f282
共有 52 個文件被更改,包括 2694 次插入186 次删除
  1. 80 0
      src/components/select-courseware-pop/index.module.less
  2. 67 0
      src/components/select-courseware-pop/index.tsx
  3. 二進制
      src/components/select-courseware-pop/selce_courseware_close.png
  4. 二進制
      src/components/select-courseware-pop/select_courseware_box.png
  5. 11 0
      src/helpers/utils.ts
  6. 14 0
      src/views/courseware-list/api.ts
  7. 68 8
      src/views/courseware-list/component/book/index.tsx
  8. 2 1
      src/views/courseware-play/component/chapter.module.less
  9. 21 4
      src/views/courseware-play/component/chapter.tsx
  10. 320 0
      src/views/courseware-play/component/instrument-info/index.module.less
  11. 295 0
      src/views/courseware-play/component/instrument-info/index.tsx
  12. 194 0
      src/views/courseware-play/component/play-item/index.module.less
  13. 268 0
      src/views/courseware-play/component/play-item/index.tsx
  14. 48 0
      src/views/courseware-play/component/play-loading/index.module.less
  15. 16 0
      src/views/courseware-play/component/play-loading/index.tsx
  16. 150 12
      src/views/courseware-play/component/point.module.less
  17. 273 9
      src/views/courseware-play/component/points.tsx
  18. 40 0
      src/views/courseware-play/component/tempo-item/index.module.less
  19. 74 0
      src/views/courseware-play/component/tempo-item/index.tsx
  20. 24 0
      src/views/courseware-play/component/theory/index.module.less
  21. 56 0
      src/views/courseware-play/component/theory/index.tsx
  22. 6 10
      src/views/courseware-play/image/chapter-default-arrow.svg
  23. 13 0
      src/views/courseware-play/image/chapter-default-arrow2.svg
  24. 7 9
      src/views/courseware-play/image/chapter-down-arrow.svg
  25. 二進制
      src/views/courseware-play/image/icon-pan.png
  26. 二進制
      src/views/courseware-play/image/icon_next.png
  27. 二進制
      src/views/courseware-play/image/icon_pause.png
  28. 二進制
      src/views/courseware-play/image/icon_play.png
  29. 二進制
      src/views/courseware-play/image/icon_pre.png
  30. 二進制
      src/views/courseware-play/image/ins-empty-icon.png
  31. 二進制
      src/views/courseware-play/image/music_bg.png
  32. 二進制
      src/views/courseware-play/image/music_pause_icon.png
  33. 二進制
      src/views/courseware-play/image/music_play_icon.png
  34. 二進制
      src/views/courseware-play/image/plh.png
  35. 二進制
      src/views/courseware-play/image/song_icon.png
  36. 二進制
      src/views/courseware-play/image/title_icon1.png
  37. 二進制
      src/views/courseware-play/image/title_icon2.png
  38. 二進制
      src/views/courseware-play/image/zy_audio_icon.png
  39. 二進制
      src/views/courseware-play/image/zy_img_icon.png
  40. 二進制
      src/views/courseware-play/image/zy_more_icon.png
  41. 二進制
      src/views/courseware-play/image/zy_ppt_icon.png
  42. 二進制
      src/views/courseware-play/image/zy_song_icon.png
  43. 二進制
      src/views/courseware-play/image/zy_title_icon.png
  44. 二進制
      src/views/courseware-play/image/zy_toggle_icon.png
  45. 二進制
      src/views/courseware-play/image/zy_vedio_icon.png
  46. 4 0
      src/views/courseware-play/index.module.less
  47. 397 95
      src/views/courseware-play/index.tsx
  48. 47 6
      src/views/tempo-practice/index.module.less
  49. 181 24
      src/views/tempo-practice/index.tsx
  50. 5 3
      src/views/tempo-practice/setting-modal/index.module.less
  51. 11 4
      src/views/tempo-practice/setting-modal/index.tsx
  52. 2 1
      vite.config.ts

+ 80 - 0
src/components/select-courseware-pop/index.module.less

@@ -0,0 +1,80 @@
+.popBox {
+    position: fixed;
+    left: 0;
+    top: 0;
+    width: 100vw;
+    height: 100vh;
+    background: rgba(0, 0, 0, 0.7);
+    z-index: 3000;
+}
+
+.popBody {
+    width: 290px;
+    height: 256px;
+    position: absolute;
+    left: 50%;
+    top: 50%;
+    transform: translate(-50%,-50%);
+    z-index: 200;
+    .popBg {
+        position: absolute;
+        left: 0;
+        top: 0;
+        width: 290px;
+        height: 256px;
+    }
+    .popClose {
+        position: absolute;
+        top: 49px;
+        right: 3px;
+        width: 24px;
+        height: 25px;
+    }
+    .list {
+        height: 110px;
+        width: 258px;
+        margin-top: 117px;
+        margin-left: 17px;
+        display: flex;
+        flex-direction: column;
+        position: relative;
+        overflow-y: scroll;
+        overflow-x: hidden;
+        &::-webkit-scrollbar
+        {
+            width:4px;
+            height:4px;
+            background-color: transparent;
+        }
+        &::-webkit-scrollbar-track
+        {
+            //-webkit-box-shadow:inset 0 0 6px rgba(0,0,0,0.3);
+            border-radius:10px;
+            background-color: transparent;
+        }
+        &::-webkit-scrollbar-thumb
+        {
+            border-radius:10px;
+            //-webkit-box-shadow:inset 0 0 6px rgba(0,0,0,.3);
+            background-color:#CBCBCB;
+        }      
+        >li {
+            width: 249px;
+            box-sizing: border-box;
+            height: 34px;
+            min-height: 34px;
+            line-height: 34px;
+            padding: 0 14px;
+            font-size: 14px;
+            color: #777777;
+            margin-bottom: 8px;
+            text-overflow: ellipsis;
+            white-space: nowrap;
+            &.active {
+                color: #0F75BB;
+                background: rgba(200, 235, 245, 1);
+                border-radius: 6px;
+            }
+        }
+    }
+}

+ 67 - 0
src/components/select-courseware-pop/index.tsx

@@ -0,0 +1,67 @@
+import { defineComponent, ref, reactive, onMounted } from 'vue';
+import styles from './index.module.less';
+import popBox from './select_courseware_box.png';
+import popClose from './selce_courseware_close.png';
+
+export default defineComponent({
+  name: 'SelectCoursewarePop',
+  props: {
+    list: {
+      type: Array,
+      default: () => []
+    },
+    kjId: {
+      type: String,
+      default: '',
+    }
+  },
+  emits: ['close', 'select'],
+  setup(props, { emit }) {
+    const data = reactive({
+      actIdx: null as any,
+    });
+    const selectItem = (item: any, index: any) => {
+      data.actIdx = index
+      if (props.kjId) {
+        emit('select', item)
+        data.actIdx = null
+      } else {
+        setTimeout(() => {
+          emit('select', item)
+          data.actIdx = null
+        }, 300);
+      }
+    }
+    const initScroll = () => {
+      const listDom = document.getElementById('list')
+      let idx = props.list.findIndex((item: any) => item.id === props?.kjId) || 0
+      listDom?.scrollTo({
+        top: idx * 40,
+        behavior: "smooth",
+      }); 
+    }
+    onMounted(() => {
+      initScroll()
+    });
+    return () => (
+      <div class={styles.popBox} 
+      onClick={(e: Event) => e.stopPropagation()}
+      onTouchmove={(e: TouchEvent) => e.stopPropagation() }
+      >
+        <div class={styles.popBody}>
+          <img class={styles.popBg} src={popBox} />
+          <img class={styles.popClose} src={popClose} onClick={(e: Event) => {
+            emit('close');
+        }} />
+          <ul class={styles.list} id="list">
+            {props.list.map((item: any, index: number) => {
+              return (
+                <li class={(data.actIdx === index || props.kjId === item.id) ? styles.active : ''} onClick={() => selectItem(item, index)}>{item.name}</li>
+              );
+            })}
+          </ul>
+        </div>
+      </div>
+    );
+  }
+});

二進制
src/components/select-courseware-pop/selce_courseware_close.png


二進制
src/components/select-courseware-pop/select_courseware_box.png


+ 11 - 0
src/helpers/utils.ts

@@ -528,3 +528,14 @@ export const sortMusical = (name: string, index: number) => {
   }
   return sortId;
 };
+
+
+/** debounce */
+export const debounce = (fn: Function, ms = 0) => {
+	let timeoutId: number | undefined;
+	return function(...args: any[]) {
+	  clearTimeout(timeoutId)
+	  // @ts-ignore
+	  timeoutId = setTimeout(() => fn.apply(this, args), ms);
+	}
+}

+ 14 - 0
src/views/courseware-list/api.ts

@@ -68,3 +68,17 @@ export const api_subjectList = (params: any) => {
     data: params
   });
 };
+
+/** 课件章节详情(全部教材) */
+export const api_lessonDetailCourseware = (params: any) => {
+  return request.post('/edu-app/lessonCoursewareKnowledgeDetail/detailCourseware', {
+    data: params
+  });
+};
+
+/** 课件章节详情(课程教材) */
+export const api_classDetailCourseware = (params: any) => {
+  return request.post('/edu-app/classLessonCourseware/classKnowledge', {
+    data: params
+  });
+};

+ 68 - 8
src/views/courseware-list/component/book/index.tsx

@@ -19,6 +19,12 @@ import CoursewareDetail from '@/custom-plugins/guide-page/courseware-detail';
 import { usePageVisibility } from '@vant/use';
 import { state } from '@/state';
 import TheNoticeBar from '@/components/the-noticeBar';
+import {
+  api_lessonDetailCourseware,
+  api_classDetailCourseware
+} from '../../api';
+import SelectCoursewarePop from '@/components/select-courseware-pop';
+
 export default defineComponent({
   name: 'the-book',
   props: {
@@ -48,6 +54,7 @@ export default defineComponent({
     const router = useRouter();
     console.log(state.user.data.phone);
     const lastTimeKey = 'lastTime' + (state?.user?.data?.phone ?? '');
+    const debounceSkip = ref(false);
     const data = reactive({
       show: false,
       width: 0,
@@ -55,8 +62,10 @@ export default defineComponent({
       transform: '',
       list: [] as any[][],
       lastTime: localStorage.getItem(lastTimeKey),
-      isClick: false
+      isClick: false,
+      coursewareList: [] as any
     });
+    const showSelectCourseware = ref(false);
     const showGuide = ref(false);
     const isend = ref(false);
     const step = ref(0);
@@ -275,21 +284,62 @@ export default defineComponent({
         }
       }
     );
-    const handleOpenPlay = (item: any) => {
+    // 检测有几个课件
+    const checkCourseware = async (item: any) => {
       if (item.id) {
         if (!item.containMaterial) {
           showToast('暂无资源');
           return;
         }
+
+        if (item.coursewareNum) {
+          try {
+            const res =
+              props.tab == 'all'
+                ? await api_lessonDetailCourseware({
+                    lessonCoursewareKnowledgeDetailId: item.id
+                  })
+                : await api_classDetailCourseware({
+                    lessonCoursewareKnowledgeDetailId: item.id
+                  });
+            if (res?.code == 200 && res.data?.length) {
+              // console.log(res.data)
+              res.data.forEach((n: any) => {
+                n.coursewareDetailKnowledgeId =
+                  n.coursewareDetailKnowledgeId || item.id;
+                n.lessonCoursewareId = item.lessonCoursewareId;
+                n.lessonCoursewareDetailId = item.lessonCoursewareDetailId;
+                n.zjName = item.name; // 章节name
+              });
+              data.coursewareList = res.data;
+              // 如果只有一个课件,直接进入该课件
+              if (res.data.length == 1) {
+                handleOpenPlay(res.data[0]);
+              } else {
+                // 如果有多个课件,需要选择一个课件进入上课页面
+                showSelectCourseware.value = true;
+              }
+            }
+          } catch {
+            //
+          }
+        }
+      }
+    };
+    const handleOpenPlay = async (item: any) => {
+      if (item.id) {
+        if( debounceSkip.value ) return;
+        debounceSkip.value = true;
         localStorage.setItem(lastTimeKey, item.id);
         const query = queryString.stringify({
-          id: item.id,
+          id: item.id, // 课件id
           lessonCoursewareId: item.lessonCoursewareId,
           courseId: props.bookData.id,
           lessonCoursewareDetailId: item.lessonCoursewareDetailId,
-          name: item.name,
+          name: item.zjName,
           subjectId: props.subjectId,
-          tab: props.tab // 当前切换的是哪个类型
+          tab: props.tab, // 当前切换的是哪个类型
+          coursewareDetailKnowledgeId: item.coursewareDetailKnowledgeId // 章节id
         });
         const url =
           location.origin + location.pathname + '#/courseware-play?' + query;
@@ -311,10 +361,12 @@ export default defineComponent({
         //     lessonCoursewareId: item.lessonCoursewareId,
         //     courseId: props.bookData.id,
         //     lessonCoursewareDetailId: item.lessonCoursewareDetailId,
-        //     name: item.name,
-        //     tab: props.tab
+        //     name: item.zjName,
+        //     tab: props.tab,
+        //     coursewareDetailKnowledgeId: item.coursewareDetailKnowledgeId
         //   }
         // });
+        debounceSkip.value = false;
       }
     };
 
@@ -374,7 +426,7 @@ export default defineComponent({
                                 }}
                                 onClick={(e: Event) => {
                                   e.stopPropagation();
-                                  handleOpenPlay(item);
+                                  checkCourseware(item);
                                 }}
                                 onTouchend={(e: TouchEvent) => {
                                   console.log(e);
@@ -436,6 +488,14 @@ export default defineComponent({
           )}
         </div>
         {/* {showGuide.value ? <CoursewareDetail onChangeShowGuide={changeShowGuide} ref={CoursewareDetailRef}></CoursewareDetail> : null} */}
+        {showSelectCourseware.value && (
+          <SelectCoursewarePop
+            list={data.coursewareList}
+            onClose={() => {
+              showSelectCourseware.value = false;
+            }}
+            onSelect={item => handleOpenPlay(item)}></SelectCoursewarePop>
+        )}
       </div>
     );
   }

+ 2 - 1
src/views/courseware-play/component/chapter.module.less

@@ -16,6 +16,7 @@
   padding: 16px 10px 12px 13px;
   flex-shrink: 0;
   font-size: 14px;
+  font-weight: 500;
 
   img {
     width: 16px;
@@ -82,7 +83,7 @@
   }
 
   .activeItem {
-    background: #ECF8FF;
+    background: #F5F6FA;
     border-radius: 9px;
 
     :global {

+ 21 - 4
src/views/courseware-play/component/chapter.tsx

@@ -1,4 +1,4 @@
-import { defineComponent, reactive, toRefs, watch } from 'vue';
+import { defineComponent, reactive, toRefs, watch, ref } from 'vue';
 import styles from './chapter.module.less';
 import iconMenuChapter from '../image/icon-menu-chapter.svg';
 import { Collapse, CollapseItem, Icon, Image, showToast } from 'vant';
@@ -21,14 +21,22 @@ export default defineComponent({
     active: {
       type: String,
       default: ''
-    }
+    },
+    popShow: {
+      type: Boolean,
+      default: false
+    },
   },
   emits: ['handleSelect'],
   setup(props, { emit }) {
     const { detail, itemActive, active } = toRefs(props);
+    // console.log(6666,itemActive.value,active.value,detail.value)
     const pointData = reactive({
-      active: active.value
+      active: active.value,
+      coursewareList: [],
+      parentItem: {},
     });
+    const showSelectCourseware = ref(false);
 
     watch(
       () => props.itemActive,
@@ -37,6 +45,14 @@ export default defineComponent({
         pointData.active = active.value;
       }
     );
+    watch(
+      () => props.popShow,
+      () => {
+        if (props.popShow) {
+          pointData.active = props.active
+        }
+      }
+    );
     return () => (
       <div class={styles.container}>
         <div class={styles.pointHead}>
@@ -86,6 +102,7 @@ export default defineComponent({
                             emit('handleSelect', {
                               itemActive: know.id,
                               itemName: know.name,
+                              coursewareNum: know.coursewareNum,
                               tabActive: item.id,
                               tabName: item.name
                             });
@@ -115,7 +132,7 @@ export default defineComponent({
               </CollapseItem>
             ))}
           </Collapse>
-        </div>
+        </div>        
       </div>
     );
   }

+ 320 - 0
src/views/courseware-play/component/instrument-info/index.module.less

@@ -0,0 +1,320 @@
+.knowledgeBg {
+  width: 100%;
+  height: 100%;
+  background: #DDF2FF;
+  padding: 16px;
+  display: flex;
+  transition: all .2s ease;
+
+  &.wrapBottom {
+    padding-bottom: 70px;
+    transition: all .2s ease;
+  }
+
+  .left {
+    width: 180px;
+    height: 100%;
+    display: flex;
+    flex-direction: column;
+    margin-right: 10px;
+    border-radius: 8px;
+    background: #fff;
+    padding: 16px 8px 12px;
+
+    .insTop {
+      display: flex;
+      flex-direction: column;
+      align-items: center;
+      padding-bottom: 8px;
+      border-bottom: 1px solid #F2F2F2;
+
+      :global {
+        .van-notice-bar {
+          padding: 0;
+        }
+      }
+
+      >img {
+        width: 54px;
+        height: 54px;
+        border-radius: 50%;
+        margin-bottom: 8px;
+      }
+
+      .musician {
+        width: 48px;
+        height: 60px;
+        overflow: hidden;
+      }
+
+      .insName {
+        color: #131415;
+        font-size: 14px;
+        font-weight: 500;
+        margin-bottom: 2px;
+      }
+
+      .insTro {
+        color: #777777;
+        font-size: 12px;
+      }
+
+      .imgSection {
+
+        position: relative;
+        width: 61px;
+        height: 61px;
+        margin-top: 6px;
+
+        .img {
+          width: 61px;
+          height: 61px;
+          border-radius: 2px;
+          overflow: hidden;
+          position: relative;
+          z-index: 9;
+        }
+
+        &::before {
+          content: '';
+          position: absolute;
+          left: 0;
+          top: 0;
+          z-index: 10;
+          display: inline-block;
+          width: 4px;
+          height: 61px;
+          background: linear-gradient(270deg, rgba(0, 0, 0, 0.13) 0%, rgba(255, 255, 255, 0) 100%);
+        }
+
+        .pan {
+          content: '';
+          position: absolute;
+          top: 2px;
+          right: -16px;
+          display: inline-block;
+          width: 56px;
+          height: 56px;
+          background: url('../../image/icon-pan.png') no-repeat center;
+          background-size: contain;
+          display: flex;
+          align-items: center;
+          justify-content: center;
+
+          img {
+            width: 40px;
+            height: 40px;
+            border-radius: 50%;
+            overflow: hidden;
+          }
+        }
+      }
+
+      .songName {
+        margin-top: 4px;
+        font-size: 14px;
+        font-weight: 500;
+        color: #131415;
+        width: 100%;
+
+        :global {
+          .van-notice-bar__wrap {
+            padding: 0;
+            justify-content: center;
+          }
+        }
+      }
+
+      .songWords {
+        width: 100%;
+        font-size: 12px;
+        color: #777777;
+        overflow: hidden;
+        text-overflow: ellipsis;
+        white-space: nowrap;
+        text-align: center;
+
+        >span {
+          margin-right: 16px;
+        }
+      }
+    }
+
+    .songColumn {
+      display: flex;
+      align-items: center;
+      margin: 14px 8px 4px;
+
+      img {
+        width: 14px;
+        height: 14px;
+        margin-right: 4px;
+      }
+
+      span {
+        font-size: 12px;
+        color: #000000;
+        font-weight: 500;
+      }
+    }
+
+    .insList {
+      width: 104%;
+      padding-right: 4%;
+      flex: 1;
+      display: flex;
+      flex-direction: column;
+      overflow-y: scroll;
+
+      .li {
+        display: flex;
+        align-items: center;
+        padding: 8px 8px;
+        position: relative;
+        height: 40px;
+
+        :global {
+          .van-notice-bar {
+            padding: 0;
+          }
+        }
+
+        .liBg {
+          width: 24px;
+          height: 24px;
+          border-radius: 8px;
+          position: relative;
+          overflow: hidden;
+
+          >img {
+            width: 100%;
+            height: 100%;
+            border-radius: 8px;
+          }
+
+          .playingIcon {
+            position: absolute;
+            left: 50%;
+            top: 50%;
+            transform: translate(-50%, -50%);
+            width: 100%;
+            height: 100%;
+          }
+        }
+
+        .liName {
+          flex: 1;
+          margin: 0 10px;
+          overflow-x: hidden;
+          text-overflow: ellipsis;
+          white-space: nowrap;
+          font-size: 13px;
+          color: #131415;
+
+          :global {
+            .van-notice-bar__wrap {
+              padding: 0;
+              justify-content: left;
+            }
+          }
+        }
+
+        .liPlay {
+          width: 20px;
+          height: 20px;
+        }
+
+        .hidePlayLoading {
+          opacity: 0;
+          display: none;
+        }
+
+        &.liActive {
+          background: #E8F6FF;
+          border-radius: 6px;
+          height: 39px;
+        }
+      }
+
+      .emptyBox {
+        flex: 1;
+        display: flex;
+        flex-direction: column;
+        align-items: center;
+        padding-top: 8px;
+
+        >img {
+          width: 86px;
+          //height: 76px;
+        }
+
+        >span {
+          font-size: 14px;
+          color: #999999;
+          margin-top: 6px;
+        }
+      }
+
+      &::-webkit-scrollbar {
+        width: 4px;
+        height: 4px;
+        background-color: transparent;
+        z-index: 999;
+      }
+
+      &::-webkit-scrollbar-thumb {
+        border-radius: 10px;
+        background-color: #CBCBCB;
+      }
+    }
+  }
+
+  .right {
+    flex: 1;
+    border-radius: 8px;
+    background: #fff;
+    padding: 16px 6px 16px 16px;
+    display: flex;
+    flex-direction: column;
+
+    .title {
+      display: flex;
+      align-items: center;
+      font-size: 14px;
+      color: #000000;
+      font-weight: 500;
+      margin-bottom: 14px;
+
+      >img {
+        width: 20px;
+        height: 20px;
+        margin-right: 6px;
+      }
+    }
+
+    .desc {
+      flex: 1;
+      overflow-y: scroll;
+      font-size: 14px;
+      line-height: 20px;
+      padding-right: 10px;
+
+      img,
+      video {
+        width: 100% !important;
+      }
+
+      &::-webkit-scrollbar {
+        width: 4px;
+        height: 4px;
+        background-color: transparent;
+      }
+
+      &::-webkit-scrollbar-thumb {
+        border-radius: 10px;
+        min-height: 50px;
+        background-color: #CBCBCB;
+      }
+    }
+  }
+}

+ 295 - 0
src/views/courseware-play/component/instrument-info/index.tsx

@@ -0,0 +1,295 @@
+import {
+  defineComponent,
+  ref,
+  reactive,
+  onMounted,
+  computed,
+  watch
+} from 'vue';
+import { useRoute } from 'vue-router';
+import request from '@/helpers/request';
+import MEmpty from '@/components/m-empty';
+import styles from './index.module.less';
+import musicBg from '../../image/music_bg.png';
+import pBg from '../../image/plh.png';
+import titleIcon1 from '../../image/title_icon1.png';
+import titleIcon2 from '../../image/title_icon2.png';
+import playIcon from '../../image/music_play_icon.png';
+import pauseIcon from '../../image/music_pause_icon.png';
+import emptyIcon from '../../image/ins-empty-icon.png';
+import songIcon from '../../image/song_icon.png';
+import { NoticeBar } from 'vant';
+import PlayItem from '../play-item';
+import PlayLoading from '../play-loading';
+
+export default defineComponent({
+  name: 'InstrumentInfo',
+  props: {
+    id: {
+      type: String,
+      default: ''
+    },
+    type: {
+      type: String,
+      default: ''
+    },
+    show: {
+      type: Boolean,
+      default: false
+    }
+  },
+  emits: ['close', 'select'],
+  setup(props, { emit }) {
+    const route = useRoute();
+    const initDone = ref(false);
+    const forms = reactive({
+      detailId: route.query.detailId,
+      loading: false,
+      dataInfo: {} as any,
+      musicList: [] as any,
+      title: ' ',
+      playState: 'pause' as 'play' | 'pause',
+      showPlayer: false,
+      listActive: 0
+    });
+    /** 选中的item */
+    const activeItem = computed(() => {
+      return forms.musicList[forms.listActive] || {};
+    });
+
+    const getDetail = async () => {
+      forms.loading = true;
+      try {
+        const { data } = await request.get(
+          '/edu-app/knowledgeWiki/detail/' + props.id
+        );
+        data.intros = data.intros.replace(
+          /<video/gi,
+          '<video class="video-music" style="width: 100% !important;" controlslist="nodownload" poster="https://oss.dayaedu.com/ktqy/1701759849244.png"'
+        );
+        forms.dataInfo = data || {};
+
+        forms.musicList = data.knowledgeWikiResources.map((item: any) => {
+          return {
+            id: item.id,
+            name: item.name,
+            url: item.url
+          };
+        });
+      } catch {}
+      forms.loading = false;
+    };
+
+    /** 播放曲目 */
+    const handlePlay = (item: any) => {
+      if (!initDone.value) initDone.value = true;
+      const index = forms.musicList.findIndex(
+        (_item: any) => _item.id === item.id
+      );
+      if (index > -1) {
+        if (forms.listActive === index) {
+          forms.playState = forms.playState === 'play' ? 'pause' : 'play';
+        } else {
+          forms.playState = 'play';
+        }
+        forms.showPlayer = true;
+        forms.listActive = index;
+      }
+    };
+    /** 音频控制 */
+    const handleChangeAudio = (
+      type: 'play' | 'pause' | 'pre' | 'next' | 'favitor'
+    ) => {
+      if (type === 'play') {
+        forms.playState = 'play';
+      } else if (type === 'pause') {
+        forms.playState = 'pause';
+      } else if (type === 'pre') {
+        if (forms.musicList[forms.listActive - 1]) {
+          handlePlay(forms.musicList[forms.listActive - 1]);
+        }
+      } else if (type === 'next') {
+        if (forms.musicList[forms.listActive + 1]) {
+          handlePlay(forms.musicList[forms.listActive + 1]);
+        }
+      }
+    };
+
+    watch(
+      () => props.show,
+      val => {
+        if (val) {
+          // onToggleAudio();
+          forms.dataInfo = {};
+          forms.musicList = [];
+          getDetail();
+        } else {
+          //  audioRef.value.pause();
+          //  data.playState = 'pause';
+          handleChangeAudio('pause');
+          try {
+            // 暂停视频
+            const doms = document.querySelectorAll('.video-music');
+            if (doms && doms.length > 0) {
+              doms.forEach((dom: any) => {
+                dom.pause();
+              });
+            }
+          } catch {
+            //
+          }
+        }
+      }
+    );
+
+    onMounted(async () => {
+      await getDetail();
+    });
+    return () => (
+      <div
+        class={[styles.knowledgeBg, forms.showPlayer ? styles.wrapBottom : '']}>
+        <div class={styles.left}>
+          {props.type === 'wiki' && (
+            <div class={styles.insTop}>
+              <div class={styles.imgSection}>
+                <img
+                  class={styles.img}
+                  src={forms.dataInfo.avatar || musicBg}
+                />
+                <div class={styles.pan}>
+                  <img src={forms.dataInfo.avatar || musicBg} />
+                </div>
+              </div>
+              {/* <div class={styles.songName}>{forms.dataInfo.name || '--'}</div>    */}
+              <NoticeBar
+                text={forms.dataInfo.name}
+                color="#000"
+                class={styles.songName}
+                delay={1.5}
+                background="none"
+              />
+              <div class={styles.songWords}>
+                {forms.dataInfo.lyricists && (
+                  <span>作词:{forms.dataInfo.lyricists}</span>
+                )}
+                {forms.dataInfo.composers && (
+                  <span>作曲:{forms.dataInfo.composers}</span>
+                )}
+              </div>
+            </div>
+          )}
+          {props.type === 'instrument' && (
+            <div class={styles.insTop}>
+              <img src={forms.dataInfo.avatar || pBg} />
+              <div class={styles.insName}>{forms.dataInfo.name || ''}</div>
+              <div class={styles.insTro}>
+                {forms.dataInfo.knowledgeWikiCategories?.[0]
+                  ?.knowledgeWikiCategoryTypeName || ''}
+              </div>
+            </div>
+          )}
+          {props.type === 'musician' && (
+            <div class={styles.insTop}>
+              <img class={styles.musician} src={forms.dataInfo.avatar || pBg} />
+              <div class={styles.insName}>{forms.dataInfo.name || ''}</div>
+              <div class={styles.insTro}>
+                {forms.dataInfo.knowledgeWikiCategories?.[0]
+                  ?.knowledgeWikiCategoryTypeName || ''}
+              </div>
+            </div>
+          )}
+          <div class={styles.songColumn}>
+            <img src={songIcon} />
+            <span>{props.type === 'wiki' ? '名曲鉴赏' : '代表作'}</span>
+          </div>
+          <div class={styles.insList}>
+            {forms.musicList.length > 0 && (
+              <>
+                {forms.musicList.map((item: any, index: number) => {
+                  return (
+                    <div
+                      class={[
+                        styles.li,
+                        initDone.value && forms.listActive === index
+                          ? styles.liActive
+                          : ''
+                      ]}
+                      onClick={(e: Event) => {
+                        e.stopPropagation();
+                        handlePlay(item);
+                      }}>
+                      <div class={styles.liBg}>
+                        <img src={musicBg} />
+                        <div
+                          class={[
+                            forms.listActive === index &&
+                            forms.playState === 'play'
+                              ? styles.playingIcon
+                              : styles.hidePlayLoading
+                          ]}>
+                          <PlayLoading />
+                        </div>
+                      </div>
+                      {/* <div class={styles.liName}>{item.name || '--'}</div> */}
+                      <NoticeBar
+                        text={item.name}
+                        color="#131415"
+                        class={styles.liName}
+                        delay={1.5}
+                        background="none"
+                      />
+                      {/* <img
+                        class={styles.liPlay}
+                        src={
+                          forms.listActive === index &&
+                          forms.playState === 'play'
+                            ? pauseIcon
+                            : playIcon
+                        }
+                      /> */}
+                    </div>
+                  );
+                })}
+              </>
+            )}
+
+            {forms.musicList.length <= 0 && (
+              <div class={styles.emptyBox}>
+                <img src={emptyIcon} />
+                <span>暂无曲目~</span>
+              </div>
+            )}
+          </div>
+        </div>
+        <div class={styles.right}>
+          <div class={styles.title}>
+            <img
+              class={styles.liBg}
+              src={props.type === 'musician' ? titleIcon2 : titleIcon1}
+            />
+            {props.type === 'wiki'
+              ? '名曲故事'
+              : props.type === 'instrument'
+              ? '乐器简介'
+              : props.type === 'musician'
+              ? '个人简介'
+              : ''}
+          </div>
+          <div class={styles.desc} v-html={forms.dataInfo.intros}></div>
+          {!forms.loading && !forms.dataInfo.intros && (
+            <MEmpty description="暂无内容" />
+          )}
+        </div>
+
+        {forms.musicList.length !== 0 && (
+          <PlayItem
+            show={forms.showPlayer}
+            playState={forms.playState}
+            item={activeItem.value}
+            onChange={value => handleChangeAudio(value)}
+          />
+        )}
+      </div>
+    );
+  }
+});

+ 194 - 0
src/views/courseware-play/component/play-item/index.module.less

@@ -0,0 +1,194 @@
+.container {
+  position: fixed;
+  left: 100px;
+  bottom: 0;
+  right: 0;
+  display: flex;
+  align-items: center;
+  height: 61px;
+  padding: 0 14px 0 34px;
+  background-color: #fff;
+  box-shadow: 0px 2px 12px 0px rgba(0, 0, 0, 0.1);
+  z-index: 10;
+  transition: all .3s;
+
+  // &.previewcontainer {
+  //   left: 0;
+  //   padding-right: 380px;
+  // }
+
+  &.containerModal {
+    position: absolute;
+    left: 0;
+
+  }
+}
+
+.hidden {
+  transform: translateY(100%);
+  opacity: 0;
+}
+
+.item {
+  position: relative;
+  display: flex;
+  align-items: center;
+  width: 100%;
+
+  .img {
+    position: relative;
+    width: 40px;
+    height: 40px;
+    border-radius: 50%;
+    margin-right: 12px;
+    background-color: #000;
+    box-shadow: 0 0 10px 4px rgba(27, 35, 55, .1);
+    padding: 2px;
+    overflow: hidden;
+    flex-shrink: 0;
+
+    :global {
+      .n-image {
+        border-radius: 50%;
+        width: 100%;
+        height: 100%;
+      }
+    }
+
+    img {
+      transition: opacity .3s;
+      opacity: 0;
+      animation: rotateImg 6s linear infinite;
+    }
+
+    &.imgRotate {
+      img {
+        animation-play-state: paused;
+      }
+    }
+
+    img[data-loaded="true"] {
+      opacity: 1;
+    }
+  }
+
+  .svgcontainer {
+    position: fixed;
+    z-index: -1000;
+    pointer-events: none;
+  }
+
+  .progress {
+    position: absolute;
+    left: 3px;
+    top: 3px;
+    width: 34px;
+    height: 34px;
+    pointer-events: none;
+    transform: rotate(180deg);
+
+    :global {
+      .n-progress-graph .n-progress-graph-circle .n-progress-graph-circle-fill {
+        stroke: url(#GradientProgress);
+      }
+    }
+  }
+
+  .title {
+    margin-right: 15px;
+    // width: 200px;
+
+    .titleName {
+      font-size: 13px;
+      font-weight: 600;
+      color: #131415;
+      line-height: 28px;
+      white-space: nowrap;
+      width: 80px;
+      overflow: hidden;
+      text-overflow: ellipsis;
+    }
+  }
+}
+
+@keyframes rotateImg {
+
+  100% {
+    transform: rotate(360deg);
+  }
+}
+
+.playBtns {
+  margin-left: 0;
+  display: flex;
+  align-items: center;
+
+  // :global {
+  //   .van-button {
+  //     width: 20px;
+  //     height: 20px;
+
+  //     img {
+  //       width: 100%;
+  //       height: 100%;
+  //     }
+  //   }
+  // }
+  .btn1 {
+    width: 20px;
+    height: 20px;
+  }
+
+  .playBtn {
+    width: 25px;
+    height: 25px;
+    margin: 0 12px;
+    border-radius: 50%;
+    background: linear-gradient(to right bottom, #44CAFE, #007AFE);
+    display: flex;
+    align-items: center;
+    justify-content: center;
+
+    img {
+      display: block;
+      width: 12px;
+      height: 12px;
+    }
+  }
+}
+
+.timeWrap {
+  flex: 1;
+  display: flex;
+  align-items: center;
+  margin-left: 20px;
+  --van-slider-bar-height: 3px;
+  --van-slider-button-width: 7px !important;
+  --van-slider-button-height: 7px !important;
+  --van-slider-inactive-background: #E8E8E8;
+  --van-slider-active-background: #C8E7FF !important;
+  --van-slider-button-background: #269EFE;
+  --van-slider-button-shadow: none;
+
+  :global {
+    .van-slider__button-wrapper {
+      border: 3px solid rgba(38, 158, 254, 0.19);
+      border-radius: 50%;
+    }
+
+    // .van-slider__button {
+    //   border: 3px solid rgba(38, 158, 254, 0.19);
+    // }
+  }
+
+  .timeProgress {
+    margin-right: 14px;
+  }
+
+  .time {
+    width: 90px;
+    white-space: nowrap;
+    flex-shrink: 0;
+    font-size: 12px;
+  }
+}

+ 268 - 0
src/views/courseware-play/component/play-item/index.tsx

@@ -0,0 +1,268 @@
+import {
+  PropType,
+  Transition,
+  computed,
+  defineComponent,
+  reactive,
+  ref,
+  watch
+} from 'vue';
+import styles from './index.module.less';
+import { NButton, NImage, NProgress, NSlider } from 'naive-ui';
+// import { IMusicItem } from '../../type';
+import icon_pre from '../../image/icon_pre.png';
+import icon_next from '../../image/icon_next.png';
+import icon_play from '../../image/icon_play.png';
+import icon_pause from '../../image/icon_pause.png';
+import { Button, Circle, Slider } from 'vant';
+// import { getSecondRPM } from '/src/utils';
+// import TheNoticeBar from '/src/components/TheNoticeBar';
+
+// 秒转分
+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: 'playItem',
+  props: {
+    item: {
+      type: Object as PropType<any>,
+      default: () => ({})
+    },
+    show: {
+      type: Boolean,
+      default: false
+    },
+    playState: {
+      type: String as PropType<'play' | 'pause'>,
+      default: 'pause'
+    },
+    type: {
+      type: String,
+      default: ''
+    }
+  },
+  emits: ['change'],
+  setup(props, { emit }) {
+    let timer = null as any;
+    const audioData = reactive({
+      isFirst: true,
+      duration: 0.1,
+      currentTime: 0
+    });
+    const audioRef = ref();
+
+    /** 加载成功 */
+    const onLoadedmetadata = () => {
+      audioData.duration = audioRef.value?.duration;
+      if (audioData.isFirst) {
+        audioData.isFirst = false;
+        return;
+      }
+      if (props.playState === 'play') {
+        audioRef.value.play();
+      }
+    };
+    /** 改变时间 */
+    const handleChangeTime = (val: number) => {
+      audioRef.value.pause();
+      audioData.currentTime = val;
+      clearTimeout(timer);
+      timer = setTimeout(() => {
+        audioRef.value.currentTime = val;
+        if (props.playState === 'play') {
+          audioRef.value.play();
+        }
+        timer = null;
+      }, 300);
+    };
+    const time = computed(() => {
+      return `${getSecondRPM(audioData.currentTime)} / ${getSecondRPM(
+        audioData.duration
+      )}`;
+    });
+
+    watch(
+      () => props.playState,
+      val => {
+        if (val === 'play') {
+          audioRef.value.play();
+        } else {
+          audioRef.value.pause();
+        }
+      }
+    );
+
+    return () => (
+      <div
+        class={[
+          styles.container,
+          // props.type === 'preview' && styles.previewcontainer,
+          // props.type === 'modal' && styles.containerModal
+          styles.containerModal,
+          props.show ? styles.show : styles.hidden
+        ]}>
+        <div class={[styles.item]}>
+          <div
+            class={[
+              styles.img,
+              props.playState !== 'play' && styles.imgRotate
+            ]}>
+            <NImage
+              lazy
+              objectFit="cover"
+              previewDisabled={true}
+              src={'https://oss.dayaedu.com/klx/16983720423251690789356356.png'}
+              onLoad={e => {
+                (e.target as any).dataset.loaded = 'true';
+              }}
+            />
+
+            <svg class={styles.svgcontainer}>
+              <defs>
+                <linearGradient id="GradientProgress">
+                  <stop stop-color="#5BECFF" offset="0%" />
+                  <stop stop-color="#259CFE" offset="100%" />
+                </linearGradient>
+              </defs>
+            </svg>
+
+            <Circle
+              class={styles.progress}
+              startPosition="bottom"
+              currentRate={(audioData.currentTime / audioData.duration) * 100}
+              // currentRate={90}
+              strokeWidth={50}
+            />
+          </div>
+          <div class={styles.title}>
+            <div class={styles.titleName}>{props.item.name}</div>
+          </div>
+
+          <div class={styles.playBtns}>
+            {/* <Button
+              color="rgba(246,246,246,1)"
+              round
+              onClick={() => emit('change', 'pre')}>
+            </Button> */}
+            <img
+              class={styles.btn1}
+              src={icon_pre}
+              onClick={(e: any) => {
+                e.stopPropagation();
+                emit('change', 'pre');
+              }}
+            />
+
+            <div
+              class={styles.playBtn}
+              onClick={(e: any) => {
+                e.stopPropagation();
+                emit('change', props.playState === 'pause' ? 'play' : 'pause');
+              }}>
+              <img
+                style={{
+                  display: props.playState === 'pause' ? '' : 'none'
+                  // transform: 'scale(1.5) translateX(1px)'
+                }}
+                src={icon_play}
+              />
+              <img
+                style={{
+                  display: props.playState === 'play' ? '' : 'none'
+                  // transform: 'scale(1.5)'
+                }}
+                src={icon_pause}
+              />
+            </div>
+
+            <img
+              class={styles.btn1}
+              src={icon_next}
+              onClick={(e: any) => {
+                e.stopPropagation();
+                emit('change', 'next');
+              }}
+            />
+            {/* <Button
+              color="rgba(57,130,246,1)"
+              class={styles.playBtn}
+              round
+              onClick={() =>
+                emit('change', props.playState === 'pause' ? 'play' : 'pause')
+              }>
+              <img
+                style={{
+                  display: props.playState === 'pause' ? '' : 'none'
+                  // transform: 'scale(1.5) translateX(1px)'
+                }}
+                src={icon_play}
+              />
+              <img
+                style={{
+                  display: props.playState === 'play' ? '' : 'none'
+                  // transform: 'scale(1.5)'
+                }}
+                src={icon_pause}
+              />
+            </Button>
+            <Button
+              color="rgba(246,246,246,1)"
+              round
+              onClick={() => emit('change', 'next')}>
+              <img src={icon_next} />
+            </Button> */}
+          </div>
+
+          <div class={styles.timeWrap}>
+            {/* <Slider
+              step={0.01}
+              class={styles.timeProgress}
+              value={audioData.currentTime}
+              max={audioData.duration}
+              onUpdate:value={val => handleChangeTime(val)}
+            /> */}
+            <Slider
+              step={0.01}
+              class={styles.timeProgress}
+              v-model={audioData.currentTime}
+              max={audioData.duration}
+              onUpdate:modelValue={val => {
+                handleChangeTime(val);
+              }}
+              onDragStart={() => {
+                // state.dragStatus = true;
+              }}
+              onDragEnd={() => {
+                // state.dragStatus = false;
+              }}
+            />
+            <div class={styles.time}>{time.value}</div>
+            <audio
+              ref={audioRef}
+              src={props.item.url}
+              onLoadedmetadata={onLoadedmetadata}
+              onEnded={() => {
+                emit('change', 'pause');
+              }}
+              onTimeupdate={() => {
+                if (timer) return;
+                audioData.currentTime = audioRef.value?.currentTime;
+              }}></audio>
+          </div>
+        </div>
+      </div>
+    );
+  }
+});

+ 48 - 0
src/views/courseware-play/component/play-loading/index.module.less

@@ -0,0 +1,48 @@
+.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: flex-end;
+  padding-bottom: 25%;
+
+  div {
+    width: 2px;
+    height: 8px;
+    background: linear-gradient(135deg, #34FFC5 0%, #1BD2FF 100%);
+    transform-origin: bottom;
+    border-radius: 5px 5px 0 0;
+    margin: 0 1px;
+  }
+
+  & 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: 2px;
+  }
+
+  100% {
+    height: 10px;
+  }
+}

+ 16 - 0
src/views/courseware-play/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>
+    );
+  }
+});

+ 150 - 12
src/views/courseware-play/component/point.module.less

@@ -12,15 +12,16 @@
 
 .pointHead {
   display: flex;
-  align-items: center;
-  padding: 13px 10px 15px 15px;
+  padding: 16px 10px 12px 12px;
   flex-shrink: 0;
-  font-size: 14px;
+  font-size: 16px;
+  font-weight: 500;
 
   img {
-    width: 16px;
-    height: 16px;
-    margin-right: 7px;
+    width: 20px;
+    height: 20px;
+    margin-right: 5px;
+    margin-top: 4px;
   }
 }
 
@@ -28,23 +29,26 @@
   flex: 1;
   overflow-x: hidden;
   overflow-y: auto;
-  padding: 0 20px;
+  padding: 0 12px;
 
   &::-webkit-scrollbar {
     width: 0;
     display: none;
   }
+  // .collapseKnow:nth-of-type(n+2) {
+  //   padding-top: 2px !important;
+  // }
 }
 
-.item {
+.matItem {
   border-radius: 6px;
   border: 1px solid #C2DBE2;
   background: linear-gradient(360deg, #F2F4F5 0%, #E7F9FF 100%);
-  margin-bottom: 15px;
+  margin: 0 15px 15px;
   overflow: hidden;
 
   .cover {
-    height: 115px;
+    height: 102px;
     background: #fff;
     overflow: hidden;
 
@@ -62,7 +66,12 @@
     font-weight: 400;
     color: #131415;
     line-height: 16px;
-
+    background: #E7F9FF;
+    .typeImg {
+      width: 13px;
+      height: 13px;
+      margin-right: 4px;
+    }
     .tag {
       padding: 4px 10px;
       border-radius: 4px;
@@ -75,7 +84,11 @@
       white-space: nowrap;
       flex-shrink: 0;
     }
-
+    .tName {
+      overflow-x: hidden;
+      text-overflow: ellipsis;
+      white-space: nowrap;
+    }
     :global {
       .van-icon {
         display: none;
@@ -94,4 +107,129 @@
       }
     }
   }
+}
+
+.kjColumn {
+  margin: 0 12px 0 6px;
+  padding: 10px 12px;
+  border-top: 1px solid #F2F2F2;
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  .kjLeft {
+    display: flex;
+    align-items: center;
+    overflow-x: hidden;
+    img {
+      width: 13px;
+      height: 17px;
+    }
+    span {
+      flex: 1;
+      overflow-x: hidden;
+      text-overflow: ellipsis;
+      white-space: nowrap;
+      font-size: 14px;
+      color: #333333;
+      font-weight: 500;
+      margin: 4px 4px 4px 6px;
+    }
+  }
+  .kjRight {
+    width: 24px;
+    height: 24px;
+  }
+}
+
+.collapse {
+
+  .collapseItem {
+    padding: 12px 12px 2px;
+  }
+
+  .collapseKnow {
+    padding: 8px 12px 6px 12px;
+    position: relative;
+    :global {
+      .van-cell {
+        margin-bottom: 8px;
+        font-size: 13px;
+        font-weight: normal !important;
+        color: #333 !important;
+      }
+    }
+  }
+  :global {
+    .van-cell {
+      background: transparent;
+      font-size: 14px;
+      color: #777;
+      padding: 0;
+      border: none;
+      line-height: 18px;
+    }
+
+    .van-collapse-item__content {
+      padding: 0;
+      background-color: transparent;
+    }
+  }
+
+  .item {
+    display: flex;
+    align-items: center;
+    margin-top: 15px;
+
+    span {
+      color: #131415;
+      font-size: 12px;
+    }
+  }
+
+  .arrow {
+    width: 12px;
+    height: 12px;
+    margin-right: 5px;
+  }
+
+  .firstArrow {
+    margin-top: 3px;
+  }
+  .itemImage {
+    width: 15px;
+    height: 15px;
+    margin-right: 6px;
+  }
+
+  .activeItem {
+    background: #ECF8FF;
+    border-radius: 9px;
+    margin-top: 4px;
+    padding: 8px 12px 2px;
+
+    :global {
+      .van-cell {
+        color: #1C9AF7;
+        font-weight: 600;
+      }
+    }
+  }
+  .activeItem2 {
+    background: #F5F6FA;
+    border-radius: 9px;
+
+    :global {
+      .van-cell {
+        color: #1C9AF7 !important;
+        font-weight: 500 !important;
+      }
+    }
+  }
+  .itemActive {
+    font-weight: 500;
+
+    span {
+      color: #1C9AF7;
+    }
+  }
 }

+ 273 - 9
src/views/courseware-play/component/points.tsx

@@ -1,4 +1,4 @@
-import { PropType, defineComponent, reactive, watch } from 'vue';
+import { PropType, defineComponent, reactive, watch, onMounted } from 'vue';
 import styles from './point.module.less';
 import iconMulv from '../image/icon-mulv.svg';
 import iconZhibo from '../image/icon-load.gif';
@@ -8,12 +8,44 @@ import iconVideo from '../image/icon-video.svg';
 import iconVideoActive from '../image/icon-video-active.svg';
 import iconSong from '../image/icon-song.svg';
 import iconSongActive from '../image/icon-song-active.svg';
+import chapterDown from '../image/chapter-down-arrow.svg';
+import chapterDefault from '../image/chapter-default-arrow.svg';
+import chapterDefault2 from '../image/chapter-default-arrow2.svg';
+import moreIcon from '../image/zy_toggle_icon.png';
+import titleIcon from '../image/zy_title_icon.png';
+import songIcon from '../image/song_icon.png';
+import audioIcon from '../image/zy_audio_icon.png';
+import vedioIcon from '../image/zy_vedio_icon.png';
+import pptIcon from '../image/zy_ppt_icon.png';
+import imgIcon from '../image/zy_img_icon.png';
+import otherIcon from '../image/zy_more_icon.png';
 
-import { Icon } from 'vant';
+
+import { Icon, Collapse, CollapseItem } from 'vant';
 import { useRoute } from 'vue-router';
 export default defineComponent({
   name: 'points',
   props: {
+    allList: {
+      type: Array as any,
+      default: () => []
+    },
+    kjId: { // 课件id
+      type: String,
+      default: ''
+    },
+    zsdId: { // 知识点id
+      type: String,
+      default: ''
+    },
+    currentCourse: { // 当前的课件
+      type: Object,
+      default: {}
+    },
+    popShow: {
+      type: Boolean,
+      default: false
+    },
     data: {
       type: Array as PropType<any[]>,
       default: () => []
@@ -22,22 +54,41 @@ export default defineComponent({
       type: String,
       default: ''
     },
-    itemName: {
+    itemName: { // 章节名称
       type: String,
       default: ''
     }
   },
-  emits: ['handleSelect'],
+  emits: ['handleSelect', 'courseSelect'],
   setup(props, { emit }) {
     const route = useRoute();
+    // 类型(VIDEO,IMG,SONG,PPT,MUSIC,LISTEN:听音,RHYTHM:节奏,THEORY:乐理知识,MUSIC_WIKI:名曲鉴赏 INSTRUMENT:乐器 MUSICIAN:音乐家),可用值:VIDEO,MUSIC,IMG,SONG,PPT,LISTEN,RHYTHM,THEORY,MUSIC_WIKI,INSTRUMENT,MUSICIAN
     const types: { [_: string]: string } = {
       SONG: '音频',
       VIDEO: '视频',
       IMG: '图片',
       MUSIC: '乐谱',
-      PPT: 'PPT'
+      PPT: 'PPT',
+      LISTEN: '听音',
+      RHYTHM: '节奏',
+      THEORY: '乐理',
+      MUSIC_WIKI: '名曲',
+      INSTRUMENT: '乐器',
+      MUSICIAN: '音乐家',
+    };
+    const imgTypes: { [_: string]: string } = {
+      SONG: audioIcon,
+      VIDEO: vedioIcon,
+      IMG: imgIcon,
+      MUSIC: songIcon,
+      PPT: pptIcon,
+      LISTEN: otherIcon,
+      RHYTHM: otherIcon,
+      THEORY: otherIcon,
+      MUSIC_WIKI: otherIcon,
+      INSTRUMENT: otherIcon,
+      MUSICIAN: otherIcon,
     };
-
     // 获取对应图片
     const getImage = (item: any) => {
       if (item.type === 'VIDEO') {
@@ -50,15 +101,228 @@ export default defineComponent({
         return props.itemActive == item.id ? iconVideoActive : iconVideo;
       }
     };
-
+    
+    const pointData = reactive({
+      allList: props.allList, // 章节下的所有课件
+      kjId: props.kjId, // 当前选中的课件id
+      zsdId: props.zsdId, // 当前选中的知识点id
+      currentCourse: props.currentCourse as any, // 当前课件
+    });
+    watch(
+      () => props.allList,
+      () => {
+        pointData.kjId = props.kjId
+        pointData.zsdId = props.zsdId
+      }
+    );
+    watch(
+      () => props.popShow,
+      () => {
+        console.log('刷新123',props.allList,props.currentCourse)
+        pointData.currentCourse = props.currentCourse
+        pointData.kjId = props.kjId
+        pointData.zsdId = props.zsdId
+      }
+    );
     return () => (
       <div class={styles.container}>
         <div class={styles.pointHead}>
           <img src={iconMulv} />
           {props.itemName}
         </div>
+        <div class={styles.kjColumn}>
+          <div class={styles.kjLeft}>
+            <img src={titleIcon} />
+            <span>{pointData.currentCourse?.name}</span>
+          </div>
+          {
+            pointData.allList?.length > 1 && 
+            <img class={styles.kjRight} src={moreIcon}
+              onClick={() => {
+                console.log(pointData.allList)
+                emit('courseSelect')
+              }}
+            />
+          }
+        </div>        
         <div class={styles.content}>
-          {props.data.map((item, index: number) => {
+          <Collapse
+            class={styles.collapse}
+            modelValue={pointData.zsdId}
+            onUpdate:modelValue={(val: any) => {
+              pointData.zsdId = val;
+            }}
+            border={false}
+            accordion>
+            {pointData.currentCourse?.knowledgeList?.map((know: any) => (
+              <CollapseItem
+                center
+                class={[
+                  styles.collapseItem,
+                  styles.collapseKnow,
+                  pointData.zsdId === know.id ? styles.activeItem2 : ''
+                ]}
+                border={false}
+                isLink={false}
+                title={know.name}
+                titleClass={'van-ellipsis'}
+                titleStyle={{ width: '80%' }}
+                name={know.id}>
+                {{
+                  default: () => (
+                    <>
+                      {know.materialInfo.map((material: any, index: number) => {
+                        return (
+                          <div
+                            class={[
+                              styles.matItem,
+                              props.itemActive == material.id ? styles.itemActive : ''
+                            ]}
+                            onClick={() => {
+                              console.log(pointData.allList)
+                              emit('handleSelect', {
+                                itemActive: material.id,
+                                zsdId: know.id,
+                                kjId: pointData.currentCourse?.id
+                              });
+                            }}>
+                            <div class={styles.cover}>
+                              <img src={material.url} />
+                            </div>
+                            <div class={styles.title}>
+                              {/* <div class={styles.tag}>{types[material.type]}</div> */}
+                              <img class={styles.typeImg} src={imgTypes[material.type]} />
+                              <div class={styles.tName}>{material.name}</div>
+                            </div>
+                          </div>
+                        );
+                      })}                                    
+                    </>
+                  ),
+                  icon: () => (
+                    <img
+                      class={styles.arrow}
+                      src={
+                        pointData.zsdId === know.id
+                          ? chapterDown
+                          : chapterDefault
+                      }
+                    />
+                  )
+                }}
+              </CollapseItem>
+            ))}
+          </Collapse>
+          
+          {/* {props.allList.length && 
+            <Collapse
+            class={styles.collapse}
+            modelValue={pointData.kjId}
+            onUpdate:modelValue={(val: any) => {
+              pointData.kjId = val;
+            }}
+            border={false}
+            accordion>
+            {props.allList.map((item: any) => (
+              <CollapseItem
+                class={[
+                  styles.collapseItem,
+                  pointData.kjId === item.id ? styles.activeItem : ''
+                ]}
+                border={false}
+                isLink={false}
+                title={item.name}
+                titleStyle={{ width: '80%' }}
+                name={item.id}>
+                {{
+                  default: () => (
+                    <>
+                      <Collapse
+                          class={styles.collapse}
+                          modelValue={pointData.zsdId}
+                          onUpdate:modelValue={(val: any) => {
+                            pointData.zsdId = val;
+                          }}
+                          border={false}
+                          accordion>
+                          {item.knowledgeList.map((know: any) => (
+                            <CollapseItem
+                              center
+                              class={[
+                                styles.collapseItem,
+                                styles.collapseKnow,
+                                pointData.zsdId === know.id ? styles.activeItem2 : ''
+                              ]}
+                              border={false}
+                              isLink={false}
+                              title={know.name}
+                              titleClass={'van-ellipsis'}
+                              titleStyle={{ width: '80%' }}
+                              name={know.id}>
+                              {{
+                                default: () => (
+                                  <>
+                                    {know.materialInfo.map((material: any, index: number) => {
+                                      return (
+                                        <div
+                                          class={[
+                                            styles.matItem,
+                                            props.itemActive == material.id ? styles.itemActive : ''
+                                          ]}
+                                          onClick={() => {
+                                            console.log(pointData.allList)
+                                            emit('handleSelect', {
+                                              itemActive: material.id,
+                                              zsdId: know.id,
+                                              kjId: item.id
+                                            });
+                                          }}>
+                                          <div class={styles.cover}>
+                                            <img src={material.url} />
+                                          </div>
+                                          <div class={styles.title}>
+                                            <div class={styles.tag}>{types[material.type]}</div>
+                                            <div class={styles.tName}>{material.name}</div>
+                                          </div>
+                                        </div>
+                                      );
+                                    })}                                    
+                                  </>
+                                ),
+                                icon: () => (
+                                  <img
+                                    class={styles.arrow}
+                                    src={
+                                      pointData.zsdId === know.id
+                                        ? chapterDown
+                                        : chapterDefault2
+                                    }
+                                  />
+                                )
+                              }}
+                            </CollapseItem>
+                          ))}
+                      </Collapse>                      
+                    </>
+                  ),
+                  icon: () => (
+                    <img
+                      class={[styles.arrow, styles.firstArrow]}
+                      src={
+                        pointData.kjId === item.id
+                          ? chapterDown
+                          : chapterDefault
+                      }
+                    />
+                  )
+                }}
+              </CollapseItem>
+            ))}
+            </Collapse>          
+          } */}
+
+
+          {/* {props.data.map((item, index: number) => {
             return (
               <div
                 class={[
@@ -79,7 +343,7 @@ export default defineComponent({
                 </div>
               </div>
             );
-          })}
+          })} */}
         </div>
       </div>
     );

+ 40 - 0
src/views/courseware-play/component/tempo-item/index.module.less

@@ -0,0 +1,40 @@
+.tempoItem {
+  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;
+  }
+}
+
+.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;
+}

+ 74 - 0
src/views/courseware-play/component/tempo-item/index.tsx

@@ -0,0 +1,74 @@
+import { defineComponent, ref, watch } from 'vue';
+import styles from './index.module.less';
+import qs from 'query-string';
+import { storage } from '@/helpers/storage';
+import { ACCESS_TOKEN } from '@/store/mutation-types';
+import { Skeleton } from 'vant';
+
+export default defineComponent({
+  name: 'tempo-item',
+  props: {
+    pageVisibility: {
+      type: String,
+      default: ''
+    },
+    show: {
+      type: Boolean,
+      default: false
+    },
+    dataJson: {
+      type: String,
+      default: ''
+    }
+  },
+  emits: ['setIframe'],
+  setup(props, { emit }) {
+    const iframeRef = ref();
+    const isLoading = ref(false);
+    const isLoaded = ref(false);
+
+    /** 页面显示和隐藏 */
+    watch(
+      () => props.pageVisibility,
+      value => {
+        if (value == 'hidden') {
+          isLoading.value = false;
+        }
+        if (value == 'hidden' && props.show) {
+          iframeRef.value?.contentWindow?.postMessage(
+            { api: 'setPlayState' },
+            '*'
+          );
+        }
+      }
+    );
+    // 是否显示当前曲谱
+    watch(
+      () => props.show,
+      val => {
+        if (!val) {
+          iframeRef.value?.contentWindow?.postMessage(
+            { api: 'setPlayState' },
+            '*'
+          );
+        }
+      }
+    );
+
+    const Authorization = storage.get(ACCESS_TOKEN);
+    const src = `${location.origin}/classroom-app/#/tempo-practice?dataJson=${props.dataJson}&Authorization=${Authorization}&modeType=courseware`;
+    return () => (
+      <div class={styles.tempoItem}>
+        <iframe
+          ref={iframeRef}
+          onLoad={(e: Event) => {
+            emit('setIframe', iframeRef.value);
+            isLoaded.value = true;
+          }}
+          class={[styles.container]}
+          frameborder="0"
+          src={src}></iframe>
+      </div>
+    );
+  }
+});

+ 24 - 0
src/views/courseware-play/component/theory/index.module.less

@@ -0,0 +1,24 @@
+.knowledgeBg {
+    width: 100%;
+    height: 100%;
+    background: #DDF2FF;
+    padding: 16px;
+    .content {
+        padding: 16px;
+        height: 100%;
+        border-radius: 8px;
+        background: #fff;
+        overflow-y: scroll;
+        &::-webkit-scrollbar
+        {
+            width:4px;
+            background-color: transparent;
+        }
+        &::-webkit-scrollbar-thumb
+        {
+            min-height: 50px;
+            border-radius:10px;
+            background-color:#CBCBCB;
+        }         
+    }
+}

+ 56 - 0
src/views/courseware-play/component/theory/index.tsx

@@ -0,0 +1,56 @@
+import { defineComponent, ref, reactive, onMounted } from 'vue';
+import { useRoute } from 'vue-router';
+import request from '@/helpers/request';
+import MEmpty from '@/components/m-empty';
+import styles from './index.module.less';
+
+export default defineComponent({
+  name: 'Theory',
+  props: {
+    id: {
+      type: String,
+      default: () => ''
+    }
+  },
+  emits: ['close', 'select'],
+  setup(props, { emit }) {
+    const route = useRoute();
+    const forms = reactive({
+      detailId: route.query.detailId,
+      loading: false,
+      dataInfo: {} as any,
+      title: ' ',
+    });
+    const getDetail = async () => {
+      forms.loading = true;
+      try {
+        const { data } = await request.get(
+          '/edu-app/lessonCoursewareKnowledgeDetail/detail/' + props.id
+        );
+        forms.dataInfo = data;
+        forms.title = data.name;
+      } catch {
+        //
+      }
+      forms.loading = false;
+    };
+    onMounted(async () => {
+      await getDetail();
+    });
+    return () => (
+      <div class={styles.knowledgeBg}>
+        <div class={styles.content}>
+          {forms.dataInfo?.desc && (
+            <div
+              v-html={forms.dataInfo.desc}></div>
+          )}
+          {!forms.dataInfo?.desc && !forms.loading && (
+            <div>
+              <MEmpty description="暂无内容" style={{ paddingTop: '40px' }} />
+            </div>
+          )}
+        </div>        
+      </div>
+    );
+  }
+});

+ 6 - 10
src/views/courseware-play/image/chapter-default-arrow.svg

@@ -1,14 +1,10 @@
 <?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="修改页面8.18" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
-        <g id="5、切换单元备份-3" transform="translate(-558.000000, -246.000000)" fill="#AAAAAA">
-            <g id="编组-9" transform="translate(542.000000, 0.000000)">
-                <g id="编组-5" transform="translate(16.000000, 242.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.38411064,3.96093277 L10.3165898,8.6799078 C10.4933719,8.89204625 10.4647098,9.2073286 10.2525714,9.38411064 C10.1627139,9.45899189 10.0494474,9.5 9.93247919,9.5 L2.06752081,9.5 C1.79137843,9.5 1.56752081,9.27614237 1.56752081,9 C1.56752081,8.88303175 1.60852892,8.7697653 1.68341017,8.6799078 L5.61588936,3.96093277 C5.7926714,3.74879432 6.10795375,3.72013229 6.3200922,3.89691433 C6.34332373,3.91627394 6.36475103,3.93770124 6.38411064,3.96093277 Z" id="展开更多" transform="translate(6.000000, 6.500000) rotate(-180.000000) translate(-6.000000, -6.500000) "></path>
-                    </g>
-                </g>
+<svg width="7px" height="10px" viewBox="0 0 7 10" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <title>展开更多备份 6</title>
+    <g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" opacity="0.501187279">
+        <g id="课件-课件列表收起" transform="translate(-565.000000, -212.000000)" fill="#333333">
+            <g id="展开更多备份-6" transform="translate(568.500000, 217.000000) rotate(-90.000000) translate(-568.500000, -217.000000) translate(563.500000, 214.000000)">
+                <path d="M5.38411064,0.460932768 L9.31658983,5.1799078 C9.49337187,5.39204625 9.46470984,5.7073286 9.25257139,5.88411064 C9.16271389,5.95899189 9.04944745,6 8.93247919,6 L1.06752081,6 C0.791378431,6 0.567520806,5.77614237 0.567520806,5.5 C0.567520806,5.38303175 0.608528916,5.2697653 0.683410167,5.1799078 L4.61588936,0.460932768 C4.7926714,0.248794319 5.10795375,0.220132287 5.3200922,0.396914328 C5.34332373,0.416273938 5.36475103,0.437701235 5.38411064,0.460932768 Z" id="展开更多" transform="translate(5.000000, 3.000000) rotate(-180.000000) translate(-5.000000, -3.000000) "></path>
             </g>
         </g>
     </g>

+ 13 - 0
src/views/courseware-play/image/chapter-default-arrow2.svg

@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="7px" height="10px" viewBox="0 0 7 10" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <title>展开更多备份 3</title>
+    <g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="课件-课件列表收起备份" transform="translate(-583.000000, -130.000000)" fill="#333333">
+            <g id="编组-9" transform="translate(550.000000, 50.000000)">
+                <g id="展开更多备份-3" transform="translate(36.500000, 85.000000) rotate(-90.000000) translate(-36.500000, -85.000000) translate(31.500000, 82.000000)">
+                    <path d="M5.38411064,0.460932768 L9.31658983,5.1799078 C9.49337187,5.39204625 9.46470984,5.7073286 9.25257139,5.88411064 C9.16271389,5.95899189 9.04944745,6 8.93247919,6 L1.06752081,6 C0.791378431,6 0.567520806,5.77614237 0.567520806,5.5 C0.567520806,5.38303175 0.608528916,5.2697653 0.683410167,5.1799078 L4.61588936,0.460932768 C4.7926714,0.248794319 5.10795375,0.220132287 5.3200922,0.396914328 C5.34332373,0.416273938 5.36475103,0.437701235 5.38411064,0.460932768 Z" id="展开更多" transform="translate(5.000000, 3.000000) rotate(-180.000000) translate(-5.000000, -3.000000) "></path>
+                </g>
+            </g>
+        </g>
+    </g>
+</svg>

+ 7 - 9
src/views/courseware-play/image/chapter-down-arrow.svg

@@ -1,13 +1,11 @@
 <?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="修改页面8.18" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
-        <g id="5、切换单元备份-3" transform="translate(-558.000000, -62.000000)" fill="#1C9AF7">
-            <g id="编组-9" transform="translate(542.000000, 0.000000)">
-                <g id="编组-11" transform="translate(16.000000, 58.000000)">
-                    <g id="展开更多" transform="translate(0.000000, 4.000000)">
-                        <path d="M6.38411064,3.96093277 L10.3165898,8.6799078 C10.4933719,8.89204625 10.4647098,9.2073286 10.2525714,9.38411064 C10.1627139,9.45899189 10.0494474,9.5 9.93247919,9.5 L2.06752081,9.5 C1.79137843,9.5 1.56752081,9.27614237 1.56752081,9 C1.56752081,8.88303175 1.60852892,8.7697653 1.68341017,8.6799078 L5.61588936,3.96093277 C5.7926714,3.74879432 6.10795375,3.72013229 6.3200922,3.89691433 C6.34332373,3.91627394 6.36475103,3.93770124 6.38411064,3.96093277 Z" transform="translate(6.000000, 6.500000) rotate(-180.000000) translate(-6.000000, -6.500000) "></path>
-                    </g>
+<svg width="10px" height="7px" viewBox="0 0 10 7" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <title>展开更多备份 2</title>
+    <g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="课件-课件列表展开" transform="translate(-563.000000, -68.000000)" fill="#1C9AF7">
+            <g id="编组-9" transform="translate(550.000000, 50.000000)">
+                <g id="展开更多备份-2" transform="translate(18.000000, 21.500000) rotate(-360.000000) translate(-18.000000, -21.500000) translate(13.000000, 18.500000)">
+                    <path d="M5.38411064,0.460932768 L9.31658983,5.1799078 C9.49337187,5.39204625 9.46470984,5.7073286 9.25257139,5.88411064 C9.16271389,5.95899189 9.04944745,6 8.93247919,6 L1.06752081,6 C0.791378431,6 0.567520806,5.77614237 0.567520806,5.5 C0.567520806,5.38303175 0.608528916,5.2697653 0.683410167,5.1799078 L4.61588936,0.460932768 C4.7926714,0.248794319 5.10795375,0.220132287 5.3200922,0.396914328 C5.34332373,0.416273938 5.36475103,0.437701235 5.38411064,0.460932768 Z" id="展开更多" transform="translate(5.000000, 3.000000) rotate(-180.000000) translate(-5.000000, -3.000000) "></path>
                 </g>
             </g>
         </g>

二進制
src/views/courseware-play/image/icon-pan.png


二進制
src/views/courseware-play/image/icon_next.png


二進制
src/views/courseware-play/image/icon_pause.png


二進制
src/views/courseware-play/image/icon_play.png


二進制
src/views/courseware-play/image/icon_pre.png


二進制
src/views/courseware-play/image/ins-empty-icon.png


二進制
src/views/courseware-play/image/music_bg.png


二進制
src/views/courseware-play/image/music_pause_icon.png


二進制
src/views/courseware-play/image/music_play_icon.png


二進制
src/views/courseware-play/image/plh.png


二進制
src/views/courseware-play/image/song_icon.png


二進制
src/views/courseware-play/image/title_icon1.png


二進制
src/views/courseware-play/image/title_icon2.png


二進制
src/views/courseware-play/image/zy_audio_icon.png


二進制
src/views/courseware-play/image/zy_img_icon.png


二進制
src/views/courseware-play/image/zy_more_icon.png


二進制
src/views/courseware-play/image/zy_ppt_icon.png


二進制
src/views/courseware-play/image/zy_song_icon.png


二進制
src/views/courseware-play/image/zy_title_icon.png


二進制
src/views/courseware-play/image/zy_toggle_icon.png


二進制
src/views/courseware-play/image/zy_vedio_icon.png


+ 4 - 0
src/views/courseware-play/index.module.less

@@ -133,6 +133,10 @@
   overflow: hidden;
   z-index: 1;
 
+  .tempoPracticeGroup {
+    width: 100%;
+  }
+
   &.itemActive {
     z-index: 10;
   }

+ 397 - 95
src/views/courseware-play/index.tsx

@@ -1,4 +1,4 @@
-import { closeToast, Icon, Popup, showDialog, showToast } from 'vant';
+import { closeToast, Form, Icon, Popup, showDialog, showToast } from 'vant';
 import {
   defineComponent,
   onMounted,
@@ -37,20 +37,32 @@ import {
   api_classLessonCoursewareQuery,
   api_lessonCoursewareKnowledgeDetailDetail
 } from './api';
+import {
+  api_lessonDetailCourseware,
+  api_classDetailCourseware
+} from '../courseware-list/api';
 import VideoItem from './component/video-item';
 import Chapter from './component/chapter';
 import {
   api_classLessonCoursewareDetail,
   api_lessonCoursewareDetail
 } from '../courseware-list/api';
-import detail from '../information/help-center/detail';
+// import detail from '../information/help-center/detail';
 import { state } from '@/state';
+import Theory from './component/theory';
+import InstrumentInfo from './component/instrument-info';
+// import TempoPractice from '../../views/tempo-practice';
+import SelectCoursewarePop from '@/components/select-courseware-pop';
+import { debounce } from '../../helpers/utils';
+import TempoItem from './component/tempo-item';
 
 export default defineComponent({
   name: 'CoursewarePlay',
   setup() {
     const pageVisibility = usePageVisibility();
     const lastTimeKey = 'lastTime' + (state?.user?.data?.phone ?? '');
+    const showSelectCourseware = ref(false);
+    const debounceSkip = ref(false);
     /** 设置播放容器 16:9 */
     const parentContainer = reactive({
       width: '100vw'
@@ -118,7 +130,7 @@ export default defineComponent({
           });
           if (res?.code == 200 && Array.isArray(res?.data?.lessonList)) {
             data.courseDetails = res.data.lessonList || [];
-            console.log('🚀 ~ data.details course:', data.courseDetails);
+            // console.log('🚀 ~ data.details course:', data.courseDetails);
           }
         } else {
           const res = await api_lessonCoursewareDetail({
@@ -130,7 +142,7 @@ export default defineComponent({
           }
         }
 
-        console.log(data.courseDetails, 'data.courseDetails');
+        // console.log(data.courseDetails, 'data.courseDetails');
       } catch {
         //
       }
@@ -140,21 +152,26 @@ export default defineComponent({
     const headeRef = ref();
     const loadingClass = ref(false); // 重新加载课件
     const data = reactive({
-      knowledgePointList: [] as any,
+      allList: [] as any, // 所选章节下的所有课件列表
+      kjId: route.query.id as string, // 所选的课件id
+      currentCourse: {} as any, // 当前选中的课件
+      zsdId: '' as string, // 知识点id
+      knowledgePointList: [] as any, //所选课件,所选知识点下所有的资源列表
       courseDetails: [] as any,
       itemList: [] as any,
       videoRefs: {} as any[],
 
       videoState: 'init' as 'init' | 'play',
       videoItemRef: null as any,
-      animationState: 'start' as 'start' | 'end'
+      animationState: 'start' as 'start' | 'end',
+      coursewareList: []
     });
     const activeData = reactive({
       isAutoPlay: true, // 是否自动播放
       subjectId: route.query.subjectId,
       lessonCoursewareId: route.query.lessonCoursewareId,
       lessonCoursewareDetailId: route.query.lessonCoursewareDetailId,
-      coursewareDetailKnowledgeId: route.query.id,
+      coursewareDetailKnowledgeId: route.query.coursewareDetailKnowledgeId,
       courseId: route.query.courseId, // 我的课程专用编号
       nowTime: 0,
       model: true, // 遮罩
@@ -165,42 +182,79 @@ export default defineComponent({
       timer: null as any,
       item: null as any
     });
+    //  切换单元临时数据
+    const temporaryData = reactive({
+      dyId: '', // 单元id
+      zjId: '' // 章节id
+    });
     const getDetail = async () => {
+      data.allList = [];
       let courseList: any[] = [];
       if (route.query.tab == 'course') {
-        const res = await api_classLessonCoursewareQuery({
-          coursewareDetailKnowledgeId: activeData.coursewareDetailKnowledgeId,
-          subjectId: activeData.subjectId,
-          page: 1,
-          rows: -1
+        // const res = await api_classLessonCoursewareQuery({
+        //   coursewareDetailKnowledgeId: activeData.coursewareDetailKnowledgeId,
+        //   subjectId: activeData.subjectId,
+        //   page: 1,
+        //   rows: -1
+        // });
+        const res = await api_classDetailCourseware({
+          lessonCoursewareKnowledgeDetailId:
+            activeData.coursewareDetailKnowledgeId // 章节id
         });
-        if (res?.code === 200 && Array.isArray(res.data.rows)) {
-          const tempRows = res.data.rows || [];
-          tempRows.forEach((item: any) => {
-            courseList.push({
-              content: item.content,
-              coverImg: item.coverImg,
-              id: item.id,
-              materialId: item.materialId,
-              name: item.materialName,
-              relOrder: 0,
-              sourceFrom: item.source,
-              type: item.materialType
+        if (res?.code === 200 && Array.isArray(res.data)) {
+          // const tempRows = res.data.rows || [];
+          // tempRows.forEach((item: any) => {
+          //   courseList.push({
+          //     content: item.content,
+          //     coverImg: item.coverImg,
+          //     id: item.id,
+          //     materialId: item.materialId,
+          //     name: item.materialName,
+          //     relOrder: 0,
+          //     sourceFrom: item.source,
+          //     type: item.materialType
+          //   });
+          // });
+          res.data.forEach((item: any) => {
+            item.knowledgeList = item.resourceList;
+            item.resourceList.forEach((n: any) => {
+              n.materialInfo = n.resourceList;
             });
           });
+          data.allList = res.data;
+          const currentCourse = res.data.find(
+            (item: any) => item.id === data.kjId
+          );
+          data.zsdId = currentCourse?.knowledgeList?.[0].id;
+          courseList = currentCourse?.knowledgeList?.[0].materialInfo || [];
+          data.currentCourse = currentCourse || {}
         }
       } else {
-        const res = await api_lessonCoursewareKnowledgeDetailDetail({
+        // const res = await api_lessonCoursewareKnowledgeDetailDetail({
+        //   lessonCoursewareKnowledgeDetailId:
+        //     activeData.coursewareDetailKnowledgeId,
+        //   subjectId: activeData.subjectId
+        // });
+        const res = await api_lessonDetailCourseware({
           lessonCoursewareKnowledgeDetailId:
-            activeData.coursewareDetailKnowledgeId,
-          subjectId: activeData.subjectId
+            activeData.coursewareDetailKnowledgeId // 章节id
         });
         if (res?.code === 200 && Array.isArray(res.data)) {
-          courseList = res.data || [];
+          data.allList = res.data;
+          const currentCourse = res.data.find(
+            (item: any) => item.id === data.kjId
+          );
+          data.zsdId = currentCourse?.knowledgeList?.[0].id;
+          courseList = currentCourse?.knowledgeList?.[0].materialInfo || [];
+          data.currentCourse = currentCourse || {}
+          // console.log('课件类型', data.allList);
         }
       }
+      // 当前的资源id
+      let resourceId: any = null;
       // 课程
       if (courseList.length > 0) {
+        resourceId = courseList[0].id;
         data.knowledgePointList = courseList.map((item: any) => {
           return {
             ...item,
@@ -213,8 +267,49 @@ export default defineComponent({
           };
         });
       }
+      // 当前章节下的所有资源列表
+      let allResource: any = [];
+      data.allList.forEach((item: any) => {
+        item.knowledgeList.forEach((material: any) => {
+          material.materialInfo.forEach((resource: any) => {
+            resource.zsdId = material.id; // 知识点id
+            resource.kjId = item.id; // 课件id
+            resource.bizId =
+              route.query.tab == 'course'
+                ? resource.materialId
+                : resource.bizId;
+            resource.url =
+              resource.type === 'SONG'
+                ? 'https://oss.dayaedu.com/ktqy/1698420034679a22d3f7a.png'
+                : resource.type === 'PPT'
+                ? 'https://oss.dayaedu.com/ktqy/12/1701931810284.png'
+                : resource.coverImg;
+          });
+          allResource = allResource.concat(material.materialInfo);
+        });
+      });
+
+      // 当前章节下,所选的课件所有资源列表
+      let allKjResource: any = [];
+      data.currentCourse?.knowledgeList?.forEach((material: any) => {
+        material.materialInfo.forEach((resource: any) => {
+          resource.zsdId = material.id; // 知识点id
+          resource.kjId = data.currentCourse.id; // 课件id
+          resource.bizId =
+            route.query.tab == 'course'
+              ? resource.materialId
+              : resource.bizId;
+          resource.url =
+            resource.type === 'SONG'
+              ? 'https://oss.dayaedu.com/ktqy/1698420034679a22d3f7a.png'
+              : resource.type === 'PPT'
+              ? 'https://oss.dayaedu.com/ktqy/12/1701931810284.png'
+              : resource.coverImg;
+        });
+        allKjResource = allKjResource.concat(material.materialInfo);
+      });
 
-      data.itemList = data.knowledgePointList.map((m: any, index: number) => {
+      data.itemList = allKjResource.map((m: any, index: number) => {
         if (!popupData.itemActive) {
           popupData.itemActive = m.id;
           popupData.itemName = m.name;
@@ -228,7 +323,14 @@ export default defineComponent({
           isRender: false // 是否渲染了
         };
       });
+      const resourceIndex = data.itemList.findIndex(
+        (resource: any) => resource.id === resourceId
+      );
+      setTimeout(() => {
+        handleSwipeChange(resourceIndex);
+      }, 0);
 
+      console.log('资源', data.itemList, resourceIndex);
       setTimeout(() => {
         data.animationState = 'end';
       }, 500);
@@ -244,6 +346,10 @@ export default defineComponent({
         clearInterval(activeData.timer);
         activeData.model = !ev.data.state;
       }
+
+      if (ev.data?.api === 'clickTempo') {
+        setModelOpen();
+      }
     };
 
     onMounted(() => {
@@ -279,7 +385,21 @@ export default defineComponent({
     });
 
     // 切换素材
-    const toggleMaterial = (itemActive: any) => {
+    const toggleMaterial = (itemActive: any, zsdId: any, kjId: any) => {
+      // 如果切换了知识点或者切换了课件,需要加载新的
+      // if (zsdId !== data.zsdId || kjId !== data.kjId) {
+      //   data.zsdId = zsdId
+      //   data.kjId = kjId
+      //   let target = { materialInfo: [] }
+      //   // 如果是切换了知识点id
+      //   const kjIndex = data.allList.findIndex((item: any) => item.id === kjId)
+      //   target = data.allList[kjIndex].knowledgeList.find((item: any) => item.id === zsdId)
+      // } else {
+      //   const index = data.itemList.findIndex((n: any) => n.id == itemActive);
+      //   if (index > -1) {
+      //     handleSwipeChange(index);
+      //   }
+      // }
       const index = data.itemList.findIndex((n: any) => n.id == itemActive);
       if (index > -1) {
         handleSwipeChange(index);
@@ -370,7 +490,7 @@ export default defineComponent({
       }
     ];
     const handleStop = () => {
-      data.videoItemRef.pause();
+      data.videoItemRef?.pause();
     };
     const acitveTimer = ref();
     // 轮播切换
@@ -383,6 +503,8 @@ export default defineComponent({
       clearTimeout(acitveTimer.value);
       activeData.model = true;
       const item = data.itemList[index];
+      data.kjId = item.kjId;
+      data.zsdId = item.zsdId;
       popupData.activeIndex = index;
       popupData.itemActive = item.id;
       popupData.itemName = item.name;
@@ -400,6 +522,7 @@ export default defineComponent({
 
     // 上一个知识点, 下一个知识点
     const handlePreAndNext = async (type: string) => {
+      // 层级关系:单元〉章节〉知识点〉课件资源
       if (type === 'up') {
         // 判断上面是否还有章节
         if (popupData.activeIndex > 0) {
@@ -420,6 +543,7 @@ export default defineComponent({
         let lessonCoursewareDetailId = '';
         let coursewareDetailKnowledgeId = '';
         let coursewareDetailKnowledgeName = '';
+        let coursewareItem = {} as any;
         while (lessonIndex >= 0) {
           lessonIndex--;
 
@@ -430,6 +554,7 @@ export default defineComponent({
                 detailItem[lessonIndex].lessonCoursewareDetailId;
               coursewareDetailKnowledgeId = detailItem[lessonIndex].id;
               coursewareDetailKnowledgeName = detailItem[lessonIndex].name;
+              coursewareItem = detailItem[lessonIndex];
             }
           }
 
@@ -439,21 +564,29 @@ export default defineComponent({
         }
         // 判断当前章节下面课程是否有内容,否则往上一个章节走
         if (lessonStatus) {
-          loadingClass.value = true;
-          activeData.coursewareDetailKnowledgeId = coursewareDetailKnowledgeId;
-          activeData.lessonCoursewareDetailId = lessonCoursewareDetailId;
-          await getDetail();
-          popupData.activeIndex = data.itemList.length - 1 || 0;
-          popupData.itemActive =
-            data.knowledgePointList[data.itemList.length - 1]?.id ||
-            data.knowledgePointList[0]?.id;
-          popupData.itemPointName = coursewareDetailKnowledgeName;
-          popupData.itemName =
-            data.knowledgePointList[data.itemList.length - 1]?.name ||
-            data.knowledgePointList[0]?.name;
-          localStorage.setItem(lastTimeKey, coursewareDetailKnowledgeId);
-          popupData.chapterOpen = false;
-          loadingClass.value = false;
+          // loadingClass.value = true;
+          // activeData.coursewareDetailKnowledgeId = coursewareDetailKnowledgeId;
+          // activeData.lessonCoursewareDetailId = lessonCoursewareDetailId;
+          // await getDetail();
+          // popupData.activeIndex = data.itemList.length - 1 || 0;
+          // popupData.itemActive =
+          //   data.knowledgePointList[data.itemList.length - 1]?.id ||
+          //   data.knowledgePointList[0]?.id;
+          // popupData.itemPointName = coursewareDetailKnowledgeName;
+          // popupData.itemName =
+          //   data.knowledgePointList[data.itemList.length - 1]?.name ||
+          //   data.knowledgePointList[0]?.name;
+          // localStorage.setItem(lastTimeKey, coursewareDetailKnowledgeId);
+          // popupData.chapterOpen = false;
+          // loadingClass.value = false;
+          // console.log
+          temporaryData.zjId = coursewareDetailKnowledgeId;
+          temporaryData.dyId = lessonCoursewareDetailId;
+          // activeData.coursewareDetailKnowledgeId = coursewareDetailKnowledgeId;
+          checkCourseware({
+            ...coursewareItem,
+            itemActive: coursewareDetailKnowledgeId
+          });
           return;
         }
 
@@ -471,6 +604,7 @@ export default defineComponent({
               coursewareDetailKnowledgeId = tempDetail[tempLessonLength - 1].id;
               coursewareDetailKnowledgeName =
                 tempDetail[tempLessonLength - 1].name;
+              coursewareItem = tempDetail[tempLessonLength - 1];
             }
             tempLessonLength--;
             if (prevLessonStatus) {
@@ -485,21 +619,29 @@ export default defineComponent({
 
         // 判断当前章节下面课程是否有内容,否则往上一个章节走
         if (prevLessonStatus) {
-          loadingClass.value = true;
-          activeData.coursewareDetailKnowledgeId = coursewareDetailKnowledgeId;
-          activeData.lessonCoursewareDetailId = lessonCoursewareDetailId;
-          await getDetail();
-          popupData.activeIndex = data.itemList.length - 1 || 0;
-          popupData.itemActive =
-            data.knowledgePointList[data.itemList.length - 1]?.id ||
-            data.knowledgePointList[0]?.id;
-          localStorage.setItem(lastTimeKey, coursewareDetailKnowledgeId);
-          popupData.itemPointName = coursewareDetailKnowledgeName;
-          popupData.itemName =
-            data.knowledgePointList[data.itemList.length - 1]?.name ||
-            data.knowledgePointList[0]?.name;
-          popupData.chapterOpen = false;
-          loadingClass.value = false;
+          // loadingClass.value = true;
+          // activeData.coursewareDetailKnowledgeId = coursewareDetailKnowledgeId;
+          // activeData.lessonCoursewareDetailId = lessonCoursewareDetailId;
+          // await getDetail();
+          // popupData.activeIndex = data.itemList.length - 1 || 0;
+          // popupData.itemActive =
+          //   data.knowledgePointList[data.itemList.length - 1]?.id ||
+          //   data.knowledgePointList[0]?.id;
+          // localStorage.setItem(lastTimeKey, coursewareDetailKnowledgeId);
+          // popupData.itemPointName = coursewareDetailKnowledgeName;
+          // popupData.itemName =
+          //   data.knowledgePointList[data.itemList.length - 1]?.name ||
+          //   data.knowledgePointList[0]?.name;
+          // popupData.chapterOpen = false;
+          // loadingClass.value = false;
+          // console.log('2', coursewareItem, coursewareDetailKnowledgeId);
+          temporaryData.zjId = coursewareDetailKnowledgeId;
+          temporaryData.dyId = lessonCoursewareDetailId;
+          // activeData.coursewareDetailKnowledgeId = coursewareDetailKnowledgeId;
+          checkCourseware({
+            ...coursewareItem,
+            itemActive: coursewareDetailKnowledgeId
+          });
           return;
         }
       } else {
@@ -508,11 +650,13 @@ export default defineComponent({
           return;
         }
 
-        // 获取当前是哪个章节
+        // 获取当前是哪个单元
         let detailIndex = data.courseDetails.findIndex(
           (item: any) => item.id == activeData.lessonCoursewareDetailId
         );
+        // 当前章节列表
         const detailItem = data.courseDetails[detailIndex]?.knowledgeList || [];
+        // 获取当前是哪个章节
         let lessonIndex = detailItem.findIndex(
           (item: any) => item.id == activeData.coursewareDetailKnowledgeId
         );
@@ -521,6 +665,7 @@ export default defineComponent({
         let lessonCoursewareDetailId = '';
         let coursewareDetailKnowledgeId = '';
         let coursewareDetailKnowledgeName = '';
+        let coursewareItem = {} as any;
         while (lessonIndex < detailItem.length - 1) {
           lessonIndex++;
           if (lessonIndex >= 0) {
@@ -530,6 +675,7 @@ export default defineComponent({
                 detailItem[lessonIndex].lessonCoursewareDetailId;
               coursewareDetailKnowledgeId = detailItem[lessonIndex].id;
               coursewareDetailKnowledgeName = detailItem[lessonIndex].name;
+              coursewareItem = detailItem[lessonIndex];
             }
           }
           if (lessonStatus) {
@@ -538,17 +684,25 @@ export default defineComponent({
         }
         // 判断当前章节下面课程是否有内容,否则往下一个章节走
         if (lessonStatus) {
-          loadingClass.value = true;
-          activeData.coursewareDetailKnowledgeId = coursewareDetailKnowledgeId;
-          activeData.lessonCoursewareDetailId = lessonCoursewareDetailId;
-          await getDetail();
-          popupData.activeIndex = 0;
-          popupData.itemActive = data.knowledgePointList[0].id;
-          popupData.itemName = data.knowledgePointList[0].name;
-          localStorage.setItem(lastTimeKey, coursewareDetailKnowledgeId);
-          popupData.itemPointName = coursewareDetailKnowledgeName;
-          popupData.chapterOpen = false;
-          loadingClass.value = false;
+          // loadingClass.value = true;
+          // activeData.coursewareDetailKnowledgeId = coursewareDetailKnowledgeId;
+          // activeData.lessonCoursewareDetailId = lessonCoursewareDetailId;
+          // await getDetail();
+          // popupData.activeIndex = 0;
+          // popupData.itemActive = data.knowledgePointList[0].id;
+          // popupData.itemName = data.knowledgePointList[0].name;
+          // localStorage.setItem(lastTimeKey, coursewareDetailKnowledgeId);
+          // popupData.itemPointName = coursewareDetailKnowledgeName;
+          // popupData.chapterOpen = false;
+          // loadingClass.value = false;
+          temporaryData.zjId = coursewareDetailKnowledgeId;
+          temporaryData.dyId = lessonCoursewareDetailId;
+          // activeData.coursewareDetailKnowledgeId = coursewareDetailKnowledgeId;
+          // console.log(coursewareItem, 'coursewareItem', temporaryData);
+          checkCourseware({
+            ...coursewareItem,
+            itemActive: coursewareDetailKnowledgeId
+          });
           return;
         }
 
@@ -565,6 +719,7 @@ export default defineComponent({
                 tempDetail[tempLessonLength].lessonCoursewareDetailId;
               coursewareDetailKnowledgeId = tempDetail[tempLessonLength].id;
               coursewareDetailKnowledgeName = tempDetail[tempLessonLength].name;
+              coursewareItem = tempDetail[tempLessonLength];
             }
             tempLessonLength++;
             if (nextLessonStatus) {
@@ -577,19 +732,27 @@ export default defineComponent({
           }
         }
 
-        // 判断当前章节下面课程是否有内容,否则往上一个章节
+        // 判断当前章节下面课程是否有内容,否则往下一个单元
         if (nextLessonStatus) {
-          loadingClass.value = true;
-          activeData.coursewareDetailKnowledgeId = coursewareDetailKnowledgeId;
-          activeData.lessonCoursewareDetailId = lessonCoursewareDetailId;
-          await getDetail();
-          popupData.activeIndex = 0;
-          popupData.itemActive = data.knowledgePointList[0].id;
-          localStorage.setItem(lastTimeKey, coursewareDetailKnowledgeId);
-          popupData.itemName = data.knowledgePointList[0].name;
-          popupData.itemPointName = coursewareDetailKnowledgeName;
-          popupData.chapterOpen = false;
-          loadingClass.value = false;
+          // loadingClass.value = true;
+          // activeData.coursewareDetailKnowledgeId = coursewareDetailKnowledgeId;
+          // activeData.lessonCoursewareDetailId = lessonCoursewareDetailId;
+          // await getDetail();
+          // popupData.activeIndex = 0;
+          // popupData.itemActive = data.knowledgePointList[0].id;
+          // localStorage.setItem(lastTimeKey, coursewareDetailKnowledgeId);
+          // popupData.itemName = data.knowledgePointList[0].name;
+          // popupData.itemPointName = coursewareDetailKnowledgeName;
+          // popupData.chapterOpen = false;
+          // loadingClass.value = false;
+          temporaryData.zjId = coursewareDetailKnowledgeId;
+          temporaryData.dyId = lessonCoursewareDetailId;
+          // activeData.coursewareDetailKnowledgeId = coursewareDetailKnowledgeId;
+          // console.log(coursewareItem, 'coursewareItem', temporaryData);
+          checkCourseware({
+            ...coursewareItem,
+            itemActive: coursewareDetailKnowledgeId
+          });
           return;
         }
       }
@@ -673,6 +836,7 @@ export default defineComponent({
 
     // 是否允许下一页
     const isDownArrow = computed(() => {
+      console.log(22222222222)
       if (popupData.activeIndex < data.itemList.length - 1) {
         return true;
       }
@@ -703,6 +867,7 @@ export default defineComponent({
           }
         }
       }
+
       // 判断当前章节下面课程是否有内容,否则往下一个章节走
       if (lessonStatus) {
         return true;
@@ -740,6 +905,86 @@ export default defineComponent({
       }
       return {};
     });
+
+    // 加载新的章节里的课件
+    const loadNewCourseware = async (item: any) => {
+      if(item.id === data.kjId) {
+        showSelectCourseware.value = false;
+        return
+      }
+      if (debounceSkip.value) return;
+      debounceSkip.value = true;
+      data.itemList = [];
+      loadingClass.value = true;
+      // activeData.coursewareDetailKnowledgeId = item.coursewareDetailKnowledgeId;
+      // activeData.lessonCoursewareDetailId = item.lessonCoursewareDetailId;
+      if (route.query.tab == 'all') {
+        activeData.coursewareDetailKnowledgeId =
+          item.coursewareDetailKnowledgeId;
+        activeData.lessonCoursewareDetailId = temporaryData.dyId;
+        localStorage.setItem(lastTimeKey, item.coursewareDetailKnowledgeId);
+      } else {
+        activeData.lessonCoursewareDetailId = temporaryData.dyId;
+        activeData.coursewareDetailKnowledgeId = temporaryData.zjId;
+        localStorage.setItem(lastTimeKey, temporaryData.zjId);
+      }
+      // console.log(item, 'item', route.query.tab);
+      // console.log(activeData, temporaryData, 'active');
+      popupData.chapterOpen = false;
+      showSelectCourseware.value = false;
+      data.kjId = item.id;
+      await getDetail();
+      popupData.activeIndex = 0;
+      popupData.itemActive = data.knowledgePointList[0].id;
+      popupData.itemName = data.knowledgePointList[0].name;
+      // 匹配到当前的章节名称
+      const dyItem = data.courseDetails.find(
+        (unit: any) => unit.id === activeData.lessonCoursewareDetailId
+      );
+      const zjItem = dyItem?.knowledgeList?.find(
+        (chapter: any) => chapter.id === activeData.coursewareDetailKnowledgeId
+      );
+      zjItem && (popupData.itemPointName = zjItem.name);
+      // console.log('章节名称', popupData.itemPointName);
+      loadingClass.value = false;
+      debounceSkip.value = false;
+    };
+    // 通过章节id,检测此章节有几个课件
+    const checkCourseware = async (item: any, checkType?: any) => {
+      // 如果有多个课件,需要选择一个课件进入上课页面
+      if (item.coursewareNum || checkType) {
+        try {
+          if (checkType) {
+            // @ts-ignore
+            temporaryData.zjId = activeData.coursewareDetailKnowledgeId
+            // @ts-ignore
+            temporaryData.dyId = activeData.lessonCoursewareDetailId
+          }
+          const res =
+            route.query.tab == 'all'
+              ? await api_lessonDetailCourseware({
+                  lessonCoursewareKnowledgeDetailId: checkType ? activeData.coursewareDetailKnowledgeId : item.itemActive
+                })
+              : await api_classDetailCourseware({
+                  lessonCoursewareKnowledgeDetailId: checkType ? activeData.coursewareDetailKnowledgeId : item.itemActive
+                });
+          if (res?.code == 200 && res.data?.length) {
+            data.coursewareList = res.data;
+            // 如果只有一个课件,直接进入该课件
+            // console.log(res.data, 'data');
+            if (res.data.length == 1) {
+              loadNewCourseware({ ...res.data[0] });
+            } else {
+              // 如果有多个课件,需要选择一个课件进入上课页面
+              showSelectCourseware.value = true;
+            }
+          }
+        } catch {
+          //
+        }
+      }
+    };
+
     return () => (
       <div id="playContent" class={styles.playContent}>
         <div
@@ -906,6 +1151,50 @@ export default defineComponent({
                         )}
                       </Transition>
                     )}
+
+                    {/* 新增:RHYTHM:节奏练习,THEORY:乐理知识,MUSIC_WIKI:名曲鉴赏 INSTRUMENT:乐器 MUSICIAN:音乐家 资源类型 */}
+                    {m.type === 'RHYTHM' && (
+                      <TempoItem
+                        key={mIndex}
+                        class={styles.tempoPracticeGroup}
+                        dataJson={m.dataJson}
+                        show={popupData.activeIndex === mIndex}
+                        pageVisibility={pageVisibility.value}
+                      />
+                      // <TempoPractice
+                      //   key={mIndex}
+                      //   class={styles.tempoPracticeGroup}
+                      //   dataJson={m?.dataJson ? JSON.parse(m?.dataJson) : {}}
+                      //   modeType={'courseware'}
+                      //   show={popupData.activeIndex === mIndex}
+                      // />
+                    )}
+                    {m.type === 'THEORY' && <Theory id={m.bizId} />}
+                    {/* {popupData.activeIndex === mIndex && ( */}
+                    <>
+                      {m.type === 'MUSIC_WIKI' && (
+                        <InstrumentInfo
+                          type={'wiki'}
+                          id={m.bizId}
+                          show={popupData.activeIndex === mIndex}
+                        />
+                      )}
+                      {m.type === 'INSTRUMENT' && (
+                        <InstrumentInfo
+                          type={'instrument'}
+                          id={m.bizId}
+                          show={popupData.activeIndex === mIndex}
+                        />
+                      )}
+                      {m.type === 'MUSICIAN' && (
+                        <InstrumentInfo
+                          type={'musician'}
+                          id={m.bizId}
+                          show={popupData.activeIndex === mIndex}
+                        />
+                      )}
+                    </>
+                    {/* )} */}
                   </div>
                 ) : (
                   <div
@@ -996,13 +1285,22 @@ export default defineComponent({
           v-model:show={popupData.open}
           onClose={handleClosePopup}>
           <Points
+            allList={data.allList}
+            currentCourse={data.currentCourse}
             data={data.knowledgePointList}
             itemActive={popupData.itemActive}
             itemName={popupData.itemPointName}
+            kjId={data.kjId}
+            zsdId={data.zsdId}
+            popShow={popupData.open}
             onHandleSelect={(res: any) => {
               popupData.open = false;
-              toggleMaterial(res.itemActive);
+              toggleMaterial(res.itemActive, res.zsdId, res.kjId);
             }}
+            onCourseSelect={ async () => {
+              popupData.open = false;
+              checkCourseware({}, 'same')
+            }}            
           />
         </Popup>
         {/* 知识点列表 */}
@@ -1018,20 +1316,24 @@ export default defineComponent({
             detail={data.courseDetails}
             itemActive={activeData.coursewareDetailKnowledgeId as any}
             active={activeData.lessonCoursewareDetailId as any}
+            popShow={popupData.chapterOpen}
             onHandleSelect={async (item: any) => {
-              loadingClass.value = true;
-              activeData.coursewareDetailKnowledgeId = item.itemActive;
-              activeData.lessonCoursewareDetailId = item.tabActive;
-              await getDetail();
-              popupData.activeIndex = 0;
-              popupData.itemActive = data.knowledgePointList[0].id;
-              popupData.itemName = data.knowledgePointList[0].name;
+              temporaryData.dyId = item.tabActive;
+              temporaryData.zjId = item.itemActive;
               popupData.itemPointName = item.itemName;
-              popupData.chapterOpen = false;
-              loadingClass.value = false;
+              checkCourseware(item);
             }}
           />
         </Popup>
+        {showSelectCourseware.value && (
+          <SelectCoursewarePop
+            list={data.coursewareList}
+            kjId={data.kjId}
+            onClose={() => {
+              showSelectCourseware.value = false;
+            }}
+            onSelect={item => loadNewCourseware(item)}></SelectCoursewarePop>
+        )}
       </div>
     );
   }

+ 47 - 6
src/views/tempo-practice/index.module.less

@@ -10,7 +10,42 @@
   background: url("./images/bg.png") no-repeat center center / cover;
 
   display: flex;
-  flex-direction: column;
+
+  &.modal {
+    .iconBack {
+      opacity: 0;
+      pointer-events: none;
+      visibility: hidden;
+    }
+  }
+
+  &.courseware {
+    .head {
+      justify-content: center;
+    }
+  }
+
+  // &.dpi3 {
+  //   .beatSection {
+
+  //     .beat {
+  //       width: 102px;
+  //       height: 136px;
+  //     }
+
+  //     &.small {
+  //       width: 50%;
+
+  //       // margin: 0 16px;
+  //       &.dpi3 {
+  //         .beat {
+  //           width: 60px;
+  //           height: 72px;
+  //         }
+  //       }
+  //     }
+  //   }
+  // }
 }
 
 .conCon {
@@ -36,6 +71,8 @@
       }
     }
 
+
+
     &.small {
       .beat {
         width: 139px !important;
@@ -89,7 +126,8 @@
   justify-content: center;
   flex: 1 auto;
   flex-wrap: wrap;
-  gap: 15px 0;
+  // gap: 15px 0;
+  // margin-bottom: 15px;
   max-width: 900px;
   margin: 0 auto;
 }
@@ -98,6 +136,8 @@
   display: flex;
   align-items: center;
   justify-content: center;
+  // margin-bottom: 15px;
+  margin: 7px 0;
 
   &.small {
     width: 50%;
@@ -113,15 +153,16 @@
       padding-left: 12px;
     }
 
+
     .beat {
       border: 2px solid #fff;
-      width: 65px;
-      height: 86px;
+      // width: 65px;
+      // height: 86px;
+      width: 60px;
+      height: 72px;
       cursor: pointer;
       margin: 0 7px;
 
-
-
       &::before,
       &::after {
         width: 19px;

+ 181 - 24
src/views/tempo-practice/index.tsx

@@ -1,4 +1,11 @@
-import { defineComponent, onMounted, reactive, ref } from 'vue';
+import {
+  defineComponent,
+  onMounted,
+  onUnmounted,
+  reactive,
+  ref,
+  watch
+} from 'vue';
 import styles from './index.module.less';
 import { postMessage } from '@/helpers/native-message';
 import icon_title from './images/icon-title.png';
@@ -29,9 +36,24 @@ import { useRoute } from 'vue-router';
 
 export default defineComponent({
   name: 'tempo-practice',
-  setup() {
+  props: {
+    dataJson: {
+      type: Object,
+      default: () => {}
+    },
+    modeType: {
+      type: String,
+      default: ''
+    },
+    show: {
+      type: Boolean,
+      default: false
+    }
+  },
+  setup(props, { expose }) {
     const route = useRoute();
     const state = reactive({
+      modeType: '' as any,
       platform: route.query.platform, // microapp 老师端应用里面打开单独处理返回逻辑
       win: route.query.win,
       settingStatus: false,
@@ -53,7 +75,8 @@ export default defineComponent({
         { text: '180', value: 180, color: '#060606' },
         { text: '190', value: 190, color: '#060606' },
         { text: '200', value: 200, color: '#060606' }
-      ]
+      ],
+      dataJson: {} as any
     });
     // 返回
     const goback = () => {
@@ -106,38 +129,144 @@ export default defineComponent({
       }
     };
 
+    // {"element":"jianpu","beat":"4-4","barLine":"1","tempo":["1","2","3"]}'
+    const onIframeHandle = (ev: MessageEvent) => {
+      // 获取配置
+      if (ev.data.api === 'getTempoSetting') {
+        window.parent.postMessage(
+          {
+            api: 'getTempoSetting',
+            data: JSON.stringify({
+              setting: {
+                element: setting.element,
+                beat: setting.beat,
+                barLine: setting.barLine,
+                tempo: setting.tempo,
+                scorePart: setting.scorePart,
+                playType: setting.playType,
+                speed: setting.speed
+              },
+              coverImg: ''
+            })
+          },
+          '*'
+        );
+      }
+
+      if (ev.data.api === 'setPlayState') {
+        if (ev.data.data) {
+          handlePlay();
+        } else {
+          handleStop();
+        }
+      }
+
+      if (ev.data.api === 'resetPlay') {
+        resetSetting();
+      }
+    };
+
+    const resetSetting = () => {
+      try {
+        let dataJson = props.dataJson;
+        if (route.query.dataJson) {
+          dataJson = JSON.parse(route.query.dataJson as any);
+        }
+        console.log(dataJson, 'dataJson', props.dataJson);
+
+        setting.element = dataJson.element;
+        setting.beat = dataJson.beat;
+        setting.barLine = dataJson.barLine;
+        setting.tempo = dataJson.tempo;
+        setting.scorePart = dataJson.scorePart;
+        setting.playType = dataJson.playType;
+        setting.speed = dataJson.speed;
+
+        state.dataJson = dataJson;
+      } catch {
+        //
+      }
+    };
+
+    // watch(
+    //   () => props.show,
+    //   val => {
+    //     console.log(val, props.show);
+    //     if (!val) {
+    //       // resetSetting();
+    //       handleStop();
+    //     } else {
+    //       resetSetting();
+    //     }
+    //   }
+    // );
+
     onMounted(() => {
+      if (route.query.modeType) {
+        state.modeType = route.query.modeType;
+      }
+      resetSetting();
       state.speedList.forEach((item: any) => {
         if (item.value === setting.speed) item.color = '#1CACF1';
       });
-      renderScore();
+      if (setting?.scorePart?.length <= 0) {
+        renderScore();
+      }
+
+      window.addEventListener('message', onIframeHandle);
+    });
+
+    onUnmounted(() => {
+      window.removeEventListener('message', onIframeHandle);
+    });
+
+    expose({
+      resetSetting
     });
     return () => (
-      <div class={[styles.tempoPractice, state.win === 'pc' ? styles.pc : '']}>
+      <div
+        onClick={() => {
+          window.parent.postMessage(
+            {
+              api: 'clickTempo'
+            },
+            '*'
+          );
+        }}
+        class={[
+          styles.tempoPractice,
+          state.win === 'pc' ? styles.pc : '',
+          state.platform === 'modal' ? styles.modal : '',
+          state.modeType === 'courseware' ? styles.courseware : ''
+        ]}>
         <div class={styles.head}>
-          <div
-            class={styles.back}
-            onClick={goback}
-            style={{ cursor: 'pointer' }}>
-            <img src={icon_back} />
-          </div>
+          {state.modeType !== 'courseware' && (
+            <div
+              class={[styles.back, styles.iconBack]}
+              onClick={goback}
+              style={{ cursor: 'pointer' }}>
+              <img src={icon_back} />
+            </div>
+          )}
           <div class={styles.title}>
             <img src={icon_title} />
           </div>
-          <div
-            class={styles.back}
-            style={{ cursor: 'pointer' }}
-            onClick={() => {
-              handleStop();
-              state.settingStatus = true;
-            }}>
-            <img src={icon_setting} />
-          </div>
+          {state.modeType !== 'courseware' && (
+            <div
+              class={styles.back}
+              style={{ cursor: 'pointer' }}
+              onClick={() => {
+                handleStop();
+                state.settingStatus = true;
+              }}>
+              <img src={icon_setting} />
+            </div>
+          )}
         </div>
 
         <div class={styles.conCon}>
           <div class={styles.container}>
-            {setting.scorePart.map((item: any, i: number) => (
+            {setting.scorePart?.map((item: any, i: number) => (
               <div
                 class={[
                   styles.beatSection,
@@ -147,7 +276,10 @@ export default defineComponent({
                 ]}>
                 {item.map((child: any, jIndex: number) => (
                   <div
-                    class={[styles.beat, child.selected ? styles.active : '']}>
+                    class={[styles.beat, child.selected ? styles.active : '']}
+                    onClick={(e: any) => {
+                      e.stopPropagation();
+                    }}>
                     <div class={styles.direction}>
                       <div
                         class={styles.up}
@@ -188,7 +320,11 @@ export default defineComponent({
           </div>
         </div>
 
-        <div class={styles.footer}>
+        <div
+          class={styles.footer}
+          onClick={(e: any) => {
+            e.stopPropagation();
+          }}>
           {/* 播放 */}
           <div class={styles.play} onClick={handlePlay}>
             {setting.playState === 'pause' ? (
@@ -223,6 +359,15 @@ export default defineComponent({
                 if (setting.speed <= 40) return;
                 setting.speed -= 1;
                 handleStop();
+
+                state.speedList.forEach((item: any) => {
+                  if (item.value === setting.speed) {
+                    item.color = '#1CACF1';
+                    setting.speed = setting.speed;
+                  } else {
+                    item.color = '#060606';
+                  }
+                });
               }}
             />
             <Popover
@@ -255,13 +400,25 @@ export default defineComponent({
                 if (setting.speed >= 200) return;
                 setting.speed += 1;
                 handleStop();
+
+                state.speedList.forEach((item: any) => {
+                  if (item.value === setting.speed) {
+                    item.color = '#1CACF1';
+                    setting.speed = setting.speed;
+                  } else {
+                    item.color = '#060606';
+                  }
+                });
               }}
             />
           </div>
         </div>
 
         <Popup v-model:show={state.settingStatus} class={styles.settingPopup}>
-          <SettingModal onClose={() => (state.settingStatus = false)} />
+          <SettingModal
+            dataJson={state.dataJson}
+            onClose={() => (state.settingStatus = false)}
+          />
         </Popup>
       </div>
     );

+ 5 - 3
src/views/tempo-practice/setting-modal/index.module.less

@@ -44,7 +44,9 @@
     display: inline-block;
     width: 31px;
     height: 32px;
-    background: url('../images/icon-close.png') no-repeat center center / contain;
+    background: url('../images/icon-close.png') no-repeat center center;
+    background-size: 96%;
+    cursor: pointer;
   }
 }
 
@@ -139,11 +141,11 @@
   align-items: center;
   justify-content: center;
   padding: 14px 0;
-  background: linear-gradient(to bottom, rgba(255, 255, 255, 0), rgba(255, 255, 255, 1));
+  background: linear-gradient(to bottom, rgba(255, 255, 255, 0.2), rgba(255, 255, 255, 1));
   border-radius: 0 0 26px 26px;
 
   .btnSubmit {
-    width: 143px;
+    width: 135px;
     height: 45px;
     line-height: 45px;
     border-radius: 20px;

+ 11 - 4
src/views/tempo-practice/setting-modal/index.tsx

@@ -18,15 +18,22 @@ import { useRoute } from 'vue-router';
 
 export default defineComponent({
   emits: ['close'],
+  props: {
+    dataJson: {
+      type: Object,
+      default: () => {}
+    }
+  },
   name: 'setting-modal',
   setup(props, { emit }) {
     const route = useRoute();
+    const { element, beat, barLine, tempo } = props.dataJson;
     const state = reactive({
       win: route.query.win,
-      element: 'jianpu' as 'jianpu' | 'staff', // 元素
-      beat: '4-4' as '4-2' | '4-3' | '4-4' | '8-3' | '8-6', // 拍号
-      barLine: '1' as '1' | '2' | '4', // 小节数
-      tempo: ['1', '2', '3'] as any[] // 节奏形筛选
+      element: element || ('jianpu' as 'jianpu' | 'staff'), // 元素
+      beat: beat || ('4-4' as '4-2' | '4-3' | '4-4' | '8-3' | '8-6'), // 拍号
+      barLine: barLine || ('1' as '1' | '2' | '4'), // 小节数
+      tempo: tempo || (['1', '2', '3'] as any[]) // 节奏形筛选
     });
 
     const tempoList = computed(() => {

+ 2 - 1
vite.config.ts

@@ -13,9 +13,10 @@ function resolve(dir: string) {
 }
 // https://vitejs.dev/config/
 // https://github.com/vitejs/vite/issues/1930 .env
-const proxyUrl = 'https://test.lexiaoya.cn/';
+// const proxyUrl = 'https://test.lexiaoya.cn/';
 // const proxyUrl = 'https://kt.colexiu.com/';
 // const proxyUrl = 'http://192.168.3.143:7989/';
+const proxyUrl = 'https://test.kt.colexiu.com/';
 export default defineConfig({
   base: './',
   plugins: [