Browse Source

feat: 学生端查看课件

TIANYONG 1 year ago
parent
commit
8913f01732

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

@@ -0,0 +1,61 @@
+.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: 249px;
+        margin-top: 117px;
+        margin-left: 17px;
+        display: flex;
+        flex-direction: column;
+        position: relative;
+        overflow-y: scroll;
+        >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;
+            }
+        }
+    }
+}

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

@@ -0,0 +1,34 @@
+import { defineComponent, ref } 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: () => []
+    }
+  },
+  emits: ['close', 'select'],
+  setup(props, { emit }) {
+    return () => (
+      <div class={styles.popBox} onClick={(e: Event) => 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}>
+            {props.list.map((item: any, index: number) => {
+              return (
+                <li onClick={() => emit('select', item)}>{item.name}</li>
+              );
+            })}
+          </ul>
+        </div>
+      </div>
+    );
+  }
+});

BIN
src/components/select-courseware-pop/selce_courseware_close.png


BIN
src/components/select-courseware-pop/select_courseware_box.png


+ 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
+  });
+};

+ 58 - 17
src/views/courseware-list/component/book/index.tsx

@@ -19,6 +19,9 @@ 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: {
@@ -55,8 +58,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 +280,46 @@ export default defineComponent({
         }
       }
     );
-    const handleOpenPlay = (item: any) => {
+    // 检测有几个课件
+    const checkCourseware = async (item: any) => {
       if (item.id) {
         if (!item.containMaterial) {
           showToast('暂无资源');
           return;
         }
+        // 如果有多个课件,需要选择一个课件进入上课页面
+        if (item.coursewareNum && item.coursewareNum > 1) {
+          try {
+            const res = props.tab == 'all' ? await api_lessonDetailCourseware({
+              lessonCoursewareKnowledgeDetailId: item.id,
+            }): await api_classDetailCourseware({
+              lessonCoursewareKnowledgeDetailId: item.id,
+            })
+            if (res?.code == 200 ) {
+              console.log(res.data)
+              data.coursewareList = res.data;
+              showSelectCourseware.value = true;
+            } 
+          } catch {
+            //
+          }
+        } else {
+          handleOpenPlay(item);
+        }
+      }
+    }
+    const handleOpenPlay = async (item: any) => {
+      if (item.id) {
         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,
           subjectId: props.subjectId,
-          tab: props.tab // 当前切换的是哪个类型
+          tab: props.tab, // 当前切换的是哪个类型
+          coursewareDetailKnowledgeId: item.coursewareDetailKnowledgeId, // 章节id
         });
         const url =
           location.origin + location.pathname + '#/courseware-play?' + query;
@@ -303,18 +333,19 @@ export default defineComponent({
             c_orientation: 0 // 0 横屏 1 竖屏
           }
         });
-        // router.push({
-        //   path: '/courseware-play',
-        //   query: {
-        //     id: item.id,
-        //     subjectId: props.subjectId,
-        //     lessonCoursewareId: item.lessonCoursewareId,
-        //     courseId: props.bookData.id,
-        //     lessonCoursewareDetailId: item.lessonCoursewareDetailId,
-        //     name: item.name,
-        //     tab: props.tab
-        //   }
-        // });
+        router.push({
+          path: '/courseware-play',
+          query: {
+            id: item.id,
+            subjectId: props.subjectId,
+            lessonCoursewareId: item.lessonCoursewareId,
+            courseId: props.bookData.id,
+            lessonCoursewareDetailId: item.lessonCoursewareDetailId,
+            name: item.name,
+            tab: props.tab,
+            coursewareDetailKnowledgeId: item.coursewareDetailKnowledgeId
+          }
+        });
       }
     };
 
@@ -374,7 +405,7 @@ export default defineComponent({
                                 }}
                                 onClick={(e: Event) => {
                                   e.stopPropagation();
-                                  handleOpenPlay(item);
+                                  checkCourseware(item);
                                 }}
                                 onTouchend={(e: TouchEvent) => {
                                   console.log(e);
@@ -436,6 +467,16 @@ 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>
     );
   }

