lex пре 1 година
родитељ
комит
5e1cd8a243

+ 7 - 2
src/components/card-preview/index.tsx

@@ -5,6 +5,7 @@ import VideoModal from './video-modal';
 import MusicModal from './music-modal';
 import SongModal from './song-modal';
 import TheEmpty from '../TheEmpty';
+import RhythmModal from './rhythm-modal';
 
 export default defineComponent({
   name: 'card-preview',
@@ -96,9 +97,13 @@ export default defineComponent({
                 frameborder="1"></iframe>
             </NSpin>
           )}
-          {!['VIDEO', 'MUSIC', 'SONG', 'PPT'].includes(item.value.type) && (
-            <TheEmpty />
+          {item.value.type === 'RHYTHM' && (
+            <RhythmModal class={styles.musicPreview} item={item.value} />
           )}
+          {/* LISTEN:听音,RHYTHM:节奏,THEORY:乐理知识,MUSIC:曲目 INSTRUMENT:乐器 MUSICIAN:音乐家) */}
+          {!['VIDEO', 'MUSIC', 'SONG', 'PPT', 'RHYTHM'].includes(
+            item.value.type
+          ) && <TheEmpty />}
         </NModal>
       </>
     );

+ 15 - 0
src/components/card-preview/rhythm-modal/index.module.less

@@ -0,0 +1,15 @@
+.musicScore {
+  width: 100%;
+  height: 518px;
+
+  iframe {
+    width: inherit;
+    height: inherit;
+
+    :global {
+      .headTopBackBtn {
+        display: none;
+      }
+    }
+  }
+}

+ 41 - 0
src/components/card-preview/rhythm-modal/index.tsx

@@ -0,0 +1,41 @@
+import { defineComponent, ref } from 'vue';
+import styles from './index.module.less';
+import { useUserStore } from '/src/store/modules/users';
+import { vaildMusicScoreUrl } from '/src/utils/urlUtils';
+
+export default defineComponent({
+  name: 'song-modal',
+  props: {
+    item: {
+      type: Object,
+      default: () => ({})
+    }
+  },
+  setup(props) {
+    const userStore = useUserStore();
+    const iframeRef = ref();
+    const isLoaded = ref(false);
+    const origin = /(localhost|192)/.test(location.host)
+      ? 'http://localhost:9002'
+      : location.origin;
+    // const src = `${origin}/classroom-app/#/tempo-practice?v=${+new Date()}&win=modal&dataJson=${
+    //   props.item.dataJson
+    // }&Authorization=${userStore.getToken}`;
+    const src = `${origin}/#/tempo-practice?v=${+new Date()}&win=modal&dataJson=${
+      props.item.dataJson
+    }&Authorization=${userStore.getToken}`;
+    return () => (
+      <div class={styles.musicScore}>
+        <iframe
+          ref={iframeRef}
+          onLoad={() => {
+            // emit('setIframe', iframeRef.value);
+            isLoaded.value = true;
+          }}
+          class={[styles.container, 'musicIframe']}
+          frameborder="0"
+          src={src}></iframe>
+      </div>
+    );
+  }
+});

+ 63 - 0
src/components/card-type/index.tsx

@@ -337,6 +337,69 @@ export default defineComponent({
                     src={props.item.coverImg || PageEnum.PPT_DEFAULT_COVER}
                   />
                 )}
+                {/* 节奏练习 */}
+                {props.item.type === 'RHYTHM' && (
+                  <NImage
+                    class={[styles.cover, styles.image]}
+                    lazy
+                    previewDisabled={true}
+                    objectFit="cover"
+                    src={props.item.coverImg || PageEnum.RHYTHM_DEFAULT_COVER}
+                  />
+                )}
+
+                {/* 听音练习 */}
+                {props.item.type === 'LISTEN' && (
+                  <NImage
+                    class={[styles.cover, styles.image]}
+                    lazy
+                    previewDisabled={true}
+                    objectFit="cover"
+                    src={props.item.coverImg}
+                  />
+                )}
+                {/* 乐理 */}
+                {props.item.type === 'THEORY' && (
+                  <NImage
+                    class={[styles.cover, styles.image]}
+                    lazy
+                    previewDisabled={true}
+                    objectFit="cover"
+                    src={props.item.coverImg || PageEnum.THEORY_DEFAULT_COVER}
+                  />
+                )}
+                {/* 名曲 */}
+                {props.item.type === 'MUSIC' && (
+                  <NImage
+                    class={[styles.cover, styles.image]}
+                    lazy
+                    previewDisabled={true}
+                    objectFit="cover"
+                    src={props.item.coverImg || PageEnum.MUSIC_DEFAULT_COVER}
+                  />
+                )}
+                {/* 乐器 */}
+                {props.item.type === 'INSTRUMENT' && (
+                  <NImage
+                    class={[styles.cover, styles.image]}
+                    lazy
+                    previewDisabled={true}
+                    objectFit="cover"
+                    src={
+                      props.item.coverImg || PageEnum.INSTRUMENT_DEFAULT_COVER
+                    }
+                  />
+                )}
+                {/* 音乐家 */}
+                {props.item.type === 'MUSICIAN' && (
+                  <NImage
+                    class={[styles.cover, styles.image]}
+                    lazy
+                    previewDisabled={true}
+                    objectFit="cover"
+                    src={props.item.coverImg || PageEnum.MUSICIAN_DEFAULT_COVER}
+                  />
+                )}
               </>
             ),
             footer: () => (

+ 14 - 1
src/enums/pageEnum.ts

@@ -13,9 +13,22 @@ export enum PageEnum {
   // ERROR_PAGE_NAME = 'ErrorPage'
   SONG_DEFAULT_COVER = 'https://oss.dayaedu.com/ktqy/1698420034679a22d3f7a.png',
   // ppt封面
-  PPT_DEFAULT_COVER = 'https://oss.dayaedu.com/ktqy/12/1701931810284.png'
+  PPT_DEFAULT_COVER = 'https://oss.dayaedu.com/ktqy/12/1701931810284.png',
+  /** 听音练习 */
+  // LISTEN_DEFAULT_COVER = 'https://oss.dayaedu.com/ktqy/171013700931689a322a6.png',
+  /** 节奏练习 */
+  RHYTHM_DEFAULT_COVER = 'https://oss.dayaedu.com/ktqy/171013700931689a322a6.png',
+  /** 乐理知识 */
+  THEORY_DEFAULT_COVER = 'https://oss.dayaedu.com/ktqy/17101370093160d479afe.png',
+  /** 曲目 */
+  MUSIC_DEFAULT_COVER = 'https://oss.dayaedu.com/ktqy/1710137009315eedcdeed.png',
+  /** 乐器 */
+  INSTRUMENT_DEFAULT_COVER = 'https://oss.dayaedu.com/ktqy/17101370093153448b2cd.png',
+  /** 音乐家 */
+  MUSICIAN_DEFAULT_COVER = 'https://oss.dayaedu.com/ktqy/1710137009316fbd65d39.png'
 }
 
+// LISTEN:听音,RHYTHM:节奏,THEORY:乐理知识,MUSIC:曲目 INSTRUMENT:乐器 MUSICIAN:音乐家)
 export const NaturalType: { [_: string]: string } = {
   IMG: '图片',
   VIDEO: '视频',

+ 0 - 7
src/views/attend-class/index.module.less

@@ -276,13 +276,6 @@
     }
   }
 
-  // &.drawerContainerSource {
-  //   :global {
-  //     .n-drawer-header {
-  //       background-color: #F5F6FA !important;
-  //     }
-  //   }
-  // }
 
   .cardContainer {
     margin-bottom: 24px;

+ 22 - 3
src/views/prepare-lessons/components/lesson-main/courseware/addCourseware.tsx

@@ -106,7 +106,8 @@ export default defineComponent({
         subjectId: '',
         detailId: ''
       } as any,
-      addOtherSource: false
+      addOtherSource: false,
+      addOtherIndex: 0 // 添加其它的索引
     });
 
     // 获取列表
@@ -363,7 +364,17 @@ export default defineComponent({
               return {
                 bizId: child.materialId,
                 type: child.type,
-                dataJson: ''
+                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
+                  })
               };
             });
           }
