lex 1 rok pred
rodič
commit
41d7bf862c
28 zmenil súbory, kde vykonal 3048 pridanie a 126 odobranie
  1. 32 4
      src/components/card-preview/index.tsx
  2. 3 3
      src/components/card-type/index.tsx
  3. 1 1
      src/views/attend-class/index.module.less
  4. 16 2
      src/views/attend-class/index.tsx
  5. 1 0
      src/views/attend-class/model/select-class/index.tsx
  6. 2 2
      src/views/prepare-lessons/components/lesson-main/courseware-presets/index.module.less
  7. 26 28
      src/views/prepare-lessons/components/lesson-main/courseware/addCourseware.tsx
  8. 6 1
      src/views/prepare-lessons/model/add-other-source/index.module.less
  9. 74 6
      src/views/prepare-lessons/model/add-other-source/index.tsx
  10. 1 1
      src/views/prepare-lessons/model/related-class/index.tsx
  11. 23 7
      src/views/prepare-lessons/model/source-instrument/components/list/index.module.less
  12. 83 60
      src/views/prepare-lessons/model/source-instrument/components/list/search-group-resources.tsx
  13. 25 4
      src/views/prepare-lessons/model/source-instrument/detail.tsx
  14. 1 1
      src/views/prepare-lessons/model/source-instrument/index.module.less
  15. 1 5
      src/views/prepare-lessons/model/source-instrument/index.tsx
  16. 268 0
      src/views/prepare-lessons/model/source-music/components/list/index.module.less
  17. 189 0
      src/views/prepare-lessons/model/source-music/components/list/index.tsx
  18. 290 0
      src/views/prepare-lessons/model/source-music/components/list/search-group-resources.tsx
  19. 541 0
      src/views/prepare-lessons/model/source-music/detail.module.less
  20. 372 0
      src/views/prepare-lessons/model/source-music/detail.tsx
  21. 126 0
      src/views/prepare-lessons/model/source-music/index.module.less
  22. 116 0
      src/views/prepare-lessons/model/source-music/index.tsx
  23. 244 0
      src/views/prepare-lessons/model/source-musician/components/list/index.module.less
  24. 189 0
      src/views/prepare-lessons/model/source-musician/components/list/index.tsx
  25. 177 0
      src/views/prepare-lessons/model/source-musician/components/list/search-group-resources.tsx
  26. 131 0
      src/views/prepare-lessons/model/source-musician/index.module.less
  27. 109 0
      src/views/prepare-lessons/model/source-musician/index.tsx
  28. 1 1
      src/views/xiaoku-music/component/play-item/index.tsx

+ 32 - 4
src/components/card-preview/index.tsx

@@ -8,6 +8,7 @@ import TheEmpty from '../TheEmpty';
 import RhythmModal from './rhythm-modal';
 import InstruemntDetail from '/src/views/prepare-lessons/model/source-instrument/detail';
 import TheoryDetail from '/src/views/prepare-lessons/model/source-knowledge/detail';