+ 8 - 3
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';
@@ -26,9 +26,13 @@ export default defineComponent({
   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,
@@ -86,6 +90,7 @@ export default defineComponent({
                             emit('handleSelect', {
                               itemActive: know.id,
                               itemName: know.name,
+                              coursewareNum: know.coursewareNum,
                               tabActive: item.id,
                               tabName: item.name
                             });
@@ -115,7 +120,7 @@ export default defineComponent({
               </CollapseItem>
             ))}
           </Collapse>
-        </div>
+        </div>        
       </div>
     );
   }

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

@@ -0,0 +1,167 @@
+.knowledgeBg {
+    width: 100%;
+    height: 100%;
+    background: #DDF2FF;
+    padding: 16px;
+    display: flex;
+    .left {
+        width: 250px;
+        height: 100%;
+        display: flex;
+        flex-direction: column;
+        margin-right: 10px;
+        border-radius: 8px;
+        background: #fff;
+        padding: 16px;
+        .insTop {
+            height: 138px;
+            min-height: 138px;
+            display: flex;
+            flex-direction: column;
+            align-items: center;
+            border-bottom: 1px solid #F2F2F2;
+            >img {
+                width: 74px;
+                height: 74px;
+                border-radius: 50%;
+                margin-bottom: 8px;
+            }
+            .musician {
+                width: 66px;
+                height: 83px;
+                overflow: hidden;
+            }
+            .insName {
+                color: #131415;
+                font-size: 16px;
+                margin-bottom: 4px;
+            }
+            .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 {
+                font-size: 16px;
+                color: #131415;
+                font-weight: 500;
+                margin: 12px 0 4px;
+            }    
+            .songWords {
+                font-size: 12px;
+                color: #777777;
+                >span {
+                    margin-right: 16px;
+                }
+            }  
+        }
+
+        .insList {
+            flex: 1;
+            display: flex;
+            flex-direction: column;
+            overflow-y: scroll;
+            .li {
+                display: flex;
+                align-items: center;
+                padding: 12px 0;
+                .liBg {
+                    width: 28px;
+                    height: 28px;
+                    border-radius: 20px;
+                }
+                .liName {
+                    flex: 1;
+                    margin: 0 10px;
+                    overflow-x: hidden;
+                    text-overflow: ellipsis;
+                    white-space: nowrap;
+                }
+                .liPlay {
+                    width: 20px;
+                    height: 20px;
+                }
+            }
+        }
+    }
+    .right {
+        flex: 1;
+        border-radius: 8px;
+        background: #fff;
+        padding: 16px;
+        display: flex;
+        flex-direction: column;
+        .title {
+            display: flex;
+            align-items: center;
+            font-size: 16px;
+            color: #000000;
+            font-weight: 500;
+            >img {
+                width: 24px;
+                height: 24px;
+                margin-right: 8px;
+            }
+        }
+        .desc {
+            flex: 1;
+            overflow-y: scroll;
+            font-size: 14px;
+            line-height: 20px;
+        
+            img,
+            video {
+              width: 100% !important;
+            }
+        }
+    }
+}

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

@@ -0,0 +1,118 @@
+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';
+import musicBg from '../../image/music_bg.png';
+import titleIcon1 from '../../image/title_icon1.png';
+import playIcon from '../../image/music_play_icon.png';
+
+export default defineComponent({
+  name: 'InstrumentInfo',
+  props: {
+    id: {
+      type: String,
+      default: ''
+    },
+    type: {
+      type: String,
+      default: ''
+    },
+  },
+  emits: ['close', 'select'],
+  setup(props, { emit }) {
+    const route = useRoute();
+    const forms = reactive({
+      detailId: route.query.detailId,
+      loading: false,
+      dataInfo: {} as any,
+      musicList: [] as any,
+      title: ' ',
+    });
+    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 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
+          };
+        });
+        console.log(1111,forms.musicList)
+      } catch {}
+      forms.loading = false;
+    };
+    onMounted(async () => {
+      await getDetail();
+    });
+    return () => (
+      <div class={styles.knowledgeBg}>
+        <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>   
+              <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} />
+              <div class={styles.insName}>{forms.dataInfo.name || ''}</div>
+              <div class={styles.insTro}>{forms.dataInfo.knowledgeWikiCategoryName || ''}</div>
+            </div>
+          }
+          { 
+            props.type === 'musician' && 
+            <div class={styles.insTop}>
+              <img class={styles.musician} src={forms.dataInfo.avatar} />
+              <div class={styles.insName}>{forms.dataInfo.name || ''}</div>
+              <div class={styles.insTro}>{forms.dataInfo.knowledgeWikiCategoryName || ''}</div>
+            </div>
+          }          
+          <div class={styles.insList}>
+          {forms.musicList.map((item: any, index: number) => {
+              return (
+                <div class={styles.li}>
+                  <img class={styles.liBg} src={musicBg} />
+                  <div class={styles.liName}>{item.name || '--'}</div>
+                  <img class={styles.liPlay} src={playIcon} />
+                </div>
+              );
+            })}
+          </div>
+        </div>       
+        <div class={styles.right}>
+          <div class={styles.title}><img class={styles.liBg} src={titleIcon1} />乐器简介</div>
+          <div class={styles.desc} v-html={forms.dataInfo.intros}></div>
+          {!forms.loading && !forms.dataInfo.intros && (
+            <MEmpty description="暂无内容" />
+          )}
+        </div>
+      </div>
+    );
+  }
+});

