Quellcode durchsuchen

Merge branch 'iteration-login' into dev

lex vor 1 Jahr
Ursprung
Commit
7de7f64b00
79 geänderte Dateien mit 3811 neuen und 1204 gelöschten Zeilen
  1. 1 0
      package.json
  2. 12 2
      src/components/TheSearch/index.tsx
  3. 30 0
      src/components/card-type/index.module.less
  4. 32 4
      src/components/card-type/index.tsx
  5. 0 1
      src/components/layout/layoutSilder.tsx
  6. 4 1
      src/components/layout/layoutTop.tsx
  7. 0 1
      src/components/upload-file/index.tsx
  8. 64 1
      src/store/modules/prepareLessons.ts
  9. 1 2
      src/store/modules/users.ts
  10. 1 1
      src/store/mutation-types.ts
  11. 15 2
      src/styles/index.less
  12. 10 0
      src/utils/contants.ts
  13. 10 0
      src/utils/request.ts
  14. 1 1
      src/views/attend-class/model/train-settings/index.module.less
  15. BIN
      src/views/attend-class/model/train-type/images/icon-delete.png
  16. 28 0
      src/views/attend-class/model/train-type/index.module.less
  17. 59 24
      src/views/attend-class/model/train-type/index.tsx
  18. 4 0
      src/views/attend-class/model/train-update/index.module.less
  19. 208 58
      src/views/attend-class/model/train-update/index.tsx
  20. 19 0
      src/views/classList/api.ts
  21. 85 0
      src/views/classList/contants.ts
  22. BIN
      src/views/classList/images/smallArrow.png
  23. BIN
      src/views/classList/images/transArrowActive.png
  24. BIN
      src/views/classList/images/transArrrow.png
  25. 190 0
      src/views/classList/index.module.less
  26. 92 67
      src/views/classList/index.tsx
  27. 161 0
      src/views/classList/modals/resetStudent.tsx
  28. 2 3
      src/views/login/api.ts
  29. 10 8
      src/views/login/components/forgotPassword.tsx
  30. 10 0
      src/views/natural-resources/api.ts
  31. 29 4
      src/views/natural-resources/components/my-collect/index.tsx
  32. 0 1
      src/views/natural-resources/components/my-resources/index.module.less
  33. 28 2
      src/views/natural-resources/components/my-resources/index.tsx
  34. 1 25
      src/views/natural-resources/components/my-resources/upload-modal/index.tsx
  35. 25 23
      src/views/natural-resources/components/my-resources/upload-modal/upload-file.tsx
  36. 1 1
      src/views/natural-resources/components/share-resources/index.tsx
  37. 2 0
      src/views/natural-resources/index.module.less
  38. 81 0
      src/views/prepare-lessons/api.ts
  39. 65 55
      src/views/prepare-lessons/components/directory-main/index.tsx
  40. 2 1
      src/views/prepare-lessons/components/directory-main/select-lessonware/index.module.less
  41. 59 23
      src/views/prepare-lessons/components/directory-main/select-lessonware/index.tsx
  42. 60 0
      src/views/prepare-lessons/components/lesson-main/courseware/index.module.less
  43. 220 18
      src/views/prepare-lessons/components/lesson-main/courseware/index.tsx
  44. 6 1
      src/views/prepare-lessons/components/lesson-main/index.tsx
  45. 168 0
      src/views/prepare-lessons/components/lesson-main/train/assign-homework.tsx
  46. 62 0
      src/views/prepare-lessons/components/lesson-main/train/index.module.less
  47. 312 14
      src/views/prepare-lessons/components/lesson-main/train/index.tsx
  48. 0 17
      src/views/prepare-lessons/components/resource-main/components/my-collect/index.module.less
  49. 0 106
      src/views/prepare-lessons/components/resource-main/components/my-collect/index.tsx
  50. 0 112
      src/views/prepare-lessons/components/resource-main/components/my-collect/resource-search-group/index.tsx
  51. 0 17
      src/views/prepare-lessons/components/resource-main/components/my-resources/index.module.less
  52. 0 106
      src/views/prepare-lessons/components/resource-main/components/my-resources/index.tsx
  53. 0 59
      src/views/prepare-lessons/components/resource-main/components/my-resources/resource-search-group/index.module.less
  54. 35 0
      src/views/prepare-lessons/components/resource-main/components/resource-item/index.module.less
  55. 201 0
      src/views/prepare-lessons/components/resource-main/components/resource-item/index.tsx
  56. 3 4
      src/views/prepare-lessons/components/resource-main/components/resource-item/resource-search-group/index.module.less
  57. 43 39
      src/views/prepare-lessons/components/resource-main/components/resource-item/resource-search-group/index.tsx
  58. 31 0
      src/views/prepare-lessons/components/resource-main/components/select-music/index.module.less
  59. 183 0
      src/views/prepare-lessons/components/resource-main/components/select-music/index.tsx
  60. 1 4
      src/views/prepare-lessons/components/resource-main/components/select-music/resource-search-group/index.module.less
  61. 12 40
      src/views/prepare-lessons/components/resource-main/components/select-music/resource-search-group/index.tsx
  62. 0 18
      src/views/prepare-lessons/components/resource-main/components/share-resources/index.module.less
  63. 0 105
      src/views/prepare-lessons/components/resource-main/components/share-resources/index.tsx
  64. 24 0
      src/views/prepare-lessons/components/resource-main/index.module.less
  65. 174 51
      src/views/prepare-lessons/components/resource-main/index.tsx
  66. BIN
      src/views/prepare-lessons/images/icon-delete.png
  67. 11 0
      src/views/prepare-lessons/index.module.less
  68. 11 1
      src/views/prepare-lessons/index.tsx
  69. 10 0
      src/views/prepare-lessons/model/attend-class/index.module.less
  70. 93 70
      src/views/prepare-lessons/model/attend-class/index.tsx
  71. 47 34
      src/views/prepare-lessons/model/select-music/index.module.less
  72. 103 33
      src/views/prepare-lessons/model/select-music/index.tsx
  73. 51 44
      src/views/prepare-lessons/model/select-music/search-group.tsx
  74. 127 0
      src/views/prepare-lessons/model/select-resources/index.module.less
  75. 40 0
      src/views/prepare-lessons/model/select-resources/index.tsx
  76. 36 0
      src/views/prepare-lessons/model/select-resources/select-item/index.module.less
  77. 187 0
      src/views/prepare-lessons/model/select-resources/select-item/index.tsx
  78. 110 0
      src/views/prepare-lessons/model/select-resources/select-item/resource-search-group/index.module.less
  79. 108 0
      src/views/prepare-lessons/model/select-resources/select-item/resource-search-group/index.tsx

+ 1 - 0
package.json

@@ -43,6 +43,7 @@
     "vue": "^3.3.4",
     "vue-router": "^4.1.6",
     "vue3-lottie": "^2.7.0",