@@ -682,6 +693,7 @@ export default defineComponent({
                                 ]}
                                 onClick={() => {
                                   forms.addOtherSource = true;
+                                  forms.addOtherIndex = index;
                                 }}>
                                 <img src={iconAddMusic} />
 
@@ -705,6 +717,7 @@ export default defineComponent({
                             ]}
                             onClick={() => {
                               forms.addOtherSource = true;
+                              forms.addOtherIndex = index;
                             }}>
                             <img src={iconAddMusic} />
 
@@ -826,7 +839,13 @@ export default defineComponent({
           preset="card"
           class={['modalTitle background', styles.addOtherSource]}
           title={'添加功能'}>
-          <AddOtherSource />
+          <AddOtherSource
+            onClose={() => (forms.addOtherSource = false)}
+            onComfirm={item => {
+              console.log({ ...item, index: forms.addOtherIndex });
+              addItem({ ...item, index: forms.addOtherIndex });
+            }}
+          />
         </NModal>
       </div>
     );

BIN
src/views/prepare-lessons/images/icon-check.png


BIN
src/views/prepare-lessons/images/icon-checked.png


+ 19 - 0
src/views/prepare-lessons/model/add-other-source/index.module.less

@@ -31,4 +31,23 @@
 
 .addOtherSourceModal {
   width: 958px;
+}
+
+.instrumentModal {
+  width: 1200px;
+  position: relative;
+  // width: 1352px;
+
+  :global {
+    .n-card-header {
+      position: absolute;
+      top: 0;
+      left: 0;
+      right: 0;
+
+      .n-card-header__main {
+        color: #fff;
+      }
+    }
+  }
 }

+ 66 - 1
src/views/prepare-lessons/model/add-other-source/index.tsx

@@ -10,6 +10,7 @@ import icon6 from '../../images/addSource/icon6.png';
 import icon7 from '../../images/addSource/icon7.png';
 import { useRouter } from 'vue-router';
 import SourceRhythm from '../source-rhythm';
+import SourceInstrument from '../content-instrument';
 
 export default defineComponent({
   name: 'add-other-source',
@@ -68,6 +69,9 @@ export default defineComponent({
         case 1:
           state.rhythmStatus = true;
           break;
+        case 2:
+          state.instrumentStatus = true;
+          break;
         case 6:
           // 直接跳转到制谱页面 (临时存储数据)
           sessionStorage.setItem('notation-open-create', '1');
@@ -91,13 +95,74 @@ export default defineComponent({
             </div>
           ))}
         </div>
+        {/*
+          百科: https://oss.dayaedu.com/ktqy/17101370093153448b2cd.png
+          jianshang https://oss.dayaedu.com/ktqy/1710137009315eedcdeed.png
+          jiezou https://oss.dayaedu.com/ktqy/171013700931689a322a6.png
+          yinyuejia https://oss.dayaedu.com/ktqy/1710137009316fbd65d39.png
+          yueli https://oss.dayaedu.com/ktqy/17101370093160d479afe.png
+        */}
         {/* 节奏练习 */}
         <NModal
           v-model:show={state.rhythmStatus}
           preset="card"
           class={['modalTitle background', styles.addOtherSourceModal]}
           title={'节奏练习'}>
-          <SourceRhythm />
+          <SourceRhythm
+            onClose={() => (state.rhythmStatus = false)}
+            onConfirm={(item: any) => {
+              state.rhythmStatus = false;
+              emit('comfirm', {
+                materialId: null,
+                coverImg: item.coverImg,
+                dataJson: item.dataJson,
+                title: '节奏练习',
+                isCollect: false,
+                isSelected: false,
+                content: null,
+                type: 'RHYTHM'
+              });
+              emit('close');
+            }}
+          />
+        </NModal>
+
+        {/* 乐器百科 */}
+        <NModal
+          v-model:show={state.instrumentStatus}
+          preset="card"
+          class={['modalTitle', styles.instrumentModal]}
+          title={'乐器百科'}>
+          <SourceInstrument
+            onConfirm={(val: any) => {
+              state.instrumentStatus = false;
+              const value = val || [];
+              const temp: any[] = [];
+              value.forEach((item: any) => {
+                temp.push({
+                  materialId: '',
+                  coverImg: item.coverImg,
+                  dataJson: null,
+                  title: '节奏练习',
+                  isCollect: false,
+                  isSelected: false,
+                  content: '',
+                  type: 'INSTRUMENT'
+                });
+              });
+              // emit('comfirm', {
+              //   materialId: '',
+              //   coverImg: item.coverImg,
+              //   dataJson: item.dataJson,
+              //   title: '节奏练习',
+              //   isCollect: false,
+              //   isSelected: false,
+              //   content: '',
+              //   type: 'RHYTHM'
+              // });
+              // emit('close');
+            }}
+          />
         </NModal>
       </>
     );

+ 198 - 0
src/views/prepare-lessons/model/content-instrument/components/list/index.module.less

@@ -0,0 +1,198 @@
+.searchGroup {
+  position: relative;
+  padding: 0;
+
+
+  .btnType {
+    gap: 0px 24px !important;
+
+    :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;
+        }
+      }
+    }
+  }
+
+
+  .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 * 0.95913);
+    position: relative;
+
+    .itemWrapBox {
+      position: absolute;
+      left: 0;
+      top: 0;
+      width: 100%;
+      height: 100%;
+      padding: 0 22px;
+    }
+  }
+
+  .itemCard {
+    position: relative;
+    cursor: pointer;
+    transition: all .2s ease;
+
+    &:hover {
+      transform: scale(1.03);
+      transition: all .2s ease;
+
+      .itemImgSection {
+        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;
+      }
+    }
+
+    .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 {
+      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);
+      border-radius: 13px;
+      overflow: hidden;
+      transform: all .2s ease;
+
+      .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: 148px;
+
+        display: flex;
+
+        img {
+          width: 100%;
+          height: 100%;
+        }
+      }
+    }
+
+    .itemImgSectionSelected {
+      border: 3px solid #198CFE;
+      transform: 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;
+    }
+  }
+}