+ 71 - 2
src/views/courseware-play/component/point.module.less

@@ -28,7 +28,7 @@
   flex: 1;
   overflow-x: hidden;
   overflow-y: auto;
-  padding: 0 20px;
+  padding: 0 7px;
 
   &::-webkit-scrollbar {
     width: 0;
@@ -36,7 +36,7 @@
   }
 }
 
-.item {
+.matItem {
   border-radius: 6px;
   border: 1px solid #C2DBE2;
   background: linear-gradient(360deg, #F2F4F5 0%, #E7F9FF 100%);
@@ -94,4 +94,73 @@
       }
     }
   }
+}
+
+.collapse {
+
+  .collapseItem {
+    padding: 7px;
+  }
+
+  .collapseKnow {
+    padding-bottom: 0;
+  }
+  :global {
+    .van-cell {
+      background: transparent;
+      font-size: 13px;
+      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;
+  }
+
+  .itemImage {
+    width: 15px;
+    height: 15px;
+    margin-right: 6px;
+  }
+
+  .activeItem {
+    background: #ECF8FF;
+    border-radius: 9px;
+
+    :global {
+      .van-cell {
+        color: #1C9AF7;
+        font-weight: 600;
+      }
+    }
+  }
+
+  .itemActive {
+    font-weight: 500;
+
+    span {
+      color: #1C9AF7;
+    }
+  }
 }

+ 147 - 5
src/views/courseware-play/component/points.tsx

@@ -8,12 +8,26 @@ 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 { 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: {
+      type: String,
+      default: ''
+    },
+    zsdId: {
+      type: String,
+      default: ''
+    },
     data: {
       type: Array as PropType<any[]>,
       default: () => []
@@ -30,12 +44,19 @@ export default defineComponent({
   emits: ['handleSelect'],
   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: '音乐家',
     };
 
     // 获取对应图片
@@ -50,7 +71,18 @@ export default defineComponent({
         return props.itemActive == item.id ? iconVideoActive : iconVideo;
       }
     };
-
+    
+    const pointData = reactive({
+      allList: props.allList,
+      kjId: props.kjId, // 当前选中的课件id
+      zsdId: props.zsdId, // 当前选中的知识点id
+    });
+    watch(
+      () => props.allList,
+      () => {
+        console.log('改变了333',props.allList, pointData.allList)
+      }
+    );
     return () => (
       <div class={styles.container}>
         <div class={styles.pointHead}>
@@ -58,7 +90,117 @@ export default defineComponent({
           {props.itemName}
         </div>
         <div class={styles.content}>
-          {props.data.map((item, index: number) => {
+          {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
+                center
+                class={[
+                  styles.collapseItem,
+                  pointData.kjId === item.id ? styles.activeItem : ''
+                ]}
+                border={false}
+                isLink={false}
+                title={item.name}
+                titleClass={'van-ellipsis'}
+                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.activeItem : ''
+                              ]}
+                              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>{material.name}</div>
+                                          </div>
+                                        </div>
+                                      );
+                                    })}                                    
+                                  </>
+                                ),
+                                icon: () => (
+                                  <img
+                                    class={styles.arrow}
+                                    src={
+                                      pointData.zsdId === know.id
+                                        ? chapterDown
+                                        : chapterDefault
+                                    }
+                                  />
+                                )
+                              }}
+                            </CollapseItem>
+                          ))}
+                      </Collapse>                      
+                    </>
+                  ),
+                  icon: () => (
+                    <img
+                      class={styles.arrow}
+                      src={
+                        pointData.kjId === item.id
+                          ? chapterDown
+                          : chapterDefault
+                      }
+                    />
+                  )
+                }}
+              </CollapseItem>
+            ))}
+            </Collapse>          
+          }
+
+
+          {/* {props.data.map((item, index: number) => {
             return (
               <div
                 class={[
@@ -79,7 +221,7 @@ export default defineComponent({
                 </div>
               </div>
             );
-          })}
+          })} */}
         </div>
       </div>
     );

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