+    "vuedraggable": "^4.1.0",
     "wavesurfer.js": "^7.0.0-beta.11"
   },
   "devDependencies": {

+ 12 - 2
src/components/TheSearch/index.tsx

@@ -1,6 +1,6 @@
 import { PropType, defineComponent, reactive } from 'vue';
 import styles from './index.module.less';
-import { InputProps, NButton, NIcon, NImage, NInput } from 'naive-ui';
+import { InputProps, NButton, NInput } from 'naive-ui';
 import icon_search from '/src/common/images/icon_search.svg';
 import icon_searchActive from '/src/common/images/icon_searchActive.svg';
 
@@ -23,7 +23,17 @@ export default defineComponent({
         class={styles.TheSearch}
         round={props.round}
         placeholder="请输入搜索关键词"
-        v-model:value={searchData.value}>
+        clearable
+        v-model:value={searchData.value}
+        onClear={() =>
+          emit('search', searchData.value ? searchData.value.trim() : '')
+        }
+        onKeyup={(e: KeyboardEvent) => {
+          e.stopPropagation();
+          if (e.code === 'Enter') {
+            emit('search', searchData.value ? searchData.value.trim() : '');
+          }
+        }}>
         {{
           prefix: () => (
             <>

+ 30 - 0
src/components/card-type/index.module.less

@@ -140,4 +140,34 @@
     z-index: 99;
     transition: all .3s ease-in-out;
   }
+
+
+  .offShelfBg {
+    position: absolute;
+    top: 0;
+    left: 0;
+    width: 100%;
+    height: 100%;
+    border-radius: 14px;
+    background-color: rgba(0, 0, 0, 0.7);
+    z-index: 10;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    flex-direction: column;
+
+    .offShelfTips {
+      font-size: 22px;
+      font-weight: 600;
+      color: #FFFFFF;
+      line-height: 30px;
+      padding-bottom: 32px;
+    }
+
+    .offShelfBtn {
+      height: 44px;
+      border-radius: 10px;
+      min-width: 124px;
+    }
+  }
 }

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

@@ -57,14 +57,20 @@ export default defineComponent({
     item: {
       type: Object as PropType<itemType>,
       default: () => ({})
+    },
+    /** 是否下架 */
+    offShelf: {
+      type: Boolean,
+      default: false
     }
   },
   /**
    * @type {string} click 点击事件
    * @type {string} collect 收藏
    * @type {string} add 添加
+   * @type {string} offShelf 下架
    */
-  emits: ['click', 'collect', 'add'],
+  emits: ['click', 'collect', 'add', 'offShelf'],
   setup(props, { emit }) {
     const isAnimation = ref(false);
     const formatType = (type: string) => {
@@ -96,6 +102,18 @@ export default defineComponent({
         onMouseleave={() => {
           isAnimation.value = false;
         }}>
+        {/* 判断是否下架 */}
+        {props.offShelf && (
+          <div class={styles.offShelfBg}>
+            <p class={styles.offShelfTips}>该资源已被平台下架</p>
+            <NButton
+              type="primary"
+              class={styles.offShelfBtn}
+              onClick={() => emit('offShelf')}>
+              确认
+            </NButton>
+          </div>
+        )}
         <NCard
           class={[
             styles['card-section'],
@@ -105,7 +123,8 @@ export default defineComponent({
           {{
             cover: () => (
               <>
-                {['IMG', 'MUSIC'].includes(props.item.type) && (
+                {/* 图片 */}
+                {props.item.type === 'IMG' && (
                   <NImage
                     class={[styles.cover, styles.image]}
                     lazy
@@ -114,6 +133,16 @@ export default defineComponent({
                     src={props.item.coverImg}
                   />
                 )}
+                {/* 乐谱 */}
+                {props.item.type === 'MUSIC' && (
+                  <NImage
+                    class={[styles.cover, styles.image]}
+                    lazy
+                    previewDisabled={true}
+                    objectFit="cover"
+                    src={props.item.coverImg}
+                  />
+                )}
                 {/* 音频 */}
                 {props.item.type === 'SONG' && (
                   <AudioPlayer
@@ -195,8 +224,7 @@ export default defineComponent({
                     onClick={(e: MouseEvent) => {
                       e.stopPropagation();
                       e.preventDefault();
-                      emit('add');
-                      console.log('add');
+                      emit('add', props.item);
                     }}>
                     添加
                   </NButton>

+ 0 - 1
src/components/layout/layoutSilder.tsx

@@ -95,7 +95,6 @@ export default defineComponent({
     };
 
     onBeforeRouteUpdate((to: any) => {
-      console.log(to, 'to');
       checkPathChange(to.path);
     });
 

+ 4 - 1
src/components/layout/layoutTop.tsx

@@ -21,7 +21,10 @@ export default defineComponent({
       <>
         <div class={styles.layoutTop}>
           <div class={styles.layoutLeft}>
-            <NImage src={schoolIcon} class={styles.schoolIcon}></NImage>
+            <NImage
+              src={schoolIcon}
+              class={styles.schoolIcon}
+              previewDisabled></NImage>
             <p>武汉市武昌区教育局 | 武汉小学</p>
           </div>
           <div class={styles.layoutRight}>

+ 0 - 1
src/components/upload-file/index.tsx

@@ -124,7 +124,6 @@ export default defineComponent({
     const fileListRef = ref<UploadFileInfo[]>([]);
     const initFileList = () => {
       if (props.fileList) {
-        console.log('downloadUrl', props.fileList);
         const splitName = props.fileList.split('/');
         fileListRef.value = [
           {

+ 64 - 1
src/store/modules/prepareLessons.ts

@@ -3,14 +3,25 @@ import { store } from '@/store';
 
 export const usePrepareStore = defineStore('prepare-lessons-store', {
   state: () => ({
+    subjectId: null as any, // 基础声部
     baseCourseware: {} as any, // 基础教学课件
     selectKey: '', // 选的哪一节课
     lessonCoursewareId: '', // 哪个教材分类
     lessonCoursewareDetailId: '', // 哪个教材详情
     treeList: [] as any[], // 左边教学课件列表
-    coursewareList: [] as any[] // 课件信息
+    coursewareList: [] as any[], // 课件信息
+    trainList: [] as any[], // 训练信息
+    tabType: 'courseware', // 备课 - 课件 | 训练 类型切换 'courseware' | 'train'
+    selectMusicStatus: false, // 乐谱状态
+    selectResourceStatus: false, // 资源状态
+    isAddResource: false, // 是否添加资源
+    isAddTrain: false // 是否添加训练
   }),
   getters: {
+    /** 获取资源状态 */
+    getSubjectId(): [string, number] {
+      return this.subjectId;
+    },
     /** 获取基础教学课件 */
     getBaseCourseware(): any {
       return this.baseCourseware;
@@ -34,9 +45,37 @@ export const usePrepareStore = defineStore('prepare-lessons-store', {
     /** 获取课件列表 */
     getCoursewareList(): any[] {
       return this.coursewareList;
+    },
+    /** 获取训练列表 */
+    getTrainList(): any[] {
+      return this.trainList;
+    },
+    /** 获取课件类型 */
+    getTabType(): string {
+      return this.tabType;
+    },
+    /** 获取乐谱状态 */
+    getSelectMusicStatus(): boolean {
+      return this.selectMusicStatus;
+    },
+    /** 获取资源状态 */
+    getSelectResourceStatus(): boolean {
+      return this.selectResourceStatus;
+    },
+    /** 获取是否添加资源 */
+    getIsAddResource(): boolean {
+      return this.isAddResource;
+    },
+    /** 获取是否添加训练 */
+    getIsAddTrain(): boolean {
+      return this.isAddTrain;
     }
   },
   actions: {
+    /** 设置基础声部 */
+    setSubjectId(subjectId: string | number) {
+      this.subjectId = subjectId;
+    },
     /** 设置基础教学课件 */
     setBaseCourseware(baseCourseware: any) {
       this.baseCourseware = baseCourseware;
@@ -60,6 +99,30 @@ export const usePrepareStore = defineStore('prepare-lessons-store', {
     /** 设置课件列表 */
     setCoursewareList(list: any[]) {
       this.coursewareList = list;
+    },
+    /** 设置训练列表 */
+    setTrainList(list: any[]) {
+      this.trainList = list;
+    },
+    /** 设置tab类型 */
+    setTabType(type: string) {
+      this.tabType = type;
+    },
+    /** 设置乐谱状态 */
+    setSelectMusicStatus(status: boolean) {
+      this.selectMusicStatus = status;
+    },
+    /** 设置资源状态 */
+    setSelectResourceStatus(status: boolean) {
+      this.selectResourceStatus = status;
+    },
+    /** 设置资源状态 */
+    setIsAddResource(status: boolean) {
+      this.isAddResource = status;
+    },
+    /** 设置训练状态 */
+    setIsAddTrain(status: boolean) {
+      this.isAddTrain = status;
     }
   }
 });

+ 1 - 2
src/store/modules/users.ts

@@ -33,7 +33,7 @@ export const useUserStore = defineStore('user-store', {
     getNickname(): string {
       return this.username;
     },
-    getUserInfo(): object {
+    getUserInfo(): any {
       return this.info;
     }
   },
@@ -57,7 +57,6 @@ export const useUserStore = defineStore('user-store', {
     async login(userInfo: any) {
       try {
         const { data } = await userLogin(userInfo);
-        console.log(data, 'data');
         const userToken = data.token_type + ' ' + data.access_token;
         const ex = 7 * 24 * 60 * 60 * 1000;
         storage.set(ACCESS_TOKEN, userToken, ex);

+ 1 - 1
src/store/mutation-types.ts

@@ -1,4 +1,4 @@
-export const ACCESS_TOKEN = 'ACCESS-TOKEN'; // 用户token
+export const ACCESS_TOKEN = 'ACCESS-TOKEN-TEACHER'; // 用户token
 export const IM_TOKEN = 'IM-TOKEN'; //
 export const CURRENT_USER = 'CURRENT-USER'; // 当前用户信息
 export const TABS_ROUTES = 'TABS-ROUTES'; // 标签页

+ 15 - 2
src/styles/index.less

@@ -1,4 +1,3 @@
-
 * {
   padding: 0;
   margin: 0;
@@ -167,7 +166,7 @@ body {
 // 给弹窗设置标题的基础样式
 .modalTitle {
   border-radius: 16px;
-  overflow: hidden;
+  // overflow: hidden;
 
   &.background {
     .n-card-header {
@@ -176,6 +175,7 @@ body {
   }
 
   .n-card-header {
+    border-radius: 16px 16px 0 0;
     position: relative;
     padding: 20px 18px;
     text-align: center;
@@ -290,4 +290,17 @@ body {
 .list-leave-to {
   opacity: 0;
   transform: translateX(30px);
+}
+
+// 拖动时
+.sortable-ghost {
+  opacity: 0.7;
+}
+
+.flip-list-move {
+  transition: transform 0.5s;
+}
+
+.no-move {
+  transition: transform 0s;
 }

+ 10 - 0
src/utils/contants.ts

@@ -28,3 +28,13 @@ export const resourceType = {
   SONG: '音频',
   VIDEO: '视频'
 };
+
+/**
+ * @description: 评测难度
+ * 入门:BEGINNER/进阶:ADVANCED/大师:PERFORMER")
+ */
+export const evaluateDifficult = {
+  BEGINNER: '入门级',
+  ADVANCED: '进阶级',
+  PERFORMER: '大师级'
+} as any;

+ 10 - 0
src/utils/request.ts

@@ -24,6 +24,16 @@ request.interceptors.request.use(
     const userStore = useUserStore();
     const Authorization = userStore.getToken || '';
     const authHeaders: any = {};
+    // if (
+    //   userStore.getUserInfo &&
+    //   userStore.getUserInfo.schoolInfos &&
+    //   userStore.getUserInfo.schoolInfos[0]?.id &&
+    //   options.data
+    // ) {
+    //   options.data['schoolId'] =
+    //     (userStore.getUserInfo && userStore.getUserInfo.schoolInfos[0]?.id) ||
+    //     '';
+    // }
     if (
       Authorization &&
       !['/api-oauth/userlogin', '/api-auth/open/sendSms'].includes(url)

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

@@ -71,7 +71,7 @@
 }
 
 .trainEditModal {
-  width: 494px;
+  width: 580px;
 }
 
 .selectMusicModal {

BIN
src/views/attend-class/model/train-type/images/icon-delete.png


+ 28 - 0
src/views/attend-class/model/train-type/index.module.less

@@ -4,12 +4,25 @@
   background: #ECF6FF;
   border-radius: 16px;
   padding: 20px;
+  position: relative;
+
+  .overflowBg {
+    position: absolute;
+    top: 0;
+    left: 0;
+    width: 100%;
+    height: 100%;
+    z-index: 9;
+    padding: 20px;
+    cursor: move;
+  }
 }
 
 .train-header {
   display: flex;
   align-items: center;
   justify-content: space-between;
+  position: relative;
 
   .title {
     display: flex;
@@ -56,6 +69,21 @@
       height: 10px;
     }
   }
+
+  .iconDelete {
+    position: absolute;
+    right: 0px;
+    top: 0px;
+    z-index: 10;
+    display: flex;
+    height: 30px;
+    padding: 0;
+
+    img {
+      height: 30px;
+      width: 30px;
+    }
+  }
 }
 
 .train-content {

+ 59 - 24
src/views/attend-class/model/train-type/index.tsx

@@ -5,25 +5,34 @@ import pTag from './images/p-tag.svg';
 import eTag from './images/e-tag.svg';
 import pEdit from './images/p-edit.svg';
 import eEdit from './images/e-edit.svg';
-// import iconPlay from './images/icon-play.svg';
 import iconPause from './images/icon-pause.svg';
 import pDelete from './images/p-delete.svg';
 import eDelete from './images/e-delete.svg';
+import iconDelete from './images/icon-delete.png';
 import { useUserStore } from '/src/store/modules/users';
 type ItemType = {
   id: string | number;
-  type: 'practice' | 'evaluation';
-  src: string;
-  name: string;
+  trainingType: 'PRACTICE' | 'EVALUATION';
+  musicId: string | number;
+  coverImg: string;
+  musicName: string;
   typeList: string[];
 };
 
 export default defineComponent({
   name: 'train-type',
   props: {
+    type: {
+      type: String as PropType<'prepare' | 'homework'>,
+      default: 'homework'
+    },
     item: {
       type: Object as PropType<ItemType>,
       default: () => ({})
+    },
+    isDelete: {
+      type: Boolean,
+      default: false
     }
   },
   emits: ['click', 'delete', 'edit'],
@@ -55,27 +64,43 @@ export default defineComponent({
       <div
         class={[
           styles.trainType,
-          props.item.type === 'evaluation' ? styles.evaluationType : ''
+          props.item.trainingType === 'EVALUATION' ? styles.evaluationType : ''
         ]}
         onClick={() => emit('click', props.item)}>
+        {props.isDelete && <div class={styles.overflowBg}></div>}
         <div class={styles['train-header']}>
           <div class={styles.title}>
             <img
-              src={props.item.type === 'evaluation' ? eTag : pTag}
+              src={props.item.trainingType === 'EVALUATION' ? eTag : pTag}
               class={styles['title-tag']}
             />
-            <NEllipsis class={styles['title-text']}>
-              {props.item.name}
+            <NEllipsis class={styles['title-text']} tooltip={false}>
+              {props.item.musicName}
             </NEllipsis>
           </div>
 
-          <NButton class={styles.btn} round onClick={onDetail}>
-            {props.item.type === 'evaluation' ? '评测模式' : '练习模式'}
-            <img src={iconPause} />
-          </NButton>
+          {props.isDelete ? (
+            <NButton
+              class={styles.iconDelete}
+              quaternary
+              round
+              onClick={(e: MouseEvent) => {
+                e.stopPropagation();
+                emit('delete', props.item);
+              }}>
+              <img src={iconDelete} />
+            </NButton>
+          ) : (
+            <NButton class={styles.btn} round onClick={onDetail}>
+              {props.item.trainingType === 'EVALUATION'
+                ? '评测模式'
+                : '练习模式'}
+              <img src={iconPause} />
+            </NButton>
+          )}
         </div>
         <div class={styles['train-content']}>
-          <NImage src={props.item.src} previewDisabled objectFit="cover" />
+          <NImage src={props.item.coverImg} previewDisabled objectFit="cover" />
           <div class={styles.preview}>
             <NButton
               strong
@@ -88,7 +113,7 @@ export default defineComponent({
         </div>
         <div class={styles['train-footer']}>
           <NSpace class={styles.type}>
-            {props.item.typeList.map((type: string) => (
+            {props.item.typeList?.map((type: string) => (
               <NTag>{type}</NTag>
             ))}
           </NSpace>
@@ -96,22 +121,32 @@ export default defineComponent({
           <NSpace size={6}>
             <n-button
               quaternary
+              disabled={props.isDelete}
               class={styles.operation}
               onClick={(e: MouseEvent) => {
                 e.stopPropagation();
                 emit('edit', props.item);
               }}>
-              <img src={props.item.type === 'evaluation' ? eEdit : pEdit} />
-            </n-button>
-            <n-button
-              quaternary
-              class={styles.operation}
-              onClick={(e: MouseEvent) => {
-                e.stopPropagation();
-                onDelete();
-              }}>
-              <img src={props.item.type === 'evaluation' ? eDelete : pDelete} />
+              <img
+                src={props.item.trainingType === 'EVALUATION' ? eEdit : pEdit}
+              />
             </n-button>
+            {props.type === 'homework' && (
+              <n-button
+                quaternary
+                disabled={props.isDelete}
+                class={styles.operation}
+                onClick={(e: MouseEvent) => {
+                  e.stopPropagation();
+                  onDelete();
+                }}>
+                <img
+                  src={
+                    props.item.trainingType === 'EVALUATION' ? eDelete : pDelete
+                  }
+                />
+              </n-button>
+            )}
           </NSpace>
         </div>
       </div>

+ 4 - 0
src/views/attend-class/model/train-update/index.module.less

@@ -36,4 +36,8 @@
       border-radius: 8px;
     }
   }
+
+  .scoreGroup {
+    display: flex;
+  }
 }

+ 208 - 58
src/views/attend-class/model/train-update/index.tsx

@@ -1,4 +1,4 @@
-import { defineComponent, reactive } from 'vue';
+import { defineComponent, nextTick, onMounted, reactive, ref } from 'vue';
 import styles from './index.module.less';
 import {
   NButton,
@@ -8,136 +8,286 @@ import {
   NInputGroup,
   NInputGroupLabel,
   NInputNumber,
-  NSpace
+  NSpace,
+  useMessage
 } from 'naive-ui';
+import {
+  lessonPreTrainingAdd,
+  lessonPreTrainingUpdate
+} from '/src/views/prepare-lessons/api';
 
 export default defineComponent({
   name: 'train-update',
-  emits: ['close'],
+  props: {
+    /** 初始数据 */
+    item: {
+      type: Object,
+      default: () => ({})
+    }
+  },
+  emits: ['close', 'confirm'],
   setup(props, { emit }) {
     // 'practice' | 'evaluation'
+    const message = useMessage();
     const forms = reactive({
-      type: 'practice',
-      minScore: null,
-      maxScore: null,
-      difficulty: '1'
+      id: null as any,
+      uploading: false,
+      baseMaxScore: 99,
+      type: 'PRACTICE',
+      musicId: '',
+      coursewareKnowledgeDetailId: '', // 章节编号
+      minScore: null as any,
+      maxScore: null as any,
+      subjectId: '',
+      coverImg: '',
+      practiceSpeed: null as any, // 练习速度
+      practiceTimes: null as any, // 练习时长
+      difficulty: 'BEGINNER', // 评测难度
+      evaluationSpeed: null as any, // 评测速度
+      evaluationScore: null as any // 评测分数
+    });
+    const formsRef = ref();
+
+    const onSubmit = async () => {
+      formsRef.value?.validate(async (err: any) => {
+        if (err) {
+          return;
+        }
+        forms.uploading = true;
+        try {
+          const params = {
+            trainingType: forms.type,
+            musicId: forms.musicId,
+            coursewareKnowledgeDetailId: forms.coursewareKnowledgeDetailId,
+            subjectId: forms.subjectId,
+            id: forms.id,
+            coverImg: forms.coverImg,
+            trainingConfigJson: ''
+          };
+          const configJson: any = {};
+          if (forms.type === 'PRACTICE') {
+            configJson.practiceChapterBegin = forms.minScore;
+            configJson.practiceChapterEnd = forms.maxScore;
+            configJson.practiceSpeed = forms.practiceSpeed;
+            configJson.trainingTimes = forms.practiceTimes;
+          } else {
+            configJson.evaluateDifficult = forms.difficulty;
+            configJson.evaluateSpeed = forms.evaluationSpeed;
+            configJson.trainingTimes = forms.evaluationScore;
+          }
+          configJson.practiceChapterMax = forms.baseMaxScore;
+          params.trainingConfigJson = configJson;
+          if (forms.id) {
+            await lessonPreTrainingUpdate(params);
+            message.success('修改成功');
+          } else {
+            await lessonPreTrainingAdd(params);
+            message.success('添加成功');
+          }
+
+          emit('close');
+          emit('confirm');
+        } catch {
+          //
+        }
+        forms.uploading = false;
+      });
+    };
+
+    onMounted(() => {
+      const item = props.item;
+      if (item.trainId) {
+        forms.id = item.trainId;
+        forms.minScore = item.practiceChapterBegin;
+        forms.maxScore = item.practiceChapterEnd;
+        forms.practiceSpeed = item.practiceSpeed;
+        if (item.trainingType === 'PRACTICE') {
+          forms.practiceTimes = item.trainingTimes;
+        } else {
+          forms.evaluationScore = item.trainingTimes;
+        }
+        forms.difficulty = item.evaluateDifficult || 'BEGINNER';
+        forms.evaluationSpeed = item.evaluateSpeed;
+      } else {
+        forms.minScore = 1;
+        forms.maxScore = item.practiceChapterMax ? item.practiceChapterMax : 1;
+      }
+      forms.baseMaxScore = item.practiceChapterMax || 99;
+      forms.musicId = item.id;
+      forms.coursewareKnowledgeDetailId = item.coursewareKnowledgeDetailId;
+      forms.subjectId = item.subjectId;
+      forms.coverImg = item.coverImg;
     });
     return () => (
       <div class={styles.trainUpdate}>
-        <NForm labelAlign="left" labelPlacement="left">
-          <NFormItem label="训练方式" path="type">
+        <NForm
+          ref={formsRef}
+          model={forms}
+          labelAlign="right"
+          labelPlacement="left">
+          <NFormItem
+            label="训练方式"
+            path="type"
+            rule={[{ required: true, message: '请选择训练方式' }]}>
             <NSpace>
               <NButton
                 secondary
                 class={[
                   styles.switch,
-                  forms.type === 'practice' ? styles.active : ''
+                  forms.type === 'PRACTICE' ? styles.active : ''
                 ]}
-                onClick={() => (forms.type = 'practice')}>
+                onClick={() => (forms.type = 'PRACTICE')}>
                 练习
               </NButton>
               <NButton
                 secondary
                 class={[
                   styles.switch,
-                  forms.type === 'evaluation' ? styles.active : ''
+                  forms.type === 'EVALUATION' ? styles.active : ''
                 ]}
-                onClick={() => (forms.type = 'evaluation')}>
+                onClick={() => (forms.type = 'EVALUATION')}>
                 评测
               </NButton>
             </NSpace>
           </NFormItem>
-          {forms.type === 'practice' && (
+          {forms.type === 'PRACTICE' && (
             <>
-              <NFormItem label="练习小节" path="minScore">
-                <NInputNumber
-                  v-model:value={forms.minScore}
-                  showButton={false}
-                  min={0}
-                  max={99}
-                  placeholder="最小练习小节"
-                  onUpdate:value={() => {
-                    forms.maxScore = null;
-                  }}
-                  style={{ width: '46%' }}
-                />
+              <div class={styles.scoreGroup}>
+                <NFormItem
+                  label="练习小节"
+                  path="minScore"
+                  rule={[{ required: true, message: '请输入最小练习小节' }]}>
+                  <NInputNumber
+                    v-model:value={forms.minScore}
+                    showButton={false}
+                    min={1}
+                    max={forms.baseMaxScore}
+                    placeholder="最小练习小节"
+                    onUpdate:value={() => {
+                      forms.maxScore = null;
+                    }}
+                    clearable
+                  />
+                </NFormItem>
                 <div
                   style={{
+                    '--n-feedback-height': '24px',
                     display: 'flex',
                     alignItems: 'center',
-                    lineHeight: '1',
-                    marginTop: '-2px',
-                    margin: '-2px 4% 0 4%'
+                    margin: '-2px 2% 0 2%',
+                    marginBottom: 'var(--n-feedback-height)'
                   }}>
                   -
                 </div>
+                <NFormItem
+                  path="maxScore"
+                  rule={[{ required: true, message: '请输入最大练习小节' }]}>
+                  <NInputNumber
+                    v-model:value={forms.maxScore}
+                    showButton={false}
+                    min={forms.minScore || 1}
+                    max={forms.baseMaxScore}
+                    placeholder="最大练习小节"
+                    clearable
+                  />
+                </NFormItem>
+              </div>
+              <NFormItem
+                label="练习速度"
+                path="practiceSpeed"
+                rule={[{ required: true, message: '请输入练习速度' }]}>
                 <NInputNumber
-                  v-model:value={forms.maxScore}
-                  showButton={false}
-                  min={forms.minScore || 0}
-                  max={99}
-                  placeholder="最大练习小节"
-                  style={{ width: '46%' }}
-                />
-              </NFormItem>
-              <NFormItem label="练习速度">
-                <NInputNumber
-                  min={0}
+                  min={60}
+                  max={270}
                   showButton={false}
                   style={{ width: '100%' }}
+                  v-model:value={forms.practiceSpeed}
+                  placeholder="练习速度范围60~270"
+                  clearable
                 />
               </NFormItem>
-              <NFormItem label="练习时长">
+              <NFormItem
+                label="练习时长"
+                path="practiceTimes"
+                rule={[{ required: true, message: '请输入练习时长' }]}>
                 <NInputGroup>
-                  <NInput />
+                  <NInputNumber
+                    min={0}
+                    showButton={false}
+                    style={{ width: '100%' }}
+                    v-model:value={forms.practiceTimes}
+                    placeholder="请输入练习时长"
+                    clearable
+                  />
                   <NInputGroupLabel>分钟</NInputGroupLabel>
                 </NInputGroup>
               </NFormItem>
             </>
           )}
 
-          {forms.type === 'evaluation' && (
+          {forms.type === 'EVALUATION' && (
             <>
-              <NFormItem label="评测难度" path="type">
+              <NFormItem
+                label="评测难度"
+                path="type"
+                rule={[{ required: true, message: '请选择评测难度' }]}>
                 <NSpace>
                   <NButton
                     secondary
                     class={[
                       styles.switch,
-                      forms.difficulty === '1' ? styles.active : ''
+                      forms.difficulty === 'BEGINNER' ? styles.active : ''
                     ]}
-                    onClick={() => (forms.difficulty = '1')}>
+                    onClick={() => (forms.difficulty = 'BEGINNER')}>
                     入门级
                   </NButton>
                   <NButton
                     secondary
                     class={[
                       styles.switch,
-                      forms.difficulty === '2' ? styles.active : ''
+                      forms.difficulty === 'ADVANCED' ? styles.active : ''
                     ]}
-                    onClick={() => (forms.difficulty = '2')}>
+                    onClick={() => (forms.difficulty = 'ADVANCED')}>
                     进阶级
                   </NButton>
                   <NButton
                     secondary
                     class={[
                       styles.switch,
-                      forms.difficulty === '3' ? styles.active : ''
+                      forms.difficulty === 'PERFORMER' ? styles.active : ''
                     ]}
-                    onClick={() => (forms.difficulty = '3')}>
+                    onClick={() => (forms.difficulty = 'PERFORMER')}>
                     大师级
                   </NButton>
                 </NSpace>
               </NFormItem>
-              <NFormItem label="评测速度">
-                <NInputGroup>
-                  <NInput />
-                  <NInputGroupLabel>分钟</NInputGroupLabel>
-                </NInputGroup>
+              <NFormItem
+                label="评测速度"
+                path="evaluationSpeed"
+                rule={[{ required: true, message: '请输入评测速度' }]}>
+                <NInputNumber
+                  min={60}
+                  max={270}
+                  showButton={false}
+                  style={{ width: '100%' }}
+                  v-model:value={forms.evaluationSpeed}
+                  placeholder="评测速度范围60~270"
+                  clearable
+                />
               </NFormItem>
-              <NFormItem label="合格分数">
+              <NFormItem
+                label="合格分数"
+                path="evaluationScore"
+                rule={[{ required: true, message: '请输入合格分数' }]}>
                 <NInputGroup>
-                  <NInput />
+                  <NInputNumber
+                    min={0}
+                    showButton={false}
+                    style={{ width: '100%' }}
+                    v-model:value={forms.evaluationScore}
+                    placeholder="请输入合格分数"
+                    clearable
+                  />
                   <NInputGroupLabel>分</NInputGroupLabel>
                 </NInputGroup>
               </NFormItem>
@@ -148,7 +298,7 @@ export default defineComponent({
             <NButton strong type="default" round onClick={() => emit('close')}>
               取消
             </NButton>
-            <NButton strong type="primary" round onClick={() => emit('close')}>
+            <NButton strong type="primary" round onClick={() => onSubmit()}>
               确认
             </NButton>
           </NSpace>

+ 19 - 0
src/views/classList/api.ts

@@ -1 +1,20 @@
 import request from '@/utils/request';
+/**
+ * 班级管理 - 班级列表
+ */
+export const classGroupList = (params: any) => {
+  return request.post('/edu-app/classGroup/page', {
+    data: params
+    // requestType: 'form'
+  });
+};
+
+/**
+ * 获取班级里的学生
+ */
+export const getCLassStudent = (params: any) => {
+  return request.post('/edu-app/student/page', {
+    data: params
+    // requestType: 'form'
+  });
+};

+ 85 - 0
src/views/classList/contants.ts

@@ -0,0 +1,85 @@
+export const threeYearSystem = [
+  { label: '全部年级', value: null },
+  { label: '一年级', value: 1 },
+  { label: '二年级', value: 2 },
+  { label: '三年级', value: 3 }
+];
+export const foreYearSystem = [
+  { label: '全部年级', value: null },
+  { label: '一年级', value: 1 },
+  { label: '二年级', value: 2 },
+  { label: '三年级', value: 3 },
+  { label: '四年级', value: 4 }
+];
+export const fiveYearSystem = [
+  { label: '全部年级', value: null },
+  { label: '一年级', value: 1 },
+  { label: '二年级', value: 2 },
+  { label: '三年级', value: 3 },
+  { label: '四年级', value: 4 },
+  { label: '五年级', value: 5 }
+];
+export const sixYearSystem = [
+  { label: '全部年级', value: null },
+  { label: '一年级', value: 1 },
+  { label: '二年级', value: 2 },
+  { label: '三年级', value: 3 },
+  { label: '四年级', value: 4 },
+  { label: '五年级', value: 5 },
+  { label: '六年级', value: 6 }
+];
+export const nineYearSystem = [
+  { label: '全部年级', value: null },
+  { label: '一年级', value: 1 },
+  { label: '二年级', value: 2 },
+  { label: '三年级', value: 3 },
+  { label: '四年级', value: 4 },
+  { label: '五年级', value: 5 },
+  { label: '六年级', value: 6 },
+  { label: '七年级', value: 7 },
+  { label: '八年级', value: 8 },
+  { label: '九年级', value: 9 }
+];
+export const classArray = [
+  { value: null, label: '全部班级' },
+  { value: 1, label: '1班' },
+  { value: 2, label: '2班' },
+  { value: 3, label: '3班' },
+  { value: 4, label: '4班' },
+  { value: 5, label: '5班' },
+  { value: 6, label: '6班' },
+  { value: 7, label: '7班' },
+  { value: 8, label: '8班' },
+  { value: 9, label: '9班' },
+  { value: 10, label: '10班' },
+  { value: 11, label: '11班' },
+  { value: 12, label: '12班' },
+  { value: 13, label: '13班' },
+  { value: 14, label: '14班' },
+  { value: 15, label: '15班' },
+  { value: 16, label: '16班' },
+  { value: 17, label: '17班' },
+  { value: 18, label: '18班' },
+  { value: 19, label: '19班' },
+  { value: 20, label: '20班' },
+  { value: 21, label: '21班' },
+  { value: 22, label: '22班' },
+  { value: 23, label: '23班' },
+  { value: 24, label: '24班' },
+  { value: 25, label: '25班' },
+  { value: 26, label: '26班' },
+  { value: 27, label: '27班' },
+  { value: 28, label: '28班' },
+  { value: 29, label: '29班' },
+  { value: 30, label: '30班' },
+  { value: 31, label: '31班' },
+  { value: 32, label: '32班' },
+  { value: 33, label: '33班' },
+  { value: 34, label: '34班' },
+  { value: 35, label: '35班' },
+  { value: 36, label: '36班' },
+  { value: 37, label: '37班' },
+  { value: 38, label: '38班' },
+  { value: 39, label: '39班' },
+  { value: 40, label: '40班' }
+];

BIN
src/views/classList/images/smallArrow.png


BIN
src/views/classList/images/transArrowActive.png


BIN
src/views/classList/images/transArrrow.png


+ 190 - 0
src/views/classList/index.module.less

@@ -1,3 +1,4 @@
+@img: './images';
 .listWrap {
   padding: 32px;
   background-color: #fff;
@@ -22,3 +23,192 @@
     }
   }
 }
+.btnGroup {
+  padding: 40px 0;
+
+  :global {
+    .n-button {
+      height: 47px;
+      min-width: 156px;
+    }
+  }
+}
+.resetStudentWrap {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  padding-top: 40px;
+  .studentTransfer {
+    position: relative;
+    .smallArrow {
+      right: 166px;
+      top: 15px;
+      position: absolute;
+      width: 12px;
+      height: 12px;
+      cursor: pointer;
+      z-index: 1000;
+    }
+
+    :global {
+      .n-legacy-transfer {
+        width: 634px;
+        min-height: 340px;
+        .n-legacy-transfer-list {
+          &:nth-child(1) {
+            .n-legacy-transfer-list-header__header {
+              &::after {
+                content: '(当前班级)';
+                font-size: 12px;
+                color: #777;
+                font-weight: 400;
+              }
+            }
+          }
+        }
+      }
+      .n-legacy-transfer-list-item {
+        &:hover {
+          background-color: #fff !important;
+        }
+      }
+      .n-legacy-transfer-list__border {
+        border: none;
+      }
+      .n-legacy-transfer-list-header__extra {
+        display: none;
+      }
+      .n-legacy-transfer-list-header {
+        display: flex;
+        flex-direction: column;
+        background: #e8f2ff;
+        align-items: flex-start;
+        height: auto;
+      }
+      .n-legacy-transfer-list-header__checkbox {
+        order: 2;
+        padding: 0 !important;
+        &::after {
+          content: '全选';
+          margin-left: 5px;
+        }
+        margin-top: 8px;
+      }
+      .n-legacy-transfer-list-header__header {
+        width: 100%;
+        padding: 14px 0;
+        font-size: 16px;
+        font-weight: 600 !important;
+        color: #131415;
+        line-height: 22px;
+        order: 1;
+        border-bottom: 1px solid rgba(0, 0, 0, 0.06);
+      }
+      .n-legacy-transfer-list {
+        padding: 0 16px;
+        min-height: 340px;
+        background: #e8f2ff;
+        border-radius: 16px 16px 0 0;
+        width: 277px;
+        .n-legacy-transfer-filter {
+          background: #e8f2ff;
+          padding: 8px 0px 0px;
+          border-bottom: none;
+        }
+        .n-input {
+          border-radius: 8px;
+          .n-input-wrapper {
+            .n-input__border {
+              border-radius: 8px;
+            }
+            .n-input__input-el {
+              height: 41px;
+              line-height: 41px;
+              background: #ffffff;
+            }
+          }
+        }
+      }
+      .n-legacy-transfer-gap {
+        width: 80px;
+        .n-button {
+          width: 34px;
+          height: 34px;
+          border-radius: 8px;
+          .n-button__state-border {
+            border: none !important;
+          }
+          &:nth-child(1) {
+            transform: rotate(180deg);
+            position: relative;
+            &:hover {
+              &::after {
+                background: url('@{img}/transArrowActive.png') no-repeat;
+                top: 0;
+                left: 0;
+
+                background-size: 34px 34px;
+              }
+            }
+            &::after {
+              position: absolute;
+              content: '';
+              width: 34px;
+              height: 34px;
+              background-color: black;
+              top: 0;
+              left: 0;
+              background: url('@{img}/transArrrow.png') no-repeat;
+              background-size: 34px 34px;
+              z-index: 100;
+            }
+          }
+          &:nth-child(2) {
+            position: relative;
+            &:hover {
+              &::after {
+                background: url('@{img}/transArrowActive.png') no-repeat;
+                top: 0;
+                left: 0;
+
+                background-size: 34px 34px;
+              }
+            }
+            &::after {
+              position: absolute;
+              content: '';
+              width: 100%;
+              height: 100%;
+              background-color: black;
+              top: 0;
+              left: 0;
+              background: url('@{img}/transArrrow.png') no-repeat;
+              background-size: 34px 34px;
+              z-index: 100;
+            }
+          }
+        }
+      }
+    }
+    .studentTransferBottom {
+      width: 100%;
+      display: flex;
+      flex-direction: row;
+      align-items: center;
+      justify-content: space-between;
+
+      .bottom {
+        padding: 0 16px;
+
+        width: 277px;
+        background-color: #e8f2ff;
+
+        border-radius: 0 0 8px 8px;
+        .bottomWrap {
+          padding: 14px 0;
+          border-top: 1px solid rgba(0, 0, 0, 0.06);
+        }
+      }
+    }
+  }
+}

+ 92 - 67
src/views/classList/index.tsx

@@ -1,4 +1,4 @@
-import { defineComponent, reactive } from 'vue';
+import { defineComponent, reactive, onMounted } from 'vue';
 import styles from './index.module.less';
 import {
   NButton,
@@ -6,18 +6,36 @@ import {
   NForm,
   NFormItem,
   NImage,
+  NModal,
   NSelect,
   NSpace
 } from 'naive-ui';
 import SearchInput from '@/components/searchInput';
 import CSelect from '@/components/CSelect';
 import Pagination from '@/components/pagination';
+import { classGroupList } from './api';
+import { useUserStore } from '/src/store/modules/users';
+import ResetStudent from './modals/resetStudent';
+import {
+  sixYearSystem,
+  fiveYearSystem,
+  threeYearSystem,
+  foreYearSystem,
+  nineYearSystem,
+  classArray
+} from './contants';
 import add from './images/add.png';
+import { get } from 'http';
 export default defineComponent({
   name: 'class-classList',
   setup(props, { emit }) {
     const state = reactive({
-      searchWord: '',
+      searchForm: {
+        keyword: null as any,
+        currentClass: null,
+        currentGradeNum: null
+      },
+
       orchestraType: null,
       courseTypeCode: null,
       loading: false,
@@ -26,47 +44,51 @@ export default defineComponent({
         rows: 10,
         pageTotal: 6
       },
-      tableList: [
-        {
-          className: '三年级1班',
-          studentNum: '36',
-          lastStudy: '人教版二年级上册 | 第一单元 |【歌表演】如愿'
-        },
-        {
-          className: '三年级2班',
-          studentNum: '43',
-          lastStudy: '人教版二年级上册 | 第一单元 |【歌表演】欢乐颂'
-        },
-        {
-          className: '三年级3班',
-          studentNum: '56',
-          lastStudy: '人教版二年级上册 | 第一单元 |【歌表演】我和我的祖国'
-        },
-        {
-          className: '三年级4班',
-          studentNum: '35',
-          lastStudy: '人教版二年级上册 | 第一单元 |【歌表演】孤勇者'
-        },
-        {
-          className: '三年级5班',
-          studentNum: '42',
-          lastStudy: '人教版二年级上册 | 第一单元 |【歌表演】大雁'
-        },
-        {
-          className: '三年级1班',
-          studentNum: '38',
-          lastStudy: '人教版二年级上册 | 第一单元 |【歌表演】暗香'
-        }
-      ] as any
+      gradeNumList: [] as any,
+      tableList: [] as any,
+      studentVisible: false,
+      activeRow: null as any
     });
+
     const search = () => {
       console.log('search', state);
     };
-
+    const userInfo = useUserStore();
+    if (userInfo.getUserInfo.schoolInfos[0].gradeYear == 'THREE_YEAR_SYSTEM') {
+      state.gradeNumList = threeYearSystem;
+    } else if (
+      userInfo.getUserInfo.schoolInfos[0].gradeYear == 'FORE_YEAR_SYSTEM'
+    ) {
+      state.gradeNumList = foreYearSystem;
+    } else if (
+      userInfo.getUserInfo.schoolInfos[0].gradeYear == 'FIVE_YEAR_SYSTEM'
+    ) {
+      state.gradeNumList = fiveYearSystem;
+    } else if (
+      userInfo.getUserInfo.schoolInfos[0].gradeYear == 'SIX_YEAR_SYSTEM'
+    ) {
+      state.gradeNumList = sixYearSystem;
+    } else {
+      state.gradeNumList = nineYearSystem;
+    }
     const onReset = () => {
       console.log('search');
     };
-    const getList = () => {
+    const getList = async () => {
+      // classGroupList
+      state.loading = true;
+      try {
+        const res = await classGroupList({
+          ...state.searchForm,
+          ...state.pagination
+        });
+        state.tableList = res.data.rows;
+        state.pagination.pageTotal = res.data.total;
+        state.loading = false;
+      } catch (e) {
+        state.loading = false;
+        console.log(e);
+      }
       console.log('getList');
     };
 
@@ -74,15 +96,18 @@ export default defineComponent({
       return [
         {
           title: '班级名称',
-          key: 'className'
+          key: 'name'
         },
         {
           title: '学生人数',
-          key: 'studentNum'
+          key: 'preStudentNum'
         },
         {
           title: '上次学习',
-          key: 'lastStudy'
+          key: 'lastStudy',
+          render(row: any) {
+            return <p>{row.lastStudy ? row.lastStudy : '--'}</p>;
+          }
         },
         {
           title: '操作',
@@ -94,10 +119,12 @@ export default defineComponent({
                   <NButton type="primary" text>
                     详情
                   </NButton>
-                  <NButton type="primary" text>
-                    修改
-                  </NButton>
-                  <NButton type="primary" text>
+                  <NButton
+                    type="primary"
+                    text
+                    onClick={() => {
+                      startResetStudent(row);
+                    }}>
                     学生调整
                   </NButton>
                   <NButton type="primary" text>
@@ -113,6 +140,14 @@ export default defineComponent({
         }
       ];
     };
+
+    const startResetStudent = (row: any) => {
+      state.activeRow = row;
+      state.studentVisible = true;
+    };
+    onMounted(() => {
+      getList();
+    });
     return () => (
       <div class={styles.listWrap}>
         <div class={styles.searchList}>
@@ -121,49 +156,31 @@ export default defineComponent({
               <SearchInput
                 {...{ placeholder: '请输入班级名称' }}
                 class={styles.searchInput}
-                searchWord={state.searchWord}
+                searchWord={state.searchForm.keyword}
                 onChangeValue={(val: string) =>
-                  (state.searchWord = val)
+                  (state.searchForm.keyword = val)
                 }></SearchInput>
             </NFormItem>
 
             <NFormItem>
               <CSelect
                 {...({
-                  options: [
-                    {
-                      label: '一年级',
-                      value: 'song0'
-                    },
-                    {
-                      label: '二年级',
-                      value: 'song1'
-                    }
-                  ],
+                  options: state.gradeNumList,
                   placeholder: '全部年级',
                   clearable: true,
                   inline: true
                 } as any)}
-                v-model:value={state.orchestraType}></CSelect>
+                v-model:value={state.searchForm.currentGradeNum}></CSelect>
             </NFormItem>
             <NFormItem>
               <CSelect
                 {...({
-                  options: [
-                    {
-                      label: '1班',
-                      value: 'song0'
-                    },
-                    {
-                      label: '2班',
-                      value: 'song1'
-                    }
-                  ],
+                  options: classArray,
                   placeholder: '全部班级',
                   clearable: true,
                   inline: true
                 } as any)}
-                v-model:value={state.courseTypeCode}></CSelect>
+                v-model:value={state.searchForm.currentClass}></CSelect>
             </NFormItem>
 
             <NFormItem>
@@ -209,6 +226,14 @@ export default defineComponent({
             saveKey="orchestraRegistration-key"
           />
         </div>
+        <NModal
+          v-model:show={state.studentVisible}
+          style={{ width: '707px' }}
+          preset="card"
+          class={['modalTitle background']}
+          title={'学员调整'}>
+          <ResetStudent activeRow={state.activeRow}></ResetStudent>
+        </NModal>
       </div>
     );
   }

+ 161 - 0
src/views/classList/modals/resetStudent.tsx

@@ -0,0 +1,161 @@
+import {
+  NButton,
+  NLegacyTransfer,
+  NSpace,
+  useMessage,
+  NPopselect,
+  NImage,
+  NDropdown
+} from 'naive-ui';
+import { defineComponent, onMounted, reactive, ref } from 'vue';
+import styles from '../index.module.less';
+import smallArrow from '../images/smallArrow.png';
+import { getCLassStudent, classGroupList } from '../api';
+export default defineComponent({
+  props: {
+    activeRow: {
+      type: Object,
+      default: () => ({ id: '' })
+    }
+  },
+  name: 'resetStudent',
+  emits: ['close'],
+  setup(props, { emit }) {
+    const message = useMessage();
+    const data = reactive({
+      uploading: false
+    });
+    const options = ref([] as any);
+    const currentStudnetList = ref([] as any);
+    const chioseOptions = ref([] as any);
+    const formRef = ref();
+    const handleSubmit = async () => {
+      data.uploading = true;
+    };
+    const classList = ref([] as any);
+    console.log(props.activeRow, 'activeRow');
+    const targetClass = reactive({
+      name: '',
+      id: ''
+    });
+    //
+
+    /**
+     * 这里干3件事  1.获取当前班的学生
+     * 2.查询所有的班级列表  并且排查当前班级
+     * 3.默认选择第一个班级 并且查出此班的学生
+     */
+    const chioseStudnet = (val: any) => {
+      console.log(val);
+    };
+    const getAllClassList = async () => {
+      try {
+        const res = await classGroupList({ page: 1, rows: 9999 });
+        classList.value = res.data.rows.map((item: any) => {
+          return {
+            label: item.name,
+            key: item.id,
+            disabled: item.id == props.activeRow.id
+          };
+        });
+        if (classList.value[0].disabled) {
+          targetClass.name = classList.value[1].label;
+          targetClass.id = classList.value[1].id;
+        } else {
+          targetClass.name = classList.value[0].label;
+          targetClass.id = classList.value[0].id;
+        }
+
+        console.log(classList.value, ' classList.value');
+      } catch (e) {
+        console.log(e);
+      }
+    };
+    const getCLassStudentList = async (id: string | number) => {
+      return await getCLassStudent({
+        page: 1,
+        rows: 999,
+        classGroupId: id
+      });
+    };
+    const chioseClass = async (val: any) => {
+      classList.value.forEach((item: any) => {
+        if (item.key == val) {
+          targetClass.name = item.label;
+          targetClass.id = item.key;
+        }
+      });
+      console.log(targetClass);
+      const res = await getCLassStudentList(val);
+      chioseOptions.value = res.data.rows.map((item: any) => {
+        return item.id;
+      });
+      console.log(chioseOptions.value, 'chioseOptions.value');
+    };
+    onMounted(async () => {
+      console.log('onMounted');
+      getAllClassList();
+      const res = await getCLassStudentList(props.activeRow.id as string);
+      currentStudnetList.value = res.data.rows.map((item: any) => {
+        return {
+          label: item.nickname + '(' + item.id + ')',
+          value: item.id
+        };
+      });
+    });
+    return () => (
+      <div class={[styles.container, styles.resetStudentWrap]}>
+        <div class={styles.studentTransfer}>
+          <NDropdown
+            key="111"
+            v-model:value={targetClass.id}
+            options={classList.value}
+            onSelect={(value: any) => {
+              chioseClass(value);
+            }}
+            scrollable>
+            <NImage
+              class={styles.smallArrow}
+              src={smallArrow}
+              previewDisabled></NImage>
+          </NDropdown>
+          <NLegacyTransfer
+            source-title={props.activeRow.name}
+            target-title={targetClass.name}
+            size="large"
+            ref={formRef}
+            options={currentStudnetList.value}
+            source-filter-placeholder="请输入学生姓名"
+            target-filter-placeholder="请输入学生姓名"
+            v-model:value={chioseOptions.value}
+            virtual-scroll
+            onUpdate:value={(val: any) => {
+              chioseStudnet(val);
+            }}
+            filterable></NLegacyTransfer>
+          <div class={styles.studentTransferBottom}>
+            <div class={[styles.bottomLeft, styles.bottom]}>
+              <div class={styles.bottomWrap}>共0名学生</div>
+            </div>
+            <div class={[styles.bottomRight, styles.bottom]}>
+              <div class={styles.bottomWrap}>共0名学生</div>
+            </div>
+          </div>
+        </div>
+
+        <NSpace class={styles.btnGroup} justify="center">
+          <NButton round onClick={() => emit('close')}>
+            取消
+          </NButton>
+          <NButton
+            round
+            loading={data.uploading}
+            type="primary"
+            onClick={() => handleSave()}>
+            保存
+          </NButton>
+        </NSpace>
+      </div>
+    );
+  }
+});

+ 2 - 3
src/views/login/api.ts

@@ -14,8 +14,7 @@ export const sendSms = (params: any) => {
  * 修改密码-
  */
 export const updatePassword = (params: any) => {
-  return request.post('/edu-app/user/updatePassword', {
-    data: params,
-    requestType: 'form'
+  return request.post('/edu-app/open/user/updatePassword', {
+    data: params
   });
 };

+ 10 - 8
src/views/login/components/forgotPassword.tsx

@@ -19,7 +19,7 @@ import { storage } from '@/utils/storage';
 import { useUserStore } from '/src/store/modules/users';
 import { sendSms, updatePassword } from '../api';
 interface FormState {
-  username: string;
+  mobile: string;
   password: string;
   grant_type: string;
   loginType: string;
@@ -42,7 +42,7 @@ export default defineComponent({
     const showPwd2 = ref(false);
     const userStore = useUserStore();
     const formInline = reactive({
-      username: '',
+      mobile: '',
       password: '',
       password1: '',
       code: '',
@@ -72,12 +72,14 @@ export default defineComponent({
           loading.value = true;
           try {
             await updatePassword({
-              ...formInline
+              ...formInline,
+              clientType: 'TEACHER'
             });
             message.success('修改成功');
             loading.value = false;
+
+            emit('changType');
             return false;
-            // emit('changType');
           } catch (e: any) {
             loading.value = false;
             message.error(e.msg);
@@ -90,14 +92,14 @@ export default defineComponent({
     };
 
     const sendMessage = async () => {
-      if (!formInline.username) {
+      if (!formInline.mobile) {
         message.error('请输入手机号');
         return;
       }
       try {
         const res = await sendSms({
           clientId: 'cooleshow-teacher',
-          mobile: formInline.username,
+          mobile: formInline.mobile,
           type: 'PASSWORD'
         });
         checkTimeOut();
@@ -132,13 +134,13 @@ export default defineComponent({
           size="large"
           model={formInline}>
           <NFormItem
-            path="username"
+            path="mobile"
             rule={[
               { required: true, message: '请输入手机号', trigger: 'blur' }
             ]}>
             <NInput
               maxlength={11}
-              v-model:value={formInline.username}
+              v-model:value={formInline.mobile}
               placeholder="请输入手机号">
               {{
                 prefix: () => (

+ 10 - 0
src/views/natural-resources/api.ts

@@ -18,6 +18,16 @@ export const favorite = (params: any) => {
   });
 };
 
+/**
+ * 资源 - 删除
+ */
+export const materialRemove = (params: any) => {
+  return request.post('/edu-app/material/remove', {
+    requestType: 'form',
+    data: params
+  });
+};
+
 /** 音乐教材-新增 */
 export const api_lessonCoursewareSave = (params: any): Promise<any> => {
   return request.post('/edu-app/lessonCourseware/save', {

+ 29 - 4
src/views/natural-resources/components/my-collect/index.tsx

@@ -3,14 +3,16 @@ import styles from './index.module.less';
 import CardType from '@/components/card-type';
 import Pagination from '@/components/pagination';
 import SearchGroupResources from './search-group-resources';
-import { favorite, materialQueryPage } from '../../api';
-import { NSpin } from 'naive-ui';
+import { favorite, materialQueryPage, materialRemove } from '../../api';
+import { NSpin, useDialog, useMessage } from 'naive-ui';
 import TheEmpty from '@/components/TheEmpty';
 import CardPreview from '@/components/card-preview';
 
 export default defineComponent({
   name: 'share-resources',
   setup() {
+    const message = useMessage();
+    const dialog = useDialog();
     const state = reactive({
       searchWord: '',
       loading: false,
@@ -66,6 +68,25 @@ export default defineComponent({
       }
     };
 
+    // 单个删除
+    const onRemove = async (item: any) => {
+      try {
+        dialog.warning({
+          title: '提示',
+          content: '该资源已下架,是否删除?',
+          positiveText: '确定',
+          negativeText: '取消',
+          onPositiveClick: async () => {
+            await materialRemove({ id: item.id });
+            message.success('删除成功');
+            onSearch(state.searchGroup);
+          }
+        });
+      } catch {
+        //
+      }
+    };
+
     onMounted(() => {
       getList();
     });
@@ -73,7 +94,7 @@ export default defineComponent({
       <>
         <SearchGroupResources onSearch={(item: any) => onSearch(item)} />
 
-        <NSpin v-model:show={state.loading}>
+        <NSpin v-model:show={state.loading} style={{ 'min-height': '50vh' }}>
           <div class={styles.list}>
             {state.tableList.map((item: any) => {
               const tmpItem = {
@@ -82,11 +103,15 @@ export default defineComponent({
                 type: item.type,
                 title: item.name,
                 isCollect: !!item.favoriteFlag,
-                isSelected: item.sourceFrom === 'PLATFORM' ? true : false
+                isSelected: item.sourceFrom === 'PLATFORM' ? true : false,
+                enableFlag: item.enableFlag ? 1 : 0,
+                openFlag: item.openFlag
               };
               return (
                 <CardType
                   item={tmpItem}
+                  offShelf={item.enableFlag ? false : true}
+                  onOffShelf={() => onRemove(item)}
                   disabledMouseHover={false}
                   onClick={(val: any) => {
                     if (val.type === 'IMG') return;

+ 0 - 1
src/views/natural-resources/components/my-resources/index.module.less

@@ -4,7 +4,6 @@
   flex-flow: row wrap;
   justify-content: flex-start;
   gap: 22px;
-  min-height: 220px;
 }
 
 .searchGroup {

+ 28 - 2
src/views/natural-resources/components/my-resources/index.tsx

@@ -3,7 +3,12 @@ import styles from './index.module.less';
 import CardType from '/src/components/card-type';
 import Pagination from '/src/components/pagination';
 import SearchGroupResources from './search-group-resources';
-import { favorite, materialQueryPage, materialRemoveAll } from '../../api';
+import {
+  favorite,
+  materialQueryPage,
+  materialRemove,
+  materialRemoveAll
+} from '../../api';
 import { NModal, NSpin, useDialog, useMessage } from 'naive-ui';
 import TheEmpty from '/src/components/TheEmpty';
 import UploadModal from './upload-modal';
@@ -114,6 +119,25 @@ export default defineComponent({
       }
     };
 
+    // 单个删除
+    const onRemove = async (item: any) => {
+      try {
+        dialog.warning({
+          title: '提示',
+          content: '该资源已下架,是否删除?',
+          positiveText: '确定',
+          negativeText: '取消',
+          onPositiveClick: async () => {
+            await materialRemove({ id: item.id });
+            message.success('删除成功');
+            onSearch(state.searchGroup);
+          }
+        });
+      } catch {
+        //
+      }
+    };
+
     onMounted(() => {
       getList();
     });
@@ -162,13 +186,15 @@ export default defineComponent({
           onDelete={onDelete}
         />
 
-        <NSpin v-model:show={state.loading}>
+        <NSpin v-model:show={state.loading} style={{ 'min-height': '50vh' }}>
           <div class={styles.list}>
             {state.tableList.map((item: any) => (
               <div class={styles.itemSection}>
                 <CardType
                   item={item}
                   disabledMouseHover={false}
+                  offShelf={item.enableFlag ? false : true}
+                  onOffShelf={() => onRemove(item)}
                   onClick={(val: any) => {
                     if (val.type === 'IMG') return;
                     state.show = true;

+ 1 - 25
src/views/natural-resources/components/my-resources/upload-modal/index.tsx

@@ -126,29 +126,6 @@ export default defineComponent({
       uploadForms.list.splice(index, 1);
     };
 
-    // const getImgBase64 = (url: any) => {
-    //   return new Promise(function (resolve) {
-    //     let dataURL = '';
-    //     const video = document.createElement('video');
-    //     video.currentTime = 1; //指定帧数
-    //     video.setAttribute('crossOrigin', 'anonymous'); //处理跨域
-    //     video.setAttribute('src', url);
-    //     video.setAttribute('width', '100');
-    //     video.setAttribute('height', '100');
-
-    //     video.addEventListener('loadeddata', function () {
-    //       const canvas: any = document.createElement('canvas'),
-    //         width = video.width, //canvas的尺寸和图片一样
-    //         height = video.height;
-    //       canvas.width = width;
-    //       canvas.height = height;
-    //       canvas.getContext('2d').drawImage(video, 0, 0, width, height); //绘制canvas
-    //       dataURL = canvas.toDataURL('image/jpeg'); //转换为base64
-    //       resolve(dataURL);
-    //     });
-    //   });
-    // };
-
     const isUpdate = computed(() => (props.list.length > 0 ? true : false));
 
     onMounted(async () => {
@@ -189,7 +166,7 @@ export default defineComponent({
                   <div class={styles.previewModal}>
                     <NImage
                       class={[styles.titleType]}
-                      src={formatType('MUSIC')}
+                      src={formatType(item.type)}
                       previewDisabled
                       objectFit="cover"
                     />
@@ -271,7 +248,6 @@ export default defineComponent({
                       fixedBox: true
                     }}
                     onFinished={(val: any) => {
-                      console.log(val, 'val');
                       uploadForms.list.push({
                         subjectIds: uploadForms.subjectIds || [],
                         openFlag: true,

+ 25 - 23
src/views/natural-resources/components/my-resources/upload-modal/upload-file.tsx

@@ -1,4 +1,4 @@
-import { NModal, NUpload, UploadFileInfo, useMessage } from 'naive-ui';
+import { NModal, NSpin, NUpload, UploadFileInfo, useMessage } from 'naive-ui';
 import { defineComponent, watch, PropType, reactive, ref } from 'vue';
 import { policy } from '@/components/upload-file/api';
 import Copper from '@/components/upload-file/copper';
@@ -386,28 +386,30 @@ export default defineComponent({
 
     return () => (
       <div class={styles.uploadFile}>
-        <NUpload
-          ref={uploadRef}
-          action={ossUploadUrl}
-          data={state}
-          v-model:fileList={fileListRef.value}
-          accept={props.accept}
-          multiple={props.multiple}
-          max={props.max}
-          disabled={props.disabled}
-          showFileList={props.showFileList}
-          showPreviewButton
-          onBeforeUpload={(options: any) => onBeforeUpload(options)}
-          onFinish={(options: any) => onFinish(options)}
-          onRemove={() => onRemove()}>
-          {props.showType === 'default' && (
-            <div class={styles.uploadBtn}>
-              <img src={iconUploadAdd} class={styles.iconUploadAdd} />
-              <p>上传</p>
-            </div>
-          )}
-          {props.showType === 'custom' && slots.custom && slots.custom()}
-        </NUpload>
+        <NSpin show={btnLoading.value} description="上传中...">
+          <NUpload
+            ref={uploadRef}
+            action={ossUploadUrl}
+            data={state}
+            v-model:fileList={fileListRef.value}
+            accept={props.accept}
+            multiple={props.multiple}
+            max={props.max}
+            disabled={props.disabled}
+            showFileList={props.showFileList}
+            showPreviewButton
+            onBeforeUpload={(options: any) => onBeforeUpload(options)}
+            onFinish={(options: any) => onFinish(options)}
+            onRemove={() => onRemove()}>
+            {props.showType === 'default' && (
+              <div class={styles.uploadBtn}>
+                <img src={iconUploadAdd} class={styles.iconUploadAdd} />
+                <p>上传</p>
+              </div>
+            )}
+            {props.showType === 'custom' && slots.custom && slots.custom()}
+          </NUpload>
+        </NSpin>
 
         <NModal
           v-model:show={visiable.value}

+ 1 - 1
src/views/natural-resources/components/share-resources/index.tsx

@@ -90,7 +90,7 @@ export default defineComponent({
           onAdd={() => (state.teachingStatus = true)}
         />
 
-        <NSpin v-model:show={state.loading}>
+        <NSpin v-model:show={state.loading} style={{ 'min-height': '50vh' }}>
           <div class={styles.list}>
             {state.tableList.map((item: any) => (
               <CardType

+ 2 - 0
src/views/natural-resources/index.module.less

@@ -2,6 +2,7 @@
   padding: 32px 20px;
   background-color: #fff;
   border-radius: 20px;
+  // min-height: calc(100% - 104px);
 
   :global {
     .n-tabs-tab-pad {
@@ -115,6 +116,7 @@
     width: 360px;
     height: 42px;
     font-size: 16px;
+
     :global {
       .n-input-wrapper {
         padding-left: 12px;

+ 81 - 0
src/views/prepare-lessons/api.ts

@@ -33,3 +33,84 @@ export const queryCourseware = (params: any) => {
     data: params
   });
 };
+
+/**
+ * 备课 - 保存课件
+ */
+export const saveCourseware = (params: any) => {
+  return request.post('/edu-app/teacherKnowledgeMaterial/saveCourseware', {
+    data: params
+  });
+};
+
+/**
+ * 备课 - 训练列表
+ */
+export const lessonPreTrainingPage = (params: any) => {
+  return request.post('/edu-app/lessonPreTraining/page', {
+    data: params
+  });
+};
+
+/**
+ * 备课 - 新增训练
+ */
+export const lessonPreTrainingAdd = (params: any) => {
+  return request.post('/edu-app/lessonPreTraining/add', {
+    data: params
+  });
+};
+
+/**
+ * 备课 - 修改训练
+ */
+export const lessonPreTrainingUpdate = (params: any) => {
+  return request.post('/edu-app/lessonPreTraining/update', {
+    data: params
+  });
+};
+
+/**
+ * 备课 - 删除训练
+ */
+export const lessonPreTrainingDelete = (params: any) => {
+  return request.post('/edu-app/lessonPreTraining/delete', {
+    data: params
+  });
+};
+
+/**
+ * 备课 - 保存预训练曲目
+ */
+export const lessonPreTrainingBatchSave = (params: any) => {
+  return request.post('/edu-app/lessonPreTraining/batchSave', {
+    data: params
+  });
+};
+
+/**
+ * 备课 - 乐谱列表
+ */
+export const musicSheetPage = (params: any) => {
+  return request.post('/edu-app/musicSheet/page', {
+    data: params
+  });
+};
+
+/**
+ * 备课 - 班级列表
+ */
+export const classGroupPage = (params: any) => {
+  return request.post('/edu-app/classGroup/page', {
+    data: params
+  });
+};
+
+/**
+ * 备课 - 布置作业
+ */
+export const lessonTrainingAdd = (params: any) => {
+  return request.post('/edu-app/lessonTraining/add', {
+    data: params
+  });
+};

+ 65 - 55
src/views/prepare-lessons/components/directory-main/index.tsx

@@ -106,64 +106,74 @@ export default defineComponent({
 
         <NScrollbar class={styles.scrollBar}>
           <NSpin show={show.value}>
-            {prepareStore.getTreeList.map((item: any, index: number) => (
-              <div class={styles.treeParent} key={'parent' + index}>
-                <div
-                  class={[styles.treeItem, styles.parentItem]}
-                  onClick={() => {
-                    prepareStore.getTreeList.forEach((child: any) => {
-                      if (item.id !== child.id) {
-                        child.selected = false;
-                      }
-                    });
-                    item.selected = item.selected ? false : true;
-                  }}>
-                  {item.knowledgeList && item.knowledgeList.length > 0 && (
-                    <span
+            <div
+              class={[
+                styles.listSection,
+                !show.value && prepareStore.getTreeList.length <= 0
+                  ? styles.emptySection
+                  : ''
+              ]}>
+              {prepareStore.getTreeList.map((item: any, index: number) => (
+                <div class={styles.treeParent} key={'parent' + index}>
+                  <div
+                    class={[styles.treeItem, styles.parentItem]}
+                    onClick={() => {
+                      prepareStore.getTreeList.forEach((child: any) => {
+                        if (item.id !== child.id) {
+                          child.selected = false;
+                        }
+                      });
+                      item.selected = item.selected ? false : true;
+                    }}>
+                    {item.knowledgeList && item.knowledgeList.length > 0 && (
+                      <span
+                        class={[
+                          styles.arrow,
+                          item.selected ? styles.arrowSelect : ''
+                        ]}></span>
+                    )}
+                    <p
                       class={[
-                        styles.arrow,
-                        item.selected ? styles.arrowSelect : ''
-                      ]}></span>
-                  )}
-                  <p
-                    class={[
-                      styles.title,
-                      item.selected ? styles.titleSelect : ''
-                    ]}>
-                    {item.name}
-                  </p>
-                </div>
+                        styles.title,
+                        item.selected ? styles.titleSelect : ''
+                      ]}>
+                      {item.name}
+                    </p>
+                  </div>
 
-                {item.selected &&
-                  item.knowledgeList &&
-                  item.knowledgeList.map((child: any, j: number) => (
-                    <div
-                      key={'child' + j}
-                      class={[
-                        styles.treeItem,
-                        styles.childItem,
-                        styles.animation,
-                        prepareStore.getSelectKey === child.id
-                          ? styles.childSelect
-                          : ''
-                      ]}
-                      onClick={() => {
-                        prepareStore.setSelectKey(child.id);
-                        prepareStore.setLessonCoursewareId(
-                          child.lessonCoursewareId
-                        );
-                        prepareStore.setLessonCoursewareDetailId(
-                          child.lessonCoursewareDetailId
-                        );
-                      }}>
-                      <span class={styles.childArrow}></span>
-                      <p class={styles.title}>{child.name}</p>
-                    </div>
-                  ))}
-              </div>
-            ))}
+                  {item.selected &&
+                    item.knowledgeList &&
+                    item.knowledgeList.map((child: any, j: number) => (
+                      <div
+                        key={'child' + j}
+                        class={[
+                          styles.treeItem,
+                          styles.childItem,
+                          styles.animation,
+                          prepareStore.getSelectKey === child.id
+                            ? styles.childSelect
+                            : ''
+                        ]}
+                        onClick={() => {
+                          prepareStore.setSelectKey(child.id);
+                          prepareStore.setLessonCoursewareId(
+                            child.lessonCoursewareId
+                          );
+                          prepareStore.setLessonCoursewareDetailId(
+                            child.lessonCoursewareDetailId
+                          );
+                        }}>
+                        <span class={styles.childArrow}></span>
+                        <p class={styles.title}>{child.name}</p>
+                      </div>
+                    ))}
+                </div>
+              ))}
+            </div>
+            {!show.value && prepareStore.getTreeList.length <= 0 && (
+              <TheEmpty />
+            )}
           </NSpin>
-          {!show.value && prepareStore.getTreeList.length <= 0 && <TheEmpty />}
         </NScrollbar>
 
         {/* 选择教材 */}

+ 2 - 1
src/views/prepare-lessons/components/directory-main/select-lessonware/index.module.less

@@ -25,10 +25,11 @@
 }
 
 .classList {
-  min-height: 50vh;
+  min-height: 60vh;
   max-height: 60vh;
 
   .content {
+    min-height: 60vh;
     padding: 32px 40px;
   }
 }

+ 59 - 23
src/views/prepare-lessons/components/directory-main/select-lessonware/index.tsx

@@ -8,6 +8,7 @@ import {
   NScrollbar,
   NSelect,
   NSpace,
+  NSpin,
   useDialog,
   useMessage
 } from 'naive-ui';
@@ -16,35 +17,46 @@ import AddTeaching, {
 } from '/src/views/natural-resources/model/add-teaching';
 import { lessonCoursewarePage, lessonCoursewareRemove } from '../../../api';
 import iconUploadBg from '../images/icon-upload-bg.svg';
+import { useCatchStore } from '/src/store/modules/catchData';
+import { useThrottleFn } from '@vueuse/core';
 
 export default defineComponent({
   name: 'select-lessonware',
   emits: ['close', 'confirm'],
   setup(props, { emit }) {
+    const catchStore = useCatchStore();
     const dialog = useDialog();
     const message = useMessage();
     const forms = reactive({
-      currentGradeNum: null,
-      bookType: null,
+      loading: false,
       list: [] as any[],
       teachingStatus: false,
-      selectItem: {} as any
+      selectItem: {} as any,
+      bookVersionId: null,
+      keyword: null,
+      currentGradeNum: null,
+      bookType: null
     });
 
     const getLessonCourseware = async () => {
+      forms.loading = true;
       try {
         const { data } = await lessonCoursewarePage({
           page: 1,
           rows: 99,
           type: 'COURSEWARE',
-          enableFlag: 1
+          enableFlag: 1,
+          bookVersionId: forms.bookVersionId,
+          keyword: forms.keyword,
+          currentGradeNum: forms.currentGradeNum,
+          bookType: forms.bookType
         });
 
-        console.log(data.rows, 'data');
         forms.list = data.rows;
       } catch {
         //
       }
+      forms.loading = false;
     };
 
     // 删除教材
@@ -65,52 +77,76 @@ export default defineComponent({
       });
     };
 
+    const throttledFn = useThrottleFn(() => getLessonCourseware(), 500);
+
     const onDetail = (item: any) => {
       emit('confirm', item);
       emit('close');
     };
 
-    onMounted(() => {
+    onMounted(async () => {
+      // 获取教材分类列表
+      await catchStore.getMusicSheetCategory();
       getLessonCourseware();
     });
     return () => (
       <div class={styles.selectLessonware}>
         <div class={styles.attendClassSearch}>
-          <NInput placeholder="请输入班级名称" clearable>
+          <NInput
+            placeholder="请输入班级名称"
+            clearable
+            v-model:value={forms.keyword}
+            onKeyup={(e: KeyboardEvent) => {
+              if (e.code === 'Enter') {
+                throttledFn();
+              }
+            }}
+            onClear={() => throttledFn()}>
             {{
-              prefix: () => <span class="icon-search-input"></span>
+              prefix: () => (
+                <span
+                  class="icon-search-input"
+                  onClick={() => throttledFn()}></span>
+              )
             }}
           </NInput>
           <NSelect
-            placeholder="版本"
+            placeholder="全部版本"
             clearable
             options={[
-              {
-                label: '一年级',
-                value: '1'
-              },
-              {
-                label: '二年级',
-                value: '2'
-              }
+              { id: null, name: '全部版本' },
+              ...catchStore.getMusicCategories
             ]}
+            labelField="name"
+            valueField="id"
+            v-model:value={forms.bookVersionId}
+            onUpdate:value={() => throttledFn()}
           />
           <NSelect
-            placeholder="请选择年级"
-            options={BOOK_DATA.grades}
+            placeholder="全部年级"
+            options={
+              [{ label: '全部年级', value: null }, ...BOOK_DATA.grades] as any
+            }
             v-model:value={forms.currentGradeNum}
             clearable
             filterable
+            onUpdate:value={() => throttledFn()}
           />
           <NSelect
-            placeholder="请选择册别"
-            options={BOOK_DATA.bookTypes}
+            placeholder="全部册别"
+            options={
+              [
+                { label: '全部册别', value: null },
+                ...BOOK_DATA.bookTypes
+              ] as any
+            }
             v-model:value={forms.bookType}
             clearable
+            onUpdate:value={() => throttledFn()}
           />
         </div>
         <NScrollbar class={styles.classList}>
-          <div class={styles.content}>
+          <NSpin show={forms.loading} class={styles.content}>
             <NSpace size={[50, 40]}>
               <div
                 class={styles.item}
@@ -189,7 +225,7 @@ export default defineComponent({
                 );
               })}
             </NSpace>
-          </div>
+          </NSpin>
         </NScrollbar>
 
         {/* 添加自定义教材 */}

+ 60 - 0
src/views/prepare-lessons/components/lesson-main/courseware/index.module.less

@@ -5,7 +5,20 @@
   padding-left: 22px !important;
   padding-right: 22px !important;
 
+  .tips {
+    color: #0378EC;
+    font-size: 16px;
+    line-height: 38px;
+  }
+
   :global {
+    .n-base-selection {
+      --n-height: 38px !important;
+      width: 160px;
+      font-size: 15px;
+      border-radius: 8px !important;
+    }
+
     .n-button {
       border-radius: 8px;
       height: 38px;
@@ -26,6 +39,21 @@
         border: 1px solid #198CFE;
       }
     }
+
+    .n-button--error-type {
+      background: #FDEBED !important;
+      color: #EC3A4E !important;
+
+      &:not(.n-button--disabled):hover,
+      &:not(.n-button--disabled):active {
+        background: #FDEBED;
+        color: #EC3A4E;
+      }
+
+      .n-button__border {
+        border: 1px solid #EC3A4E;
+      }
+    }
   }
 }
 
@@ -33,6 +61,15 @@
   margin-top: 12px;
   // // 52 + 28 + 38
   max-height: calc(var(--window-page-lesson-height) - 148px);
+
+  .listSection {
+    min-height: calc(var(--window-page-lesson-height) - 148px);
+  }
+
+  .emptySection {
+    display: flex;
+    align-items: center;
+  }
 }
 
 .list {
@@ -47,4 +84,27 @@
   width: 800px;
   border-radius: 16px;
   overflow: hidden;
+}
+
+.itemBlock {
+  position: relative;
+
+  .itemOperation {
+    position: absolute;
+    top: 0;
+    left: 0;
+    width: 100%;
+    height: 100%;
+    text-align: right;
+    z-index: 98;
+    cursor: move;
+  }
+
+  .iconDelete {
+    width: 27px;
+    height: 27px;
+    margin-top: 8px;
+    margin-right: 8px;
+    cursor: pointer;
+  }
 }

+ 220 - 18
src/views/prepare-lessons/components/lesson-main/courseware/index.tsx

@@ -1,21 +1,41 @@
 import { defineComponent, onMounted, reactive, watch } from 'vue';
 import styles from './index.module.less';
-import { NButton, NModal, NScrollbar, NSpace, NSpin } from 'naive-ui';
+import {
+  NButton,
+  NModal,
+  NScrollbar,
+  NSelect,
+  NSpace,
+  NSpin,
+  useMessage,
+  useDialog
+} from 'naive-ui';
 import CardType from '/src/components/card-type';
 import AttendClass from '/src/views/prepare-lessons/model/attend-class';
 import { usePrepareStore } from '/src/store/modules/prepareLessons';
+import { useCatchStore } from '/src/store/modules/catchData';
 import TheEmpty from '/src/components/TheEmpty';
-import { queryCourseware } from '../../../api';
+import { queryCourseware, saveCourseware } from '../../../api';
+import Draggable from 'vuedraggable';
+import iconDelete from '../../../images/icon-delete.png';
+import { useRouter } from 'vue-router';
 
 export default defineComponent({
   name: 'courseware-modal',
   setup() {
+    const catchStore = useCatchStore();
     const prepareStore = usePrepareStore();
+    const router = useRouter();
+    const dialog = useDialog();
+    const message = useMessage();
     const forms = reactive({
+      coursewareList: [] as any,
       loadingStatus: false,
-      showAttendClass: false
+      showAttendClass: false,
+      drag: false
     });
 
+    // 获取列表
     const getList = async () => {
       forms.loadingStatus = true;
       try {
@@ -23,8 +43,7 @@ export default defineComponent({
         if (!prepareStore.getSelectKey) return;
         const { data } = await queryCourseware({
           coursewareDetailKnowledgeId: prepareStore.getSelectKey,
-          lessonCoursewareId: prepareStore.getLessonCoursewareId,
-          lessonCoursewareDetailId: prepareStore.getLessonCoursewareDetailId,
+          subjectId: prepareStore.getSubjectId,
           pag: 1,
           rows: 99
         });
@@ -33,6 +52,7 @@ export default defineComponent({
         tempRows.forEach((row: any) => {
           temp.push({
             id: row.id,
+            materialId: row.materialId,
             coverImg: row.coverImg,
             type: row.materialType,
             title: row.materialName,
@@ -42,6 +62,7 @@ export default defineComponent({
           });
         });
 
+        forms.coursewareList = temp || [];
         prepareStore.setCoursewareList(temp || []);
       } catch {
         //
@@ -49,26 +70,149 @@ export default defineComponent({
       forms.loadingStatus = false;
     };
 
-    // 监听选择的key
+    // 监听选择的key 左侧选择了其它的课
     watch(
       () => prepareStore.getSelectKey,
       () => {
         getList();
       }
     );
+    watch(
+      () => prepareStore.getIsAddResource,
+      (val: boolean) => {
+        if (val) {
+          getList();
+          prepareStore.setIsAddResource(false);
+        }
+      }
+    );
+
+    // 删除
+    const onDelete = (item: any) => {
+      //
+      const index = forms.coursewareList.findIndex(
+        (c: any) => c.id === item.id
+      );
+      forms.coursewareList.splice(index, 1);
+      prepareStore.setCoursewareList(forms.coursewareList);
+    };
 
-    onMounted(() => {
-      getList();
+    // 完成编辑
+    const onOverEdit = async () => {
+      dialog.warning({
+        title: '提示',
+        content: `是否完成编辑?`,
+        positiveText: '确定',
+        negativeText: '取消',
+        onPositiveClick: async () => {
+          try {
+            if (forms.coursewareList.length <= 0) {
+              forms.drag = false;
+              message.success('编辑成功');
+              return;
+            }
+            const temp: any = [];
+            forms.coursewareList.forEach((item: any) => {
+              temp.push({
+                materialName: item.title,
+                materialType: item.type,
+                materialId: item.id
+              });
+            });
+            // 保存课件
+            await saveCourseware({
+              coursewareDetailKnowledgeId: prepareStore.getSelectKey,
+              lessonCoursewareId: prepareStore.getLessonCoursewareId,
+              lessonCoursewareDetailId:
+                prepareStore.getLessonCoursewareDetailId,
+              materialList: [...temp]
+            });
+
+            forms.drag = false;
+            message.success('编辑成功');
+
+            prepareStore.setCoursewareList(forms.coursewareList);
+          } catch {
+            //
+          }
+        }
+      });
+    };
+
+    // 预览上课
+    const onPreviewAttend = () => {
+      const { href } = router.resolve({
+        path: '/attend-class',
+        query: {
+          type: 'preview',
+          subjectId: prepareStore.getSubjectId,
+          detailId: prepareStore.getSelectKey
+        }
+      });
+      window.open(href, +new Date() + '');
+    };
+
+    onMounted(async () => {
+      // 获取教材分类列表
+      await catchStore.getSubjects();
+
+      const subjectList = catchStore.getSubjectList;
+      if (subjectList.length > 0) {
+        prepareStore.setSubjectId(subjectList[0].id);
+      }
+
+      await getList();
     });
+
     return () => (
       <div class={styles.coursewareModal}>
         <div class={styles.btnGroup}>
-          <NSpace>
-            <NButton type="default">编辑</NButton>
-          </NSpace>
+          {forms.drag ? (
+            <NSpace>
+              <NButton type="default" onClick={onOverEdit}>
+                完成编辑
+              </NButton>
+              <NButton
+                type="error"
+                onClick={() => {
+                  forms.drag = false;
+                  getList();
+                }}>
+                退出编辑
+              </NButton>
+              <NButton
+                type="error"
+                onClick={() => {
+                  forms.coursewareList = [];
+                  prepareStore.setCoursewareList([]);
+                }}>
+                清空资源
+              </NButton>
+              <span class={styles.tips}>拖动可将资源进行排序</span>
+            </NSpace>
+          ) : (
+            <NSpace>
+              <NSelect
+                placeholder="选择声部"
+                options={catchStore.getSubjectList}
+                labelField="name"
+                valueField="id"
+                value={prepareStore.getSubjectId}
+                onUpdate:value={(val: any) => {
+                  prepareStore.setSubjectId(val);
+                  getList();
+                }}
+              />
+              <NButton type="default" onClick={() => (forms.drag = true)}>
+                编辑
+              </NButton>
+            </NSpace>
+          )}
 
           <NSpace>
-            <NButton type="default">预览</NButton>
+            <NButton type="default" onClick={onPreviewAttend}>
+              预览
+            </NButton>
             <NButton
               type="primary"
               onClick={() => (forms.showAttendClass = true)}>
@@ -79,13 +223,71 @@ export default defineComponent({
 
         <NScrollbar class={styles.listContainer}>
           <NSpin show={forms.loadingStatus}>
-            <div class={styles.list}>
-              {prepareStore.getCoursewareList.map((item: any) => (
-                <CardType isShowCollect={false} item={item} />
-              ))}
+            <div
+              class={[
+                styles.listSection,
+                !forms.loadingStatus &&
+                prepareStore.getCoursewareList.length <= 0
+                  ? styles.emptySection
+                  : ''
+              ]}>
+              {forms.coursewareList.length > 0 && (
+                <>
+                  {forms.drag ? (
+                    <Draggable
+                      v-model:modelValue={forms.coursewareList}
+                      itemKey="id"
+                      componentData={{
+                        animation: 200,
+                        group: 'description'
+                      }}
+                      class={styles.list}>
+                      {{
+                        item: (element: any) => {
+                          const item = element.element;
+                          return (
+                            <div
+                              data-id={item.id}
+                              class={[styles.itemBlock, 'row-nav']}>
+                              <CardType
+                                class={[styles.itemContent, 'handle']}
+                                isShowCollect={false}
+                                item={item}
+                              />
+                              <div class={styles.itemOperation}>
+                                <img
+                                  src={iconDelete}
+                                  class={styles.iconDelete}
+                                  onClick={(e: MouseEvent) => {
+                                    e.stopPropagation();
+                                    onDelete(item);
+                                  }}
+                                />
+                              </div>
+                            </div>
+                          );
+                        }
+                      }}
+                    </Draggable>
+                  ) : (
+                    <div class={styles.list}>
+                      {forms.coursewareList.map((item: any) => (
+                        <CardType
+                          class={[styles.itemContent, 'handle']}
+                          isShowCollect={false}
+                          item={item}
+                        />
+                      ))}
+                    </div>
+                  )}
+                </>
+              )}
+
+              {!forms.loadingStatus &&
+                prepareStore.getCoursewareList.length <= 0 && (
+                  <TheEmpty description="暂无课件" />
+                )}
             </div>
-            {!forms.loadingStatus &&
-              prepareStore.getCoursewareList.length <= 0 && <TheEmpty />}
           </NSpin>
         </NScrollbar>
 

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

@@ -3,10 +3,12 @@ import styles from './index.module.less';
 import { NTabPane, NTabs } from 'naive-ui';
 import Courseware from './courseware';
 import Train from './train';
+import { usePrepareStore } from '/src/store/modules/prepareLessons';
 
 export default defineComponent({
   name: 'lesson-main',
   setup() {
+    const prepareStore = usePrepareStore();
     return () => (
       <div class={styles['lesson-main']}>
         <NTabs
@@ -14,7 +16,10 @@ export default defineComponent({
           defaultValue="courseware"
           paneClass={styles.paneTitle}
           justifyContent="center"
-          paneWrapperClass={styles.paneWrapperContainer}>
+          paneWrapperClass={styles.paneWrapperContainer}
+          onUpdate:value={(val: string) => {
+            prepareStore.setTabType(val);
+          }}>
           <NTabPane name="courseware" tab="课件">
             <Courseware />
           </NTabPane>

+ 168 - 0
src/views/prepare-lessons/components/lesson-main/train/assign-homework.tsx

@@ -0,0 +1,168 @@
+import { defineComponent, reactive, ref } from 'vue';
+import styles from './index.module.less';
+import {
+  NButton,
+  NDatePicker,
+  NForm,
+  NFormItem,
+  NSelect,
+  NSpace,
+  useMessage
+} from 'naive-ui';
+import { BOOK_DATA } from '/src/views/natural-resources/model/add-teaching';
+import { classGroupPage, lessonTrainingAdd } from '../../../api';
+import dayjs from 'dayjs';
+
+export default defineComponent({
+  name: 'assign-homework',
+  props: {
+    /** 初始数据 */
+    trainList: {
+      type: Array,
+      default: () => []
+    }
+  },
+  emits: ['close', 'confirm'],
+  setup(props, { emit }) {
+    const message = useMessage();
+
+    const forms = reactive({
+      currentTime: dayjs(dayjs().format('YYYY-MM-DD')).valueOf(),
+      id: null as any,
+      uploading: false,
+      classList: [] as any,
+      currentGradeNum: null,
+      expireDate: dayjs().add(7, 'day').format('YYYY-MM-DD') as any, // 默认7天
+      classGroupId: null as any
+    });
+    const formsRef = ref();
+
+    const getClassList = async () => {
+      try {
+        const { data } = await classGroupPage({
+          currentGradeNum: forms.currentGradeNum,
+          page: 1,
+          rows: 99
+        });
+        console.log(data);
+        const temp = data.rows || [];
+        temp.forEach((row: any) => {
+          forms.classList.push({
+            label: row.currentClass + '班',
+            value: row.id
+          });
+        });
+      } catch {
+        //
+      }
+    };
+    const onSubmit = async () => {
+      formsRef.value?.validate(async (err: any) => {
+        if (err) {
+          return;
+        }
+        forms.uploading = true;
+        try {
+          const trainList = props.trainList || [];
+          const details: any[] = [];
+          trainList.forEach((item: any) => {
+            details.push({
+              trainingType: item.trainingType,
+              musicId: item.musicId,
+              trainingConfigJsonObject: item.trainingConfigJson
+            });
+          });
+          const params = {
+            lessonTrainingDetails: details,
+            expireDate: forms.expireDate + ' 23:59:59',
+            classGroupId: forms.classGroupId
+          };
+          await lessonTrainingAdd(params);
+          message.success('布置成功');
+          emit('close');
+        } catch {
+          //
+        }
+        forms.uploading = false;
+      });
+    };
+
+    return () => (
+      <div class={styles.assignHomeworkContainer}>
+        <NForm
+          ref={formsRef}
+          model={forms}
+          labelAlign="right"
+          labelWidth={'auto'}
+          labelPlacement="left">
+          <NFormItem
+            label="年级"
+            path="currentGradeNum"
+            rule={[
+              {
+                required: true,
+                message: '请选择年级',
+                trigger: 'change',
+                type: 'number'
+              }
+            ]}>
+            <NSelect
+              v-model:value={forms.currentGradeNum}
+              placeholder="请选择年级"
+              options={BOOK_DATA.grades}
+              clearable
+              onUpdate:value={() => {
+                getClassList();
+              }}
+            />
+          </NFormItem>
+          <NFormItem
+            label="班级"
+            path="classGroupId"
+            rule={[
+              { required: true, message: '请选择班级', trigger: 'change' }
+            ]}>
+            <NSelect
+              v-model:value={forms.classGroupId}
+              placeholder="请选择班级"
+              clearable
+              options={forms.classList}
+            />
+          </NFormItem>
+          <NFormItem
+            label="截止日期"
+            path="expireDate"
+            rule={[
+              { required: true, message: '请选择截止日期', trigger: 'change' }
+            ]}>
+            <NDatePicker
+              v-model:formatted-value={forms.expireDate}
+              type="date"
+              clearable
+              valueFormat="yyyy-MM-dd"
+              style={{ width: '100%' }}
+              isDateDisabled={(ts: number) => {
+                return ts < forms.currentTime;
+              }}
+            />
+          </NFormItem>
+
+          <NSpace class={styles.updateBtnGroup}>
+            <NButton strong type="default" round onClick={() => emit('close')}>
+              取消
+            </NButton>
+            <NButton
+              strong
+              type="primary"
+              round
+              disabled={forms.uploading}
+              loading={forms.uploading}
+              onClick={onSubmit}>
+              确认
+            </NButton>
+          </NSpace>
+        </NForm>
+      </div>
+    );
+  }
+});

+ 62 - 0
src/views/prepare-lessons/components/lesson-main/train/index.module.less

@@ -5,7 +5,20 @@
   padding-left: 22px !important;
   padding-right: 22px !important;
 
+  .tips {
+    color: #0378EC;
+    font-size: 16px;
+    line-height: 38px;
+  }
+
   :global {
+    .n-base-selection {
+      --n-height: 38px !important;
+      width: 160px;
+      font-size: 15px;
+      border-radius: 8px !important;
+    }
+
     .n-button {
       border-radius: 8px;
       height: 38px;
@@ -26,6 +39,21 @@
         border: 1px solid #198CFE;
       }
     }
+
+    .n-button--error-type {
+      background: #FDEBED !important;
+      color: #EC3A4E !important;
+
+      &:not(.n-button--disabled):hover,
+      &:not(.n-button--disabled):active {
+        background: #FDEBED;
+        color: #EC3A4E;
+      }
+
+      .n-button__border {
+        border: 1px solid #EC3A4E;
+      }
+    }
   }
 }
 
@@ -33,6 +61,15 @@
   margin-top: 12px;
   // // 52 + 28 + 38
   max-height: calc(var(--window-page-lesson-height) - 148px);
+
+  .listSection {
+    min-height: calc(var(--window-page-lesson-height) - 148px);
+  }
+
+  .emptySection {
+    display: flex;
+    align-items: center;
+  }
 }
 
 .list {
@@ -41,4 +78,29 @@
   flex-flow: row wrap;
   justify-content: flex-start;
   gap: 20px;
+}
+
+.trainEditModal {
+  width: 580px;
+}
+
+.assignHomework {
+  width: 520px;
+}
+
+.assignHomeworkContainer {
+  padding: 24px 30px;
+
+
+  .updateBtnGroup {
+    padding: 0;
+    justify-content: center !important;
+
+    :global {
+      .n-button {
+        height: 48px !important;
+        min-width: 156px;
+      }
+    }
+  }
 }

+ 312 - 14
src/views/prepare-lessons/components/lesson-main/train/index.tsx

@@ -1,35 +1,333 @@
-import { defineComponent, reactive } from 'vue';
+import { defineComponent, onMounted, reactive, watch } from 'vue';
 import styles from './index.module.less';
-import { NButton, NScrollbar, NSpace } from 'naive-ui';
-import CardType from '/src/components/card-type';
+import {
+  NButton,
+  NModal,
+  NScrollbar,
+  NSelect,
+  NSpace,
+  NSpin,
+  useDialog,
+  useMessage
+} from 'naive-ui';
+import { usePrepareStore } from '/src/store/modules/prepareLessons';
+import { useCatchStore } from '/src/store/modules/catchData';
+import TrainType from '/src/views/attend-class/model/train-type';
+import TheEmpty from '/src/components/TheEmpty';
+import Draggable from 'vuedraggable';
+import {
+  lessonPreTrainingBatchSave,
+  lessonPreTrainingPage
+} from '../../../api';
+import { evaluateDifficult } from '/src/utils/contants';
+import TrainUpdate from '/src/views/attend-class/model/train-update';
+import AssignHomework from './assign-homework';
 
 export default defineComponent({
   name: 'courseware-modal',
   setup() {
+    const catchStore = useCatchStore();
+    const prepareStore = usePrepareStore();
+    const dialog = useDialog();
+    const message = useMessage();
     const forms = reactive({
       showAttendClass: false,
-      list: [] as any
+      list: [] as any,
+      drag: false,
+      loadingStatus: false,
+      trainList: [] as any,
+      assignHomeworkStatus: false,
+      editStatus: false,
+      editItem: {} as any
+    });
+
+    // 完成编辑
+    const onOverEdit = async () => {
+      dialog.warning({
+        title: '提示',
+        content: `是否完成编辑?`,
+        positiveText: '确定',
+        negativeText: '取消',
+        onPositiveClick: async () => {
+          try {
+            if (forms.trainList.length <= 0) {
+              forms.drag = false;
+              message.success('编辑成功');
+              return;
+            }
+            // 保存课件
+            await lessonPreTrainingBatchSave({
+              coursewareKnowledgeDetailId: prepareStore.getSelectKey,
+              subjectId: prepareStore.getSubjectId,
+              lessonPreTrainingDetails: forms.trainList
+            });
+
+            forms.drag = false;
+            message.success('编辑成功');
+
+            prepareStore.setCoursewareList(forms.trainList);
+          } catch {
+            //
+          }
+        }
+      });
+    };
+
+    // 获取列表
+    const getList = async () => {
+      forms.loadingStatus = true;
+      try {
+        // 判断是否有选择对应的课件
+        if (!prepareStore.getSelectKey) return;
+        const { data } = await lessonPreTrainingPage({
+          coursewareKnowledgeDetailId: prepareStore.getSelectKey,
+          subjectId: prepareStore.getSubjectId,
+          pag: 1,
+          rows: 99
+        });
+        const tempRows = data.rows || [];
+        const temp: any = [];
+
+        tempRows.forEach((row: any) => {
+          let tList: string[] = [];
+          const configJson = row.trainingConfigJson;
+          if (row.trainingType === 'EVALUATION') {
+            tList = [
+              `${evaluateDifficult[configJson.evaluateDifficult]}`,
+              '全部小节',
+              `速度${configJson.evaluateSpeed}`,
+              `${configJson.trainingTimes}分钟`
+            ];
+          } else {
+            tList = [
+              `${configJson.practiceChapterBegin}-${configJson.practiceChapterEnd}小节`,
+              `速度${configJson.practiceSpeed}`,
+              `${configJson.trainingTimes}分钟`
+            ];
+          }
+          temp.push({
+            typeList: tList || [],
+            ...row
+          });
+        });
+
+        forms.trainList = temp || [];
+        prepareStore.setTrainList(temp || []);
+      } catch {
+        //
+      }
+      forms.loadingStatus = false;
+    };
+
+    // 监听选择的key 左侧选择了其它的课
+    watch(
+      () => prepareStore.getSelectKey,
+      () => {
+        forms.trainList = [];
+        getList();
+      }
+    );
+    watch(
+      () => prepareStore.getIsAddTrain,
+      (val: boolean) => {
+        if (val) {
+          forms.trainList = [];
+          getList();
+          prepareStore.setIsAddTrain(false);
+        }
+      }
+    );
+
+    // 删除
+    const onDelete = (item: any) => {
+      //
+      const index = forms.trainList.findIndex((c: any) => c.id === item.id);
+      forms.trainList.splice(index, 1);
+      prepareStore.setCoursewareList(forms.trainList);
+    };
+
+    onMounted(async () => {
+      // 获取教材分类列表
+      await catchStore.getSubjects();
+
+      const subjectList = catchStore.getSubjectList;
+      if (subjectList.length > 0) {
+        prepareStore.setSubjectId(subjectList[0].id);
+      }
+
+      await getList();
     });
     return () => (
       <div class={styles.coursewareModal}>
         <div class={styles.btnGroup}>
-          <NSpace>
-            <NButton type="default">添加训练</NButton>
-            <NButton type="default">编辑</NButton>
-          </NSpace>
+          {forms.drag ? (
+            <NSpace>
+              <NButton type="default" onClick={onOverEdit}>
+                完成编辑
+              </NButton>
+              <NButton
+                type="error"
+                onClick={() => {
+                  forms.drag = false;
+                  getList();
+                }}>
+                退出编辑
+              </NButton>
+              <NButton
+                type="error"
+                onClick={() => {
+                  forms.trainList = [];
+                  prepareStore.setTrainList([]);
+                }}>
+                清空资源
+              </NButton>
+              <span class={styles.tips}>拖动可将资源进行排序</span>
+            </NSpace>
+          ) : (
+            <NSpace>
+              <NSelect
+                placeholder="选择声部"
+                options={catchStore.getSubjectList}
+                labelField="name"
+                valueField="id"
+                value={prepareStore.getSubjectId}
+                onUpdate:value={(val: any) => {
+                  prepareStore.setSubjectId(val);
+                }}
+              />
+              <NButton type="default" onClick={() => (forms.drag = true)}>
+                编辑
+              </NButton>
+            </NSpace>
+          )}
 
           <NSpace>
-            <NButton type="primary">布置训练</NButton>
+            <NButton
+              type="primary"
+              disabled={forms.drag}
+              onClick={() => {
+                if (forms.trainList.length <= 0) {
+                  message.error('训练内容不能为空');
+                  return;
+                }
+                forms.assignHomeworkStatus = true;
+              }}>
+              布置训练
+            </NButton>
           </NSpace>
         </div>
 
         <NScrollbar class={styles.listContainer}>
-          <div class={styles.list}>
-            {forms.list.map((item: any) => (
-              <CardType isShowCollect={false} item={item} />
-            ))}
-          </div>
+          <NSpin show={forms.loadingStatus}>
+            <div
+              class={[
+                styles.listSection,
+                !forms.loadingStatus && prepareStore.getTrainList.length <= 0
+                  ? styles.emptySection
+                  : ''
+              ]}>
+              {forms.trainList.length > 0 && (
+                <>
+                  {forms.drag ? (
+                    <Draggable
+                      v-model:modelValue={forms.trainList}
+                      itemKey="id"
+                      // tag="transition-group"
+                      componentData={{
+                        itemKey: 'id',
+                        tag: 'div',
+                        //   type: 'transition-group',
+                        //   name: !forms.drag ? 'flip-list' : null,
+                        animation: 200,
+                        group: 'description',
+                        disabled: false
+                        // ghostClass: 'ghost',
+                        //   ondragstart: () => {
+                        //     forms.drag = true;
+                        //   },
+                        //   ondragend: () => {
+                        //     forms.drag = false;
+                        //   }
+                      }}
+                      class={styles.list}>
+                      {{
+                        item: (element: any) => {
+                          const item = element.element;
+                          return (
+                            <div data-id={item.id} class={styles.itemBlock}>
+                              <TrainType
+                                item={item}
+                                isDelete
+                                type="prepare"
+                                onDelete={(child: any) => onDelete(child)}
+                              />
+                            </div>
+                          );
+                        }
+                      }}
+                    </Draggable>
+                  ) : (
+                    <div class={styles.list}>
+                      {forms.trainList.map((item: any) => (
+                        <TrainType
+                          item={item}
+                          type="prepare"
+                          onEdit={(child: any) => {
+                            console.log('edit', child);
+                            const { trainingConfigJson, id, musicId, ...res } =
+                              child;
+                            forms.editItem = {
+                              ...res,
+                              id: musicId,
+                              trainId: id,
+                              ...trainingConfigJson
+                            };
+                            forms.editStatus = true;
+                          }}
+                        />
+                      ))}
+                    </div>
+                  )}
+                </>
+              )}
+
+              {!forms.loadingStatus &&
+                prepareStore.getTrainList.length <= 0 && (
+                  <TheEmpty description="暂无训练" />
+                )}
+            </div>
+          </NSpin>
         </NScrollbar>
+
+        {/* 编辑 */}
+        <NModal
+          v-model:show={forms.editStatus}
+          class={['modalTitle background', styles.trainEditModal]}
+          preset="card"
+          title="训练设置">
+          <TrainUpdate
+            item={forms.editItem}
+            onClose={() => (forms.editStatus = false)}
+            onConfirm={() => {
+              forms.editItem = {};
+              prepareStore.setIsAddTrain(true);
+            }}
+          />
+        </NModal>
+
+        {/* 添加自定义教材 */}
+        <NModal
+          v-model:show={forms.assignHomeworkStatus}
+          preset="card"
+          showIcon={false}
+          class={['modalTitle background', styles.assignHomework]}
+          title={'布置作业'}
+          blockScroll={false}>
+          <AssignHomework
+            trainList={forms.trainList}
+            onClose={() => (forms.assignHomeworkStatus = false)}
+          />
+        </NModal>
       </div>
     );
   }

+ 0 - 17
src/views/prepare-lessons/components/resource-main/components/my-collect/index.module.less

@@ -1,17 +0,0 @@
-.listContainer {
-  margin: 10px 0;
-  max-height: calc(var(--window-page-lesson-height) - 224px - 20px);
-
-  .list {
-    padding: 10px 0;
-    text-align: center;
-
-    &>div {
-      margin-bottom: 20px;
-
-      &:last-child {
-        margin-bottom: 0;
-      }
-    }
-  }
-}

+ 0 - 106
src/views/prepare-lessons/components/resource-main/components/my-collect/index.tsx

@@ -1,106 +0,0 @@
-import { defineComponent, onMounted, reactive, ref } from 'vue';
-import ResourceSearchGroup from './resource-search-group';
-import { NScrollbar, NSpin } from 'naive-ui';
-import styles from './index.module.less';
-import CardType from '/src/components/card-type';
-import { materialQueryPage } from '/src/views/natural-resources/api';
-import TheEmpty from '/src/components/TheEmpty';
-export default defineComponent({
-  name: 'share-resources',
-  setup() {
-    const scrollContentRef = ref();
-    const state = reactive({
-      searchWord: '',
-      loading: false,
-      pageTotal: 0,
-      finshed: false, // 是否加载完
-      pagination: {
-        page: 1,
-        rows: 20
-      },
-      searchGroup: {
-        type: '', //
-        keyword: '',
-        bookVersionId: null,
-        subjectId: null,
-        sourceType: 4
-      },
-      tableList: [] as any,
-      teachingStatus: false,
-      show: false,
-      item: {} as any
-    });
-    const getList = async () => {
-      try {
-        state.loading = true;
-        const { data } = await materialQueryPage({
-          ...state.searchGroup,
-          ...state.pagination
-        });
-        state.loading = false;
-        const tempRows = data.rows || [];
-        const temp: any = [];
-        tempRows.forEach((row: any) => {
-          temp.push({
-            id: row.id,
-            coverImg: row.coverImg,
-            type: row.type,
-            title: row.name,
-            isCollect: !!row.favoriteFlag,
-            isSelected: row.sourceFrom === 'PLATFORM' ? true : false,
-            content: row.content
-          });
-        });
-        state.tableList.push(...temp);
-
-        state.finshed = data.pages <= data.current ? true : false;
-      } catch {
-        state.loading = false;
-      }
-    };
-
-    const onSearch = async (item: any) => {
-      state.pagination.page = 1;
-      state.tableList = [];
-      state.searchGroup = Object.assign(state.searchGroup, item);
-      getList();
-    };
-
-    onMounted(() => {
-      getList();
-    });
-    return () => (
-      <div>
-        <ResourceSearchGroup onSearch={(item: any) => onSearch(item)} />
-        <NScrollbar
-          class={styles.listContainer}
-          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.pagination.page = state.pagination.page + 1;
-              getList();
-            }
-          }}>
-          <div class={styles.list} ref={scrollContentRef}>
-            {state.tableList.map((item: any) => (
-              <CardType isShowAdd item={item} />
-            ))}
-          </div>
-
-          <NSpin
-            show={state.loading}
-            style={{ display: 'flex' }}
-            size={'small'}></NSpin>
-
-          {!state.loading && state.tableList.length <= 0 && <TheEmpty />}
-        </NScrollbar>
-      </div>
-    );
-  }
-});

+ 0 - 112
src/views/prepare-lessons/components/resource-main/components/my-collect/resource-search-group/index.tsx

@@ -1,112 +0,0 @@
-import { defineComponent, onMounted, reactive, ref } from 'vue';
-import styles from './index.module.less';
-import { NButton, NInput, NSelect, NSpace } from 'naive-ui';
-import { resourceTypeArray } from '/src/utils/searchArray';
-import { useCatchStore } from '/src/store/modules/catchData';
-import { useDebounceFn } from '@vueuse/core';
-
-export default defineComponent({
-  name: 'resource-search-group',
-  emits: ['search'],
-  setup(props, { emit }) {
-    const catchStore = useCatchStore();
-    const forms = reactive({
-      type: '', //
-      keyword: '',
-      bookVersionId: null,
-      subjectId: null
-    });
-    const resourceList = ref([] as any);
-
-    const onSearch = () => {
-      emit('search', forms);
-    };
-
-    const debouncedRequest = useDebounceFn(() => onSearch(), 500);
-
-    onMounted(async () => {
-      resourceList.value = [
-        {
-          label: '全部',
-          value: ''
-        },
-        ...resourceTypeArray
-      ];
-      // 获取教材分类列表
-      await catchStore.getMusicSheetCategory();
-      // 获取声部列表
-      await catchStore.getSubjects();
-    });
-    return () => (
-      <>
-        <div class={styles.searchGroup}>
-          <NSpace size="small" class={styles.btnType}>
-            {resourceList.value.map((item: any) => (
-              <NButton
-                type={forms.type === item.value ? 'primary' : 'default'}
-                secondary={forms.type === item.value ? false : true}
-                round
-                size="small"
-                focusable={false}
-                onClick={() => {
-                  forms.type = item.value;
-                  debouncedRequest();
-                }}>
-                {item.label}
-              </NButton>
-            ))}
-          </NSpace>
-
-          <div class={styles.searchSelect}>
-            <NSelect
-              placeholder="教材"
-              options={catchStore.getMusicCategories}
-              clearable
-              labelField="name"
-              valueField="id"
-              v-model:value={forms.bookVersionId}
-              onUpdate:value={() => {
-                onSearch();
-              }}
-            />
-            <NSelect
-              placeholder="乐器"
-              options={catchStore.getSubjectList}
-              clearable
-              labelField="name"
-              valueField="id"
-              v-model:value={forms.subjectId}
-              onUpdate:value={() => {
-                onSearch();
-              }}
-            />
-          </div>
-
-          <NInput
-            type="text"
-            placeholder="请输入搜索关键词"
-            clearable
-            v-model:value={forms.keyword}
-            class={styles.inputSearch}
-            onKeyup={(e: KeyboardEvent) => {
-              if (e.code === 'Enter') {
-                debouncedRequest();
-              }
-            }}
-            onClear={() => {
-              forms.keyword = '';
-              debouncedRequest();
-            }}>
-            {{
-              prefix: () => (
-                <span
-                  class={'icon-search-input'}
-                  onClick={() => debouncedRequest()}></span>
-              )
-            }}
-          </NInput>
-        </div>
-      </>
-    );
-  }
-});

+ 0 - 17
src/views/prepare-lessons/components/resource-main/components/my-resources/index.module.less

@@ -1,17 +0,0 @@
-.listContainer {
-  margin: 10px 0;
-  max-height: calc(var(--window-page-lesson-height) - 224px - 20px);
-
-  .list {
-    padding: 10px 0;
-    text-align: center;
-
-    &>div {
-      margin-bottom: 20px;
-
-      &:last-child {
-        margin-bottom: 0;
-      }
-    }
-  }
-}

+ 0 - 106
src/views/prepare-lessons/components/resource-main/components/my-resources/index.tsx

@@ -1,106 +0,0 @@
-import { defineComponent, onMounted, reactive, ref } from 'vue';
-import ResourceSearchGroup from './resource-search-group';
-import { NScrollbar, NSpin } from 'naive-ui';
-import styles from './index.module.less';
-import CardType from '/src/components/card-type';
-import { materialQueryPage } from '/src/views/natural-resources/api';
-import TheEmpty from '/src/components/TheEmpty';
-export default defineComponent({
-  name: 'share-resources',
-  setup() {
-    const scrollContentRef = ref();
-    const state = reactive({
-      searchWord: '',
-      loading: false,
-      pageTotal: 0,
-      finshed: false, // 是否加载完
-      pagination: {
-        page: 1,
-        rows: 20
-      },
-      searchGroup: {
-        type: '', //
-        keyword: '',
-        bookVersionId: null,
-        subjectId: null,
-        sourceType: 3
-      },
-      tableList: [] as any,
-      teachingStatus: false,
-      show: false,
-      item: {} as any
-    });
-    const getList = async () => {
-      try {
-        state.loading = true;
-        const { data } = await materialQueryPage({
-          ...state.searchGroup,
-          ...state.pagination
-        });
-        state.loading = false;
-        const tempRows = data.rows || [];
-        const temp: any = [];
-        tempRows.forEach((row: any) => {
-          temp.push({
-            id: row.id,
-            coverImg: row.coverImg,
-            type: row.type,
-            title: row.name,
-            isCollect: !!row.favoriteFlag,
-            isSelected: row.sourceFrom === 'PLATFORM' ? true : false,
-            content: row.content
-          });
-        });
-        state.tableList.push(...temp);
-
-        state.finshed = data.pages <= data.current ? true : false;
-      } catch {
-        state.loading = false;
-      }
-    };
-
-    const onSearch = async (item: any) => {
-      state.pagination.page = 1;
-      state.tableList = [];
-      state.searchGroup = Object.assign(state.searchGroup, item);
-      getList();
-    };
-
-    onMounted(() => {
-      getList();
-    });
-    return () => (
-      <div>
-        <ResourceSearchGroup onSearch={(item: any) => onSearch(item)} />
-        <NScrollbar
-          class={styles.listContainer}
-          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.pagination.page = state.pagination.page + 1;
-              getList();
-            }
-          }}>
-          <div class={styles.list} ref={scrollContentRef}>
-            {state.tableList.map((item: any) => (
-              <CardType isShowAdd item={item} />
-            ))}
-          </div>
-
-          <NSpin
-            show={state.loading}
-            style={{ display: 'flex' }}
-            size={'small'}></NSpin>
-
-          {!state.loading && state.tableList.length <= 0 && <TheEmpty />}
-        </NScrollbar>
-      </div>
-    );
-  }
-});

+ 0 - 59
src/views/prepare-lessons/components/resource-main/components/my-resources/resource-search-group/index.module.less

@@ -1,59 +0,0 @@
-.searchGroup {
-  padding: 0 20px;
-
-  .searchSelect {
-    padding: 20px 0;
-    display: flex;
-    justify-content: flex-start;
-    gap: 0px 16px;
-  }
-
-  :global {
-    .n-select {
-      max-width: 152px;
-    }
-
-    .n-base-selection,
-    .n-input {
-      border-radius: 8px;
-      min-height: 40px;
-      height: 40px;
-      font-size: 15px;
-      --n-height: 40px !important;
-    }
-  }
-}
-
-.inputSearch {
-  :global {
-    .n-input-wrapper {
-      padding-left: 12px;
-      padding-right: 4px;
-    }
-  }
-
-  .searchBtn {
-    height: 34px;
-    border-radius: 8px;
-    font-size: 15px;
-    font-weight: 500;
-  }
-}
-
-.btnType {
-  gap: 0px 6px !important;
-
-  :global {
-    .n-button {
-      height: 28px;
-      padding: 0 13px;
-      font-size: 15px;
-      color: rgba(0, 0, 0, .6);
-
-      &.n-button--primary-type {
-        font-weight: bold;
-        color: #fff;
-      }
-    }
-  }
-}

+ 35 - 0
src/views/prepare-lessons/components/resource-main/components/resource-item/index.module.less

@@ -0,0 +1,35 @@
+.listContainer {
+  margin: 10px 0;
+  max-height: calc(var(--window-page-lesson-height) - 224px - 20px);
+  // overflow-x: auto;
+
+  &.listNoMusic {
+    max-height: calc(var(--window-page-lesson-height) - 224px - 20px + 60px);
+
+    .listSection {
+      min-height: calc(var(--window-page-lesson-height) - 224px - 20px + 60px);
+    }
+  }
+
+  .listSection {
+    min-height: calc(var(--window-page-lesson-height) - 224px - 20px);
+  }
+
+  .emptySection {
+    display: flex;
+    align-items: center;
+  }
+
+  .list {
+    padding: 10px 0;
+    text-align: center;
+
+    &>div {
+      margin-bottom: 20px;
+
+      &:last-child {
+        margin-bottom: 0;
+      }
+    }
+  }
+}

+ 201 - 0
src/views/prepare-lessons/components/resource-main/components/resource-item/index.tsx

@@ -0,0 +1,201 @@
+import { PropType, defineComponent, onMounted, reactive, watch } from 'vue';
+import ResourceSearchGroup from './resource-search-group';
+import { NScrollbar, NSpin, useDialog, useMessage } from 'naive-ui';
+import styles from './index.module.less';
+import CardType from '/src/components/card-type';
+import { materialQueryPage } from '/src/views/natural-resources/api';
+import TheEmpty from '/src/components/TheEmpty';
+import { usePrepareStore } from '/src/store/modules/prepareLessons';
+import { useThrottleFn } from '@vueuse/core';
+import { saveCourseware } from '/src/views/prepare-lessons/api';
+
+const formatType = (type: string) => {
+  if (type === 'shareResources') {
+    return 2;
+  } else if (type === 'myResources') {
+    return 3;
+  } else if (type === 'myCollect') {
+    return 4;
+  }
+};
+export default defineComponent({
+  name: 'share-resources',
+  props: {
+    type: {
+      type: String as PropType<'shareResources' | 'myResources' | 'myCollect'>,
+      default: 'shareResources'
+    }
+  },
+  setup(props) {
+    const prepareStore = usePrepareStore();
+    const message = useMessage();
+    const dialog = useDialog();
+    const state = reactive({
+      loading: false,
+      finshed: false, // 是否加载完
+      pagination: {
+        page: 1,
+        rows: 20
+      },
+      searchGroup: {
+        type: props.type === 'shareResources' ? 'MUSIC' : '', //
+        keyword: '',
+        bookVersionId: null,
+        subjectId: null,
+        sourceType: formatType(props.type),
+        enableFlag: true
+      },
+      tableList: [] as any
+    });
+    const getList = async () => {
+      try {
+        if (!prepareStore.getSubjectId) return;
+        if (state.pagination.page === 1) {
+          state.loading = true;
+        }
+        const { data } = await materialQueryPage({
+          ...state.searchGroup,
+          ...state.pagination,
+          subjectId: prepareStore.getSubjectId
+        });
+        state.loading = false;
+        const tempRows = data.rows || [];
+        const temp: any = [];
+        tempRows.forEach((row: any) => {
+          temp.push({
+            id: row.id,
+            coverImg: row.coverImg,
+            type: row.type,
+            title: row.name,
+            isCollect: !!row.favoriteFlag,
+            isSelected: row.sourceFrom === 'PLATFORM' ? true : false,
+            content: row.content
+          });
+        });
+        state.tableList.push(...temp);
+
+        state.finshed = data.pages <= data.current ? true : false;
+      } catch {
+        state.loading = false;
+      }
+    };
+
+    const onSearch = async (item: any) => {
+      state.pagination.page = 1;
+      state.tableList = [];
+      state.searchGroup = Object.assign(state.searchGroup, item);
+      getList();
+    };
+
+    // 声部变化时
+    watch(
+      () => prepareStore.getSubjectId,
+      () => {
+        onSearch(state.searchGroup);
+      }
+    );
+
+    const throttledFn = useThrottleFn(() => {
+      state.pagination.page = state.pagination.page + 1;
+      getList();
+    }, 500);
+
+    // 添加资源
+    const onAdd = async (item: any) => {
+      dialog.warning({
+        title: '提示',
+        content: `是否添加"${item.title}"资源?`,
+        positiveText: '确定',
+        negativeText: '取消',
+        onPositiveClick: async () => {
+          try {
+            console.log(item, 'any');
+            const temp: any = [];
+            prepareStore.getCoursewareList.forEach((item: any) => {
+              temp.push({
+                materialName: item.title,
+                materialType: item.type,
+                materialId: item.materialId,
+                id: item.id
+              });
+            });
+
+            // 保存课件
+            await saveCourseware({
+              coursewareDetailKnowledgeId: prepareStore.getSelectKey,
+              lessonCoursewareId: prepareStore.getLessonCoursewareId,
+              lessonCoursewareDetailId:
+                prepareStore.getLessonCoursewareDetailId,
+              materialList: [
+                ...temp,
+                {
+                  materialName: item.title,
+                  materialType: item.type,
+                  materialId: item.id
+                }
+              ]
+            });
+
+            message.success('添加成功');
+            prepareStore.setIsAddResource(true);
+          } catch {
+            //
+          }
+        }
+      });
+    };
+
+    onMounted(() => {
+      getList();
+    });
+    return () => (
+      <div>
+        <ResourceSearchGroup
+          type={props.type}
+          onSearch={(item: any) => onSearch(item)}
+        />
+        <NScrollbar
+          class={[
+            styles.listContainer,
+            state.searchGroup.type !== 'MUSIC' ? styles.listNoMusic : ''
+          ]}
+          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
+            ) {
+              throttledFn();
+            }
+          }}>
+          <NSpin show={state.loading} size={'small'}>
+            <div
+              class={[
+                styles.listSection,
+                !state.loading && state.tableList.length <= 0
+                  ? styles.emptySection
+                  : ''
+              ]}>
+              {state.tableList.length > 0 && (
+                <div class={styles.list}>
+                  {state.tableList.map((item: any) => (
+                    <CardType
+                      isShowAdd
+                      item={item}
+                      onAdd={(item: any) => onAdd(item)}
+                    />
+                  ))}
+                </div>
+              )}
+              {!state.loading && state.tableList.length <= 0 && <TheEmpty />}
+            </div>
+          </NSpin>
+        </NScrollbar>
+      </div>
+    );
+  }
+});

+ 3 - 4
src/views/prepare-lessons/components/resource-main/components/share-resources/resource-search-group/index.module.less → src/views/prepare-lessons/components/resource-main/components/resource-item/resource-search-group/index.module.less

@@ -2,16 +2,13 @@
   padding: 0 20px;
 
   .searchSelect {
-    padding: 20px 0;
+    padding: 20px 0 0;
     display: flex;
     justify-content: flex-start;
     gap: 0px 16px;
   }
 
   :global {
-    .n-select {
-      max-width: 152px;
-    }
 
     .n-base-selection,
     .n-input {
@@ -25,6 +22,8 @@
 }
 
 .inputSearch {
+  margin-top: 20px;
+
   :global {
     .n-input-wrapper {
       padding-left: 12px;

+ 43 - 39
src/views/prepare-lessons/components/resource-main/components/my-resources/resource-search-group/index.tsx → src/views/prepare-lessons/components/resource-main/components/resource-item/resource-search-group/index.tsx

@@ -1,47 +1,57 @@
-import { defineComponent, onMounted, reactive, ref } from 'vue';
+import { PropType, defineComponent, onMounted, reactive, ref } from 'vue';
 import styles from './index.module.less';
 import { NButton, NInput, NSelect, NSpace } from 'naive-ui';
 import { resourceTypeArray } from '/src/utils/searchArray';
 import { useCatchStore } from '/src/store/modules/catchData';
-import { useDebounceFn } from '@vueuse/core';
+import { useThrottleFn } from '@vueuse/core';
 
 export default defineComponent({
   name: 'resource-search-group',
   emits: ['search'],
+  props: {
+    type: {
+      type: String as PropType<'shareResources' | 'myResources' | 'myCollect'>,
+      default: 'shareResources'
+    }
+  },
   setup(props, { emit }) {
     const catchStore = useCatchStore();
     const forms = reactive({
-      type: '', //
+      type: 'MUSIC', //
       keyword: '',
-      bookVersionId: null,
-      subjectId: null
+      bookVersionId: null
     });
-    const resourceList = ref([] as any);
+    const resourceType = ref([] as any);
 
     const onSearch = () => {
       emit('search', forms);
     };
 
-    const debouncedRequest = useDebounceFn(() => onSearch(), 500);
+    const debouncedRequest = useThrottleFn(() => onSearch(), 500);
 
     onMounted(async () => {
-      resourceList.value = [
-        {
+      if (props.type === 'myResources' || props.type === 'myCollect') {
+        resourceType.value.push({
           label: '全部',
           value: ''
-        },
-        ...resourceTypeArray
-      ];
+        });
+        forms.type = ''; // 默认全部
+      }
+      resourceTypeArray.forEach((item: any) => {
+        if (props.type === 'myResources') {
+          item.value !== 'MUSIC' && resourceType.value.push(item);
+        } else {
+          resourceType.value.push(item);
+        }
+      });
       // 获取教材分类列表
       await catchStore.getMusicSheetCategory();
-      // 获取声部列表
-      await catchStore.getSubjects();
     });
     return () => (
       <>
         <div class={styles.searchGroup}>
           <NSpace size="small" class={styles.btnType}>
-            {resourceList.value.map((item: any) => (
+            {resourceType.value.map((item: any) => (
               <NButton
                 type={forms.type === item.value ? 'primary' : 'default'}
                 secondary={forms.type === item.value ? false : true}
@@ -57,30 +67,24 @@ export default defineComponent({
             ))}
           </NSpace>
 
-          <div class={styles.searchSelect}>
-            <NSelect
-              placeholder="教材"
-              options={catchStore.getMusicCategories}
-              clearable
-              labelField="name"
-              valueField="id"
-              v-model:value={forms.bookVersionId}
-              onUpdate:value={() => {
-                onSearch();
-              }}
-            />
-            <NSelect
-              placeholder="乐器"
-              options={catchStore.getSubjectList}
-              clearable
-              labelField="name"
-              valueField="id"
-              v-model:value={forms.subjectId}
-              onUpdate:value={() => {
-                onSearch();
-              }}
-            />
-          </div>
+          {forms.type === 'MUSIC' && (
+            <div class={styles.searchSelect}>
+              <NSelect
+                placeholder="全部教材"
+                options={[
+                  { name: '全部教材', id: null },
+                  ...catchStore.getMusicCategories
+                ]}
+                clearable
+                labelField="name"
+                valueField="id"
+                v-model:value={forms.bookVersionId}
+                onUpdate:value={() => {
+                  onSearch();
+                }}
+              />
+            </div>
+          )}
 
           <NInput
             type="text"

+ 31 - 0
src/views/prepare-lessons/components/resource-main/components/select-music/index.module.less

@@ -0,0 +1,31 @@
+.listContainer {
+  margin: 10px 0;
+  max-height: calc(var(--window-page-lesson-height) - 204px);
+  // overflow-x: auto;
+
+  .listSection {
+    min-height: calc(var(--window-page-lesson-height) - 204px);
+  }
+
+  .emptySection {
+    display: flex;
+    align-items: center;
+  }
+
+  .list {
+    padding: 10px 0;
+    text-align: center;
+
+    &>div {
+      margin-bottom: 20px;
+
+      &:last-child {
+        margin-bottom: 0;
+      }
+    }
+  }
+}
+
+.trainEditModal {
+  width: 580px;
+}

+ 183 - 0
src/views/prepare-lessons/components/resource-main/components/select-music/index.tsx

@@ -0,0 +1,183 @@
+import { defineComponent, onMounted, reactive, watch } from 'vue';
+import ResourceSearchGroup from './resource-search-group';
+import { NModal, NScrollbar, NSpin, useDialog, useMessage } from 'naive-ui';
+import styles from './index.module.less';
+import CardType from '/src/components/card-type';
+import TheEmpty from '/src/components/TheEmpty';
+import { useThrottleFn } from '@vueuse/core';
+import { usePrepareStore } from '/src/store/modules/prepareLessons';
+import { musicSheetPage } from '/src/views/prepare-lessons/api';
+import TrainUpdate from '/src/views/attend-class/model/train-update';
+import requestOrigin from 'umi-request';
+export default defineComponent({
+  name: 'share-resources',
+  setup() {
+    const prepareStore = usePrepareStore();
+    const dialog = useDialog();
+    const message = useMessage();
+    const state = reactive({
+      loading: false,
+      finshed: false, // 是否加载完
+      pagination: {
+        page: 1,
+        rows: 20
+      },
+      searchGroup: {
+        keyword: '',
+        musicSheetCategoriesId: '',
+        status: 1,
+        versionFlag: false,
+        subjectId: null
+      },
+      tableList: [] as any,
+      editStatus: false,
+      editItem: {} as any
+    });
+    const getList = async () => {
+      try {
+        if (!prepareStore.getSubjectId) return;
+        if (state.pagination.page === 1) {
+          state.loading = true;
+        }
+        const { data } = await musicSheetPage({
+          ...state.searchGroup,
+          ...state.pagination,
+          subjectId: prepareStore.getSubjectId
+        });
+        state.loading = false;
+        const tempRows = data.rows || [];
+        const temp: any = [];
+        tempRows.forEach((row: any) => {
+          temp.push({
+            id: row.id,
+            coverImg: row.titleImg,
+            type: 'MUSIC',
+            title: row.musicSheetName,
+            isCollect: false,
+            isSelected: true,
+            content: row.id,
+            xmlFileUrl: row.xmlFileUrl
+          });
+        });
+        state.tableList.push(...temp);
+
+        state.finshed = data.pages <= data.current ? true : false;
+      } catch {
+        state.loading = false;
+      }
+    };
+
+    const onSearch = async (item: any) => {
+      state.pagination.page = 1;
+      state.tableList = [];
+      state.searchGroup = Object.assign(state.searchGroup, item);
+      getList();
+    };
+
+    // 声部变化时
+    watch(
+      () => prepareStore.getSubjectId,
+      () => {
+        onSearch(state.searchGroup);
+      }
+    );
+
+    const throttledFn = useThrottleFn(() => {
+      state.pagination.page = state.pagination.page + 1;
+      getList();
+    }, 500);
+
+    // 添加资源
+    const onAdd = async (item: any) => {
+      let xmlStatus = 'init';
+      // 第一个声部小节
+      let firstMeasures: any = null;
+      try {
+        // 获取文件
+        const res = await requestOrigin.get(item.xmlFileUrl, {
+          mode: 'cors'
+        });
+        const xmlParse = new DOMParser().parseFromString(res, 'text/xml');
+        const parts = xmlParse.getElementsByTagName('part');
+        firstMeasures = parts[0]?.getElementsByTagName('measure');
+        xmlStatus = 'success';
+      } catch (error) {
+        xmlStatus = 'error';
+      }
+
+      // 判断读取小节数
+      if (xmlStatus == 'success') {
+        item.practiceChapterMax = firstMeasures.length;
+      } else {
+        item.practiceChapterMax = 0;
+      }
+      item.coursewareKnowledgeDetailId = prepareStore.getSelectKey;
+      item.subjectId = prepareStore.getSubjectId;
+
+      state.editItem = item;
+      state.editStatus = true;
+    };
+
+    onMounted(() => {
+      getList();
+    });
+    return () => (
+      <div>
+        <ResourceSearchGroup onSearch={(item: any) => onSearch(item)} />
+        <NScrollbar
+          class={styles.listContainer}
+          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
+            ) {
+              throttledFn();
+            }
+          }}>
+          <NSpin show={state.loading} size={'small'}>
+            <div
+              class={[
+                styles.listSection,
+                !state.loading && state.tableList.length <= 0
+                  ? styles.emptySection
+                  : ''
+              ]}>
+              {state.tableList.length > 0 && (
+                <div class={styles.list}>
+                  {state.tableList.map((item: any) => (
+                    <CardType
+                      isShowAdd
+                      item={item}
+                      onAdd={(child: any) => onAdd(child)}
+                    />
+                  ))}
+                </div>
+              )}
+              {!state.loading && state.tableList.length <= 0 && <TheEmpty />}
+            </div>
+          </NSpin>
+        </NScrollbar>
+
+        <NModal
+          v-model:show={state.editStatus}
+          class={['modalTitle background', styles.trainEditModal]}
+          preset="card"
+          title="训练设置">
+          <TrainUpdate
+            item={state.editItem}
+            onClose={() => (state.editStatus = false)}
+            onConfirm={() => {
+              state.editItem = {};
+              prepareStore.setIsAddTrain(true);
+            }}
+          />
+        </NModal>
+      </div>
+    );
+  }
+});

+ 1 - 4
src/views/prepare-lessons/components/resource-main/components/my-collect/resource-search-group/index.module.less → src/views/prepare-lessons/components/resource-main/components/select-music/resource-search-group/index.module.less

@@ -2,16 +2,13 @@
   padding: 0 20px;
 
   .searchSelect {
-    padding: 20px 0;
+    padding: 0 0 20px;
     display: flex;
     justify-content: flex-start;
     gap: 0px 16px;
   }
 
   :global {
-    .n-select {
-      max-width: 152px;
-    }
 
     .n-base-selection,
     .n-input {

+ 12 - 40
src/views/prepare-lessons/components/resource-main/components/share-resources/resource-search-group/index.tsx → src/views/prepare-lessons/components/resource-main/components/select-music/resource-search-group/index.tsx

@@ -1,9 +1,8 @@
 import { defineComponent, onMounted, reactive } from 'vue';
 import styles from './index.module.less';
 import { NButton, NInput, NSelect, NSpace } from 'naive-ui';
-import { resourceTypeArray } from '/src/utils/searchArray';
 import { useCatchStore } from '/src/store/modules/catchData';
-import { useDebounceFn } from '@vueuse/core';
+import { useThrottleFn } from '@vueuse/core';
 
 export default defineComponent({
   name: 'resource-search-group',
@@ -11,17 +10,15 @@ export default defineComponent({
   setup(props, { emit }) {
     const catchStore = useCatchStore();
     const forms = reactive({
-      type: 'MUSIC', //
       keyword: '',
-      bookVersionId: null,
-      subjectId: null
+      musicSheetCategoriesId: null
     });
 
     const onSearch = () => {
       emit('search', forms);
     };
 
-    const debouncedRequest = useDebounceFn(() => onSearch(), 500);
+    const throttledFn = useThrottleFn(() => onSearch(), 500);
 
     onMounted(async () => {
       // 获取教材分类列表
@@ -32,42 +29,17 @@ export default defineComponent({
     return () => (
       <>
         <div class={styles.searchGroup}>
-          <NSpace size="small" class={styles.btnType}>
-            {resourceTypeArray.map((item: any) => (
-              <NButton
-                type={forms.type === item.value ? 'primary' : 'default'}
-                secondary={forms.type === item.value ? false : true}
-                round
-                size="small"
-                focusable={false}
-                onClick={() => {
-                  forms.type = item.value;
-                  debouncedRequest();
-                }}>
-                {item.label}
-              </NButton>
-            ))}
-          </NSpace>
-
           <div class={styles.searchSelect}>
             <NSelect
-              placeholder="教材"
-              options={catchStore.getMusicCategories}
-              clearable
-              labelField="name"
-              valueField="id"
-              v-model:value={forms.bookVersionId}
-              onUpdate:value={() => {
-                onSearch();
-              }}
-            />
-            <NSelect
-              placeholder="乐器"
-              options={catchStore.getSubjectList}
+              placeholder="全部教材"
+              options={[
+                { name: '全部教材', id: null },
+                ...catchStore.getMusicCategories
+              ]}
               clearable
               labelField="name"
               valueField="id"
-              v-model:value={forms.subjectId}
+              v-model:value={forms.musicSheetCategoriesId}
               onUpdate:value={() => {
                 onSearch();
               }}
@@ -82,18 +54,18 @@ export default defineComponent({
             class={styles.inputSearch}
             onKeyup={(e: KeyboardEvent) => {
               if (e.code === 'Enter') {
-                debouncedRequest();
+                throttledFn();
               }
             }}
             onClear={() => {
               forms.keyword = '';
-              debouncedRequest();
+              throttledFn();
             }}>
             {{
               prefix: () => (
                 <span
                   class={'icon-search-input'}
-                  onClick={() => debouncedRequest()}></span>
+                  onClick={() => throttledFn()}></span>
               )
             }}
           </NInput>

+ 0 - 18
src/views/prepare-lessons/components/resource-main/components/share-resources/index.module.less

@@ -1,18 +0,0 @@
-.listContainer {
-  margin: 10px 0;
-  max-height: calc(var(--window-page-lesson-height) - 224px - 20px);
-  // overflow-x: auto;
-
-  .list {
-    padding: 10px 0;
-    text-align: center;
-
-    &>div {
-      margin-bottom: 20px;
-
-      &:last-child {
-        margin-bottom: 0;
-      }
-    }
-  }
-}

+ 0 - 105
src/views/prepare-lessons/components/resource-main/components/share-resources/index.tsx

@@ -1,105 +0,0 @@
-import { defineComponent, onMounted, reactive, ref } from 'vue';
-import ResourceSearchGroup from './resource-search-group';
-import { NScrollbar, NSpin } from 'naive-ui';
-import styles from './index.module.less';
-import CardType from '/src/components/card-type';
-import { materialQueryPage } from '/src/views/natural-resources/api';
-import TheEmpty from '/src/components/TheEmpty';
-export default defineComponent({
-  name: 'share-resources',
-  setup() {
-    const scrollContentRef = ref();
-    const state = reactive({
-      searchWord: '',
-      loading: false,
-      pageTotal: 0,
-      finshed: false, // 是否加载完
-      pagination: {
-        page: 1,
-        rows: 20
-      },
-      searchGroup: {
-        type: 'MUSIC', //
-        keyword: '',
-        bookVersionId: null,
-        subjectId: null,
-        sourceType: 2
-      },
-      tableList: [] as any,
-      teachingStatus: false,
-      show: false,
-      item: {} as any
-    });
-    const getList = async () => {
-      try {
-        state.loading = true;
-        const { data } = await materialQueryPage({
-          ...state.searchGroup,
-          ...state.pagination
-        });
-        state.loading = false;
-        const tempRows = data.rows || [];
-        const temp: any = [];
-        tempRows.forEach((row: any) => {
-          temp.push({
-            id: row.id,
-            coverImg: row.coverImg,
-            type: row.type,
-            title: row.name,
-            isCollect: !!row.favoriteFlag,
-            isSelected: row.sourceFrom === 'PLATFORM' ? true : false,
-            content: row.content
-          });
-        });
-        state.tableList.push(...temp);
-
-        state.finshed = data.pages <= data.current ? true : false;
-      } catch {
-        state.loading = false;
-      }
-    };
-
-    const onSearch = async (item: any) => {
-      state.pagination.page = 1;
-      state.tableList = [];
-      state.searchGroup = Object.assign(state.searchGroup, item);
-      getList();
-    };
-
-    onMounted(() => {
-      getList();
-    });
-    return () => (
-      <div>
-        <ResourceSearchGroup onSearch={(item: any) => onSearch(item)} />
-        <NScrollbar
-          class={styles.listContainer}
-          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.pagination.page = state.pagination.page + 1;
-              getList();
-            }
-          }}>
-          <div class={styles.list} ref={scrollContentRef}>
-            {state.tableList.map((item: any) => (
-              <CardType isShowAdd item={item} />
-            ))}
-          </div>
-          <NSpin
-            show={state.loading}
-            style={{ display: 'flex' }}
-            size={'small'}></NSpin>
-
-          {!state.loading && state.tableList.length <= 0 && <TheEmpty />}
-        </NScrollbar>
-      </div>
-    );
-  }
-});

+ 24 - 0
src/views/prepare-lessons/components/resource-main/index.module.less

@@ -47,4 +47,28 @@
     height: 18px;
     cursor: pointer;
   }
+
+
+}
+
+.selectMusicModal {
+  position: relative;
+  width: 1352px;
+
+  :global {
+    .n-card-header {
+      position: absolute;
+      top: 0;
+      left: 0;
+      right: 0;
+
+      .n-card-header__main {
+        color: #fff;
+      }
+    }
+  }
+}
+
+.trainEditModal {
+  width: 580px;
 }

+ 174 - 51
src/views/prepare-lessons/components/resource-main/index.tsx

@@ -1,62 +1,185 @@
-import { defineComponent } from 'vue';
+import { defineComponent, reactive } from 'vue';
 import styles from './index.module.less';
-import { NTabs, NTabPane } from 'naive-ui';
-import ShareResources from './components/share-resources';
-import MyResources from './components/my-resources';
-import MyCollect from './components/my-collect';
+import { NTabs, NTabPane, NModal } from 'naive-ui';
+import SelectMusicModal from '../../model/select-music';
+import { usePrepareStore } from '/src/store/modules/prepareLessons';
+import SelectResources from '../../model/select-resources';
+import SelectMusic from './components/select-music';
+import ResourceItem from './components/resource-item';
+import TrainUpdate from '/src/views/attend-class/model/train-update';
+import requestOrigin from 'umi-request';
 
 export default defineComponent({
   name: 'resource-main',
-  props: {
-    selectionHeight: {
-      type: String,
-      default: '100%'
-    }
-  },
   setup() {
+    const prepareStore = usePrepareStore();
+    const forms = reactive({
+      tabType: 'shareResources',
+      selectMusicStatus: false,
+      selectResourceStatus: false,
+      editStatus: false,
+      editItem: {} as any
+    });
+
+    const onAdd = async (item: any) => {
+      let xmlStatus = 'init';
+      // 第一个声部小节
+      let firstMeasures: any = null;
+      try {
+        // 获取文件
+        const res = await requestOrigin.get(item.xmlFileUrl, {
+          mode: 'cors'
+        });
+        const xmlParse = new DOMParser().parseFromString(res, 'text/xml');
+        const parts = xmlParse.getElementsByTagName('part');
+        firstMeasures = parts[0]?.getElementsByTagName('measure');
+        xmlStatus = 'success';
+      } catch (error) {
+        xmlStatus = 'error';
+      }
+
+      // 判断读取小节数
+      if (xmlStatus == 'success') {
+        item.practiceChapterMax = firstMeasures.length;
+      } else {
+        item.practiceChapterMax = 0;
+      }
+      item.coursewareKnowledgeDetailId = prepareStore.getSelectKey;
+      item.subjectId = prepareStore.getSubjectId;
+
+      forms.editItem = item;
+      forms.editStatus = true;
+    };
+
     return () => (
-      <div class={styles['resource-main']}>
-        <NTabs
-          animated
-          defaultValue="shareResources"
-          paneClass={styles.paneTitle}
-          paneWrapperClass={styles.paneWrapperContainer}>
-          {{
-            suffix: () => (
-              <div class={styles.iconScreen}>
-                <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
-                  <g fill="none">
-                    <path
-                      d="M5 6a1 1 0 0 1 1-1h2a1 1 0 0 0 0-2H6a3 3 0 0 0-3 3v2a1 1 0 0 0 2 0V6zm0 12a1 1 0 0 0 1 1h2a1 1 0 1 1 0 2H6a3 3 0 0 1-3-3v-2a1 1 0 1 1 2 0v2zM18 5a1 1 0 0 1 1 1v2a1 1 0 1 0 2 0V6a3 3 0 0 0-3-3h-2a1 1 0 1 0 0 2h2zm1 13a1 1 0 0 1-1 1h-2a1 1 0 1 0 0 2h2a3 3 0 0 0 3-3v-2a1 1 0 1 0-2 0v2z"
-                      fill="#198CFE"></path>
-                  </g>
-                </svg>
-              </div>
-            ),
-            default: () => (
-              <>
-                <NTabPane
-                  name="shareResources"
-                  tab="共享资源"
-                  displayDirective="show:lazy">
-                  <ShareResources />
+      <div
+        class={[
+          styles['resource-main'],
+          forms.selectMusicStatus || forms.selectResourceStatus
+            ? styles.resourceClose
+            : ''
+        ]}>
+        {prepareStore.getTabType === 'courseware' ? (
+          <NTabs
+            animated
+            value={forms.tabType}
+            paneClass={styles.paneTitle}
+            paneWrapperClass={styles.paneWrapperContainer}
+            onUpdate:value={(val: string) => {
+              forms.tabType = val;
+            }}>
+            {{
+              suffix: () => (
+                <div
+                  class={styles.iconScreen}
+                  onClick={() => {
+                    forms.selectResourceStatus = true;
+                    prepareStore.setSelectResourceStatus(true);
+                  }}>
+                  <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
+                    <g fill="none">
+                      <path
+                        d="M5 6a1 1 0 0 1 1-1h2a1 1 0 0 0 0-2H6a3 3 0 0 0-3 3v2a1 1 0 0 0 2 0V6zm0 12a1 1 0 0 0 1 1h2a1 1 0 1 1 0 2H6a3 3 0 0 1-3-3v-2a1 1 0 1 1 2 0v2zM18 5a1 1 0 0 1 1 1v2a1 1 0 1 0 2 0V6a3 3 0 0 0-3-3h-2a1 1 0 1 0 0 2h2zm1 13a1 1 0 0 1-1 1h-2a1 1 0 1 0 0 2h2a3 3 0 0 0 3-3v-2a1 1 0 1 0-2 0v2z"
+                        fill="#198CFE"></path>
+                    </g>
+                  </svg>
+                </div>
+              ),
+              default: () => (
+                <>
+                  <NTabPane
+                    name="shareResources"
+                    tab="共享资源"
+                    displayDirective="show:lazy">
+                    <ResourceItem type="shareResources" />
+                  </NTabPane>
+                  <NTabPane
+                    name="myResources"
+                    tab="我的资源"
+                    displayDirective="show:lazy">
+                    <ResourceItem type="myResources" />
+                  </NTabPane>
+                  <NTabPane
+                    name="myCollect"
+                    tab="我的收藏"
+                    displayDirective="show:lazy">
+                    <ResourceItem type="myCollect" />
+                  </NTabPane>
+                </>
+              )
+            }}
+          </NTabs>
+        ) : (
+          <NTabs
+            animated
+            value="selectMusic"
+            paneClass={styles.paneTitle}
+            justifyContent="center"
+            paneWrapperClass={styles.paneWrapperContainer}>
+            {{
+              suffix: () => (
+                <div
+                  class={styles.iconScreen}
+                  onClick={() => {
+                    forms.selectMusicStatus = true;
+                    prepareStore.setSelectMusicStatus(true);
+                  }}>
+                  <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
+                    <g fill="none">
+                      <path
+                        d="M5 6a1 1 0 0 1 1-1h2a1 1 0 0 0 0-2H6a3 3 0 0 0-3 3v2a1 1 0 0 0 2 0V6zm0 12a1 1 0 0 0 1 1h2a1 1 0 1 1 0 2H6a3 3 0 0 1-3-3v-2a1 1 0 1 1 2 0v2zM18 5a1 1 0 0 1 1 1v2a1 1 0 1 0 2 0V6a3 3 0 0 0-3-3h-2a1 1 0 1 0 0 2h2zm1 13a1 1 0 0 1-1 1h-2a1 1 0 1 0 0 2h2a3 3 0 0 0 3-3v-2a1 1 0 1 0-2 0v2z"
+                        fill="#198CFE"></path>
+                    </g>
+                  </svg>
+                </div>
+              ),
+              default: () => (
+                <NTabPane name="selectMusic" tab="选择曲目">
+                  <SelectMusic />
                 </NTabPane>
-                <NTabPane
-                  name="myResources"
-                  tab="我的资源"
-                  displayDirective="show:lazy">
-                  <MyResources />
-                </NTabPane>
-                <NTabPane
-                  name="myCollect"
-                  tab="我的收藏"
-                  displayDirective="show:lazy">
-                  <MyCollect />
-                </NTabPane>
-              </>
-            )
+              )
+            }}
+          </NTabs>
+        )}
+        <NModal
+          v-model:show={forms.selectResourceStatus}
+          onUpdate:show={(val: any) => {
+            if (!val) {
+              prepareStore.setSelectResourceStatus(val);
+            }
+          }}
+          class={['modalTitle', styles.selectMusicModal]}
+          preset="card"
+          title={'选择资源'}>
+          <SelectResources />
+        </NModal>
+
+        <NModal
+          v-model:show={forms.selectMusicStatus}
+          onUpdate:show={(val: any) => {
+            if (!val) {
+              prepareStore.setSelectMusicStatus(val);
+            }
           }}
-        </NTabs>
+          class={['modalTitle', styles.selectMusicModal]}
+          preset="card"
+          title={'选择曲目'}>
+          <SelectMusicModal onAdd={(item: any) => onAdd(item)} />
+        </NModal>
+        <NModal
+          v-model:show={forms.editStatus}
+          class={['modalTitle background', styles.trainEditModal]}
+          preset="card"
+          title="训练设置">
+          <TrainUpdate
+            item={forms.editItem}
+            onClose={() => (forms.editStatus = false)}
+            onConfirm={() => {
+              forms.editItem = {};
+              prepareStore.setIsAddTrain(true);
+            }}
+          />
+        </NModal>
       </div>
     );
   }

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


+ 11 - 0
src/views/prepare-lessons/index.module.less

@@ -18,4 +18,15 @@
     background-color: #fff;
     border-radius: 20px;
   }
+
+  .resourceMain {
+    transition: all .2s ease-in-out;
+
+    &.resourceClose {
+      transition: all .2s ease-in-out;
+      width: 0;
+      transform: translateX(100%);
+      opacity: 0;
+    }
+  }
 }

+ 11 - 1
src/views/prepare-lessons/index.tsx

@@ -4,10 +4,12 @@ import DirectoryList from './components/directory-main';
 import LessonMain from './components/lesson-main';
 import ResourceMain from './components/resource-main';
 import { useResizeObserver } from '@vueuse/core';
+import { usePrepareStore } from '/src/store/modules/prepareLessons';
 
 export default defineComponent({
   name: 'prepare-lessons',
   setup() {
+    const prepareStore = usePrepareStore();
     const directroyRef = ref();
     onMounted(() => {
       useResizeObserver(
@@ -33,7 +35,15 @@ export default defineComponent({
           <LessonMain />
         </div>
         {/* 资源 */}
-        <div class={styles.resourceMain} id="resourceRef">
+        <div
+          class={[
+            styles.resourceMain
+            // prepareStore.getSelectMusicStatus ||
+            // prepareStore.getSelectResourceStatus
+            //   ? styles.resourceClose
+            //   : ''
+          ]}
+          id="resourceRef">
           <ResourceMain />
         </div>
       </div>

+ 10 - 0
src/views/prepare-lessons/model/attend-class/index.module.less

@@ -30,7 +30,17 @@
 
 .classList {
   max-height: 60vh;
+  min-height: 60vh;
   padding: 0 40px;
+
+  .listSection {
+    min-height: 60vh;
+  }
+
+  .emptySection {
+    display: flex;
+    align-items: center;
+  }
 }
 
 .thingItem {

+ 93 - 70
src/views/prepare-lessons/model/attend-class/index.tsx

@@ -1,14 +1,29 @@
-import { defineComponent } from 'vue';
+import { defineComponent, onMounted, reactive, ref } from 'vue';
 import styles from './index.module.less';
-import { NInput, NScrollbar, NSelect, NThing } from 'naive-ui';
-import iconSearch from '../../images/icon-search.png';
+import { NInput, NScrollbar, NSelect, NSpin, NThing } from 'naive-ui';
 import { useRouter } from 'vue-router';
+import { BOOK_DATA } from '/src/views/natural-resources/model/add-teaching';
+import { classGroupPage } from '../../api';
+import { useThrottleFn } from '@vueuse/core';
+import TheEmpty from '/src/components/TheEmpty';
+
+const classList: any = [];
+for (let i = 1; i <= 40; i++) {
+  classList.push({ label: i + '班', value: i });
+}
 
 export default defineComponent({
   name: 'attend-class',
   emits: ['close'],
   setup(props, { emit }) {
     const router = useRouter();
+    const forms = reactive({
+      keyword: null,
+      currentGradeNum: null,
+      currentClass: null
+    });
+    const list = ref([] as any);
+    const loading = ref(false);
     const onAttendClass = (i: any) => {
       emit('close');
 
@@ -17,88 +32,96 @@ export default defineComponent({
       });
       window.open(href, +new Date() + '');
     };
+
+    const getList = async () => {
+      loading.value = true;
+      try {
+        const { data } = await classGroupPage({
+          page: 1,
+          rows: 99,
+          ...forms
+        });
+        console.log(data, 'data');
+        list.value = data.rows || [];
+      } catch {
+        //
+      }
+      loading.value = false;
+    };
+
+    const throttleFn = useThrottleFn(() => getList(), 500);
+
+    onMounted(() => {
+      getList();
+    });
+
     return () => (
       <div class={styles.attendClass}>
         <div class={styles.attendClassSearch}>
-          <NInput placeholder="请输入班级名称" clearable>
+          <NInput
+            placeholder="请输入班级名称"
+            clearable
+            v-model:value={forms.keyword}
+            onKeyup={(e: KeyboardEvent) => {
+              if (e.code === 'Enter') {
+                throttleFn();
+              }
+            }}
+            onClear={() => throttleFn()}>
             {{
-              prefix: () => <span class="icon-search-input"></span>
+              prefix: () => (
+                <span
+                  class="icon-search-input"
+                  onClick={() => throttleFn()}></span>
+              )
             }}
           </NInput>
           <NSelect
-            placeholder="年级"
+            placeholder="全部年级"
             clearable
-            options={[
-              {
-                label: '一年级',
-                value: '1'
-              },
-              {
-                label: '二年级',
-                value: '2'
-              },
-              {
-                label: '三年级',
-                value: '3'
-              },
-              {
-                label: '四年级',
-                value: '4'
-              },
-              {
-                label: '五年级',
-                value: '5'
-              },
-              {
-                label: '六年级',
-                value: '6'
-              }
-            ]}
+            options={
+              [{ label: '全部年级', value: null }, ...BOOK_DATA.grades] as any
+            }
+            v-model:value={forms.currentGradeNum}
+            onUpdate:value={() => throttleFn()}
           />
           <NSelect
-            placeholder="班级"
+            placeholder="全部班级"
             clearable
-            options={[
-              {
-                label: '一班',
-                value: '1'
-              },
-              {
-                label: '二班',
-                value: '2'
-              },
-              {
-                label: '三班',
-                value: '3'
-              },
-              {
-                label: '四班',
-                value: '4'
-              },
-              {
-                label: '五班',
-                value: '5'
-              }
-            ]}
+            options={[{ label: '全部班级', value: null }, ...classList]}
+            v-model:value={forms.currentClass}
+            onUpdate:value={() => throttleFn()}
           />
         </div>
         <NScrollbar class={styles.classList}>
-          {[1, 2, 3, 4, 5, 6, 7].map(i => (
-            <div onClick={() => onAttendClass(i)}>
-              <NThing class={styles.thingItem}>
-                {{
-                  header: () => (
-                    <div class={styles.title}>一年级{i}班 23人</div>
-                  ),
-                  default: () => (
-                    <div class={styles.content}>
-                      人教版二年级上册 | 第一单元 |【歌表演】我和我的祖国
-                    </div>
-                  )
-                }}
-              </NThing>
+          <NSpin show={loading.value}>
+            <div
+              class={[
+                styles.listSection,
+                !loading.value && list.value.length <= 0
+                  ? styles.emptySection
+                  : ''
+              ]}>
+              {list.value.map((item: any) => (
+                <div onClick={() => onAttendClass(item)}>
+                  <NThing class={styles.thingItem}>
+                    {{
+                      header: () => (
+                        <div class={styles.title}>
+                          {item.name} {item.preStudentNum}人
+                        </div>
+                      ),
+                      default: () =>
+                        item.lastStudy && (
+                          <div class={styles.content}>{item.lastStudy}</div>
+                        )
+                    }}
+                  </NThing>
+                </div>
+              ))}
+              {!loading.value && list.value.length <= 0 && <TheEmpty />}
             </div>
-          ))}
+          </NSpin>
         </NScrollbar>
       </div>
     );

+ 47 - 34
src/views/prepare-lessons/model/select-music/index.module.less

@@ -52,6 +52,16 @@
 .listContainer {
   margin-bottom: 20px;
   max-height: 50vh;
+  min-height: 50vh;
+
+  .listSection {
+    min-height: 50vh;
+  }
+
+  .emptySection {
+    display: flex;
+    align-items: center;
+  }
 }
 
 .list {
@@ -69,14 +79,14 @@
   padding: 0 40px;
 
   :global {
-    .n-form {
-      .n-form-item .n-form-item-label {
-        font-size: 17px;
-        font-weight: 600;
-        color: #131415;
-        line-height: 24px;
-      }
+    .n-form-item .n-form-item-label {
+      font-size: 17px;
+      font-weight: 600;
+      color: #131415;
+      line-height: 24px;
+    }
 
+    .n-form-item {
       .n-button {
         height: 32px;
         font-size: 17px;
@@ -87,39 +97,42 @@
       .n-button--primary-type {
         color: #131415;
       }
+    }
 
-      .n-form-item-feedback-wrapper {
-        min-height: 14px;
-      }
+    .n-form-item-feedback-wrapper {
+      min-height: 14px;
     }
   }
+}
 
-  .inputSearch {
-    position: absolute;
-    top: 4px;
-    right: 40px;
-    width: 360px;
-    height: 42px;
-    font-size: 16px;
-
-    img {
-      width: 18px;
-      height: 18px;
-    }
 
-    :global {
-      .n-input-wrapper {
-        padding-left: 12px;
-        padding-right: 4px;
-        height: 42px !important;
-      }
+.inputSearch {
+  position: absolute;
+  top: 4px;
+  right: 40px;
+  width: 360px !important;
+  height: 42px !important;
+  font-size: 16px;
 
-      .n-button {
-        height: 34px;
-        font-size: 15px;
-        font-weight: 500;
-        width: auto;
-      }
+  :global {
+    .n-input-wrapper {
+      padding-left: 12px;
+      padding-right: 4px;
     }
   }
+
+  .searchBtn {
+    height: 34px;
+    border-radius: 8px;
+    font-size: 15px;
+    font-weight: 500;
+  }
+}
+
+.spaceSection {
+  width: 76%;
+
+  &>div {
+    line-height: var(--n-blank-height);
+  }
 }

+ 103 - 33
src/views/prepare-lessons/model/select-music/index.tsx

@@ -1,40 +1,81 @@
-import { NButton, NIcon, NScrollbar, NTabPane, NTabs } from 'naive-ui';
-import { defineComponent, reactive } from 'vue';
+import { NScrollbar, NSpin, NTabPane, NTabs } from 'naive-ui';
+import { defineComponent, onMounted, reactive } from 'vue';
 import styles from './index.module.less';
 import CardType from '@/components/card-type';
 import SearchGroup from './search-group';
-import listData from '@views/xiaoku-music/data.json';
+import TheEmpty from '/src/components/TheEmpty';
+import { useThrottleFn } from '@vueuse/core';
+import { usePrepareStore } from '/src/store/modules/prepareLessons';
+import { musicSheetPage } from '../../api';
 
 export default defineComponent({
   name: 'select-music',
-  emits: ['select'],
+  emits: ['select', 'add'],
   setup(props, { emit }) {
-    const forms = reactive({
-      list: [],
-      height: '100%' as any
+    const prepareStore = usePrepareStore();
+    const state = reactive({
+      loading: false,
+      finshed: false, // 是否加载完
+      pagination: {
+        page: 1,
+        rows: 20
+      },
+      searchGroup: {
+        keyword: '',
+        musicSheetCategoriesId: '',
+        status: 1,
+        versionFlag: false,
+        subjectId: null
+      },
+      tableList: [] as any
     });
-
-    const formatData = () => {
-      const rows = listData.rows || [];
-      console.log(rows);
-      const tempList: any = [];
-      rows.forEach((row: any, i: number) => {
-        if (i <= 10) {
-          tempList.push({
+    const getList = async () => {
+      try {
+        if (state.pagination.page === 1) {
+          state.loading = true;
+        }
+        const { data } = await musicSheetPage({
+          ...state.searchGroup,
+          ...state.pagination,
+          subjectId: prepareStore.getSubjectId
+        });
+        state.loading = false;
+        const tempRows = data.rows || [];
+        const temp: any = [];
+        tempRows.forEach((row: any) => {
+          temp.push({
             id: row.id,
+            coverImg: row.titleImg,
             type: 'MUSIC',
             title: row.musicSheetName,
-            url: row.fixedTone ? row.fixedTone.split(',')[0] : '',
-            isCollect: i % 3 ? false : true,
-            isSelected: i % 4 ? false : true
+            isCollect: false,
+            isSelected: true,
+            content: row.id,
+            xmlFileUrl: row.xmlFileUrl
           });
-        }
-      });
+        });
+        state.tableList.push(...temp);
+        state.finshed = data.pages <= data.current ? true : false;
+      } catch {
+        state.loading = false;
+      }
+    };
 
-      forms.list = tempList || [];
+    const onSearch = async (item: any) => {
+      state.pagination.page = 1;
+      state.tableList = [];
+      state.searchGroup = Object.assign(state.searchGroup, item);
+      getList();
     };
 
-    formatData();
+    const throttledFn = useThrottleFn(() => {
+      state.pagination.page = state.pagination.page + 1;
+      getList();
+    }, 500);
+
+    onMounted(() => {
+      getList();
+    });
     return () => (
       <div class={styles.selectMusic}>
         <NTabs
@@ -44,17 +85,46 @@ export default defineComponent({
           justifyContent="center"
           paneWrapperClass={styles.paneWrapperContainer}>
           <NTabPane name="shareResources" tab="选择曲目">
-            <SearchGroup />
-            <NScrollbar class={styles.listContainer}>
-              <div class={styles.list}>
-                {forms.list.map((item: any) => (
-                  <CardType
-                    isShowAdd
-                    item={item}
-                    onAdd={(i: any) => emit('select', i)}
-                  />
-                ))}
-              </div>
+            <SearchGroup onSearch={(item: any) => onSearch(item)} />
+            <NScrollbar
+              class={styles.listContainer}
+              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
+                ) {
+                  throttledFn();
+                }
+              }}>
+              <NSpin show={state.loading} size={'small'}>
+                <div
+                  class={[
+                    styles.listSection,
+                    !state.loading && state.tableList.length <= 0
+                      ? styles.emptySection
+                      : ''
+                  ]}>
+                  {state.tableList.length > 0 && (
+                    <div class={styles.list}>
+                      {state.tableList.map((item: any) => (
+                        <CardType
+                          isShowAdd
+                          item={item}
+                          onAdd={() => emit('add', item)}
+                        />
+                      ))}
+                    </div>
+                  )}
+                  {!state.loading && state.tableList.length <= 0 && (
+                    <TheEmpty />
+                  )}
+                </div>
+              </NSpin>
             </NScrollbar>
           </NTabPane>
         </NTabs>

+ 51 - 44
src/views/prepare-lessons/model/select-music/search-group.tsx

@@ -1,61 +1,68 @@
-import { defineComponent, reactive, ref } from 'vue';
+import { defineComponent, reactive, onMounted } from 'vue';
 import styles from './index.module.less';
-import { NButton, NForm, NFormItem, NInput, NSpace } from 'naive-ui';
+import { NButton, NForm, NFormItem, NSpace } from 'naive-ui';
 import TheSearch from '/src/components/TheSearch';
+import { useCatchStore } from '/src/store/modules/catchData';
+import { useThrottleFn } from '@vueuse/core';
 
 export default defineComponent({
   name: 'search-group',
-  setup() {
-    const isFocus = ref(false);
+  emits: ['search', 'add'],
+  setup(props, { emit }) {
+    const catchStore = useCatchStore();
     const forms = reactive({
-      search: ''
+      keyword: '',
+      musicSheetCategoriesId: null
+    });
+
+    const onSearch = () => {
+      emit('search', forms);
+    };
+
+    const throttledFn = useThrottleFn(() => {
+      onSearch();
+    }, 500);
+
+    onMounted(async () => {
+      // 获取教材分类列表
+      await catchStore.getMusicSheetCategory();
+      // 获取声部列表
+      await catchStore.getSubjects();
     });
     return () => (
       <div class={styles.searchGroup}>
         <NForm labelAlign="left" labelPlacement="left">
           <NFormItem label="教材:">
-            <NSpace>
-              <NButton secondary strong type="primary" focusable={false}>
-                全部
-              </NButton>
-              <NButton quaternary focusable={false}>
-                人教版
-              </NButton>
-              <NButton quaternary focusable={false}>
-                声部训练
-              </NButton>
-              <NButton quaternary focusable={false}>
-                小曲目
-              </NButton>
-              <NButton quaternary focusable={false}>
-                考级曲目
-              </NButton>
-            </NSpace>
-          </NFormItem>
-          <NFormItem label="乐器:">
-            <NSpace>
-              <NButton secondary strong type="primary">
-                全部
-              </NButton>
-              <NButton quaternary focusable={false} type="default">
-                竖笛
-              </NButton>
-              <NButton quaternary focusable={false} type="default">
-                排萧
-              </NButton>
-              <NButton quaternary focusable={false} type="default">
-                口风琴
-              </NButton>
-              <NButton quaternary focusable={false} type="default">
-                陶笛
-              </NButton>
-              <NButton quaternary focusable={false} type="default">
-                葫芦丝
-              </NButton>
+            <NSpace class={styles.spaceSection}>
+              {catchStore.getAllMusicCategories.map((music: any) => (
+                <NButton
+                  secondary={forms.musicSheetCategoriesId === music.id}
+                  quaternary={forms.musicSheetCategoriesId !== music.id}
+                  strong
+                  focusable={false}
+                  type={
+                    forms.musicSheetCategoriesId === music.id
+                      ? 'primary'
+                      : 'default'
+                  }
+                  onClick={() => {
+                    forms.musicSheetCategoriesId = music.id;
+                    throttledFn();
+                  }}>
+                  {music.name}
+                </NButton>
+              ))}
             </NSpace>
           </NFormItem>
+          <TheSearch
+            class={styles.inputSearch}
+            round
+            onSearch={(val: string) => {
+              forms.keyword = val;
+              throttledFn();
+            }}
+          />
         </NForm>
-        <TheSearch class={styles.inputSearch} round />
       </div>
     );
   }

+ 127 - 0
src/views/prepare-lessons/model/select-resources/index.module.less

@@ -0,0 +1,127 @@
+.selectMusic {
+  padding-bottom: 10px;
+
+  :global {
+    .n-tabs-tab-pad {
+      width: 80px !important;
+    }
+
+    .n-tabs-nav {
+      padding: 12px 20px 24px;
+    }
+
+    .n-tabs-tab {
+      color: #8B8D98;
+      font-size: 22px;
+      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;
+    }
+  }
+
+  .close {
+    padding: 0;
+
+    :global {
+      .n-icon {
+        font-size: var(--n-close-size);
+        color: var(--n-close-icon-color);
+      }
+    }
+  }
+}
+
+.listContainer {
+  margin-bottom: 20px;
+  max-height: 50vh;
+}
+
+// .list {
+//   margin-top: 10px;
+//   padding: 0 40px 0;
+//   margin-bottom: 12px;
+//   display: flex;
+//   flex-flow: row wrap;
+//   justify-content: flex-start;
+//   gap: 22px;
+// }
+
+// .searchGroup {
+//   position: relative;
+//   padding: 0 40px;
+
+//   :global {
+//     .n-form {
+//       .n-form-item .n-form-item-label {
+//         font-size: 17px;
+//         font-weight: 600;
+//         color: #131415;
+//         line-height: 24px;
+//       }
+
+//       .n-button {
+//         height: 32px;
+//         font-size: 17px;
+//         border-radius: 8px;
+//         color: rgba(0, 0, 0, 0.6);
+//       }
+
+//       .n-button--primary-type {
+//         color: #131415;
+//       }
+
+//       .n-form-item-feedback-wrapper {
+//         min-height: 14px;
+//       }
+//     }
+//   }
+
+//   .inputSearch {
+//     position: absolute;
+//     top: 4px;
+//     right: 40px;
+//     width: 360px;
+//     height: 42px;
+//     font-size: 16px;
+
+//     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;
+//       }
+//     }
+//   }
+// }

+ 40 - 0
src/views/prepare-lessons/model/select-resources/index.tsx

@@ -0,0 +1,40 @@
+import { NTabPane, NTabs } from 'naive-ui';
+import { defineComponent } from 'vue';
+import styles from './index.module.less';
+import SelectItem from './select-item';
+
+export default defineComponent({
+  name: 'select-music',
+  emits: ['select'],
+  setup(props, { emit }) {
+    return () => (
+      <div class={styles.selectMusic}>
+        <NTabs
+          animated
+          defaultValue="shareResources"
+          paneClass={styles.paneTitle}
+          justifyContent="center"
+          paneWrapperClass={styles.paneWrapperContainer}>
+          <NTabPane
+            name="shareResources"
+            tab="共享资源"
+            displayDirective="show:lazy">
+            <SelectItem type="shareResources" />
+          </NTabPane>
+          <NTabPane
+            name="myResources"
+            tab="我的资源"
+            displayDirective="show:lazy">
+            <SelectItem type="myResources" />
+          </NTabPane>
+          <NTabPane
+            name="myCollect"
+            tab="我的收藏"
+            displayDirective="show:lazy">
+            <SelectItem type="myCollect" />
+          </NTabPane>
+        </NTabs>
+      </div>
+    );
+  }
+});

+ 36 - 0
src/views/prepare-lessons/model/select-resources/select-item/index.module.less

@@ -0,0 +1,36 @@
+.listContainer {
+  margin: 0;
+  max-height: calc(var(--window-page-lesson-height) - 224px - 20px);
+
+  .listSection {
+    min-height: calc(var(--window-page-lesson-height) - 224px - 20px);
+  }
+
+  .emptySection {
+    display: flex;
+    align-items: center;
+  }
+
+  .list {
+    margin-top: 10px;
+    padding: 0 40px 0;
+    margin-bottom: 12px;
+    display: flex;
+    flex-flow: row wrap;
+    justify-content: flex-start;
+    gap: 22px;
+  }
+
+  .list {
+    // padding: 10px 0;
+    // text-align: center;
+
+    &>div {
+      margin-bottom: 20px;
+
+      &:last-child {
+        margin-bottom: 0;
+      }
+    }
+  }
+}

+ 187 - 0
src/views/prepare-lessons/model/select-resources/select-item/index.tsx

@@ -0,0 +1,187 @@
+import { PropType, defineComponent, onMounted, reactive } from 'vue';
+import ResourceSearchGroup from './resource-search-group';
+import { NScrollbar, NSpin, useDialog, useMessage } from 'naive-ui';
+import styles from './index.module.less';
+import CardType from '/src/components/card-type';
+import { materialQueryPage } from '/src/views/natural-resources/api';
+import TheEmpty from '/src/components/TheEmpty';
+import { usePrepareStore } from '/src/store/modules/prepareLessons';
+import { saveCourseware } from '../../../api';
+
+const formatType = (type: string) => {
+  if (type === 'shareResources') {
+    return 2;
+  } else if (type === 'myResources') {
+    return 3;
+  } else if (type === 'myCollect') {
+    return 4;
+  }
+};
+
+export default defineComponent({
+  name: 'share-resources',
+  props: {
+    type: {
+      type: String as PropType<'shareResources' | 'myResources' | 'myCollect'>,
+      default: 'shareResources'
+    }
+  },
+  setup(props) {
+    const prepareStore = usePrepareStore();
+    const message = useMessage();
+    const dialog = useDialog();
+    const state = reactive({
+      loading: false,
+      finshed: false, // 是否加载完
+      pagination: {
+        page: 1,
+        rows: 20
+      },
+      searchGroup: {
+        type: props.type === 'shareResources' ? 'MUSIC' : '', //
+        keyword: '',
+        bookVersionId: null,
+        subjectId: null,
+        sourceType: formatType(props.type),
+        enableFlag: true
+      },
+      tableList: [] as any
+    });
+
+    // 查询列表
+    const getList = async () => {
+      try {
+        if (state.pagination.page === 1) {
+          state.loading = true;
+        }
+        const { data } = await materialQueryPage({
+          ...state.searchGroup,
+          ...state.pagination,
+          subjectId: prepareStore.getSubjectId
+        });
+        state.loading = false;
+        const tempRows = data.rows || [];
+        const temp: any = [];
+        tempRows.forEach((row: any) => {
+          temp.push({
+            id: row.id,
+            coverImg: row.coverImg,
+            type: row.type,
+            title: row.name,
+            isCollect: !!row.favoriteFlag,
+            isSelected: row.sourceFrom === 'PLATFORM' ? true : false,
+            content: row.content
+          });
+        });
+        state.tableList.push(...temp);
+
+        state.finshed = data.pages <= data.current ? true : false;
+      } catch {
+        state.loading = false;
+      }
+    };
+
+    const onSearch = async (item: any) => {
+      state.pagination.page = 1;
+      state.tableList = [];
+      state.searchGroup = Object.assign(state.searchGroup, item);
+      getList();
+    };
+
+    // 添加资源
+    const onAdd = async (item: any) => {
+      dialog.warning({
+        title: '提示',
+        content: `是否添加"${item.title}"资源?`,
+        positiveText: '确定',
+        negativeText: '取消',
+        onPositiveClick: async () => {
+          try {
+            console.log(item, 'any');
+            const temp: any = [];
+            prepareStore.getCoursewareList.forEach((item: any) => {
+              temp.push({
+                materialId: item.materialId,
+                materialName: item.title,
+                materialType: item.type,
+                id: item.id
+              });
+            });
+
+            // 保存课件
+            await saveCourseware({
+              coursewareDetailKnowledgeId: prepareStore.getSelectKey,
+              lessonCoursewareId: prepareStore.getLessonCoursewareId,
+              lessonCoursewareDetailId:
+                prepareStore.getLessonCoursewareDetailId,
+              materialList: [
+                ...temp,
+                {
+                  materialName: item.title,
+                  materialType: item.type,
+                  materialId: item.id
+                }
+              ]
+            });
+
+            message.success('添加成功');
+            prepareStore.setIsAddResource(true);
+          } catch {
+            //
+          }
+        }
+      });
+    };
+
+    onMounted(() => {
+      getList();
+    });
+    return () => (
+      <div>
+        <ResourceSearchGroup
+          type={props.type}
+          onSearch={(item: any) => onSearch(item)}
+        />
+        <NScrollbar
+          class={styles.listContainer}
+          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 show={state.loading} size={'small'}>
+            <div
+              class={[
+                styles.listSection,
+                !state.loading && state.tableList.length <= 0
+                  ? styles.emptySection
+                  : ''
+              ]}>
+              {state.tableList.length > 0 && (
+                <div class={styles.list}>
+                  {state.tableList.map((item: any) => (
+                    <CardType
+                      isShowAdd
+                      item={item}
+                      onAdd={(item: any) => onAdd(item)}
+                    />
+                  ))}
+                </div>
+              )}
+              {!state.loading && state.tableList.length <= 0 && <TheEmpty />}
+            </div>
+          </NSpin>
+        </NScrollbar>
+      </div>
+    );
+  }
+});

+ 110 - 0
src/views/prepare-lessons/model/select-resources/select-item/resource-search-group/index.module.less

@@ -0,0 +1,110 @@
+.searchGroup {
+  position: relative;
+  padding: 0 40px;
+
+
+  .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;
+        }
+      }
+    }
+  }
+
+  :global {
+    .n-form {
+      position: relative;
+    }
+
+    .n-form-item {
+      .n-form-item-label {
+        font-size: 17px;
+        font-weight: 600;
+        color: #131415;
+        line-height: 24px;
+      }
+
+      .n-button {
+        height: 32px;
+        font-size: 17px;
+        border-radius: 8px;
+        color: rgba(0, 0, 0, 0.6);
+      }
+
+      .n-button--primary-type {
+        color: #131415;
+      }
+    }
+
+    .n-form-item-feedback-wrapper {
+      min-height: 14px;
+    }
+  }
+
+  .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;
+    padding-bottom: 20px;
+    border-bottom: 1px solid #F2F2F2;
+    margin-bottom: 20px;
+
+    .addTrain {
+      height: 37px;
+      border-radius: 8px;
+      font-size: 18px;
+      background-color: #E8F4FF;
+      color: #0378EC;
+
+      img {
+        width: 16px;
+        height: 16px;
+        margin-right: 8px;
+      }
+    }
+  }
+}
+
+.spaceSection {
+  // width: 72%;
+
+  &>div {
+    line-height: var(--n-blank-height);
+  }
+}

+ 108 - 0
src/views/prepare-lessons/model/select-resources/select-item/resource-search-group/index.tsx

@@ -0,0 +1,108 @@
+import { PropType, defineComponent, onMounted, reactive, ref } from 'vue';
+import styles from './index.module.less';
+import { NButton, NForm, NFormItem, NSpace } from 'naive-ui';
+import { resourceTypeArray } from '/src/utils/searchArray';
+import { useCatchStore } from '/src/store/modules/catchData';
+import { useThrottleFn } from '@vueuse/core';
+import TheSearch from '/src/components/TheSearch';
+
+export default defineComponent({
+  name: 'resource-search-group',
+  props: {
+    type: {
+      type: String as PropType<'shareResources' | 'myResources' | 'myCollect'>,
+      default: 'shareResources'
+    }
+  },
+  emits: ['search'],
+  setup(props, { emit }) {
+    const catchStore = useCatchStore();
+    const forms = reactive({
+      type: 'MUSIC', //
+      keyword: '',
+      bookVersionId: null
+    });
+    const resourceType = ref([] as any);
+
+    const onSearch = () => {
+      emit('search', forms);
+    };
+
+    const throttleFn = useThrottleFn(() => onSearch(), 500);
+
+    onMounted(async () => {
+      if (props.type === 'myResources' || props.type === 'myCollect') {
+        resourceType.value.push({
+          label: '全部',
+          value: ''
+        });
+        forms.type = ''; // 默认全部
+      }
+      resourceTypeArray.forEach((item: any) => {
+        if (props.type === 'myResources') {
+          item.value !== 'MUSIC' && resourceType.value.push(item);
+        } else {
+          resourceType.value.push(item);
+        }
+      });
+
+      // 获取教材分类列表
+      await catchStore.getMusicSheetCategory();
+    });
+    return () => (
+      <div class={styles.searchGroup}>
+        <div class={styles.searchCatatory}>
+          <NSpace size="small" class={styles.btnType}>
+            {resourceType.value.map((item: any) => (
+              <NButton
+                type={forms.type === item.value ? 'primary' : 'default'}
+                secondary={forms.type === item.value ? false : true}
+                round
+                size="small"
+                focusable={false}
+                onClick={() => {
+                  forms.type = item.value;
+                  onSearch();
+                }}>
+                {item.label}
+              </NButton>
+            ))}
+          </NSpace>
+
+          <TheSearch
+            class={styles.inputSearch}
+            round
+            onSearch={(val: string) => {
+              forms.keyword = val;
+              throttleFn();
+            }}
+          />
+        </div>
+        <NForm labelAlign="left" labelPlacement="left">
+          {forms.type === 'MUSIC' && (
+            <NFormItem label="教材:">
+              <NSpace class={styles.spaceSection}>
+                {catchStore.getAllMusicCategories.map((music: any) => (
+                  <NButton
+                    secondary={forms.bookVersionId === music.id}
+                    quaternary={forms.bookVersionId !== music.id}
+                    strong
+                    focusable={false}
+                    type={
+                      forms.bookVersionId === music.id ? 'primary' : 'default'
+                    }
+                    onClick={() => {
+                      forms.bookVersionId = music.value;
+                      throttleFn();
+                    }}>
+                    {music.name}
+                  </NButton>
+                ))}
+              </NSpace>
+            </NFormItem>
+          )}
+        </NForm>
+      </div>
+    );
+  }
+});