+ 180 - 0
src/views/prepare-lessons/model/content-instrument/components/list/index.tsx

@@ -0,0 +1,180 @@
+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';
+
+export default defineComponent({
+  name: 'instrument-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: 'INSTRUMENT', //
+        keyword: '',
+        wikiCategoryId: props.categoryId
+      },
+      tableList: [] as any,
+      teachingStatus: false,
+      show: false,
+      item: {} as any
+
+      // selectIds: [] 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);
+      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': `55vh`
+          }}
+          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-instruments-detail',
+                    //   query: {
+                    //     id: item.id,
+                    //     name: item.name
+                    //   }
+                    // });
+                  }}>
+                  <div
+                    class={styles.itemWrapBox}
+                    onClick={() => onSelect(item)}>
+                    <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]}></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>
+      </div>
+    );
+  }
+});

+ 87 - 0
src/views/prepare-lessons/model/content-instrument/components/list/search-group-resources.tsx

@@ -0,0 +1,87 @@
+import { PropType, defineComponent, onMounted, reactive } from 'vue';
+import styles from './index.module.less';
+import { NButton, NSpace } from 'naive-ui';
+import TheSearch from '/src/components/TheSearch';
+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 || ''
+    });
+
+    const onSearch = () => {
+      emit('search', forms);
+    };
+    onMounted(async () => {
+      // 获取教材分类列表
+      // await catchStore.getMusicSheetCategory()
+    });
+    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>
+            ) : (
+              <span></span>
+            )}
+            {props.categoryChildList.map((item: any) => (
+              <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>
+            ))}
+          </NSpace>
+          <TheSearch
+            class={styles.inputSearch}
+            placeholder="请输入乐器关键词"
+            round
+            onSearch={(val: string) => {
+              forms.keyword = val;
+              onSearch();
+            }}
+          />
+        </div>
+      </div>
+    );
+  }
+});