@@ -0,0 +1,13 @@
+.knowledgeBg {
+    width: 100%;
+    height: 100%;
+    background: #DDF2FF;
+    padding: 16px;
+    .content {
+        padding: 16px;
+        height: 100%;
+        border-radius: 8px;
+        background: #fff;
+        overflow-y: scroll;
+    }
+}

+ 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>
+    );
+  }
+});

BIN
src/views/courseware-play/image/icon-pan.png


BIN
src/views/courseware-play/image/music_bg.png


BIN
src/views/courseware-play/image/music_play_icon.png


BIN
src/views/courseware-play/image/title_icon1.png


+ 168 - 42
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,6 +37,7 @@ 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 {
@@ -45,12 +46,17 @@ import {
 } from '../courseware-list/api';
 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';
 
 export default defineComponent({
   name: 'CoursewarePlay',
   setup() {
     const pageVisibility = usePageVisibility();
     const lastTimeKey = 'lastTime' + (state?.user?.data?.phone ?? '');
+    const showSelectCourseware = ref(false);
     /** 设置播放容器 16:9 */
     const parentContainer = reactive({
       width: '100vw'
@@ -140,21 +146,25 @@ 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
+      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, // 遮罩
@@ -166,37 +176,58 @@ export default defineComponent({
       item: null as any
     });
     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 || [];
         }
       } else {
-        const res = await api_lessonCoursewareKnowledgeDetailDetail({
-          lessonCoursewareKnowledgeDetailId:
-            activeData.coursewareDetailKnowledgeId,
-          subjectId: activeData.subjectId
+        // const res = await api_lessonCoursewareKnowledgeDetailDetail({
+        //   lessonCoursewareKnowledgeDetailId:
+        //     activeData.coursewareDetailKnowledgeId,
+        //   subjectId: activeData.subjectId
+        // });
+        const res = await api_lessonDetailCourseware({
+          lessonCoursewareKnowledgeDetailId: 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 || [];
+          console.log('课件类型',data.allList)
         }
       }
       // 课程
@@ -213,7 +244,18 @@ export default defineComponent({
           };
         });
       }
-
+      data.allList.forEach((item: any) => {
+        item.knowledgeList.forEach((material: any) => {
+          material.materialInfo.forEach((resource: any) => {
+            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
+          })
+        })
+      })
+      console.log('资源1',data.allList)
       data.itemList = data.knowledgePointList.map((m: any, index: number) => {
         if (!popupData.itemActive) {
           popupData.itemActive = m.id;
@@ -279,11 +321,36 @@ export default defineComponent({
     });
 
     // 切换素材
-    const toggleMaterial = (itemActive: any) => {
-      const index = data.itemList.findIndex((n: any) => n.id == itemActive);
-      if (index > -1) {
-        handleSwipeChange(index);
+    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)
+        data.itemList = target?.materialInfo.map((m: any, index: number) => {
+          if (!popupData.itemActive) {
+            popupData.itemActive = m.id;
+            popupData.itemName = m.name;
+          }
+          return {
+            ...m,
+            iframeRef: null,
+            videoEle: null,
+            autoPlay: false, //加载完成是否自动播放
+            isprepare: false, // 视频是否加载完成
+            isRender: false // 是否渲染了
+          };
+        });
+      } else {
+        const index = data.itemList.findIndex((n: any) => n.id == itemActive);
+        if (index > -1) {
+          handleSwipeChange(index);
+        }
       }
+
     };
     /** 延迟收起模态框 */
     const setModelOpen = () => {
@@ -740,6 +807,42 @@ export default defineComponent({
       }
       return {};
     });
+
+    // 加载新的章节里的课件
+    const loadNewCourseware = async (item: any) => {
+      loadingClass.value = true;
+      activeData.coursewareDetailKnowledgeId = item.coursewareDetailKnowledgeId;
+      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;
+      loadingClass.value = false;
+    }
+    // 通过章节id,检测此章节有几个课件
+    const checkCourseware = async (item: any) => {
+        // 如果有多个课件,需要选择一个课件进入上课页面
+        if (item.coursewareNum && item.coursewareNum > 1) {
+          try {
+            const res = route.query.tab == 'all' ? await api_lessonDetailCourseware({
+              lessonCoursewareKnowledgeDetailId: item.itemActive,
+            }): await api_classDetailCourseware({
+              lessonCoursewareKnowledgeDetailId: item.itemActive,
+            })
+            if (res?.code == 200 ) {
+              data.coursewareList = res.data;
+              showSelectCourseware.value = true;
+            } 
+          } catch {
+            //
+          }
+        } else {
+          loadNewCourseware(item);
+        }
+    };
+
     return () => (
       <div id="playContent" class={styles.playContent}>
         <div
@@ -906,6 +1009,23 @@ export default defineComponent({
                         )}
                       </Transition>
                     )}
+
+                    {/* 新增:RHYTHM:节奏练习,THEORY:乐理知识,MUSIC_WIKI:名曲鉴赏 INSTRUMENT:乐器 MUSICIAN:音乐家 资源类型 */}
+                    {m.type === 'RHYTHM' && 
+                      <TempoPractice dataJson={m?.dataJson ? JSON.parse(m?.dataJson) : {}} />
+                    }
+                    {m.type === 'THEORY' && (
+                      <Theory id={m.bizId} />
+                    )}
+                    {m.type === 'MUSIC_WIKI' && (
+                      <InstrumentInfo type={'wiki'} id={m.bizId} />
+                    )}
+                    {m.type === 'INSTRUMENT' && (
+                      <InstrumentInfo type={'instrument'} id={m.bizId} />
+                    )}
+                    {m.type === 'MUSICIAN' && (
+                      <InstrumentInfo type={'musician'} id={m.bizId} />
+                    )}                                                            
                   </div>
                 ) : (
                   <div
@@ -996,12 +1116,15 @@ export default defineComponent({
           v-model:show={popupData.open}
           onClose={handleClosePopup}>
           <Points
+            allList={data.allList}
             data={data.knowledgePointList}
             itemActive={popupData.itemActive}
             itemName={popupData.itemPointName}
+            kjId={data.kjId}
+            zsdId={data.zsdId}
             onHandleSelect={(res: any) => {
               popupData.open = false;
-              toggleMaterial(res.itemActive);
+              toggleMaterial(res.itemActive, res.zsdId, res.kjId);
             }}
           />
         </Popup>
@@ -1019,19 +1142,22 @@ export default defineComponent({
             itemActive={activeData.coursewareDetailKnowledgeId as any}
             active={activeData.lessonCoursewareDetailId as any}
             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;
               popupData.itemPointName = item.itemName;
-              popupData.chapterOpen = false;
-              loadingClass.value = false;
+              checkCourseware(item)
             }}
           />
         </Popup>
+        {
+          showSelectCourseware.value && 
+          <SelectCoursewarePop 
+            list={data.coursewareList} 
+            onClose={() => {
+              showSelectCourseware.value = false;
+            }}
+            onSelect={(item) => loadNewCourseware(item)}
+          ></SelectCoursewarePop>
+        }        
       </div>
     );
   }

+ 6 - 0
src/views/tempo-practice/index.tsx

@@ -29,6 +29,12 @@ import { useRoute } from 'vue-router';
 
 export default defineComponent({
   name: 'tempo-practice',
+  props: {
+    dataJson: {
+      type: Object,
+      default: () => {}
+    } 
+  },
   setup() {
     const route = useRoute();
     const state = reactive({

+ 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://dev.kt.colexiu.com/';
 export default defineConfig({
   base: './',
   plugins: [