소스 검색

Merge branch 'iteration-classroom' into dev

lex 1 년 전
부모
커밋
f7d76cd6ff
40개의 변경된 파일3323개의 추가작업 그리고 228개의 파일을 삭제
  1. 32 4
      src/components/card-preview/index.tsx
  2. 3 1
      src/components/card-preview/rhythm-modal/index.tsx
  3. 3 3
      src/components/card-type/index.tsx
  4. 1 1
      src/views/attend-class/index.module.less
  5. 16 2
      src/views/attend-class/index.tsx
  6. 1 0
      src/views/attend-class/model/select-class/index.tsx
  7. 21 4
      src/views/prepare-lessons/components/lesson-main/courseware-presets/index.module.less
  8. 12 4
      src/views/prepare-lessons/components/lesson-main/courseware-presets/index.tsx
  9. 1 0
      src/views/prepare-lessons/components/lesson-main/courseware/addCourseware.module.less
  10. 28 30
      src/views/prepare-lessons/components/lesson-main/courseware/addCourseware.tsx
  11. 9 1
      src/views/prepare-lessons/components/lesson-main/index.tsx
  12. BIN
      src/views/prepare-lessons/images/icon-arrow-p.png
  13. BIN
      src/views/prepare-lessons/images/icon-edit-name.png
  14. 1 1
      src/views/prepare-lessons/model/add-item-model/index.tsx
  15. 10 4
      src/views/prepare-lessons/model/add-other-source/index.module.less
  16. 88 15
      src/views/prepare-lessons/model/add-other-source/index.tsx
  17. 15 16
      src/views/prepare-lessons/model/courseware-type/index.module.less
  18. 1 1
      src/views/prepare-lessons/model/related-class/index.module.less
  19. 7 6
      src/views/prepare-lessons/model/related-class/index.tsx
  20. 31 13
      src/views/prepare-lessons/model/source-instrument/components/list/index.module.less
  21. 83 60
      src/views/prepare-lessons/model/source-instrument/components/list/search-group-resources.tsx
  22. 25 4
      src/views/prepare-lessons/model/source-instrument/detail.tsx
  23. 1 1
      src/views/prepare-lessons/model/source-instrument/index.module.less
  24. 1 5
      src/views/prepare-lessons/model/source-instrument/index.tsx
  25. 1 0
      src/views/prepare-lessons/model/source-knowledge/detail.tsx
  26. 61 15
      src/views/prepare-lessons/model/source-knowledge/index.module.less
  27. 118 36
      src/views/prepare-lessons/model/source-knowledge/index.tsx
  28. 268 0
      src/views/prepare-lessons/model/source-music/components/list/index.module.less
  29. 189 0
      src/views/prepare-lessons/model/source-music/components/list/index.tsx
  30. 290 0
      src/views/prepare-lessons/model/source-music/components/list/search-group-resources.tsx
  31. 541 0
      src/views/prepare-lessons/model/source-music/detail.module.less
  32. 372 0
      src/views/prepare-lessons/model/source-music/detail.tsx
  33. 126 0
      src/views/prepare-lessons/model/source-music/index.module.less
  34. 116 0
      src/views/prepare-lessons/model/source-music/index.tsx
  35. 244 0
      src/views/prepare-lessons/model/source-musician/components/list/index.module.less
  36. 189 0
      src/views/prepare-lessons/model/source-musician/components/list/index.tsx
  37. 177 0
      src/views/prepare-lessons/model/source-musician/components/list/search-group-resources.tsx
  38. 131 0
      src/views/prepare-lessons/model/source-musician/index.module.less
  39. 109 0
      src/views/prepare-lessons/model/source-musician/index.tsx
  40. 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 - 1
src/components/card-preview/rhythm-modal/index.tsx