+ 465 - 0
src/views/prepare-lessons/model/content-instrument/detail.module.less

@@ -0,0 +1,465 @@
+.container {
+  display: flex;
+  flex-direction: column;
+  height: 100%;
+
+  .iconBack {
+    width: 36px;
+    height: 36px;
+  }
+
+  :global {
+    .n-breadcrumb>ul {
+      display: flex;
+      align-items: center;
+
+      .n-breadcrumb-item {
+        display: flex;
+        align-items: center;
+      }
+
+      .n-breadcrumb-item__separator {
+        display: none;
+      }
+
+      .n-breadcrumb-item__link {
+        padding: 5px 18px;
+        background: #FFFFFF;
+        border-radius: 16px;
+        color: #21225D;
+        line-height: 20px;
+      }
+    }
+
+    .n-breadcrumb .n-breadcrumb-item:last-child .n-breadcrumb-item__link {
+      color: #fff;
+      background: var(--product-color);
+    }
+
+  }
+
+  &> :global(.n-space) {
+    height: 36px;
+    flex-shrink: 0;
+  }
+
+  .separator {
+    width: 9px;
+    height: 15px;
+    margin: 0 16px;
+  }
+}
+
+.wrap {
+  padding-top: 15px;
+  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%;
+
+      &.otherImg {
+        width: 125px;
+        height: 143px;
+      }
+
+      img {
+        width: 100%;
+      }
+    }
+
+    .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('../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;
+    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;
+  // height: 100%;
+  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,
+    .icon3 {
+      display: inline-block;
+      width: 23px;
+      height: 23px;
+      margin-right: 8px;
+      background: url('../images/icon-1.png') no-repeat center;
+      background-size: contain;
+    }
+
+    .icon3 {
+      background: url('../images/icon-3.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;
+    }
+
+  }
+}