+import MusicDetail from '/src/views/prepare-lessons/model/source-music/detail';
 
 export default defineComponent({
   name: 'card-preview',
@@ -103,9 +104,23 @@ export default defineComponent({
             <RhythmModal class={styles.musicPreview} item={item.value} />
           )}
 
-          {item.value.type === 'INSTRUMENT' && (
+          {(item.value.type === 'INSTRUMENT' ||
+            item.value.type === 'MUSICIAN') && (
             <div class={styles.instrumentGroup}>
-              <InstruemntDetail type="modal" id={item.value.content} />
+              <InstruemntDetail
+                type="modal"
+                contentType={item.value.type}
+                id={item.value.content}
+              />
+            </div>
+          )}
+          {item.value.type === 'MUSIC_WIKI' && (
+            <div class={styles.instrumentGroup}>
+              <MusicDetail
+                type="modal"
+                contentType={item.value.type}
+                id={item.value.content}
+              />
             </div>
           )}
 
@@ -115,7 +130,18 @@ export default defineComponent({
             </div>
           )}
 
-          {/* LISTEN:听音,RHYTHM:节奏,THEORY:乐理知识,MUSIC:曲目 INSTRUMENT:乐器 MUSICIAN:音乐家) */}
+          {/* LISTEN:听音,RHYTHM:节奏,THEORY:乐理知识,MUSIC_WIKI:曲目 INSTRUMENT:乐器 MUSICIAN:音乐家) */}
+          {/*  VIDEO("视频"),
+    MUSIC("曲目"),
+    IMG("图片"),
+    SONG("音频"),
+    PPT("ppt"),
+    LISTEN("听音练习"),
+    RHYTHM("节奏练习"),
+    THEORY("乐理知识"),
+    MUSIC_WIKI("名曲鉴赏"),
+    INSTRUMENT("乐器"),
+    MUSICIAN("音乐家"), */}
           {![
             'VIDEO',
             'MUSIC',
@@ -123,7 +149,9 @@ export default defineComponent({
             'PPT',
             'RHYTHM',
             'INSTRUMENT',
-            'THEORY'
+            'THEORY',
+            'MUSICIAN',
+            'MUSIC_WIKI'
           ].includes(item.value.type) && <TheEmpty />}
         </NModal>
       </>

+ 3 - 3
src/components/card-type/index.tsx

@@ -18,7 +18,7 @@ import { api_musicSheetDetail } from '/src/api/user';
 import JSZip, { file } from 'jszip';
 import { saveAs } from 'file-saver';
 
-// LISTEN:听音,RHYTHM:节奏,THEORY:乐理知识,MUSIC:曲目 INSTRUMENT:乐器 MUSICIAN:音乐家)
+// LISTEN:听音,RHYTHM:节奏,THEORY:乐理知识,MUSIC_WIKI:曲目 INSTRUMENT:乐器 MUSICIAN:音乐家)
 type itemType = {
   id: string | number;
   type:
@@ -30,7 +30,7 @@ type itemType = {
     | 'LISTEN'
     | 'RHYTHM'
     | 'THEORY'
-    | 'MUSIC'
+    | 'MUSIC_WIKI'
     | 'INSTRUMENT'
     | 'MUSICIAN';
   coverImg: string;
@@ -369,7 +369,7 @@ export default defineComponent({
                   />
                 )}
                 {/* 名曲 */}
-                {props.item.type === 'MUSIC' && (
+                {props.item.type === 'MUSIC_WIKI' && (
                   <NImage
                     class={[styles.cover, styles.image]}
                     lazy

+ 1 - 1
src/views/attend-class/index.module.less

@@ -867,5 +867,5 @@
 }
 
 .selectClassModal {
-  width: 900px;
+  width: 1000px;
 }

+ 16 - 2
src/views/attend-class/index.tsx

@@ -83,6 +83,7 @@ import SourceList from './model/source-list';
 import RhythmModal from './component/rhythm-modal';
 import InstruemntDetail from '/src/views/prepare-lessons/model/source-instrument/detail';
 import TheotyDetail from '/src/views/prepare-lessons/model/source-knowledge/detail';
+import MusicDetail from '/src/views/prepare-lessons/model/source-music/detail';
 
 export type ToolType = 'init' | 'pen' | 'whiteboard' | 'call';
 export type ToolItem = {
@@ -457,7 +458,11 @@ export default defineComponent({
           );
         }
 
-        if (activeItem.type === 'INSTRUMENT') {
+        if (
+          activeItem.type === 'INSTRUMENT' ||
+          activeItem.type === 'MUSICIAN' ||
+          activeItem.type === 'MUSIC_WIKI'
+        ) {
           activeItem.iframeRef?.handleChangeAudio('pause');
         }
 
@@ -1559,10 +1564,19 @@ export default defineComponent({
                             m.iframeRef = el;
                           }}
                         />
-                      ) : m.type === 'INSTRUMENT' ? (
+                      ) : m.type === 'INSTRUMENT' || m.type === 'MUSICIAN' ? (
                         <InstruemntDetail
                           type="preview"
                           id={m.content}
+                          contentType={m.type}
+                          activeStatus={popupData.activeIndex === mIndex}
+                          ref={(el: any) => (m.iframeRef = el)}
+                        />
+                      ) : m.type === 'MUSIC_WIKI' ? (
+                        <MusicDetail
+                          type="preview"
+                          id={m.content}
+                          contentType={m.type}
                           activeStatus={popupData.activeIndex === mIndex}
                           ref={(el: any) => (m.iframeRef = el)}
                         />

+ 1 - 0
src/views/attend-class/model/select-class/index.tsx

@@ -80,6 +80,7 @@ export default defineComponent({
                   <div class={styles.itemWrapBox}>
                     <CoursewareType
                       isShowPreviewBtn
+                      isShowOpenFlag={false}
                       item={item}
                       onClick={() => {
                         emit('confirm', {

+ 2 - 2
src/views/prepare-lessons/components/lesson-main/courseware-presets/index.module.less

@@ -144,8 +144,8 @@
   padding: 12px 0 12px;
   gap: 20px 0;
   margin: 10px -10px 0;
-  // min-height: 300px;
-  height: 313px;
+  min-height: 313px;
+  // height: 313px;
 
 
   &.listSame {

+ 26 - 28
src/views/prepare-lessons/components/lesson-main/courseware/addCourseware.tsx

@@ -164,13 +164,10 @@ export default defineComponent({
     };
 
     // 删除
-    const onDelete = (item: any, index: number) => {
+    const onDelete = (j: number, index: number) => {
       const coursewareItem = forms.coursewareList[index];
       if (!coursewareItem) return;
-      const childIndex = coursewareItem.list.findIndex(
-        (c: any) => c.id === item.id
-      );
-      coursewareItem.list.splice(childIndex, 1);
+      coursewareItem.list.splice(j, 1);
     };
 
     // 完成编辑
@@ -309,7 +306,6 @@ export default defineComponent({
       nextTick(() => {
         if (point) {
           const rowGroupDom = document.querySelectorAll('.row-group');
-          console.log(rowGroupDom, 'row');
           const dom = rowGroupDom[item.index].querySelectorAll('.row-nav');
           // const dom = document.querySelectorAll('.row-nav');
           let isAdd = false;
@@ -366,15 +362,12 @@ export default defineComponent({
           message.error('请至少添加一个资源');
           return;
         }
-        await onSaveCourseWare();
-
-        emit('change', { status: false });
-        eventGlobal.emit('teacher-slideshow', false);
+        await onSaveCourseWare(true);
       } catch {
         //
       }
     };
-    const onSaveCourseWare = async () => {
+    const onSaveCourseWare = async (hasBack = false) => {
       try {
         const params = {
           name: forms.name,
@@ -388,24 +381,20 @@ export default defineComponent({
           let tempItem: any = [];
           if (Array.isArray(item.list) && item.list.length > 0) {
             tempItem = item.list.map((child: any) => {
-              console.log(
-                !['IMG', 'VIDEO', 'SONG', 'MUSIC', 'PPT'].includes(child.type),
-                child
-              );
               return {
                 bizId: child.materialId,
                 type: child.type,
-                dataJson:
-                  !['IMG', 'VIDEO', 'SONG', 'MUSIC', 'PPT'].includes(
-                    child.type
-                  ) &&
-                  JSON.stringify({
-                    setting: child.dataJson,
-                    coverImg: child.coverImg,
-                    bizId: child.bizId,
-                    content: child.content,
-                    name: child.title
-                  })
+                dataJson: !['IMG', 'VIDEO', 'SONG', 'MUSIC', 'PPT'].includes(
+                  child.type
+                )
+                  ? JSON.stringify({
+                      setting: child.dataJson,
+                      coverImg: child.coverImg,
+                      bizId: child.bizId,
+                      content: child.content,
+                      name: child.title
+                    })
+                  : ''
               };
             });
           }
@@ -424,6 +413,11 @@ export default defineComponent({
           await api_teacherChapterLessonCoursewareAdd(params);
           message.success('添加成功');
         }
+
+        if (hasBack) {
+          emit('change', { status: false });
+          eventGlobal.emit('teacher-slideshow', false);
+        }
       } catch {
         //
       }
@@ -738,7 +732,7 @@ export default defineComponent({
                                     class={styles.iconDelete}
                                     onClick={(e: MouseEvent) => {
                                       e.stopPropagation();
-                                      onDelete(item, index);
+                                      onDelete(element.index, index);
                                     }}
                                   />
                                 </div>
@@ -815,7 +809,9 @@ export default defineComponent({
         {/* 弹窗查看 */}
         <CardPreview
           size={
-            ['INSTRUMENT', 'THEORY'].includes(forms.item.type) ? 'large' : ''
+            ['INSTRUMENT', 'THEORY', 'MUSIC_WIKI'].includes(forms.item.type)
+              ? 'large'
+              : ''
           }
           v-model:show={forms.show}
           item={forms.item}
@@ -921,6 +917,8 @@ export default defineComponent({
               } else {
                 addCoursewareItem({ ...item, index: forms.addOtherIndex });
               }
+
+              console.log(forms.coursewareList, 'courseware');
             }}
           />
         </NModal>

+ 6 - 1
src/views/prepare-lessons/model/add-other-source/index.module.less

@@ -39,7 +39,8 @@
   width: 958px;
 }
 
-.instrumentModal {
+.instrumentModal,
+.musicModal {
   width: 1200px;
   position: relative;
   // width: 1352px;
@@ -58,6 +59,10 @@
   }
 }
 
+.musicModal {
+  width: 1360px;
+}
+
 .theoryModal {
   width: 1360px;
   position: relative;

+ 74 - 6
src/views/prepare-lessons/model/add-other-source/index.tsx

@@ -12,6 +12,8 @@ import { useRouter } from 'vue-router';
 import SourceRhythm from '../source-rhythm';
 import SourceInstrument from '../source-instrument';
 import SourceKnowledge from '../source-knowledge';
+import SourceMusician from '../source-musician';
+import SourceMusic from '../source-music';
 
 export default defineComponent({
   name: 'add-other-source',
@@ -19,11 +21,11 @@ export default defineComponent({
   setup(props, { emit }) {
     const router = useRouter();
     const sourceList = ref([
-      {
-        image: icon1,
-        name: '听音练习',
-        index: 0
-      },
+      // {
+      //   image: icon1,
+      //   name: '听音练习',
+      //   index: 0
+      // },
       {
         image: icon2,
         name: '节奏练习',
@@ -63,7 +65,7 @@ export default defineComponent({
       instrumentStatus: false, //
       musicianStatus: false //
     });
-    // LISTEN:听音,RHYTHM:节奏,THEORY:乐理知识,MUSIC:曲目 INSTRUMENT:乐器 MUSICIAN:音乐家)
+    // LISTEN:听音,RHYTHM:节奏,THEORY:乐理知识,MUSIC_WIKI:曲目 INSTRUMENT:乐器 MUSICIAN:音乐家)
 
     const onDetail = (item: any) => {
       switch (item.index) {
@@ -73,6 +75,12 @@ export default defineComponent({
         case 2:
           state.instrumentStatus = true;
           break;
+        case 3:
+          state.musicStatus = true;
+          break;
+        case 4:
+          state.musicianStatus = true;
+          break;
         case 5:
           state.theoryStatus = true;
           break;
@@ -190,6 +198,66 @@ export default defineComponent({
             }}
           />
         </NModal>
+
+        {/* 音乐家 */}
+        <NModal
+          v-model:show={state.musicianStatus}
+          preset="card"
+          class={['modalTitle', styles.instrumentModal]}
+          title={'音乐家'}>
+          <SourceMusician
+            onClose={() => (state.musicianStatus = false)}
+            onConfirm={(val: any) => {
+              state.musicianStatus = false;
+              const value = val || [];
+              const temp: any[] = [];
+              value.forEach((item: any) => {
+                temp.push({
+                  materialId: item.materialId,
+                  coverImg: item.coverImg,
+                  dataJson: null,
+                  title: item.title,
+                  isCollect: false,
+                  isSelected: false,
+                  content: item.content,
+                  type: 'MUSICIAN'
+                });
+              });
+              emit('comfirm', temp);
+              emit('close');
+            }}
+          />
+        </NModal>
+
+        {/* 名曲鉴赏 */}
+        <NModal
+          v-model:show={state.musicStatus}
+          preset="card"
+          class={['modalTitle', styles.musicModal]}
+          title={'名曲鉴赏'}>
+          <SourceMusic
+            onClose={() => (state.musicStatus = false)}
+            onConfirm={(val: any) => {
+              state.musicStatus = false;
+              const value = val || [];
+              const temp: any[] = [];
+              value.forEach((item: any) => {
+                temp.push({
+                  materialId: item.materialId,
+                  coverImg: item.coverImg,
+                  dataJson: null,
+                  title: item.title,
+                  isCollect: false,
+                  isSelected: false,
+                  content: item.content,
+                  type: 'MUSIC_WIKI'
+                });
+              });
+              emit('comfirm', temp);
+              emit('close');
+            }}
+          />
+        </NModal>
       </>
     );
   }

+ 1 - 1
src/views/prepare-lessons/model/related-class/index.tsx

@@ -120,7 +120,7 @@ export default defineComponent({
             onUpdate:value={() => throttleFn()}
           />
           <NInput
-            placeholder="请输课件标题关键词"
+            placeholder="请输课件标题关键词"
             clearable
             v-model:value={forms.searchGroup.keyword}
             onKeyup={(e: KeyboardEvent) => {

+ 23 - 7
src/views/prepare-lessons/model/source-instrument/components/list/index.module.less

@@ -4,7 +4,7 @@
 
 
   .btnType {
-    gap: 0px 24px !important;
+    gap: 0px 12px !important;
     flex-wrap: nowrap !important;
 
     &>div {
@@ -46,14 +46,22 @@
       }
     }
 
+    .carouselGroup {
+      display: flex;
+      width: 660px
+    }
+
     .carouselContainer {
-      max-width: 550px;
+      width: 550px;
+      margin-right: 12px;
 
       :global {
         .n-carousel__slide {
           width: auto !important;
           display: flex;
           align-items: center;
+          padding-left: 10px;
+          padding-right: 10px;
         }
       }
     }
@@ -119,6 +127,15 @@
 //   padding: 0 33px;
 // }
 
+.instrumentList {
+  :global {
+    .n-spin-content {
+      overflow: hidden;
+      padding: 0 33px;
+    }
+  }
+}
+
 .list {
   margin-top: 12px;
   display: flex;
@@ -128,7 +145,6 @@
   min-height: 232px;
   margin-left: -22px;
   margin-right: -22px;
-  padding: 0 33px;
 
   .itemWrap {
     width: calc(100% / 6);
@@ -148,12 +164,9 @@
   .itemCard {
     position: relative;
     cursor: pointer;
-    transition: all .2s ease;
 
-    &:hover {
-      transform: scale(1.03);
-      transition: all .2s ease;
 
+    &:hover {
       .itemImgSection {
         // background: linear-gradient(360deg, #DBF1FF 0%, #E7F9FF 100%);
         // box-shadow: 2px 2 8px 0px rgba(0, 0, 0, 0.1);
@@ -161,6 +174,8 @@
         // border: 3px solid rgba(0, 122, 254, 1);
         box-sizing: border-box;
         transition: all .2s ease;
+        transform: scale(1.03);
+        transition: all .2s ease;
       }
     }
 
@@ -191,6 +206,7 @@
       transform: all .2s ease;
       background-color: transparent;
       border: 2px solid transparent;
+      transition: all .2s ease;
 
       .iconCheck {
         position: absolute;

+ 83 - 60
src/views/prepare-lessons/model/source-instrument/components/list/search-group-resources.tsx

@@ -27,6 +27,10 @@ export default defineComponent({
       maxIndex: 0
     });
 
+    const state = reactive({
+      showSlide: false
+    });
+
     const onSearch = () => {
       emit('search', forms);
     };
@@ -42,9 +46,25 @@ export default defineComponent({
     onMounted(async () => {
       // 获取教材分类列表
       // await catchStore.getMusicSheetCategory()
-      // nextTick(() => {
-      //   carouselRef.value?.to(100);
-      // });
+      nextTick(() => {
+        // carouselRef.value?.to(100);
+
+        // 最外层宽度
+        const carouselContainer = document.querySelector('.carouselContainer');
+        const carouselContainerWidth =
+          (carouselContainer &&
+            carouselContainer.getBoundingClientRect().width) ||
+          0;
+        const slideDoms = document.querySelectorAll('.n-carousel__slide');
+        let slideWidth = 0;
+        slideDoms.forEach(doom => {
+          const rect = doom.getBoundingClientRect();
+          slideWidth += rect.width;
+        });
+        if (slideWidth >= carouselContainerWidth) {
+          state.showSlide = true;
+        }
+      });
     });
     return () => (
       <div class={styles.searchGroup}>
@@ -72,64 +92,67 @@ export default defineComponent({
             ) : (
               ''
             )}
-            <NCarousel
-              ref={carouselRef}
-              slidesPerView={'auto'}
-              loop={false}
-              class={styles.carouselContainer}
-              showDots={false}
-              spaceBetween={20}
-              currentIndex={forms.currentIndex}
-              onUpdate:currentIndex={(val: any) => {
-                //
-                // if (val > forms.maxIndex) {
-                //   forms.maxIndex = val;
-                //   carouselRef.value?.to(0);
-                // }
-                forms.currentIndex = val;
-              }}>
-              {props.categoryChildList.map((item: any) => (
-                <NCarouselItem>
-                  <NButton
-                    type={
-                      forms.wikiCategoryId === item.id ? 'primary' : 'default'
-                    }
-                    secondary={forms.wikiCategoryId === item.id ? false : true}
-                    round
-                    size="small"
-                    focusable={false}
-                    onClick={() => {
-                      forms.wikiCategoryId = item.id;
-                      onSearch();
-                    }}>
-                    {item.name}
-                  </NButton>
-                </NCarouselItem>
-              ))}
-            </NCarousel>
 
-            <NSpace class={styles.swipeControll}>
-              <div onClick={() => onChangeSlide('left')}>
-                <NImage
-                  previewDisabled
-                  class={[
-                    styles.leftIcon
-                    // forms.currentIndex === 0 && styles.disabled
-                  ]}
-                  src={iconSlideRight}
-                />
-              </div>
-              <div onClick={() => onChangeSlide('right')}>
-                <NImage
-                  // class={
-                  //   // forms.currentIndex == forms.openTableList.length - 4 &&
-                  //   styles.disabled
-                  // }
-                  previewDisabled
-                  src={iconSlideRight}
-                />
-              </div>
-            </NSpace>
+            <div class={[styles.carouselGroup]}>
+              <NCarousel
+                ref={carouselRef}
+                slidesPerView={'auto'}
+                loop={false}
+                class={[styles.carouselContainer, 'carouselContainer']}
+                showDots={false}
+                // spaceBetween={20}
+                draggable={state.showSlide}
+                currentIndex={forms.currentIndex}
+                onUpdate:currentIndex={(val: any) => {
+                  forms.currentIndex = val;
+                }}>
+                {props.categoryChildList.map((item: any) => (
+                  <NCarouselItem>
+                    <NButton
+                      type={
+                        forms.wikiCategoryId === item.id ? 'primary' : 'default'
+                      }
+                      secondary={
+                        forms.wikiCategoryId === item.id ? false : true
+                      }
+                      round
+                      size="small"
+                      focusable={false}
+                      onClick={() => {
+                        forms.wikiCategoryId = item.id;
+                        onSearch();
+                      }}>
+                      {item.name}
+                    </NButton>
+                  </NCarouselItem>
+                ))}
+              </NCarousel>
+
+              {state.showSlide && (
+                <NSpace class={styles.swipeControll}>
+                  <div onClick={() => onChangeSlide('left')}>
+                    <NImage
+                      previewDisabled
+                      class={[
+                        styles.leftIcon
+                        // forms.currentIndex === 0 && styles.disabled
+                      ]}
+                      src={iconSlideRight}
+                    />
+                  </div>
+                  <div onClick={() => onChangeSlide('right')}>
+                    <NImage
+                      // class={
+                      //   // forms.currentIndex == forms.openTableList.length - 4 &&
+                      //   styles.disabled
+                      // }
+                      previewDisabled
+                      src={iconSlideRight}
+                    />
+                  </div>
+                </NSpace>
+              )}
+            </div>
           </NSpace>
           <TheSearch
             class={styles.inputSearch}

+ 25 - 4
src/views/prepare-lessons/model/source-instrument/detail.tsx

@@ -39,6 +39,10 @@ export default defineComponent({
     activeStatus: {
       type: Boolean,
       default: false
+    },
+    contentType: {
+      type: String,
+      default: ''
     }
   },
   setup(props, { expose }) {
@@ -49,7 +53,7 @@ export default defineComponent({
       rows: 20,
       status: true,
       name: '', // 关键词
-      type: route.query.type
+      type: props.contentType
     });
     const data = reactive({
       loading: false,
@@ -132,12 +136,28 @@ export default defineComponent({
           : '';
       res.data.intros = res.data.intros.replace(
         /<video/gi,
-        '<video style="width: 100% !important;" controlslist="nodownload"'
+        '<video class="video-music" style="width: 100% !important;" controlslist="nodownload"'
       );
       data.details = res.data;
       data.loading = false;
     };
 
+    const onStopAll = (type: 'play' | 'pause' | 'pre' | 'next' | 'favitor') => {
+      handleChangeAudio(type);
+
+      try {
+        // 暂停视频
+        const doms = document.querySelectorAll('.video-music');
+        if (doms && doms.length > 0) {
+          doms.forEach((dom: any) => {
+            dom.pause();
+          });
+        }
+      } catch {
+        //
+      }
+    };
+
     onMounted(() => {
       getDetail();
     });
@@ -146,13 +166,13 @@ export default defineComponent({
       () => props.activeStatus,
       () => {
         if (!props.activeStatus) {
-          handleChangeAudio('pause');
+          onStopAll('pause');
         }
       }
     );
 
     expose({
-      handleChangeAudio
+      handleChangeAudio: onStopAll
     });
     return () => (
       <div
@@ -292,6 +312,7 @@ export default defineComponent({
                 <NSlider
                   v-model:value={data.fontSize}
                   vertical
+                  placement="left"
                   min={12}
                   max={32}
                 />

+ 1 - 1
src/views/prepare-lessons/model/source-instrument/index.module.less

@@ -14,7 +14,7 @@
       padding: 0 20px 0;
 
       .n-tabs-nav-scroll-wrapper {
-        padding: 20px 0 30px;
+        padding: 25px 0 30px;
       }
     }
 

+ 1 - 5
src/views/prepare-lessons/model/source-instrument/index.tsx

@@ -11,7 +11,6 @@ export default defineComponent({
   name: 'content-instrument',
   emits: ['confirm', 'close'],
   setup(props, { emit }) {
-    const tabValue = sessionStorage.getItem('content-instrument-tab');
     const router = useRouter();
     const state = reactive({
       tabValue: '',
@@ -32,7 +31,7 @@ export default defineComponent({
         state.categoryList = data.rows || [];
         if (state.categoryList.length) {
           nextTick(() => {
-            state.tabValue = tabValue || 'name-' + state.categoryList[0].id;
+            state.tabValue = 'name-' + state.categoryList[0].id;
           });
         }
       } catch {
@@ -76,9 +75,6 @@ export default defineComponent({
                 justifyContent="center"
                 // animated
                 paneWrapperClass={styles.paneWrapperContainer}
-                onUpdate:value={(val: any) => {
-                  sessionStorage.setItem('content-instrument-tab', val);
-                }}
                 v-model:value={state.tabValue}>
                 {state.categoryList.map((category: any) => (
                   <NTabPane name={`name-${category.id}`} tab={category.name}>

+ 268 - 0
src/views/prepare-lessons/model/source-music/components/list/index.module.less

@@ -0,0 +1,268 @@
+.searchGroup {
+  position: relative;
+  padding: 0;
+
+
+  .btnType {
+    flex-wrap: nowrap !important;
+
+    &>div {
+      display: flex;
+      align-items: center;
+    }
+
+
+
+    .swipeControll {
+      height: 25Px;
+
+      .leftIcon {
+        transform: rotate(180deg);
+      }
+
+      img {
+        cursor: pointer;
+        width: 25Px;
+        height: 25Px;
+      }
+
+      .disabled {
+        opacity: 0.4;
+        cursor: not-allowed;
+      }
+    }
+
+    :global {
+      .n-button {
+        height: 37px;
+        padding: 0 24px;
+        font-size: 18px;
+        color: rgba(0, 0, 0, .6);
+
+        &.n-button--primary-type {
+          font-weight: bold;
+          color: #fff;
+        }
+      }
+    }
+
+    .carouselGroup {
+      display: flex;
+      // width: 660px
+    }
+
+    .carouselContainer {
+      width: 730px;
+      margin-right: 12px;
+
+      :global {
+        .n-carousel__slide {
+          width: auto !important;
+          display: flex;
+          align-items: center;
+          padding-left: 10px;
+          padding-right: 10px;
+        }
+      }
+    }
+  }
+
+
+  .inputSearch {
+    width: 360px;
+    height: 42px;
+    font-size: 16px;
+    --n-height: 42px !important;
+
+    img {
+      width: 18px;
+      height: 18px;
+    }
+
+    :global {
+      .n-input-wrapper {
+        padding-left: 12px;
+        padding-right: 4px;
+        height: 42px !important;
+      }
+
+      .n-button {
+        height: 34px;
+        font-size: 15px;
+        font-weight: 500;
+        width: auto;
+      }
+    }
+  }
+
+  .searchCatatory {
+    display: flex;
+    justify-content: space-between;
+
+    border-bottom: 1px solid #F2F2F2;
+    padding-bottom: 12px;
+    margin-bottom: 12px;
+
+    &.border {
+      padding-bottom: 24px;
+      border-bottom: 1px solid #F2F2F2;
+    }
+
+    .addTrain {
+      height: 37px;
+      border-radius: 8px;
+      font-size: 18px;
+      background-color: #E8F4FF;
+      color: #0378EC;
+
+      img {
+        width: 16px;
+        height: 16px;
+        margin-right: 8px;
+      }
+    }
+  }
+}
+
+.searchGroups {
+  padding: 0 33px;
+}
+
+.instrumentList {
+  :global {
+    .n-spin-content {
+      overflow: hidden;
+      padding: 0 33px;
+    }
+  }
+}
+
+.list {
+  margin-top: 12px;
+  display: flex;
+  flex-flow: row wrap;
+  justify-content: flex-start;
+  gap: 20px 0;
+  min-height: 232px;
+  margin-left: -10px;
+  margin-right: -10px;
+
+  .itemWrap {
+    width: calc(100% / 3);
+    padding-bottom: calc(100% / 3 * 0.1957894);
+    position: relative;
+    height: 0;
+    cursor: pointer;
+
+    .itemWrapBox {
+      position: absolute;
+      left: 0;
+      top: 0;
+      width: 100%;
+      height: 100%;
+      padding: 0 10px;
+    }
+  }
+
+  .iconCheck {
+    position: absolute;
+    top: 7px;
+    right: 7px;
+    width: 20px;
+    height: 20px;
+    background: url('../../../../images/icon-check.png') no-repeat center;
+    background-size: contain;
+  }
+
+  .itemCard {
+    position: relative;
+    cursor: pointer;
+    height: 100%;
+    background: #F4F4F4;
+    border-radius: 13px;
+    transition: all .2s ease;
+    border: 2px solid #F4F4F4;
+
+    &:hover {
+      transform: scale(1.01);
+      border: 2px solid rgba(0, 122, 254, 1);
+      transition: all .2s ease;
+    }
+
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    padding: 0 17px;
+
+    .musicBg {
+      width: 50px;
+      height: 50px;
+      border-radius: 8px;
+      margin-right: 10px;
+    }
+
+    .itemName {
+      display: flex;
+      align-items: center;
+      font-weight: bold;
+    }
+  }
+
+  .itemImgSectionSelected {
+    .iconCheck {
+      background-image: url('../../../../images/icon-checked.png');
+    }
+  }
+}
+
+.popSelect {
+  font-size: 16px;
+  width: 200px;
+  box-shadow: 0px 2 16px 0px rgba(0, 0, 0, 0.08);
+  border-radius: 11px;
+  --n-option-height: 34px;
+
+  :global {
+    .n-base-select-option__content {
+      width: 80% !important;
+    }
+  }
+}
+
+.spaceSection {
+  padding-bottom: 20px;
+}
+
+.textBtn {
+  background: #fff;
+  border-radius: 8Px;
+  padding: 4Px 17Px;
+  font-size: max(16px, 13Px);
+  color: rgba(0, 0, 0, 0.6);
+  cursor: pointer;
+  display: flex;
+  align-items: center;
+  font-weight: 500;
+
+  .iconArrow {
+    display: inline-block;
+    margin-left: 8px;
+    width: 8px;
+    height: 5px;
+    background: url('../../../images/icon-arrow2.png') no-repeat center center / contain;
+    transform: rotate(180deg);
+  }
+
+  &:hover,
+  &.textBtnActive {
+    background: #D2ECFF;
+    font-weight: 500;
+    color: #131415;
+  }
+
+  &:hover {
+    .iconArrow {
+      transform: rotate(0deg);
+    }
+  }
+}

+ 189 - 0
src/views/prepare-lessons/model/source-music/components/list/index.tsx

@@ -0,0 +1,189 @@
+import { PropType, defineComponent, onMounted, reactive } from 'vue';
+import styles from './index.module.less';
+import SearchGroupResources from './search-group-resources';
+import { NButton, NImage, NScrollbar, NSpin } from 'naive-ui';
+import TheEmpty from '/src/components/TheEmpty';
+import Pagination from '/src/components/pagination';
+import musicBg from '../../../../../xiaoku-music/images/icon_default.png';
+import { api_knowledgeWiki_page } from '/src/views/content-information/api';
+import { useRouter } from 'vue-router';
+import CardPreview from '/src/components/card-preview';
+
+export default defineComponent({
+  name: 'music-list',
+  props: {
+    categoryId: {
+      type: String,
+      default: ''
+    },
+    categoryChildList: {
+      type: Array as PropType<any>,
+      default: () => []
+    },
+    selectItems: {
+      type: Array as PropType<any>,
+      default: () => []
+    }
+  },
+  emits: ['close', 'confirm'],
+  setup(props, { emit }) {
+    const router = useRouter();
+    const state = reactive({
+      searchWord: '',
+      loading: false,
+      finshed: false, // 是否加载完
+      pageTotal: 0,
+      pagination: {
+        page: 1,
+        rows: 18
+      },
+      searchGroup: {
+        type: 'MUSIC', //
+        keyword: '',
+        wikiCategoryId: props.categoryId
+      },
+      tableList: [] as any,
+      teachingStatus: false,
+      show: false,
+      item: {} as any
+    });
+
+    const getList = async () => {
+      state.loading = true;
+      try {
+        const { data } = await api_knowledgeWiki_page({
+          ...state.pagination,
+          ...state.searchGroup
+        });
+        state.tableList.push(...data.rows);
+
+        state.pageTotal = Number(data.total);
+        state.finshed = data.pages <= data.current ? true : false;
+      } catch {
+        //
+      }
+      state.loading = false;
+    };
+
+    const onSearch = async (item: any) => {
+      state.pagination.page = 1;
+      const { wikiCategoryIdChild, wikiCategoryId, keyword } = item;
+      state.searchGroup = Object.assign(state.searchGroup, {
+        wikiCategoryId: wikiCategoryIdChild || wikiCategoryId,
+        keyword
+      });
+      getList();
+    };
+
+    // 更新
+    const onSelect = (item: any) => {
+      const ids = props.selectItems || [];
+      const index = ids.findIndex((i: any) => i.id === item.id);
+      if (index !== -1) {
+        ids.splice(index, 1);
+      } else {
+        ids.push(item);
+      }
+
+      emit('confirm', ids);
+    };
+
+    onMounted(() => {
+      getList();
+    });
+
+    return () => (
+      <div class={styles.instrumentList}>
+        <SearchGroupResources
+          class={styles.searchGroups}
+          categoryChildList={props.categoryChildList || []}
+          wikiCategoryId={props.categoryId}
+          onSearch={(item: any) => onSearch(item)}
+        />
+        <NScrollbar
+          class={styles.listContainer}
+          style={{
+            'max-height': `50vh`
+          }}
+          onScroll={(e: any) => {
+            const clientHeight = e.target?.clientHeight;
+            const scrollTop = e.target?.scrollTop;
+            const scrollHeight = e.target?.scrollHeight;
+            // 是否到底,是否加载完
+            if (
+              clientHeight + scrollTop + 20 >= scrollHeight &&
+              !state.finshed &&
+              !state.loading
+            ) {
+              state.pagination.page = state.pagination.page + 1;
+              getList();
+            }
+          }}>
+          <NSpin v-model:show={state.loading} style={{ 'min-height': '50vh' }}>
+            <div class={styles.list}>
+              {state.tableList.map((item: any) => (
+                <div
+                  class={styles.itemWrap}
+                  onClick={() => {
+                    // router.push({
+                    //   path: '/content-music-detail',
+                    //   query: {
+                    //     id: item.id,
+                    //     name: item.name
+                    //   }
+                    // });
+                    state.item = {
+                      content: item.id,
+                      title: item.name,
+                      type: 'MUSIC_WIKI'
+                    };
+                    state.show = true;
+                  }}>
+                  <div class={styles.itemWrapBox}>
+                    <div
+                      class={[
+                        styles.itemCard,
+                        props.selectItems.findIndex(
+                          (i: any) => i.id === item.id
+                        ) !== -1 && styles.itemImgSectionSelected
+                      ]}>
+                      <div class={styles.itemName}>
+                        <img
+                          src={item.avatar || musicBg}
+                          class={styles.musicBg}
+                        />
+
+                        <span class={styles.name}>{item.name}</span>
+                      </div>
+
+                      <i
+                        class={[styles.iconCheck]}
+                        onClick={(e: any) => {
+                          e.stopPropagation();
+                          onSelect(item);
+                        }}></i>
+                    </div>
+                  </div>
+                </div>
+              ))}
+
+              {!state.loading && state.tableList.length <= 0 && (
+                <TheEmpty
+                  style={{ minHeight: '50vh' }}
+                  description="暂无名曲鉴赏"
+                />
+              )}
+            </div>
+          </NSpin>
+        </NScrollbar>
+
+        {/* 弹窗查看 */}
+        <CardPreview
+          size={'large'}
+          v-model:show={state.show}
+          item={state.item}
+        />
+      </div>
+    );
+  }
+});

+ 290 - 0
src/views/prepare-lessons/model/source-music/components/list/search-group-resources.tsx

@@ -0,0 +1,290 @@
+import {
+  PropType,
+  computed,
+  defineComponent,
+  nextTick,
+  onMounted,
+  reactive,
+  ref
+} from 'vue';
+import styles from './index.module.less';
+import {
+  NButton,
+  NCarousel,
+  NCarouselItem,
+  NForm,
+  NFormItem,
+  NImage,
+  NPopselect,
+  NSpace
+} from 'naive-ui';
+import TheSearch from '/src/components/TheSearch';
+import iconSlideRight from '/src/views/prepare-lessons/images/icon-slide-right.png';
+export default defineComponent({
+  name: 'search-group',
+  props: {
+    categoryChildList: {
+      type: Array as PropType<any>,
+      default: () => []
+    },
+    wikiCategoryId: {
+      type: String,
+      default: ''
+    }
+  },
+  emits: ['search', 'add'],
+  expose: ['init'],
+  setup(props, { emit }) {
+    // const catchStore = useCatchStore();
+    const forms = reactive({
+      keyword: '',
+      wikiCategoryId: props.wikiCategoryId || '',
+      wikiCategoryIdChild: '',
+      childIds: [] as any,
+      currentIndex: 0
+    });
+    const carouselRef = ref();
+
+    const onSearch = () => {
+      emit('search', forms);
+    };
+
+    const selectChildObj = (item: any, index: number) => {
+      const obj: any = {};
+      item?.forEach((child: any) => {
+        if (child.id === forms.wikiCategoryIdChild) {
+          obj.selected = true;
+          obj.name = child.name;
+        }
+      });
+      return obj;
+    };
+
+    const childList = computed(() => {
+      const categoryChildList = props.categoryChildList || [];
+      const child = categoryChildList.find(
+        (item: any) => item.id === forms.wikiCategoryId
+      );
+      if (child && child.childrenList.length) {
+        child.childrenList.forEach((child: any) => {
+          const i = child.childrenList;
+          if (i && i.length > 0) {
+            i.forEach((j: any) => {
+              j.label = j.name;
+              j.value = j.id;
+            });
+
+            i.unshift({
+              label: '全部',
+              value: child.id,
+              name: child.name,
+              id: child.id
+            });
+          }
+        });
+
+        return (
+          [
+            {
+              label: '全部',
+              value: '',
+              id: '',
+              name: '全部',
+              childrenList: []
+            },
+            ...child.childrenList
+          ] || []
+        );
+      }
+      return [];
+    });
+
+    const state = reactive({
+      showSlide: false
+    });
+    const onChangeSlide = (type: string) => {
+      if (type === 'left') {
+        carouselRef.value?.prev();
+      } else if (type === 'right') {
+        carouselRef.value?.next();
+      }
+    };
+
+    onMounted(() => {
+      nextTick(() => {
+        // 最外层宽度
+        const carouselContainer = document.querySelector('.carouselContainer');
+        const carouselContainerWidth =
+          (carouselContainer &&
+            carouselContainer.getBoundingClientRect().width) ||
+          0;
+        const slideDoms = document.querySelectorAll('.n-carousel__slide');
+        let slideWidth = 0;
+        slideDoms.forEach(doom => {
+          const rect = doom.getBoundingClientRect();
+          slideWidth += rect.width;
+        });
+        if (slideWidth >= carouselContainerWidth) {
+          state.showSlide = true;
+        }
+      });
+    });
+
+    return () => (
+      <div class={styles.searchGroup}>
+        <div
+          class={[
+            styles.searchCatatory,
+            childList.value.length > 0 ? styles.border : ''
+          ]}>
+          <NSpace size="small" class={styles.btnType}>
+            {props.categoryChildList.length > 0 ? (
+              <NButton
+                type={
+                  forms.wikiCategoryId === props.wikiCategoryId
+                    ? 'primary'
+                    : 'default'
+                }
+                secondary={
+                  forms.wikiCategoryId === props.wikiCategoryId ? false : true
+                }
+                round
+                size="small"
+                focusable={false}
+                onClick={() => {
+                  forms.wikiCategoryId = props.wikiCategoryId;
+                  forms.wikiCategoryIdChild = '';
+                  onSearch();
+                }}>
+                全部
+              </NButton>
+            ) : (
+              <span></span>
+            )}
+
+            <div class={[styles.carouselGroup]}>
+              <NCarousel
+                ref={carouselRef}
+                slidesPerView={'auto'}
+                loop={false}
+                class={[styles.carouselContainer, 'carouselContainer']}
+                showDots={false}
+                // spaceBetween={20}
+                draggable={state.showSlide}
+                currentIndex={forms.currentIndex}
+                onUpdate:currentIndex={(val: any) => {
+                  //
+                  // if (val > forms.maxIndex) {
+                  //   forms.maxIndex = val;
+                  //   carouselRef.value?.to(0);
+                  // }
+                  forms.currentIndex = val;
+                }}>
+                {props.categoryChildList.map((item: any) => (
+                  <NCarouselItem>
+                    <NButton
+                      type={
+                        forms.wikiCategoryId === item.id ? 'primary' : 'default'
+                      }
+                      secondary={
+                        forms.wikiCategoryId === item.id ? false : true
+                      }
+                      round
+                      size="small"
+                      focusable={false}
+                      onClick={() => {
+                        forms.wikiCategoryId = item.id;
+                        onSearch();
+                      }}>
+                      {item.name}
+                    </NButton>
+                  </NCarouselItem>
+                ))}
+              </NCarousel>
+
+              {state.showSlide && (
+                <NSpace class={styles.swipeControll}>
+                  <div onClick={() => onChangeSlide('left')}>
+                    <NImage
+                      previewDisabled
+                      class={[
+                        styles.leftIcon
+                        // forms.currentIndex === 0 && styles.disabled
+                      ]}
+                      src={iconSlideRight}
+                    />
+                  </div>
+                  <div onClick={() => onChangeSlide('right')}>
+                    <NImage
+                      // class={
+                      //   // forms.currentIndex == forms.openTableList.length - 4 &&
+                      //   styles.disabled
+                      // }
+                      previewDisabled
+                      src={iconSlideRight}
+                    />
+                  </div>
+                </NSpace>
+              )}
+            </div>
+          </NSpace>
+          <TheSearch
+            class={styles.inputSearch}
+            placeholder="请输入名曲鉴赏关键词"
+            round
+            onSearch={(val: string) => {
+              forms.keyword = val;
+              onSearch();
+            }}
+          />
+        </div>
+
+        {childList.value.length > 0 && (
+          <div class={[styles.collapseWrap]}>
+            <NSpace class={[styles.spaceSection]}>
+              {childList.value.map((music: any, index: number) => (
+                <>
+                  {music.childrenList.length > 0 ? (
+                    <NPopselect
+                      options={music.childrenList}
+                      trigger="hover"
+                      v-model:value={forms.wikiCategoryIdChild}
+                      onUpdate:value={() => {
+                        onSearch();
+                      }}
+                      key={music.id}
+                      class={styles.popSelect}>
+                      <span
+                        class={[
+                          styles.textBtn,
+                          selectChildObj(music.childrenList, index).selected &&
+                            styles.textBtnActive
+                        ]}>
+                        {selectChildObj(music.childrenList, index).name ||
+                          music.name}
+                        <i class={styles.iconArrow}></i>
+                      </span>
+                    </NPopselect>
+                  ) : (
+                    <span
+                      class={[
+                        styles.textBtn,
+                        forms.wikiCategoryIdChild === music.id &&
+                          styles.textBtnActive
+                      ]}
+                      onClick={() => {
+                        forms.wikiCategoryIdChild = music.id;
+                        onSearch();
+                      }}>
+                      {music.name}
+                    </span>
+                  )}
+                </>
+              ))}
+            </NSpace>
+          </div>
+        )}
+      </div>
+    );
+  }
+});

+ 541 - 0
src/views/prepare-lessons/model/source-music/detail.module.less

@@ -0,0 +1,541 @@
+.container {
+  display: flex;
+  flex-direction: column;
+  height: 100%;
+
+  &.containerPreview {
+    padding: 68px 78px 68px 68px;
+    // height: calc(100% - 136px);
+    background-color: #fff;
+
+    .wrapBottom {
+      padding-bottom: 60px !important;
+    }
+  }
+
+  &.containerModal {
+    .content {
+      border-top-left-radius: 0;
+      border-top-right-radius: 0;
+    }
+  }
+}
+
+.wrap {
+  flex: 1;
+  transition: padding .3s;
+  overflow: hidden;
+
+  &.wrapBottom {
+    padding-bottom: 108px;
+
+  }
+}
+
+.content {
+  display: flex;
+  flex-direction: column;
+  height: 100%;
+  background: #DDF2FF;
+  border-radius: 20px;
+  // max-height: 90vh;
+}
+
+.tools {
+  padding: 20px;
+  display: flex;
+  align-items: center;
+  flex-shrink: 0;
+
+  :global {
+    .n-input {
+      margin-left: auto;
+      width: 361px;
+    }
+
+    .n-input__input-el {
+      height: 100%;
+      line-height: 100%;
+    }
+  }
+}
+
+.contentWrap {
+  position: relative;
+  flex: 1;
+  display: flex;
+  padding: 20px 55px 20px 20px;
+  overflow: hidden;
+  gap: 0 32px;
+}
+
+.musicList {
+  background-color: #fff;
+  border-radius: 16px;
+
+  width: 470px;
+  min-width: 294px;
+  height: 100%;
+  overflow-x: hidden;
+  overflow-y: auto;
+  min-width: 330Px;
+
+  &::-webkit-scrollbar {
+    width: 0;
+    display: none;
+  }
+
+  .instrumentGroup {
+    padding-top: 27px;
+    padding-bottom: 20px;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    flex-direction: column;
+
+    .instrumentImg {
+      width: 125px;
+      height: 125px;
+      overflow: hidden;
+      border-radius: 50%;
+    }
+
+    .instrumentName {
+      padding: 13px 0 5px;
+      font-size: max(18px, 14Px);
+      font-weight: 600;
+      color: #131415;
+      line-height: 25px;
+      letter-spacing: 1px;
+    }
+
+    .instrumentTag {
+      font-size: max(13px, 12Px);
+      color: #777777;
+      line-height: 18px;
+    }
+  }
+
+
+
+  .wrapList {
+    width: 470px;
+    padding: 0 17px;
+    min-width: 294px;
+    min-height: 100%;
+    // background: #fff;
+    border-radius: 16px;
+
+    :global {
+      .n-empty .n-empty__description {
+        font-size: calc(14px, 12Px);
+      }
+    }
+
+
+    .titlec {
+      padding: 20px 0;
+      font-size: max(18px, 14Px);
+      font-weight: 600;
+      color: #000000;
+      line-height: 25px;
+      border-top: 1px solid #F2F2F2;
+      display: flex;
+      align-items: center;
+    }
+
+    .icon2 {
+      width: 23px;
+      height: 23px;
+      margin-right: 8px;
+      background: url('../../../content-information/images/icon-2.png') no-repeat center;
+      background-size: contain;
+    }
+  }
+
+  .empty {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    height: 50vh;
+    // height: 100%;
+  }
+}
+
+.itemContainer {
+  width: 100%;
+  border-radius: 16px;
+  padding: 4px 8px;
+  // background-color: #fff;
+
+  &:first-child {
+    padding-top: 8px;
+  }
+
+  &:last-child {
+    // border-radius: 0 0 16px 16px;
+    padding-bottom: 8px;
+  }
+}
+
+.item {
+  position: relative;
+  display: flex;
+  align-items: center;
+  padding: 10px;
+  border-radius: 12px;
+
+  cursor: pointer;
+
+  &:hover {
+    background-color: rgba(0, 0, 0, .05);
+  }
+
+  &.active {
+    background-color: #DDF2FF;
+
+    .arrow {
+      opacity: 1;
+    }
+  }
+
+  .img {
+    position: relative;
+    width: 60px;
+    height: 60px;
+    border-radius: 18px;
+    margin-right: 12px;
+    // box-shadow: 0 0 10px 4px rgba(27, 35, 55, .1);
+    overflow: hidden;
+    flex-shrink: 0;
+
+    :global {
+      .n-image {
+        width: 60px;
+        height: 60px;
+      }
+    }
+
+    img {
+      transition: opacity .3s;
+      opacity: 0;
+      height: 100%;
+      width: 100%;
+    }
+
+    img[data-loaded="true"] {
+      opacity: 1;
+    }
+  }
+
+  .title {
+    flex: 1;
+    overflow: hidden;
+    display: flex;
+    flex-direction: column;
+    align-items: flex-start;
+
+    .titleName {
+      font-size: calc(17px, 12Px);
+      font-weight: 600;
+      color: #131415;
+      line-height: 28px;
+      width: 100%;
+    }
+
+    .titleDes {
+      font-size: 14px;
+      font-weight: 400;
+      color: #777777;
+      line-height: 20px;
+      max-width: 100%;
+      white-space: nowrap;
+      text-overflow: ellipsis;
+      overflow: hidden;
+    }
+  }
+
+  .btn {
+    margin-left: auto;
+    width: 84px;
+    height: 40px;
+    background: linear-gradient(to right, #44CAFF, #259DFE);
+    border: none;
+    padding: 0;
+    font-weight: bold !important;
+    flex-shrink: 0;
+    min-width: 62px;
+    min-height: 30px;
+
+    :global {
+      .n-button__content {
+        &>img {
+          margin-left: 10px;
+          width: 9px;
+          height: 12px;
+        }
+      }
+    }
+  }
+
+  .arrow {
+    position: absolute;
+    top: 50%;
+    right: 12px;
+    transform: translate(124%, -50%);
+    opacity: 0;
+  }
+
+  .showPlayLoading {
+    opacity: 0;
+  }
+
+}
+
+.loadingWrap {
+  display: flex;
+  justify-content: center;
+  min-height: 80px;
+}
+
+.musicStaff {
+  display: flex;
+  flex-direction: column;
+  position: relative;
+  left: -8px;
+  flex: 1;
+  background-color: #fff;
+  border-radius: 16px;
+  padding-bottom: 18px;
+  z-index: 1;
+  overflow: hidden;
+
+  &::-webkit-scrollbar {
+    width: 0;
+    display: none;
+  }
+
+
+  .musicTitle {
+    padding: 27px 27px 13px;
+    font-size: max(18px, 14Px);
+    font-weight: 600;
+    color: #000000;
+    line-height: 25px;
+    display: flex;
+    align-items: center;
+
+    .icon1 {
+      display: inline-block;
+      width: 23px;
+      height: 23px;
+      margin-right: 8px;
+      background: url('../../../content-information/images/icon-1.png') no-repeat center;
+      background-size: contain;
+    }
+  }
+
+  .musicContent {
+    flex: 1;
+    overflow-y: auto;
+    height: 100%;
+    padding: 0 27px;
+
+    &>img {
+      width: 100%;
+    }
+
+    section,
+    &>div {
+      font-size: inherit !important;
+    }
+  }
+}
+
+.staffImgs {
+  flex: 1;
+  overflow-y: auto;
+  height: 100%;
+  padding: 0 30px;
+
+  &>img {
+    width: 100%;
+  }
+}
+
+
+:global {
+
+  .van-fade-enter-active,
+  .van-fade-leave-active {
+    transition: all 0.3s;
+  }
+
+  .van-fade-enter-from,
+  .van-fade-leave-to {
+    opacity: 0;
+  }
+}
+
+.changeSizeSection {
+  position: absolute;
+  right: 10px;
+  bottom: 50%;
+  width: 35px;
+  transform: translate(0, 50%);
+  background: #fff;
+  border-radius: 7px;
+  display: flex;
+  align-items: center;
+  flex-direction: column;
+  padding: 13px 0;
+
+  .iconT {
+    width: 15px;
+    height: 15px;
+  }
+
+  .iconAddT,
+  .iconPlusT {
+    width: 23px;
+    height: 23px;
+    cursor: pointer;
+  }
+
+  .iconAddT {
+    margin-top: 13px;
+    margin-bottom: 8px;
+  }
+
+  .iconPlusT {
+    margin-top: 8px;
+  }
+
+  :global {
+    .n-slider {
+      height: 125px;
+      --n-handle-size: 15px !important;
+      --n-rail-height: 0 !important;
+    }
+
+  }
+}
+
+.musicTop,
+.musicInfo {
+  display: flex;
+  align-items: center;
+  flex-direction: column;
+  padding-top: 36px;
+  padding-bottom: 22px;
+}
+
+.musicInfo {
+  flex-direction: row;
+}
+
+.musicImg {
+  position: relative;
+  width: 100px;
+  height: 100px;
+  border-radius: 2px;
+  z-index: 9;
+  margin-right: 54px;
+  margin-left: 31px;
+
+  .img {
+    position: relative;
+    z-index: 9;
+    width: 100px;
+    height: 100px;
+    border-radius: 2px;
+  }
+
+  .panSection {
+    position: absolute;
+    right: -44px;
+    top: 5px;
+    width: 95px;
+    height: 95px;
+    z-index: 0;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+
+    .img2 {
+      position: relative;
+      z-index: 1;
+      width: 64px;
+      height: 64px;
+      border-radius: 50%;
+    }
+  }
+
+
+  .iconPan {
+    position: absolute;
+    left: 0;
+    right: 0;
+    width: 95px;
+    height: 95px;
+    z-index: 0;
+  }
+
+  &::before {
+    content: ' ';
+    position: absolute;
+    top: 0;
+    left: 0;
+    z-index: 10;
+    display: inline-block;
+    width: 5px;
+    height: 100px;
+    background: linear-gradient(270deg, rgba(0, 0, 0, 0.18) 0%, rgba(255, 255, 255, 0) 100%);
+  }
+
+  &::after {
+    content: ' ';
+    position: absolute;
+    left: -31px;
+    bottom: 0;
+    z-index: 8;
+    width: 148px;
+    height: 16px;
+    background: linear-gradient(180deg, rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 0.3) 100%);
+    filter: blur(2.3328px);
+    border-radius: 50%;
+  }
+}
+
+.info {
+  text-align: left;
+}
+
+.musicInfo {
+  // width: 500px;
+
+  .name {
+    font-size: max(21px, 15Px);
+    font-weight: 600;
+    color: #131415;
+    line-height: 25px;
+    padding-bottom: 8px;
+    max-width: 220px;
+  }
+
+  .c {
+    font-size: max(13px, 12Px);
+    color: #777777;
+    line-height: 18px;
+
+    span {
+      flex-shrink: 0;
+    }
+
+    &>div {
+      display: flex;
+      margin-right: 20px;
+      max-width: 220px;
+    }
+  }
+}

+ 372 - 0
src/views/prepare-lessons/model/source-music/detail.tsx

@@ -0,0 +1,372 @@
+import {
+  NBreadcrumb,
+  NBreadcrumbItem,
+  NButton,
+  NImage,
+  NSlider,
+  NSpace,
+  NSpin
+} from 'naive-ui';
+import { computed, defineComponent, onMounted, reactive, watch } from 'vue';
+import styles from './detail.module.less';
+import icon_back from '../../../xiaoku-music/images/icon_back.png';
+import icon_arrow from '../../../xiaoku-music/images/icon_arrow.png';
+import icon_play from '../../../xiaoku-music/images/icon_play.png';
+import icon_pause from '../../../xiaoku-music/images/icon_pause.png';
+import icon_default from '../../../xiaoku-music/images/icon_default.png';
+import icon_separator from '../../../xiaoku-music/images/icon_separator.png';
+import iconT from '/src/views/content-information/images/icon-t.png';
+import iconAddT from '/src/views/content-information/images/icon-add-t.png';
+import iconPlusT from '/src/views/content-information/images/icon-plus-t.png';
+import musicBg from '../../../xiaoku-music/images/icon_default.png';
+import iconPan from '/src/views/content-information/images/icon-pan.png';
+import { useRoute, useRouter } from 'vue-router';
+import PlayLoading from '../../../xiaoku-music/component/play-loading';
+import TheNoticeBar from '/src/components/TheNoticeBar';
+import TheEmpty from '/src/components/TheEmpty';
+import PlayItem from '../../../xiaoku-music/component/play-item';
+import { api_knowledgeWiki_detail } from '/src/views/content-information/api';
+
+export default defineComponent({
+  name: 'music-detail',
+  props: {
+    id: {
+      type: String,
+      default: ''
+    },
+    type: {
+      type: String,
+      default: ''
+    },
+    activeStatus: {
+      type: Boolean,
+      default: false
+    },
+    contentType: {
+      type: String,
+      default: ''
+    }
+  },
+  setup(props, { expose }) {
+    const route = useRoute();
+    const data = reactive({
+      loading: false,
+      finshed: false,
+      reshing: false,
+      details: {} as any,
+      list: [] as any,
+      listActive: 0,
+      playState: 'pause' as 'play' | 'pause',
+      showPlayer: false,
+      showPreivew: false,
+      previewUrl: '',
+      showCloseBtn: true,
+      fontSize: 18 // 默认18
+    });
+
+    /** 选中的item */
+    const activeItem = computed(() => {
+      return data.list[data.listActive] || {};
+    });
+
+    /** 播放曲目 */
+    const handlePlay = (item: any) => {
+      const index = data.list.findIndex((_item: any) => _item.id === item.id);
+      if (index > -1) {
+        if (data.listActive === index) {
+          data.playState = data.playState === 'play' ? 'pause' : 'play';
+        } else {
+          data.playState = 'play';
+        }
+        data.showPlayer = true;
+        data.listActive = index;
+      }
+    };
+
+    /** 音频控制 */
+    const handleChangeAudio = (
+      type: 'play' | 'pause' | 'pre' | 'next' | 'favitor'
+    ) => {
+      if (type === 'play') {
+        data.playState = 'play';
+      } else if (type === 'pause') {
+        data.playState = 'pause';
+      } else if (type === 'pre') {
+        if (data.list[data.listActive - 1]) {
+          handlePlay(data.list[data.listActive - 1]);
+        }
+      } else if (type === 'next') {
+        if (data.list[data.listActive + 1]) {
+          handlePlay(data.list[data.listActive + 1]);
+        }
+      }
+    };
+
+    const getDetail = async () => {
+      data.loading = true;
+      let res = {} as any;
+      try {
+        res = await api_knowledgeWiki_detail({
+          id: props.id || route.query.id
+        });
+      } catch (error) {
+        console.log(error);
+      }
+      if (data.reshing) {
+        data.list = [];
+        data.reshing = false;
+      }
+
+      data.finshed = true;
+      try {
+        data.list = res.data?.knowledgeWikiResources || [];
+        data.list.forEach((item: any) => {
+          item.audioFileUrl = item.url;
+          item.musicSheetName = item.name;
+        });
+        const knowledgeWikiCategories = res.data?.knowledgeWikiCategories || [];
+        res.data.knowledgeName =
+          knowledgeWikiCategories.length > 0
+            ? knowledgeWikiCategories[0].name
+            : '';
+        res.data.intros = res.data.intros.replace(
+          /<video/gi,
+          '<video class="video-music" style="width: 100% !important;" controlslist="nodownload"'
+        );
+        data.details = res.data;
+      } catch {
+        //
+      }
+
+      data.loading = false;
+    };
+
+    const onStopAll = (type: 'play' | 'pause' | 'pre' | 'next' | 'favitor') => {
+      handleChangeAudio(type);
+
+      try {
+        // 暂停视频
+        const doms = document.querySelectorAll('.video-music');
+        if (doms && doms.length > 0) {
+          doms.forEach((dom: any) => {
+            dom.pause();
+          });
+        }
+      } catch {
+        //
+      }
+    };
+
+    onMounted(() => {
+      getDetail();
+    });
+
+    watch(
+      () => props.activeStatus,
+      () => {
+        if (!props.activeStatus) {
+          onStopAll('pause');
+        }
+      }
+    );
+
+    expose({
+      handleChangeAudio: onStopAll
+    });
+    return () => (
+      <div
+        class={[
+          styles.container,
+          props.type === 'preview' && styles.containerPreview,
+          props.type === 'modal' && styles.containerModal
+        ]}>
+        <div class={[styles.wrap, data.showPlayer ? styles.wrapBottom : '']}>
+          <div class={styles.content}>
+            <div class={styles.contentWrap}>
+              <div class={[styles.musicList, 'musicList-container']}>
+                <div class={styles.wrapList}>
+                  <div class={styles.musicInfo}>
+                    <div class={styles.musicImg}>
+                      <img
+                        src={data.details?.avatar || musicBg}
+                        class={styles.img}
+                      />
+                      <div class={styles.panSection}>
+                        <img src={iconPan} class={styles.iconPan} />
+                        <img
+                          src={data.details?.avatar || musicBg}
+                          class={styles.img2}
+                        />
+                      </div>
+                    </div>
+
+                    <div class={styles.info}>
+                      <div class={styles.name}>
+                        <TheNoticeBar
+                          text={data.details.name}
+                          style={{ marginRight: '0' }}
+                        />
+                        {/* {data.details.name} */}
+                      </div>
+                      <div class={styles.c}>
+                        {data.details.composers ? (
+                          <div>
+                            <span>作曲:</span>
+                            <TheNoticeBar
+                              text={data.details.composers}
+                              style={{ marginRight: '0' }}
+                            />
+                          </div>
+                        ) : (
+                          ''
+                        )}
+                        {data.details.lyricists ? (
+                          <div>
+                            <span>作词:</span>
+                            <TheNoticeBar
+                              text={data.details.lyricists}
+                              style={{ marginRight: '0' }}
+                            />
+                          </div>
+                        ) : (
+                          ''
+                        )}
+                      </div>
+                    </div>
+                  </div>
+
+                  <div class={styles.titlec}>
+                    <i class={styles.icon2}></i>名曲鉴赏
+                  </div>
+
+                  {data.list.map((item: any, index: any) => {
+                    return (
+                      <div class={styles.itemContainer}>
+                        <div
+                          class={[styles.item]}
+                          onClick={(e: Event) => {
+                            e.stopPropagation();
+                            handlePlay(item);
+                          }}>
+                          <div class={styles.img}>
+                            <NImage
+                              lazy
+                              objectFit="cover"
+                              previewDisabled={true}
+                              src={item.titleImg || icon_default}
+                              onLoad={e => {
+                                (e.target as any).dataset.loaded = 'true';
+                              }}
+                            />
+                            <PlayLoading
+                              class={[
+                                data.listActive === index &&
+                                data.playState === 'play'
+                                  ? ''
+                                  : styles.showPlayLoading
+                              ]}
+                            />
+                          </div>
+                          <div class={styles.title}>
+                            <div class={styles.titleName}>
+                              <TheNoticeBar
+                                text={item.name}
+                                style={{ marginRight: '12px' }}
+                              />
+                            </div>
+                          </div>
+
+                          <NButton
+                            color="#259CFE"
+                            textColor="#fff"
+                            round
+                            class={styles.btn}
+                            type="primary"
+                            onClick={(e: Event) => {
+                              e.stopPropagation();
+                              handlePlay(item);
+                            }}>
+                            播放
+                            <img
+                              src={
+                                data.listActive === index &&
+                                data.playState === 'play'
+                                  ? icon_pause
+                                  : icon_play
+                              }
+                            />
+                          </NButton>
+
+                          <img class={styles.arrow} src={icon_arrow} />
+                        </div>
+                      </div>
+                    );
+                  })}
+                  {!data.finshed && (
+                    <div class={styles.loadingWrap}>
+                      <NSpin show={true}></NSpin>
+                    </div>
+                  )}
+                  {!data.loading && data.list.length === 0 && (
+                    <div class={styles.empty}>
+                      <TheEmpty
+                        description="暂无名曲鉴赏"
+                        style={{ paddingTop: '0px' }}></TheEmpty>
+                    </div>
+                  )}
+                </div>
+              </div>
+
+              <div class={styles.musicStaff}>
+                <div class={styles.musicTitle}>
+                  <i class={styles.icon1}></i>名曲故事
+                </div>
+                <div
+                  class={styles.musicContent}
+                  v-html={data.details?.intros}
+                  style={{ fontSize: data.fontSize + 'px' }}></div>
+              </div>
+
+              <div class={styles.changeSizeSection}>
+                <img src={iconT} class={styles.iconT} />
+                <img
+                  src={iconAddT}
+                  class={styles.iconAddT}
+                  onClick={() => {
+                    if (data.fontSize >= 32) return;
+                    data.fontSize += 1;
+                  }}
+                />
+                <NSlider
+                  v-model:value={data.fontSize}
+                  placement="left"
+                  vertical
+                  min={12}
+                  max={32}
+                />
+                <img
+                  src={iconPlusT}
+                  class={styles.iconPlusT}
+                  onClick={() => {
+                    if (data.fontSize <= 12) return;
+                    data.fontSize -= 1;
+                  }}
+                />
+              </div>
+            </div>
+          </div>
+        </div>
+
+        {data.list.length !== 0 && (
+          <PlayItem
+            type={props.type}
+            show={data.showPlayer}
+            playState={data.playState}
+            item={activeItem.value}
+            onChange={value => handleChangeAudio(value)}
+          />
+        )}
+      </div>
+    );
+  }
+});

+ 126 - 0
src/views/prepare-lessons/model/source-music/index.module.less

@@ -0,0 +1,126 @@
+.container {
+
+
+  :global {
+    .n-tabs-tab-pad {
+      width: 80px !important;
+    }
+
+    .n-tabs-nav {
+      padding: 0 20px 0;
+
+      .n-tabs-nav-scroll-wrapper {
+        padding: 25px 0 30px;
+      }
+    }
+
+    .n-tabs-tab {
+      color: #8B8D98;
+      font-size: max(22px, 14Px);
+      padding-top: 0;
+      padding-bottom: 6px;
+      line-height: 22px;
+
+      &.n-tabs-tab--active {
+        font-weight: 600 !important;
+        color: #131415 !important;
+      }
+    }
+
+    .n-tabs-tab__label {
+      z-index: 10;
+    }
+
+    .n-tabs-bar {
+      height: 10px;
+      background: linear-gradient(90deg, #77BBFF 0%, rgba(163, 231, 255, 0.22) 100%);
+      z-index: 0;
+      bottom: 2px;
+    }
+
+    .n-tab-pane {
+      padding: 0 12px 0 !important;
+    }
+
+    .n-pagination {
+      margin-top: 36px !important;
+    }
+  }
+
+  .separator {
+    width: 9px;
+    height: 15px;
+    margin: 0 16px;
+  }
+}
+
+.wrap {
+  transition: padding 0.3s;
+  // overflow: hidden;
+}
+
+.listWrap {
+  padding: 0;
+  background-color: #fff;
+  border-radius: 20px;
+
+
+  &.listWrapEmpty {
+    display: flex;
+    flex-direction: column;
+    justify-content: center;
+  }
+
+  :global {
+    .n-tabs-tab-pad {
+      width: 50px !important;
+    }
+
+    // .n-tabs-nav {
+    //   padding: 0px 20px 0;
+    // }
+
+    // .n-tabs-tab {
+    //   color: #8B8D98;
+    //   font-size: max(22px, 14Px);
+    //   padding-top: 0;
+    //   padding-bottom: 6px;
+    //   line-height: 22px;
+
+    //   &.n-tabs-tab--active {
+    //     font-weight: 600 !important;
+    //     color: #131415 !important;
+    //   }
+    // }
+
+    // .n-tabs-tab__label {
+    //   z-index: 10;
+    // }
+
+    // .n-tabs-bar {
+    //   height: 10px;
+    //   background: linear-gradient(90deg, #77BBFF 0%, rgba(163, 231, 255, 0.22) 100%);
+    //   z-index: 0;
+    //   bottom: 2px;
+    // }
+
+    .n-tab-pane {
+      padding: 0 !important;
+    }
+
+    // .n-pagination {
+    //   margin-top: 36px !important;
+    // }
+  }
+}
+
+.btnGroup {
+  padding: 20px 0;
+
+  :global {
+    .n-button {
+      height: 47px;
+      min-width: 156px;
+    }
+  }
+}

+ 116 - 0
src/views/prepare-lessons/model/source-music/index.tsx

@@ -0,0 +1,116 @@
+import {
+  NBreadcrumb,
+  NBreadcrumbItem,
+  NButton,
+  NSpace,
+  NTabPane,
+  NTabs
+} from 'naive-ui';
+import { defineComponent, nextTick, reactive } from 'vue';
+import styles from './index.module.less';
+import { useRouter } from 'vue-router';
+import List from './components/list';
+import { api_knowledgeWikiCategoryType_page } from '/src/views/content-information/api';
+import TheEmpty from '/src/components/TheEmpty';
+import { PageEnum } from '/src/enums/pageEnum';
+
+// 164px 244px
+export default defineComponent({
+  name: 'content-music',
+  emits: ['close', 'confirm'],
+  setup(props, { emit }) {
+    const state = reactive({
+      tabValue: '',
+      categoryList: [] as any,
+      loading: false,
+      selectItems: [] as any
+    });
+
+    const getCategoryList = async () => {
+      state.loading = true;
+      try {
+        const { data } = await api_knowledgeWikiCategoryType_page({
+          type: 'MUSIC',
+          page: 1,
+          rows: 99
+        });
+
+        state.categoryList = data.rows || [];
+        if (state.categoryList.length) {
+          nextTick(() => {
+            state.tabValue = 'name-' + state.categoryList[0].id;
+          });
+        }
+      } catch {
+        //
+      }
+      state.loading = false;
+    };
+
+    getCategoryList();
+
+    // 添加
+    const onSubmit = async () => {
+      const tempList: any = [];
+      state.selectItems.forEach((item: any) => {
+        tempList.push({
+          coverImg: PageEnum.MUSIC_DEFAULT_COVER,
+          title: item.name,
+          materialId: item.id,
+          content: item.id
+        });
+      });
+      emit('confirm', tempList);
+    };
+    return () => (
+      <div class={styles.container}>
+        <div class={styles.wrap}>
+          <div
+            class={[
+              styles.listWrap,
+              !state.loading &&
+                state.categoryList.length <= 0 &&
+                styles.listWrapEmpty
+            ]}>
+            {!state.loading && state.categoryList.length <= 0 && (
+              <TheEmpty description="暂无名曲鉴赏" />
+            )}
+            <div style={{ minHeight: '55vh' }}>
+              <NTabs
+                defaultValue="myResources"
+                paneClass={styles.paneTitle}
+                justifyContent="center"
+                paneWrapperClass={styles.paneWrapperContainer}
+                v-model:value={state.tabValue}>
+                {state.categoryList.map((category: any) => (
+                  <NTabPane
+                    name={`name-${category.id}`}
+                    tab={category.name}
+                    // displayDirective="show:lazy"
+                  >
+                    <List
+                      selectItems={state.selectItems}
+                      categoryId={category.id}
+                      categoryChildList={category.childrenList}
+                      onConfirm={(ids: any) => {
+                        state.selectItems = ids || [];
+                      }}
+                    />
+                  </NTabPane>
+                ))}
+              </NTabs>
+            </div>
+          </div>
+        </div>
+        <NSpace class={styles.btnGroup} justify="center">
+          <NButton round onClick={() => emit('close')}>
+            取消
+          </NButton>
+          <NButton round type="primary" onClick={onSubmit}>
+            确认添加
+          </NButton>
+        </NSpace>
+      </div>
+    );
+  }
+});

+ 244 - 0
src/views/prepare-lessons/model/source-musician/components/list/index.module.less

@@ -0,0 +1,244 @@
+.searchGroup {
+  position: relative;
+  padding: 0;
+
+
+  .btnType {
+    gap: 0px 24px !important;
+    flex-wrap: nowrap !important;
+
+    &>div {
+      display: flex;
+      align-items: center;
+    }
+
+
+    .swipeControll {
+      height: 25Px;
+
+      .leftIcon {
+        transform: rotate(180deg);
+      }
+
+      img {
+        cursor: pointer;
+        width: 25Px;
+        height: 25Px;
+      }
+
+      .disabled {
+        opacity: 0.4;
+        cursor: not-allowed;
+      }
+    }
+
+    :global {
+      .n-button {
+        height: 37px;
+        padding: 0 24px;
+        font-size: 18px;
+        color: rgba(0, 0, 0, .6);
+
+        &.n-button--primary-type {
+          font-weight: bold;
+          color: #fff;
+        }
+      }
+    }
+
+    .carouselGroup {
+      display: flex;
+      width: 660px
+    }
+
+    .carouselContainer {
+      width: 550px;
+      margin-right: 12px;
+
+      :global {
+        .n-carousel__slide {
+          width: auto !important;
+          display: flex;
+          align-items: center;
+          padding-left: 10px;
+          padding-right: 10px;
+        }
+      }
+    }
+  }
+
+
+  .inputSearch {
+    width: 360px;
+    height: 42px;
+    font-size: 16px;
+    --n-height: 42px !important;
+
+    img {
+      width: 18px;
+      height: 18px;
+    }
+
+    :global {
+      .n-input-wrapper {
+        padding-left: 12px;
+        padding-right: 4px;
+        height: 42px !important;
+      }
+
+      .n-button {
+        height: 34px;
+        font-size: 15px;
+        font-weight: 500;
+        width: auto;
+      }
+    }
+  }
+
+  .searchCatatory {
+    display: flex;
+    justify-content: space-between;
+
+    border-bottom: 1px solid #F2F2F2;
+    padding-bottom: 12px;
+    margin-bottom: 12px;
+
+    .addTrain {
+      height: 37px;
+      border-radius: 8px;
+      font-size: 18px;
+      background-color: #E8F4FF;
+      color: #0378EC;
+
+      img {
+        width: 16px;
+        height: 16px;
+        margin-right: 8px;
+      }
+    }
+  }
+}
+
+.searchGroups {
+  padding: 0 33px;
+}
+
+// .listContainer {
+//   padding: 0 33px;
+// }
+
+.list {
+  margin-top: 12px;
+  display: flex;
+  flex-flow: row wrap;
+  justify-content: flex-start;
+  gap: 20px 0;
+  min-height: 232px;
+  margin-left: -22px;
+  margin-right: -22px;
+  padding: 0 33px;
+
+  .itemWrap {
+    width: calc(100% / 6);
+    padding-bottom: calc(100% / 6 * 1.1355555);
+    position: relative;
+
+    .itemWrapBox {
+      position: absolute;
+      left: 0;
+      top: 0;
+      width: 100%;
+      height: 100%;
+      padding: 0 22px;
+    }
+  }
+
+  .itemCard {
+    position: relative;
+    cursor: pointer;
+
+    &:hover {
+
+      .itemImgSection {
+        border-radius: 13px;
+        // border: 3px solid rgba(0, 122, 254, 1);
+        box-sizing: border-box;
+        transition: all .2s ease;
+        transform: scale(1.03);
+        transition: all .2s ease;
+      }
+    }
+
+    .itemTag {
+      position: absolute;
+      right: 0;
+      top: 0;
+      display: inline-block;
+      font-size: 12Px;
+      font-weight: 600;
+      color: #FFFFFF;
+      line-height: 17Px;
+      text-shadow: 2Px 2Px 8Px rgba(0, 0, 0, 0.1);
+      line-height: 23Px;
+      padding: 0 7Px;
+      background: linear-gradient(135deg, #02BAFF 0%, #007AFE 100%);
+      box-shadow: 2Px 2 8Px 0Px rgba(0, 0, 0, 0.1);
+      border-radius: 0Px 13Px 0Px 13Px;
+    }
+
+    .itemImgSection {
+      transition: all .2s ease;
+      position: relative;
+      width: 148px;
+      height: 169px;
+      // box-shadow: 2px 2px 8px 0px rgba(0, 0, 0, 0.1);
+      border-radius: 13px;
+      overflow: hidden;
+      transform: all .2s ease;
+      background-color: transparent;
+      border: 2px solid transparent;
+
+      .iconCheck {
+        position: absolute;
+        top: 7px;
+        right: 7px;
+        width: 20px;
+        height: 20px;
+        background: url('../../../../images/icon-check.png') no-repeat center;
+        background-size: contain;
+      }
+
+      .img {
+        width: 148px;
+        height: 169px;
+        background: linear-gradient(360deg, #DBF1FF 0%, #E7F9FF 100%);
+
+        display: flex;
+
+        img {
+          width: 100%;
+          height: 100%;
+        }
+      }
+    }
+
+    .itemImgSectionSelected {
+      border: 2px solid #198CFE;
+      // transition: all .2s ease;
+
+      .iconCheck {
+        background: url('../../../../images/icon-checked.png') no-repeat center;
+        background-size: contain;
+      }
+    }
+
+    .itemTitle {
+      padding-top: 10px;
+      font-size: 18px;
+      font-weight: 600;
+      color: #131415;
+      line-height: 25px;
+      text-align: center;
+    }
+  }
+}

+ 189 - 0
src/views/prepare-lessons/model/source-musician/components/list/index.tsx

@@ -0,0 +1,189 @@
+import { PropType, defineComponent, onMounted, reactive } from 'vue';
+import styles from './index.module.less';
+import SearchGroupResources from './search-group-resources';
+import { NImage, NScrollbar, NSpin } from 'naive-ui';
+import TheEmpty from '/src/components/TheEmpty';
+import Pagination from '/src/components/pagination';
+import { useRouter } from 'vue-router';
+import { api_knowledgeWiki_page } from '/src/views/content-information/api';
+import CardPreview from '/src/components/card-preview';
+
+export default defineComponent({
+  name: 'musician-list',
+  props: {
+    categoryId: {
+      type: String,
+      default: ''
+    },
+    categoryChildList: {
+      type: Array as PropType<any>,
+      default: () => []
+    },
+    selectItems: {
+      type: Array as PropType<any>,
+      default: () => []
+    }
+  },
+  emits: ['confirm'],
+  setup(props, { emit }) {
+    const router = useRouter();
+    const state = reactive({
+      searchWord: '',
+      loading: false,
+      pageTotal: 0,
+      finshed: false, // 是否加载完
+      pagination: {
+        page: 1,
+        rows: 18
+      },
+      searchGroup: {
+        type: 'MUSICIAN', //
+        keyword: '',
+        wikiCategoryId: props.categoryId
+      },
+      tableList: [] as any,
+      teachingStatus: false,
+      show: false,
+      item: {} as any
+    });
+
+    const getList = async () => {
+      state.loading = true;
+      try {
+        const { data } = await api_knowledgeWiki_page({
+          ...state.pagination,
+          ...state.searchGroup
+        });
+        const temp = data.rows || [];
+        temp.forEach((item: any) => {
+          if (
+            item.knowledgeWikiCategories &&
+            item.knowledgeWikiCategories.length
+          ) {
+            item.categories =
+              item.knowledgeWikiCategories[0].knowledgeWikiCategoryTypeName;
+          }
+        });
+        state.tableList.push(...temp);
+
+        state.pageTotal = Number(data.total);
+        state.finshed = data.pages <= data.current ? true : false;
+      } catch {
+        //
+      }
+      state.loading = false;
+    };
+
+    const onSearch = async (item: any) => {
+      state.pagination.page = 1;
+      state.searchGroup = Object.assign(state.searchGroup, item);
+      state.tableList = [];
+      getList();
+    };
+
+    // 更新
+    const onSelect = (item: any) => {
+      const ids = props.selectItems || [];
+      const index = ids.findIndex((i: any) => i.id === item.id);
+      if (index !== -1) {
+        ids.splice(index, 1);
+      } else {
+        ids.push(item);
+      }
+
+      emit('confirm', ids);
+    };
+
+    onMounted(() => {
+      getList();
+    });
+
+    return () => (
+      <div class={styles.instrumentList}>
+        <SearchGroupResources
+          class={styles.searchGroups}
+          categoryChildList={props.categoryChildList || []}
+          onSearch={(item: any) => onSearch(item)}
+          wikiCategoryId={props.categoryId}
+        />
+        <NScrollbar
+          class={styles.listContainer}
+          style={{
+            'max-height': `50vh`
+          }}
+          onScroll={(e: any) => {
+            const clientHeight = e.target?.clientHeight;
+            const scrollTop = e.target?.scrollTop;
+            const scrollHeight = e.target?.scrollHeight;
+            // 是否到底,是否加载完
+            if (
+              clientHeight + scrollTop + 20 >= scrollHeight &&
+              !state.finshed &&
+              !state.loading
+            ) {
+              state.pagination.page = state.pagination.page + 1;
+              getList();
+            }
+          }}>
+          <NSpin v-model:show={state.loading} style={{ 'min-height': '50vh' }}>
+            <div class={styles.list}>
+              {state.tableList.map((item: any) => (
+                <div
+                  class={styles.itemWrap}
+                  onClick={() => {
+                    state.item = {
+                      content: item.id,
+                      title: item.name,
+                      type: 'MUSICIAN'
+                    };
+                    state.show = true;
+                  }}>
+                  <div class={styles.itemWrapBox}>
+                    <div class={styles.itemCard}>
+                      <div
+                        class={[
+                          styles.itemImgSection,
+                          props.selectItems.findIndex(
+                            (i: any) => i.id === item.id
+                          ) !== -1 && styles.itemImgSectionSelected
+                        ]}>
+                        <NImage
+                          src={item.avatar}
+                          class={styles.img}
+                          objectFit="cover"
+                          previewDisabled
+                        />
+
+                        <i
+                          class={[styles.iconCheck]}
+                          onClick={(e: any) => {
+                            e.stopPropagation();
+                            onSelect(item);
+                          }}></i>
+                      </div>
+                      <div class={styles.itemTitle}>{item.name}</div>
+                    </div>
+                  </div>
+                </div>
+              ))}
+
+              {!state.loading && state.tableList.length <= 0 && (
+                <TheEmpty
+                  style={{ minHeight: '50vh' }}
+                  description="暂无乐器百科"
+                />
+              )}
+            </div>
+          </NSpin>
+        </NScrollbar>
+
+        {/* 弹窗查看 */}
+        <CardPreview
+          size={'large'}
+          v-model:show={state.show}
+          item={state.item}
+        />
+      </div>
+    );
+  }
+});

+ 177 - 0
src/views/prepare-lessons/model/source-musician/components/list/search-group-resources.tsx

@@ -0,0 +1,177 @@
+import { PropType, defineComponent, onMounted, reactive, ref } from 'vue';
+import styles from './index.module.less';
+import { NButton, NCarousel, NCarouselItem, NImage, NSpace } from 'naive-ui';
+import TheSearch from '/src/components/TheSearch';
+import iconSlideRight from '/src/views/prepare-lessons/images/icon-slide-right.png';
+import { nextTick } from 'process';
+export default defineComponent({
+  name: 'search-group',
+  props: {
+    categoryChildList: {
+      type: Array as PropType<any>,
+      default: () => []
+    },
+    wikiCategoryId: {
+      type: String,
+      default: ''
+    }
+  },
+  emits: ['search', 'add'],
+  expose: ['init'],
+  setup(props, { emit }) {
+    // const catchStore = useCatchStore();
+    const forms = reactive({
+      currentIndex: 0,
+      keyword: '',
+      wikiCategoryId: props.wikiCategoryId || '',
+      maxIndex: 0
+    });
+
+    const state = reactive({
+      showSlide: false
+    });
+
+    const onSearch = () => {
+      emit('search', forms);
+    };
+
+    const carouselRef = ref();
+    const onChangeSlide = (type: string) => {
+      if (type === 'left') {
+        carouselRef.value?.prev();
+      } else if (type === 'right') {
+        carouselRef.value?.next();
+      }
+    };
+    onMounted(async () => {
+      // 获取教材分类列表
+      // await catchStore.getMusicSheetCategory()
+      // nextTick(() => {
+      //   carouselRef.value?.to(100);
+      // });
+      nextTick(() => {
+        // carouselRef.value?.to(100);
+
+        // 最外层宽度
+        const carouselContainer = document.querySelector('.carouselContainer');
+        const carouselContainerWidth =
+          (carouselContainer &&
+            carouselContainer.getBoundingClientRect().width) ||
+          0;
+        const slideDoms = document.querySelectorAll('.n-carousel__slide');
+        let slideWidth = 0;
+        slideDoms.forEach(doom => {
+          const rect = doom.getBoundingClientRect();
+          slideWidth += rect.width;
+        });
+        if (slideWidth >= carouselContainerWidth) {
+          state.showSlide = true;
+        }
+      });
+    });
+    return () => (
+      <div class={styles.searchGroup}>
+        <div class={[styles.searchCatatory]}>
+          <NSpace size="small" class={styles.btnType}>
+            {props.categoryChildList.length > 0 ? (
+              <NButton
+                type={
+                  forms.wikiCategoryId === props.wikiCategoryId
+                    ? 'primary'
+                    : 'default'
+                }
+                secondary={
+                  forms.wikiCategoryId === props.wikiCategoryId ? false : true
+                }
+                round
+                size="small"
+                focusable={false}
+                onClick={() => {
+                  forms.wikiCategoryId = props.wikiCategoryId;
+                  onSearch();
+                }}>
+                全部
+              </NButton>
+            ) : (
+              ''
+            )}
+            <div class={styles.carouselGroup}>
+              <NCarousel
+                ref={carouselRef}
+                slidesPerView={'auto'}
+                loop={false}
+                class={[styles.carouselContainer, 'carouselContainer']}
+                showDots={false}
+                // spaceBetween={20}
+                draggable={state.showSlide}
+                currentIndex={forms.currentIndex}
+                onUpdate:currentIndex={(val: any) => {
+                  //
+                  // if (val > forms.maxIndex) {
+                  //   forms.maxIndex = val;
+                  //   carouselRef.value?.to(0);
+                  // }
+                  forms.currentIndex = val;
+                }}>
+                {props.categoryChildList.map((item: any) => (
+                  <NCarouselItem>
+                    <NButton
+                      type={
+                        forms.wikiCategoryId === item.id ? 'primary' : 'default'
+                      }
+                      secondary={
+                        forms.wikiCategoryId === item.id ? false : true
+                      }
+                      round
+                      size="small"
+                      focusable={false}
+                      onClick={() => {
+                        forms.wikiCategoryId = item.id;
+                        onSearch();
+                      }}>
+                      {item.name}
+                    </NButton>
+                  </NCarouselItem>
+                ))}
+              </NCarousel>
+
+              {state.showSlide && (
+                <NSpace class={styles.swipeControll}>
+                  <div onClick={() => onChangeSlide('left')}>
+                    <NImage
+                      previewDisabled
+                      class={[
+                        styles.leftIcon
+                        // forms.currentIndex === 0 && styles.disabled
+                      ]}
+                      src={iconSlideRight}
+                    />
+                  </div>
+                  <div onClick={() => onChangeSlide('right')}>
+                    <NImage
+                      // class={
+                      //   // forms.currentIndex == forms.openTableList.length - 4 &&
+                      //   styles.disabled
+                      // }
+                      previewDisabled
+                      src={iconSlideRight}
+                    />
+                  </div>
+                </NSpace>
+              )}
+            </div>
+          </NSpace>
+          <TheSearch
+            class={styles.inputSearch}
+            placeholder="请输入乐器关键词"
+            round
+            onSearch={(val: string) => {
+              forms.keyword = val;
+              onSearch();
+            }}
+          />
+        </div>
+      </div>
+    );
+  }
+});

+ 131 - 0
src/views/prepare-lessons/model/source-musician/index.module.less

@@ -0,0 +1,131 @@
+.container {
+  .iconBack {
+    width: 36px;
+    height: 36px;
+  }
+
+
+  :global {
+    .n-tabs-tab-pad {
+      width: 80px !important;
+    }
+
+    .n-tabs-nav {
+      padding: 0 20px 0;
+
+      .n-tabs-nav-scroll-wrapper {
+        padding: 25px 0 30px;
+      }
+    }
+
+    .n-tabs-tab {
+      color: #8B8D98;
+      font-size: max(22px, 14Px);
+      padding-top: 12px;
+      padding-bottom: 6px;
+      line-height: 22px;
+
+      &.n-tabs-tab--active {
+        font-weight: 600 !important;
+        color: #131415 !important;
+      }
+    }
+
+    .n-tabs-tab__label {
+      z-index: 10;
+    }
+
+    .n-tabs-bar {
+      height: 10px;
+      background: linear-gradient(90deg, #77BBFF 0%, rgba(163, 231, 255, 0.22) 100%);
+      z-index: 0;
+      bottom: 2px;
+    }
+
+    .n-tab-pane {
+      padding-top: 0 !important;
+    }
+
+  }
+
+  &> :global(.n-space) {
+    // height: 36px;
+    flex-shrink: 0;
+  }
+
+  .separator {
+    width: 9px;
+    height: 15px;
+    margin: 0 16px;
+  }
+}
+
+.wrap {
+  flex: 1;
+  transition: padding 0.3s;
+}
+
+.listWrap {
+  padding: 0;
+  background-color: #fff;
+  border-radius: 20px;
+
+  &.listWrapEmpty {
+    display: flex;
+    flex-direction: column;
+    justify-content: center;
+  }
+
+  :global {
+    .n-tabs-tab-pad {
+      width: 80px !important;
+    }
+
+    .n-tabs-nav {
+      padding: 0px 20px 0;
+    }
+
+    .n-tabs-tab {
+      color: #8B8D98;
+      font-size: max(22px, 14Px);
+      padding-top: 0;
+      padding-bottom: 6px;
+      line-height: 22px;
+
+      &.n-tabs-tab--active {
+        font-weight: 600 !important;
+        color: #131415 !important;
+      }
+    }
+
+    .n-tabs-tab__label {
+      z-index: 10;
+    }
+
+    .n-tabs-bar {
+      height: 10px;
+      background: linear-gradient(90deg, #77BBFF 0%, rgba(163, 231, 255, 0.22) 100%);
+      z-index: 0;
+      bottom: 2px;
+    }
+
+    .n-tab-pane {
+      padding: 0 !important;
+    }
+
+    .n-pagination {
+      margin-top: 36px !important;
+    }
+  }
+}
+
+.btnGroup {
+  padding: 20px 0;
+
+  :global {
+    .n-button {
+      height: 47px;
+      min-width: 156px;
+    }
+  }
+}

+ 109 - 0
src/views/prepare-lessons/model/source-musician/index.tsx

@@ -0,0 +1,109 @@
+import { NButton, NSpace, NTabPane, NTabs } from 'naive-ui';
+import { defineComponent, nextTick, reactive } from 'vue';
+import styles from './index.module.less';
+import { useRoute, useRouter } from 'vue-router';
+import List from './components/list';
+import { api_knowledgeWikiCategoryType_page } from '/src/views/content-information/api';
+import TheEmpty from '/src/components/TheEmpty';
+import { PageEnum } from '/src/enums/pageEnum';
+
+export default defineComponent({
+  name: 'content-instrument',
+  emits: ['confirm', 'close'],
+  setup(props, { emit }) {
+    const state = reactive({
+      tabValue: '',
+      categoryList: [] as any,
+      loading: false,
+      selectItems: [] as any
+    });
+
+    const getCategoryList = async () => {
+      state.loading = true;
+      try {
+        const { data } = await api_knowledgeWikiCategoryType_page({
+          type: 'MUSICIAN',
+          page: 1,
+          rows: 99
+        });
+
+        state.categoryList = data.rows || [];
+        if (state.categoryList.length) {
+          nextTick(() => {
+            state.tabValue = 'name-' + state.categoryList[0].id;
+          });
+        }
+      } catch {
+        //
+      }
+      state.loading = false;
+    };
+
+    getCategoryList();
+
+    // 添加
+    const onSubmit = async () => {
+      const tempList: any = [];
+      state.selectItems.forEach((item: any) => {
+        tempList.push({
+          coverImg: PageEnum.MUSICIAN_DEFAULT_COVER,
+          title: item.name,
+          materialId: item.id,
+          content: item.id
+        });
+      });
+      emit('confirm', tempList);
+    };
+    return () => (
+      <div class={styles.container}>
+        <div class={styles.wrap}>
+          <div
+            class={[
+              styles.listWrap,
+              !state.loading &&
+                state.categoryList.length <= 0 &&
+                styles.listWrapEmpty
+            ]}>
+            {!state.loading && state.categoryList.length <= 0 && (
+              <TheEmpty description="暂无音乐家" />
+            )}
+            <div style={{ minHeight: '55vh' }}>
+              <NTabs
+                defaultValue="myResources"
+                paneClass={styles.paneTitle}
+                justifyContent="center"
+                // animated
+                paneWrapperClass={styles.paneWrapperContainer}
+                onUpdate:value={(val: any) => {
+                  sessionStorage.setItem('content-instrument-tab', val);
+                }}
+                v-model:value={state.tabValue}>
+                {state.categoryList.map((category: any) => (
+                  <NTabPane name={`name-${category.id}`} tab={category.name}>
+                    <List
+                      selectItems={state.selectItems}
+                      categoryId={category.id}
+                      categoryChildList={category.childrenList}
+                      onConfirm={(ids: any) => {
+                        state.selectItems = ids || [];
+                      }}
+                    />
+                  </NTabPane>
+                ))}
+              </NTabs>
+            </div>
+          </div>
+        </div>
+
+        <NSpace class={styles.btnGroup} justify="center">
+          <NButton round onClick={() => emit('close')}>
+            取消
+          </NButton>
+          <NButton round type="primary" onClick={onSubmit}>
+            确认添加
+          </NButton>
+        </NSpace>
+      </div>
+    );
+  }
+});

+ 1 - 1
src/views/xiaoku-music/component/play-item/index.tsx

@@ -48,7 +48,7 @@ export default defineComponent({
     const audioRef = ref();
     /** 加载成功 */
     const onLoadedmetadata = () => {
-      audioData.duration = audioRef.value.duration;
+      audioData.duration = audioRef.value?.duration;
       if (audioData.isFirst) {
         audioData.isFirst = false;
         return;