@@ -27,7 +27,9 @@ export default defineComponent({
         userStore.getToken
       }&platform=modal`;
     }
-
+    if (props.item.dataJson) {
+      src += '&dataJson=' + props.item.dataJson;
+    }
     return () => (
       <div class={styles.musicScore}>
         <iframe

+ 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', {

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

@@ -58,11 +58,13 @@
   align-items: center;
   justify-content: space-between;
   font-weight: 600;
-  font-size: max(17px, 13Px);
+  font-size: max(17px, 14Px);
   color: #000000;
   line-height: 23px;
+  height: 23px;
   text-align: left;
   padding-bottom: 20px;
+  box-sizing: content-box;
 
   .titleLeft {
     display: flex;
@@ -97,7 +99,7 @@
       cursor: pointer;
       padding-left: 10px;
       font-weight: 400;
-      font-size: max(13px, 11Px);
+      font-size: max(13px, 12Px);
       color: #999999;
 
       &:hover {
@@ -105,6 +107,15 @@
       }
     }
 
+    .iconP {
+      width: 8px;
+      height: 11px;
+      display: inline-block;
+      background: url('../../../images/icon-arrow-p.png') no-repeat center;
+      background-size: contain;
+      margin-left: 4px;
+    }
+
     .swipeControll {
       height: 25px;
 
@@ -133,13 +144,19 @@
   padding: 12px 0 12px;
   gap: 20px 0;
   margin: 10px -10px 0;
-  min-height: 300px;
+  min-height: 313px;
+  // height: 313px;
+
 
   &.listSame {
     margin-top: 0;
     padding-top: 0;
   }
 
+  .empty1 {
+    // padding: 10px 0;
+  }
+
   .itemWrap {
     width: calc(100% / 4);
     // padding-bottom: calc(100% / 3 * 0.73333);
@@ -162,7 +179,7 @@
 }
 
 .attendClassModal1 {
-  width: 1200px;
+  width: 1360px;
   border-radius: 16px;
   overflow: hidden;
 }

+ 12 - 4
src/views/prepare-lessons/components/lesson-main/courseware-presets/index.tsx

@@ -439,7 +439,7 @@ export default defineComponent({
               type="primary"
               onClick={() => {
                 eventGlobal.emit('teacher-slideshow', true);
-                emit('change', { status: true });
+                emit('change', { status: true, type: 'create' });
               }}>
               <NImage
                 class={styles.addBtnIcon}
@@ -469,6 +469,7 @@ export default defineComponent({
                           eventGlobal.emit('teacher-slideshow', true);
                           emit('change', {
                             status: true,
+                            type: 'update',
                             groupItem: { id: item.id }
                           });
                         }}
@@ -483,7 +484,9 @@ export default defineComponent({
                     </div>
                   </div>
                 ))}
-                {!forms.loading && forms.tableList.length <= 0 && <TheEmpty />}
+                {!forms.loading && forms.tableList.length <= 0 && (
+                  <TheEmpty class={styles.empty1} />
+                )}
               </div>
             </NSpin>
           </div>
@@ -499,7 +502,7 @@ export default defineComponent({
                       class={styles.more}
                       onClick={() => (forms.showRelatedClass = true)}>
                       查看更多
-                      <NIcon>
+                      {/* <NIcon size={15}>
                         <svg
                           xmlns="http://www.w3.org/2000/svg"
                           viewBox="0 0 24 24">
@@ -507,7 +510,8 @@ export default defineComponent({
                             d="M8.59 16.59L13.17 12L8.59 7.41L10 6l6 6l-6 6l-1.41-1.41z"
                             fill="currentColor"></path>
                         </svg>
-                      </NIcon>
+                      </NIcon> */}
+                      <i class={styles.iconP}></i>
                     </span>
                   )}
                 </div>
@@ -588,6 +592,10 @@ export default defineComponent({
             coursewareDetailKnowledgeId={prepareStore.getSelectKey}
             onClose={() => (forms.showRelatedClass = false)}
             onAdd={(item: any) => onAddCourseware(item)}
+            onClick={(item: any) => {
+              onPreviewAttend(item.id);
+              forms.showRelatedClass = false;
+            }}
           />
         </NModal>
 

+ 1 - 0
src/views/prepare-lessons/components/lesson-main/courseware/addCourseware.module.less

@@ -450,6 +450,7 @@
   align-items: center;
   justify-content: center;
   flex-direction: column;
+  background-color: #fff;
 
   img {
     width: 30Px;

+ 28 - 30
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 {
         //
       }
@@ -507,7 +501,7 @@ export default defineComponent({
               <NInput
                 placeholder="请输入课件标题"
                 v-model:value={forms.name}
-                maxlength={15}
+                maxlength={20}
                 clearable
               />
             </div>
@@ -669,7 +663,7 @@ export default defineComponent({
                         }}
                       </NTooltip>
                     )}
-                    {forms.coursewareList.length > 1 && (
+                    {index < forms.coursewareList.length - 1 && (
                       <NTooltip showArrow={false}>
                         {{
                           trigger: () => (
@@ -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>

+ 9 - 1
src/views/prepare-lessons/components/lesson-main/index.tsx

@@ -14,6 +14,7 @@ export default defineComponent({
     const prepareStore = usePrepareStore();
     const state = reactive({
       editCoursewareShow: false, // 是否编辑课件
+      coursewareType: 'create' as 'create' | 'edit',
       editCourseware: {} as any, //
       editWorkShow: false, // 是否编辑预设
       editWork: {} as any // 预设模板编号
@@ -52,13 +53,20 @@ export default defineComponent({
           {!state.editWorkShow && (
             <NTabPane
               name="courseware"
-              tab={state.editCoursewareShow ? '编辑课件' : '课件'}
+              tab={
+                state.editCoursewareShow
+                  ? state.coursewareType === 'create'
+                    ? '创建课件'
+                    : '编辑课件'
+                  : '课件'
+              }
               displayDirective="if">
               {state.editCoursewareShow ? (
                 <Courseware
                   groupItem={state.editCourseware}
                   onChange={(val: any) => {
                     state.editCoursewareShow = val.status;
+                    state.coursewareType = val.type;
                     prepareStore.setIsEditResource(val.status);
 
                     if (!val.status) {

BIN
src/views/prepare-lessons/images/icon-arrow-p.png


BIN
src/views/prepare-lessons/images/icon-edit-name.png


+ 1 - 1
src/views/prepare-lessons/model/add-item-model/index.tsx

@@ -23,7 +23,7 @@ export default defineComponent({
     return () => (
       <div class={styles.addCoursewareItem}>
         <NCheckboxGroup v-model:value={selects.value}>
-          <NGrid yGap={14} cols={2}>
+          <NGrid yGap={18} cols={2}>
             {props.coursewareList.map((courseware: any, index: number) => (
               <NGi>
                 <NCheckbox value={index}>{courseware.name}</NCheckbox>

+ 10 - 4
src/views/prepare-lessons/model/add-other-source/index.module.less

@@ -13,18 +13,19 @@
       width: 127px;
       height: 127px;
       transition: all .2s ease;
-      border: 3px solid transparent;
+      border: 2px solid transparent;
       border-radius: 17px;
       box-sizing: content-box;
 
       &:hover {
-        border: 3px solid #198CFE;
+        border: 2px solid #198CFE;
         transform: scale(1.02);
         transition: all .2s ease;
       }
     }
 
     .name {
+      padding-top: 4px;
       text-align: center;
       font-size: max(15px, 12Px);
       color: #131415;
@@ -38,7 +39,8 @@
   width: 958px;
 }
 
-.instrumentModal {
+.instrumentModal,
+.musicModal {
   width: 1200px;
   position: relative;
   // width: 1352px;
@@ -57,7 +59,11 @@
   }
 }
 
+.musicModal {
+  width: 1360px;
+}
+
 .theoryModal {
-  width: 1200px;
+  width: 1360px;
   position: relative;
 }

+ 88 - 15
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;
@@ -171,16 +179,81 @@ export default defineComponent({
             onClose={() => (state.theoryStatus = false)}
             onConfirm={(val: any) => {
               state.theoryStatus = false;
-              emit('comfirm', {
-                materialId: val.materialId,
-                coverImg: val.coverImg,
-                dataJson: null,
-                title: val.title,
-                isCollect: false,
-                isSelected: false,
-                content: val.content,
-                type: 'THEORY'
+              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: 'THEORY'
+                });
               });
+              emit('comfirm', temp);
+              emit('close');
+            }}
+          />
+        </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');
             }}
           />

+ 15 - 16
src/views/prepare-lessons/model/courseware-type/index.module.less

@@ -53,20 +53,6 @@
     border-radius: 10px;
     overflow: hidden;
     background-color: #fff;
-    transition: all .2s ease;
-    transition: all 0.2s ease;
-
-    &:hover {
-      transform: scale(1.01);
-      transition: all 0.2s ease;
-
-      // &::after {
-      //   content: '';
-      //   position: absolute;
-      //   inset: 0;
-      //   background-color: rgba(0, 0, 0, 0.3);
-      // }
-    }
 
     .status {
       position: absolute;
@@ -85,6 +71,19 @@
       .n-image {
         width: 100%;
         height: 100%;
+        transition: all .2s ease;
+
+        &:hover {
+          transform: scale(1.05);
+          transition: all 0.2s ease;
+
+          // &::after {
+          //   content: '';
+          //   position: absolute;
+          //   inset: 0;
+          //   background-color: rgba(0, 0, 0, 0.3);
+          // }
+        }
       }
     }
 
@@ -177,7 +176,7 @@
 
       &:hover {
         span {
-          color: #1677FF;
+          // color: #1677FF;
         }
 
         .iconEditName {
@@ -190,7 +189,7 @@
     }
 
     .subjectName {
-      padding-top: 8px;
+      padding-top: 5px;
       font-size: 11Px;
       color: #777777;
       line-height: 16Px;

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

@@ -56,7 +56,7 @@
   padding: 0 0 12px;
   gap: 20px 0;
   margin: 0 -10px 0;
-  min-height: 300px;
+  min-height: 202px;
 
   &.listSame {
     margin-top: 0;

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

@@ -26,7 +26,7 @@ export default defineComponent({
       default: ''
     }
   },
-  emits: ['close', 'add'],
+  emits: ['close', 'add', 'click'],
   setup(props, { emit }) {
     const forms = reactive({
       loading: false,
@@ -55,9 +55,9 @@ export default defineComponent({
         const result = data.rows || [];
         const tempList: any = [];
         result.forEach((item: any) => {
-          const index = forms.tableList.findIndex(
-            (i: any) => i.fromChapterLessonCoursewareId === item.id
-          );
+          // const index = forms.tableList.findIndex(
+          //   (i: any) => i.fromChapterLessonCoursewareId === item.id
+          // );
           const firstItem: any =
             item.chapterKnowledgeList[0]?.chapterKnowledgeMaterialList[0];
           tempList.push({
@@ -69,7 +69,7 @@ export default defineComponent({
             name: item.name,
             coverImg: firstItem?.bizInfo.coverImg,
             type: firstItem?.bizInfo.type,
-            isAdd: index !== -1 ? true : false
+            isAdd: item.addFlag
           });
         });
 
@@ -120,7 +120,7 @@ export default defineComponent({
             onUpdate:value={() => throttleFn()}
           />
           <NInput
-            placeholder="请输课件标题关键词"
+            placeholder="请输课件标题关键词"
             clearable
             v-model:value={forms.searchGroup.keyword}
             onKeyup={(e: KeyboardEvent) => {
@@ -182,6 +182,7 @@ export default defineComponent({
                           onAdd={() => {
                             emit('add', item);
                           }}
+                          onClick={() => emit('click', item)}
                         />
                       </div>
                     </div>

+ 31 - 13
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,19 +164,18 @@
   .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);
+        // background: linear-gradient(360deg, #DBF1FF 0%, #E7F9FF 100%);
+        // box-shadow: 2px 2 8px 0px rgba(0, 0, 0, 0.1);
         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;
       }
     }
 
@@ -185,11 +200,13 @@
       position: relative;
       width: 148px;
       height: 148px;
-      background: linear-gradient(360deg, #DBF1FF 0%, #E7F9FF 100%);
-      box-shadow: 2px 2px 8px 0px rgba(0, 0, 0, 0.1);
+      // 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;
+      transition: all .2s ease;
 
       .iconCheck {
         position: absolute;
@@ -204,6 +221,7 @@
       .img {
         width: 148px;
         height: 148px;
+        background: linear-gradient(360deg, #DBF1FF 0%, #E7F9FF 100%);
 
         display: flex;
 
@@ -215,8 +233,8 @@
     }
 
     .itemImgSectionSelected {
-      border: 3px solid #198CFE;
-      transform: all .2s ease;
+      border: 2px solid #198CFE;
+      // transition: all .2s ease;
 
       .iconCheck {
         background: url('../../../../images/icon-checked.png') no-repeat center;

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

+ 1 - 0
src/views/prepare-lessons/model/source-knowledge/detail.tsx

@@ -113,6 +113,7 @@ export default defineComponent({
               v-model:value={state.fontSize}
               vertical
               min={12}
+              placement="left"
               max={32}
             />
             <img

+ 61 - 15
src/views/prepare-lessons/model/source-knowledge/index.module.less

@@ -85,8 +85,8 @@
   }
 
   .scrollBar {
-    margin-top: 12px;
-    padding: 0 20px;
+    margin: 17px 0;
+    padding: 0 17px;
     // max-height: calc(100% - 64px - 52px - 36px);
 
     &.empty {
@@ -98,7 +98,7 @@
 
 
   .directoryList {
-    width: 300px;
+    width: 350px;
     background: #FFFFFF;
     border-radius: 17px;
     flex-shrink: 0;
@@ -121,31 +121,28 @@
   }
 
   .treeItem {
+    position: relative;
     display: flex;
     align-items: center;
     line-height: 54px;
     border-radius: 10px;
-    padding: 0 5px;
+    padding: 0 0 0 15px;
     cursor: pointer;
     border-radius: 10px;
-    font-size: max(17px, 13Px);
+    font-size: max(15px, 13Px);
 
-    &:hover {
-      background: #F5F6FA;
-    }
+    // &:hover {
+    //   background: #EAEDF2;
+    // }
 
     .title {
       padding-left: 8px;
-      overflow: hidden;
-      white-space: nowrap;
-      text-overflow: ellipsis;
-      max-width: 280px !important;
       color: rgba(0, 0, 0, .5);
       display: flex;
       align-items: center;
 
       .dir {
-        flex-shrink: 1;
+        flex-shrink: 0;
         display: inline-block;
         width: 16px;
         height: 18px;
@@ -154,6 +151,13 @@
         margin-right: 6px;
       }
 
+      p {
+        overflow: hidden;
+        white-space: nowrap;
+        text-overflow: ellipsis;
+        max-width: 180px !important;
+      }
+
       &.titleSelect {
         color: var(--n-color);
         font-weight: bold;
@@ -184,15 +188,22 @@
 
     &.childItem {
       padding-left: 30px;
-      font-size: max(15px, 12Px);
+      font-size: max(13px, 12Px);
+
+      margin: 0 10px;
 
       .title {
         color: #131415;
+        max-width: 180px;
+        overflow: hidden;
+        text-overflow: ellipsis;
+        white-space: nowrap;
+        display: block;
       }
     }
 
     &.childSelect {
-      background: #F5F6FA;
+      background: #EAEDF2;
 
       .title {
         color: var(--n-color);
@@ -319,4 +330,39 @@
       min-width: 156px;
     }
   }
+}
+
+.treeParentSelected {
+  background-color: #F8F8F8;
+  border-radius: 8px;
+  padding-bottom: 10px;
+
+  .parentItem {
+    // border-bottom: 1px solid #E7E8E8;
+    position: relative;
+    margin-bottom: 10px;
+
+    &::after {
+      content: '';
+      position: absolute;
+      bottom: 0;
+      left: 0;
+      display: block;
+      width: 100%;
+      height: 1px;
+      background-color: #E7E8E8;
+    }
+  }
+}
+
+
+.checkbox {
+  position: absolute;
+  right: 20px;
+}
+
+.childItem {
+  .checkbox {
+    right: 10px;
+  }
 }

+ 118 - 36
src/views/prepare-lessons/model/source-knowledge/index.tsx

@@ -2,6 +2,8 @@ import { defineComponent, onMounted, reactive, ref } from 'vue';
 import styles from './index.module.less';
 import {
   NButton,
+  NCheckbox,
+  NCheckboxGroup,
   // NBreadcrumb,
   // NBreadcrumbItem,
   // NScrollbar,
@@ -30,7 +32,8 @@ export default defineComponent({
       fontSize: 18,
       tableList: [] as any,
       selectKey: null,
-      details: {} as any
+      details: {} as any,
+      selectCheckboxs: [] as any
     });
 
     const getDetails = async () => {
@@ -42,7 +45,7 @@ export default defineComponent({
         });
 
         state.tableList = data || [];
-        if (state.tableList.length) {
+        if (state.tableList.length > 0) {
           const item =
             state.tableList[0].lessonCoursewareDetailKnowledgeDetailList;
           state.tableList[0].selected = true;
@@ -51,6 +54,10 @@ export default defineComponent({
             state.selectKey = child.id;
             await getDetail();
           }
+          state.tableList.forEach((item: any) => {
+            item.checked = false;
+            item.indeterminate = false;
+          });
         }
       } catch {
         //
@@ -74,12 +81,32 @@ export default defineComponent({
     };
 
     const onSubmit = () => {
-      emit('confirm', {
-        coverImg: PageEnum.THEORY_DEFAULT_COVER,
-        title: state.details.name,
-        materialId: state.selectKey,
-        content: state.selectKey
+      const items: any[] = [];
+      for (const i in state.selectCheckboxs) {
+        const ids = state.selectCheckboxs[i];
+        const item = state.tableList[i];
+        if (Array.isArray(item.lessonCoursewareDetailKnowledgeDetailList)) {
+          item.lessonCoursewareDetailKnowledgeDetailList.forEach(
+            (child: any) => {
+              if (ids.includes(child.id)) {
+                items.push(child);
+              }
+            }
+          );
+        }
+      }
+
+      const result: any[] = [];
+      items.forEach(item => {
+        result.push({
+          coverImg: PageEnum.THEORY_DEFAULT_COVER,
+          title: item.name,
+          materialId: item.id,
+          content: item.id
+        });
       });
+
+      emit('confirm', result);
     };
 
     onMounted(() => {
@@ -98,11 +125,16 @@ export default defineComponent({
                       ? styles.empty
                       : ''
                   ]}
-                  style={{ minHeight: '100%' }}>
-                  <NSpin show={show.value}>
+                  style={{ height: '100%' }}>
+                  <NSpin show={show.value} style={{ height: '100%' }}>
                     <div class={[styles.listSection]}>
                       {state.tableList.map((item: any, index: number) => (
-                        <div class={styles.treeParent} key={'parent' + index}>
+                        <div
+                          class={[
+                            styles.treeParent,
+                            item.selected && styles.treeParentSelected
+                          ]}
+                          key={'parent' + index}>
                           <div
                             class={[styles.treeItem, styles.parentItem]}
                             onClick={() => {
@@ -132,35 +164,85 @@ export default defineComponent({
                                   styles.dir,
                                   item.selected ? styles.dirSelect : ''
                                 ]}></span>
-                              {item.name}
+                              <p>{item.name}</p>
                             </p>
+                            <div
+                              class={styles.checkbox}
+                              onClick={(e: any) => {
+                                e.stopPropagation();
+                              }}>
+                              <NCheckbox
+                                checked={item.checked}
+                                indeterminate={item.indeterminate}
+                                onUpdate:checked={(val: boolean) => {
+                                  item.checked = val;
+
+                                  const child =
+                                    item.lessonCoursewareDetailKnowledgeDetailList ||
+                                    [];
+                                  if (val) {
+                                    const ids: any = [];
+                                    child.forEach((c: any) => {
+                                      ids.push(c.id);
+                                    });
+                                    state.selectCheckboxs[index] = ids;
+                                  } else {
+                                    state.selectCheckboxs[index] = [];
+                                  }
+                                  item.indeterminate = false;
+                                }}></NCheckbox>
+                            </div>
                           </div>
+                          <NCheckboxGroup
+                            value={state.selectCheckboxs[index]}
+                            onUpdate:value={val => {
+                              state.selectCheckboxs[index] = val;
 
-                          {item.selected &&
-                            item.lessonCoursewareDetailKnowledgeDetailList &&
-                            item.lessonCoursewareDetailKnowledgeDetailList.map(
-                              (child: any, j: number) => (
-                                <div
-                                  key={'child' + j}
-                                  class={[
-                                    styles.treeItem,
-                                    styles.childItem,
-                                    styles.animation,
-                                    state.selectKey === child.id
-                                      ? styles.childSelect
-                                      : ''
-                                  ]}
-                                  onClick={() => {
-                                    if (state.selectKey === child.id) return;
-                                    state.selectKey = child.id;
-                                    getDetail();
-                                    musicContentRef.value.$el.scrollTo(0, 0);
-                                  }}>
-                                  <span class={styles.childArrow}></span>
-                                  <p class={styles.title}>{child.name}</p>
-                                </div>
-                              )
-                            )}
+                              const child =
+                                item.lessonCoursewareDetailKnowledgeDetailList ||
+                                [];
+                              if (val.length <= 0) {
+                                item.checked = false;
+                                item.indeterminate = false;
+                              } else if (val.length === child.length) {
+                                item.checked = true;
+                                item.indeterminate = false;
+                              } else {
+                                item.checked = false;
+                                item.indeterminate = true;
+                              }
+                            }}>
+                            {item.selected &&
+                              item.lessonCoursewareDetailKnowledgeDetailList &&
+                              item.lessonCoursewareDetailKnowledgeDetailList.map(
+                                (child: any, j: number) => (
+                                  <div
+                                    key={'child' + j}
+                                    class={[
+                                      styles.treeItem,
+                                      styles.childItem,
+                                      styles.animation,
+                                      state.selectKey === child.id
+                                        ? styles.childSelect
+                                        : ''
+                                    ]}
+                                    onClick={() => {
+                                      if (state.selectKey === child.id) return;
+                                      state.selectKey = child.id;
+                                      getDetail();
+                                      musicContentRef.value.$el.scrollTo(0, 0);
+                                    }}>
+                                    <span class={styles.childArrow}></span>
+                                    <p class={styles.title}>{child.name}</p>
+                                    <div
+                                      class={styles.checkbox}
+                                      onClick={(e: any) => e.stopPropagation()}>
+                                      <NCheckbox value={child.id}></NCheckbox>
+                                    </div>
+                                  </div>
+                                )
+                              )}
+                          </NCheckboxGroup>
                         </div>
                       ))}
                     </div>

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