+ 317 - 0
src/views/prepare-lessons/model/content-instrument/detail.tsx

@@ -0,0 +1,317 @@
+import {
+  NBreadcrumb,
+  NBreadcrumbItem,
+  NButton,
+  NImage,
+  NSlider,
+  NSpace,
+  NSpin
+} from 'naive-ui';
+import { computed, defineComponent, onMounted, reactive } 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 '../images/icon-t.png';
+import iconAddT from '../images/icon-add-t.png';
+import iconPlusT from '../images/icon-plus-t.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: 'instrument-detail',
+  setup() {
+    const route = useRoute();
+    const router = useRouter();
+    const forms = reactive({
+      page: 1,
+      rows: 20,
+      status: true,
+      name: '', // 关键词
+      type: route.query.type
+    });
+    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: route.query.id });
+      } catch (error) {
+        console.log(error);
+      }
+      if (data.reshing) {
+        data.list = [];
+        data.reshing = false;
+      }
+
+      data.finshed = true;
+      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].knowledgeWikiCategoryTypeName
+          : '';
+      res.data.intros = res.data.intros.replace(
+        /<video/gi,
+        '<video style="width: 100% !important;" controlslist="nodownload"'
+      );
+      data.details = res.data;
+      data.loading = false;
+    };
+
+    onMounted(() => {
+      getDetail();
+    });
+    return () => (
+      <div class={styles.container}>
+        <NSpace align="center" wrapItem={false} size={16}>
+          <img
+            style={{ cursor: 'pointer' }}
+            src={icon_back}
+            class={styles.iconBack}
+            onClick={() => {
+              const path =
+                forms.type === 'MUSICIAN'
+                  ? '/content-musician'
+                  : '/content-instruments';
+              router.push({ path });
+            }}
+          />
+          <NBreadcrumb separator="">
+            <NBreadcrumbItem
+              onClick={() => {
+                const path =
+                  forms.type === 'MUSICIAN'
+                    ? '/content-musician'
+                    : '/content-instruments';
+                router.push({ path });
+              }}>
+              {forms.type === 'MUSICIAN' ? '音乐家' : '乐器百科'}
+            </NBreadcrumbItem>
+            <img class={styles.separator} src={icon_separator} />
+            <NBreadcrumbItem>{route.query.name}</NBreadcrumbItem>
+          </NBreadcrumb>
+        </NSpace>
+
+        <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.instrumentGroup}>
+                    <NImage
+                      class={[
+                        styles.instrumentImg,
+                        forms.type === 'MUSICIAN' && styles.otherImg
+                      ]}
+                      src={data.details?.avatar}
+                      objectFit="cover"
+                    />
+
+                    <p class={styles.instrumentName}>{data.details.name}</p>
+                    <p class={styles.instrumentTag}>
+                      {data.details.knowledgeName}
+                    </p>
+                  </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
+                            // data.listActive === index && styles.active
+                          ]}
+                          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="暂无代表作"></TheEmpty>
+                    </div>
+                  )}
+                </div>
+              </div>
+
+              <div class={styles.musicStaff}>
+                <div class={styles.musicTitle}>
+                  <i
+                    class={
+                      forms.type === 'MUSICIAN' ? styles.icon3 : styles.icon1
+                    }></i>
+                  {forms.type === 'MUSICIAN' ? '个人简介' : '乐器简介'}
+                </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}
+                  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
+            show={data.showPlayer}
+            playState={data.playState}
+            item={activeItem.value}
+            onChange={value => handleChangeAudio(value)}
+          />
+        )}
+      </div>
+    );
+  }
+});

+ 136 - 0
src/views/prepare-lessons/model/content-instrument/index.module.less

@@ -0,0 +1,136 @@
+.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: 20px 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 {
+  // padding-top: 12px;
+  flex: 1;
+  transition: padding 0.3s;
+  // overflow: hidden;
+}
+
+.listWrap {
+  padding: 0;
+  background-color: #fff;
+  border-radius: 20px;
+  // min-height: 100%;
+  min-height: calc(100vh - 192px);
+
+  &.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;
+    }
+  }
+}

+ 99 - 0
src/views/prepare-lessons/model/content-instrument/index.tsx

@@ -0,0 +1,99 @@
+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';
+
+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: '',
+      categoryList: [] as any,
+      loading: false,
+      selectItems: [] as any
+    });
+
+    const getCategoryList = async () => {
+      state.loading = true;
+      try {
+        const { data } = await api_knowledgeWikiCategoryType_page({
+          type: 'INSTRUMENT',
+          page: 1,
+          rows: 99
+        });
+
+        state.categoryList = data.rows || [];
+        if (state.categoryList.length) {
+          nextTick(() => {
+            state.tabValue = tabValue || 'name-' + state.categoryList[0].id;
+          });
+        }
+      } catch {
+        //
+      }
+      state.loading = false;
+    };
+
+    getCategoryList();
+
+    // 添加
+    const onSubmit = async () => {
+      emit('confirm', state.selectItems);
+    };
+    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="暂无乐器百科" />
+            )}
+            <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>
+
+        <NSpace class={styles.btnGroup} justify="center">
+          <NButton round onClick={() => emit('close')}>
+            取消
+          </NButton>
+          <NButton round type="primary" onClick={onSubmit}>
+            确认添加
+          </NButton>
+        </NSpace>
+      </div>
+    );
+  }
+});

+ 27 - 0
src/views/prepare-lessons/model/source-rhythm/index.module.less

@@ -0,0 +1,27 @@
+.sourceRhythm {
+  width: 100%;
+  line-height: 0;
+
+
+  iframe {
+    width: 100%;
+    height: 518px;
+
+    :global {
+      .headTopBackBtn {
+        display: none;
+      }
+    }
+  }
+
+  .btnGroup {
+    padding: 20px 0;
+
+    :global {
+      .n-button {
+        height: 47px;
+        min-width: 156px;
+      }
+    }
+  }
+}

+ 57 - 3
src/views/prepare-lessons/model/source-rhythm/index.tsx

@@ -1,16 +1,70 @@
-import { defineComponent } from 'vue';
+import { defineComponent, onMounted, onUnmounted, ref } from 'vue';
 import styles from './index.module.less';
 import { useUserStore } from '/src/store/modules/users';
+import { NButton, NSpace, NSpin } from 'naive-ui';
 
 export default defineComponent({
   name: 'source-rhythm',
   emits: ['close', 'confirm'],
   setup(props, { emit }) {
     const userStore = useUserStore();
-    const src = `${origin}/classroom-app/#/tempo-practice?Authorization=${userStore.getToken}&win=pc`;
+    const iframeRef = ref();
+    const loading = ref(true);
+    // const src = `${origin}/classroom-app/#/tempo-practice?v=${Date.now()}&Authorization=${
+    //   userStore.getToken
+    // }&win=modal`;
+    const src = `http://localhost:9002/#/tempo-practice?v=${Date.now()}&Authorization=${
+      userStore.getToken
+    }&win=modal`;
+
+    // emit('confirm');
+    const onSubmit = () => {
+      iframeRef.value?.contentWindow?.postMessage(
+        { api: 'getTempoSetting' },
+        '*'
+      );
+    };
+
+    const iframeHandle = (ev: MessageEvent) => {
+      if (ev.data?.api === 'getTempoSetting') {
+        const data = ev.data.data ? JSON.parse(ev.data.data) : {};
+        emit('confirm', {
+          dataJson: JSON.stringify(data.setting),
+          coverImg: 'https://oss.dayaedu.com/ktqy/171013700931689a322a6.png'
+        });
+      }
+    };
+    onMounted(() => {
+      window.addEventListener('message', iframeHandle);
+    });
+
+    onUnmounted(() => {
+      window.removeEventListener('message', iframeHandle);
+    });
+
     return () => (
       <div class={styles.sourceRhythm}>
-        <iframe src={src}></iframe>
+        <NSpin show={loading.value}>
+          <iframe
+            ref={iframeRef}
+            onLoad={() => {
+              // emit('setIframe', iframeRef.value);
+              // isLoaded.value = true;
+              loading.value = false;
+            }}
+            class={[styles.container, 'musicIframe']}
+            frameborder="0"
+            src={src}></iframe>
+
+          <NSpace class={styles.btnGroup} justify="center">
+            <NButton round onClick={() => emit('close')}>
+              取消
+            </NButton>
+            <NButton round type="primary" onClick={onSubmit}>
+              确认添加
+            </NButton>
+          </NSpace>
+        </NSpin>
       </div>
     );
   }