Browse Source

添加课件功能

lex 1 year ago
parent
commit
ea66884e97
29 changed files with 2622 additions and 105 deletions
  1. 1 1
      dev-dist/sw.js
  2. 1 1
      public/version.json
  3. 15 1
      src/components/TheMessageDialog/index.tsx
  4. 75 0
      src/views/prepare-lessons/api.ts
  5. 193 0
      src/views/prepare-lessons/components/lesson-main/courseware-presets/index.module.less
  6. 478 0
      src/views/prepare-lessons/components/lesson-main/courseware-presets/index.tsx
  7. 475 0
      src/views/prepare-lessons/components/lesson-main/courseware/addCourseware.module.less
  8. 693 0
      src/views/prepare-lessons/components/lesson-main/courseware/addCourseware.tsx
  9. 73 30
      src/views/prepare-lessons/components/lesson-main/index.tsx
  10. 27 60
      src/views/prepare-lessons/components/resource-main/components/resource-item/index.tsx
  11. 4 2
      src/views/prepare-lessons/components/resource-main/components/resource-item/resource-search-group/index.tsx
  12. 11 7
      src/views/prepare-lessons/components/resource-main/index.tsx
  13. BIN
      src/views/prepare-lessons/images/icon-arrow-right.png
  14. BIN
      src/views/prepare-lessons/images/icon-c-add.png
  15. BIN
      src/views/prepare-lessons/images/icon-c-delete.png
  16. BIN
      src/views/prepare-lessons/images/icon-c-down.png
  17. BIN
      src/views/prepare-lessons/images/icon-c-up.png
  18. BIN
      src/views/prepare-lessons/images/icon-courseware-delete.png
  19. BIN
      src/views/prepare-lessons/images/icon-courseware-edit.png
  20. BIN
      src/views/prepare-lessons/images/icon-courseware.png
  21. BIN
      src/views/prepare-lessons/images/icon-edit-name.png
  22. BIN
      src/views/prepare-lessons/images/icon-knowlage.png
  23. BIN
      src/views/prepare-lessons/images/icon-slide-right.png
  24. 6 1
      src/views/prepare-lessons/index.tsx
  25. 185 0
      src/views/prepare-lessons/model/courseware-type/index.module.less
  26. 104 0
      src/views/prepare-lessons/model/courseware-type/index.tsx
  27. 82 0
      src/views/prepare-lessons/model/related-class/index.module.less
  28. 197 0
      src/views/prepare-lessons/model/related-class/index.tsx
  29. 2 2
      vite.config.ts

+ 1 - 1
dev-dist/sw.js

@@ -82,7 +82,7 @@ define(['./workbox-5357ef54'], (function (workbox) { 'use strict';
     "revision": "3ca0b8505b4bec776b69afdba2768812"
   }, {
     "url": "index.html",
-    "revision": "0.3vo3lspd3m"
+    "revision": "0.u2i0a8cqof"
   }], {});
   workbox.cleanupOutdatedCaches();
   workbox.registerRoute(new workbox.NavigationRoute(workbox.createHandlerBoundToURL("index.html"), {

+ 1 - 1
public/version.json

@@ -1 +1 @@
-{"version":1709737149806}
+{"version":1709877240718}

+ 15 - 1
src/components/TheMessageDialog/index.tsx

@@ -16,13 +16,25 @@ export default defineComponent({
     content: {
       type: String,
       default: ''
+    },
+    contentDirection: {
+      type: String as PropType<'left' | 'center' | 'right'>,
+      default: 'center'
+    },
+    loading: {
+      type: Boolean,
+      default: false
     }
   },
   emits: ['close', 'confirm'],
   setup(props, { emit }) {
     return () => (
       <div class={styles.studentRemove}>
-        <p v-html={props.content}></p>
+        <p
+          style={{
+            textAlign: props.contentDirection
+          }}
+          v-html={props.content}></p>
 
         <NSpace class={styles.btnGroupModal} justify="center">
           <NButton round onClick={() => emit('close')}>
@@ -31,6 +43,8 @@ export default defineComponent({
           <NButton
             round
             type="primary"
+            loading={props.loading}
+            disabled={props.loading}
             onClick={() => {
               emit('confirm');
             }}>

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

@@ -205,3 +205,78 @@ export const lessonPreTrainingV2Remove = (params: any) => {
 export const lessonPreTrainingV2Detail = (params: any) => {
   return request.get('/edu-app/lessonPreTrainingV2/detail/' + params.id);
 };
+
+/**
+ * 课件列表
+ */
+export const teacherChapterLessonCoursewareList = (params: any) => {
+  return request.post('/edu-app/teacherChapterLessonCourseware/list', {
+    data: params
+  });
+};
+
+/**
+ * 课件 修改信息
+ */
+export const api_updateCoursewareInfo = (params: any) => {
+  return request.post(
+    '/edu-app/teacherChapterLessonCourseware/updateCoursewareInfo',
+    {
+      data: params
+    }
+  );
+};
+
+/**
+ * 课件 查询公开课件列表
+ */
+export const api_queryOpenCoursewareByPage = (params: any) => {
+  return request.post(
+    '/edu-app/teacherChapterLessonCourseware/queryOpenCoursewareByPage',
+    {
+      data: params
+    }
+  );
+};
+
+/**
+ * 课件 新增公开课件到我的课件
+ */
+export const api_addByOpenCourseware = (params: any) => {
+  return request.post(
+    '/edu-app/teacherChapterLessonCourseware/addByOpenCourseware',
+    {
+      data: params
+    }
+  );
+};
+
+/**
+ * 课件 新增课件
+ */
+export const api_add = (params: any) => {
+  return request.post('/edu-app//teacherChapterLessonCourseware/add', {
+    data: params
+  });
+};
+
+/**
+ * 课件 删除我的课件
+ */
+export const api_eacherChapterLessonCoursewareRemove = (params: any) => {
+  return request.post(
+    '/edu-app/teacherChapterLessonCourseware/remove?id=' + params.id,
+    {
+      requestType: 'form'
+    }
+  );
+};
+
+/**
+ * 课件 修改我的课件
+ */
+export const api_eacherChapterLessonCoursewareUpdate = (params: any) => {
+  return request.post('/edu-app/teacherChapterLessonCourseware/update', {
+    data: params
+  });
+};

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

@@ -0,0 +1,193 @@
+.coursewarePresetsContainer {
+  padding-bottom: 20px;
+
+  :global {
+    .n-base-selection {
+      --n-height: max(36px, 32Px) !important;
+      width: 220px;
+      font-size: max(13px, 12Px);
+      border-radius: 7px !important;
+    }
+
+    .n-base-selection-input__content {
+      font-size: max(15px, 13Px);
+    }
+  }
+}
+
+.openLoading {
+  min-height: 200px;
+}
+
+.coursewarePresets {
+  max-height: calc(var(--window-page-lesson-height) - 100px);
+  padding: 0 20px 0px;
+}
+
+.btnSubjectList {
+  :global {
+    .n-base-selection-input {
+      padding-left: 8px;
+    }
+  }
+}
+
+.addBtnIcon {
+  width: 13px !important;
+  height: 14px !important;
+  margin-right: 8px;
+}
+
+.addBtn {
+  height: max(36px, 32Px) !important;
+  background: #198cfe !important;
+  border-radius: 7Px !important;
+  padding: 0 20Px !important;
+  font-weight: 600 !important;
+  font-size: max(14px, 12Px) !important;
+}
+
+
+.title {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  font-weight: 600;
+  font-size: max(17px, 13Px);
+  color: #000000;
+  line-height: 23px;
+  text-align: left;
+  padding-bottom: 20px;
+
+  .titleLeft {
+    display: flex;
+    align-items: center;
+  }
+
+  .icon {
+    display: inline-block;
+    width: 20px;
+    height: 20px;
+    margin-right: 3px;
+  }
+
+  .iconWork {
+    background: url('../../../images/icon-knowlage.png') no-repeat center center;
+    background-size: contain;
+  }
+
+  .iconCourseware {
+    background: url('../../../images/icon-courseware.png') no-repeat center center;
+    background-size: contain;
+  }
+
+  &.line {
+    margin-top: 15px;
+    border-top: 1px solid #F2F2F2;
+    padding-top: 30px;
+
+    .more {
+      display: flex;
+      align-items: center;
+      cursor: pointer;
+      padding-left: 10px;
+      font-weight: 400;
+      font-size: max(13px, 11Px);
+      color: #999999;
+
+      &:hover {
+        color: #1677FF;
+      }
+    }
+
+    .swipeControll {
+      height: 25px;
+
+      .leftIcon {
+        transform: rotate(180deg);
+      }
+
+      img {
+        cursor: pointer;
+        width: 25px;
+        height: 25px;
+      }
+    }
+  }
+}
+
+.list {
+  display: flex;
+  flex-flow: row wrap;
+  justify-content: flex-start;
+  padding: 12px 0 12px;
+  gap: 20px 0;
+  margin: 10px -10px 0;
+  min-height: 300px;
+
+  &.listSame {
+    margin-top: 0;
+    padding-top: 0;
+  }
+
+  .itemWrap {
+    width: calc(100% / 4);
+    // padding-bottom: calc(100% / 3 * 0.73333);
+    // position: relative;
+
+    .itemWrapBox {
+      //   position: absolute;
+      //   left: 0;
+      //   top: 0;
+      //   width: 100%;
+      //   height: 100%;
+      padding: 0 10px;
+    }
+  }
+
+}
+
+.itemWrapBox {
+  padding: 0 10px;
+}
+
+.attendClassModal {
+  width: 1200px;
+  border-radius: 16px;
+  overflow: hidden;
+}
+
+.removeVisiable1 {
+  width: 432px;
+
+  :global {
+    .n-card-header {
+      font-size: max(22px, 16Px);
+    }
+  }
+
+  .studentRemove {
+    padding: 20px 40px 0;
+
+    p {
+      font-size: max(18px, 14Px);
+      color: #777777;
+      line-height: 30px;
+
+      span {
+        color: #EA4132;
+      }
+    }
+  }
+
+  .btnGroupModal {
+    padding: 32px 0;
+
+    :global {
+      .n-button {
+        height: 47px;
+        min-width: 156px;
+      }
+    }
+  }
+}

+ 478 - 0
src/views/prepare-lessons/components/lesson-main/courseware-presets/index.tsx

@@ -0,0 +1,478 @@
+import { defineComponent, onMounted, reactive, watch } from 'vue';
+import styles from './index.module.less';
+import {
+  NButton,
+  NCarousel,
+  NIcon,
+  NImage,
+  NInput,
+  NModal,
+  NScrollbar,
+  NSelect,
+  NSpace,
+  NSpin,
+  useMessage
+} from 'naive-ui';
+import { usePrepareStore } from '/src/store/modules/prepareLessons';
+import add from '@/views/studentList/images/add.png';
+import iconSlideRight from '../../../images/icon-slide-right.png';
+import CoursewareType from '../../../model/courseware-type';
+import TheEmpty from '/src/components/TheEmpty';
+import RelatedClass from '../../../model/related-class';
+import { useResizeObserver } from '@vueuse/core';
+import {
+  api_addByOpenCourseware,
+  api_eacherChapterLessonCoursewareRemove,
+  api_queryOpenCoursewareByPage,
+  api_updateCoursewareInfo,
+  teacherChapterLessonCoursewareList
+} from '../../../api';
+import { useRoute } from 'vue-router';
+import TheMessageDialog from '/src/components/TheMessageDialog';
+import { eventGlobal } from '/src/utils';
+
+export default defineComponent({
+  name: 'courseware-presets',
+  emits: ['change'],
+  setup(props, { emit }) {
+    const prepareStore = usePrepareStore();
+    const message = useMessage();
+    const route = useRoute();
+    const localStorageSubjectId = localStorage.getItem(
+      'prepareLessonSubjectId'
+    );
+    const forms = reactive({
+      // 选取参数带的,后取缓存
+      messageLoading: false,
+      subjectId: route.query.subjectId
+        ? Number(route.query.subjectId)
+        : localStorageSubjectId
+        ? Number(localStorageSubjectId)
+        : null,
+      courseScheduleSubjectId: route.query.courseScheduleSubjectId,
+      bodyWidth: '100%',
+      loading: false,
+      openLoading: false,
+      showRelatedClass: false,
+      tableList: [] as any,
+      openTableList: [] as any,
+      selectItem: {} as any,
+      editTitleVisiable: false,
+      editTitle: null,
+      editBtnLoading: false,
+      preRemoveVisiable: false,
+      carouselIndex: 0
+    });
+
+    const getCoursewareList = async () => {
+      forms.loading = true;
+      try {
+        // 判断是否有选择对应的课件 或声部
+        if (!prepareStore.getSelectKey) return (forms.loading = false);
+        const { data } = await teacherChapterLessonCoursewareList({
+          subjectId: prepareStore.getSubjectId,
+          coursewareDetailKnowledgeId: prepareStore.getSelectKey
+        });
+        if (!Array.isArray(data)) {
+          return;
+        }
+        const tempList: any = [];
+        data.forEach((item: any) => {
+          const firstItem: any =
+            item.chapterKnowledgeList[0]?.chapterKnowledgeMaterialList[0];
+          tempList.push({
+            id: item.id,
+            openFlag: item.openFlag,
+            openFlagEnable: item.openFlagEnable,
+            subjectNames: item.subjectNames,
+            fromChapterLessonCoursewareId: item.fromChapterLessonCoursewareId,
+            name: item.name,
+            coverImg: firstItem?.bizInfo.coverImg,
+            type: firstItem?.bizInfo.type
+          });
+        });
+
+        forms.tableList = tempList;
+      } catch {
+        //
+      }
+      forms.loading = false;
+    };
+
+    const getOpenCoursewareList = async () => {
+      // 查询公开课件列表
+      forms.openLoading = true;
+      try {
+        // 判断是否有选择对应的课件 或声部
+        if (!prepareStore.getSelectKey) return (forms.openLoading = false);
+        const { data } = await api_queryOpenCoursewareByPage({
+          subjectId: prepareStore.getSubjectId,
+          coursewareDetailKnowledgeId: prepareStore.getSelectKey,
+          page: 1,
+          rows: 20
+        });
+        const result = data.rows || [];
+        const tempList: any = [];
+        result.forEach((item: any) => {
+          const index = forms.tableList.findIndex(
+            (i: any) => i.fromChapterLessonCoursewareId === item.id
+          );
+          const firstItem: any =
+            item.chapterKnowledgeList[0]?.chapterKnowledgeMaterialList[0];
+          tempList.push({
+            id: item.id,
+            openFlag: item.openFlag,
+            openFlagEnable: item.openFlagEnable,
+            subjectNames: item.subjectNames,
+            fromChapterLessonCoursewareId: item.fromChapterLessonCoursewareId,
+            name: item.name,
+            coverImg: firstItem?.bizInfo.coverImg,
+            type: firstItem?.bizInfo.type,
+            isAdd: index !== -1 ? true : false
+          });
+        });
+
+        forms.openTableList = tempList;
+      } catch {
+        //
+      }
+      forms.openLoading = false;
+    };
+
+    // 监听选择的key 左侧选择了其它的课
+    watch(
+      () => [prepareStore.getSelectKey, prepareStore.getSubjectId],
+      async () => {
+        await getCoursewareList();
+        await getOpenCoursewareList();
+      }
+    );
+
+    // 检测数据是否存在
+    watch(
+      () => forms.tableList,
+      () => {
+        // fromChapterLessonCoursewareId;
+        forms.openTableList.forEach((item: any) => {
+          const index = forms.tableList.findIndex(
+            (i: any) => i.fromChapterLessonCoursewareId === item.id
+          );
+          item.isAdd = index !== -1 ? true : false;
+        });
+      }
+    );
+
+    const checkSubjectIds = () => {
+      const subjectList = prepareStore.getSubjectList;
+
+      // 并且没有声部时才会更新
+      if (subjectList.length > 0) {
+        // 并且声部在列表中
+        const localStorageSubjectId = localStorage.getItem(
+          'prepareLessonSubjectId'
+        );
+        // // 先取 上次上课声部,在取班级声部 最后取缓存
+        let subjectId = null;
+        let index = -1;
+        if (forms.courseScheduleSubjectId) {
+          // 判断浏览器上面是否有
+          index = subjectList.findIndex(
+            (subject: any) => subject.id == forms.courseScheduleSubjectId
+          );
+          if (index >= 0) {
+            subjectId = Number(forms.courseScheduleSubjectId);
+          }
+        }
+        // 判断班级上面声部 & 还没有声部
+        if (forms.subjectId && !subjectId) {
+          // 判断浏览器上面是否有
+          index = subjectList.findIndex(
+            (subject: any) => subject.id == forms.subjectId
+          );
+          if (index >= 0) {
+            subjectId = Number(forms.subjectId);
+          }
+        }
+        // 缓存声部 & 还没有声部
+        if (localStorageSubjectId && !subjectId) {
+          // 判断浏览器上面是否有
+          index = subjectList.findIndex(
+            (subject: any) => subject.id == localStorageSubjectId
+          );
+          if (index >= 0) {
+            subjectId = Number(localStorageSubjectId);
+          }
+        }
+        if (subjectId && index >= 0) {
+          prepareStore.setSubjectId(subjectId);
+        } else {
+          // 判断是否有缓存
+          prepareStore.setSubjectId(subjectList[0].id);
+        }
+
+        // 保存
+        localStorage.setItem(
+          'prepareLessonSubjectId',
+          prepareStore.getSubjectId as any
+        );
+      }
+    };
+
+    onMounted(async () => {
+      prepareStore.setClassGroupId(route.query.classGroupId as any);
+      // 获取教材分类列表
+      checkSubjectIds();
+
+      useResizeObserver(
+        document.querySelector('#coursewarePresets') as HTMLElement,
+        (entries: any) => {
+          const entry = entries[0];
+          const { width } = entry.contentRect;
+          forms.bodyWidth = width + 'px';
+        }
+      );
+
+      await getCoursewareList();
+      await getOpenCoursewareList();
+    });
+
+    // 重命名
+    const onEditTitleSubmit = async () => {
+      try {
+        await api_updateCoursewareInfo({
+          id: forms.selectItem.id,
+          name: forms.editTitle
+        });
+        message.success('修改成功');
+        getCoursewareList();
+        forms.editTitleVisiable = false;
+      } catch {
+        //
+      }
+    };
+
+    // 删除
+    const onRemove = async () => {
+      forms.messageLoading = true;
+      try {
+        await api_eacherChapterLessonCoursewareRemove({
+          id: forms.selectItem.id
+        });
+        message.success('删除成功');
+        getCoursewareList();
+        forms.preRemoveVisiable = false;
+      } catch {
+        //
+      }
+      setTimeout(() => {
+        forms.messageLoading = false;
+      }, 100);
+    };
+
+    // 添加课件
+    const onAddCourseware = async (item: any) => {
+      if (forms.messageLoading) return;
+      forms.messageLoading = true;
+      try {
+        await api_addByOpenCourseware({ id: item.id });
+        message.success('添加成功');
+        getCoursewareList();
+      } catch {
+        //
+      }
+      setTimeout(() => {
+        forms.messageLoading = false;
+      }, 100);
+    };
+    return () => (
+      <div class={styles.coursewarePresetsContainer}>
+        <NScrollbar class={styles.coursewarePresets}>
+          <div class={styles.title} id="coursewarePresets">
+            <div class={styles.titleLeft}>
+              <i class={[styles.icon, styles.iconWork]}></i>
+              我的课件
+            </div>
+          </div>
+
+          <NSpace>
+            <NSelect
+              placeholder="选择声部"
+              class={styles.btnSubjectList}
+              options={[
+                { name: '全部声部', id: '' },
+                ...prepareStore.getSubjectList
+              ]}
+              labelField="name"
+              valueField="id"
+              value={prepareStore.getSubjectId}
+              onUpdate:value={(val: any) => {
+                prepareStore.setSubjectId(val);
+                // 保存
+              }}
+            />
+            <NButton
+              class={styles.addBtn}
+              type="primary"
+              onClick={() => {
+                eventGlobal.emit('teacher-slideshow', true);
+                emit('change', { status: true });
+              }}>
+              <NImage
+                class={styles.addBtnIcon}
+                previewDisabled
+                src={add}></NImage>
+              添加课件
+            </NButton>
+          </NSpace>
+          <div style={{ overflow: 'hidden' }}>
+            <NSpin show={forms.loading}>
+              <div class={styles.list}>
+                {forms.tableList.map((item: any) => (
+                  <div class={[styles.itemWrap, styles.itemBlock, 'row-nav']}>
+                    <div class={styles.itemWrapBox}>
+                      <CoursewareType
+                        operate
+                        isEditName
+                        item={item}
+                        onEditName={() => {
+                          forms.selectItem = item;
+                          forms.editTitle = item.name;
+                          forms.editTitleVisiable = true;
+                        }}
+                        onDelete={() => {
+                          forms.selectItem = item;
+                          forms.preRemoveVisiable = true;
+                        }}
+                      />
+                    </div>
+                  </div>
+                ))}
+                {!forms.loading && forms.tableList.length <= 0 && <TheEmpty />}
+              </div>
+            </NSpin>
+          </div>
+
+          <div class={[styles.title, styles.line]}>
+            <div class={styles.titleLeft}>
+              <i class={[styles.icon, styles.iconCourseware]}></i>
+              相关课件
+              {forms.openTableList.length > 4 && (
+                <span
+                  class={styles.more}
+                  onClick={() => (forms.showRelatedClass = true)}>
+                  查看更多
+                  <NIcon>
+                    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
+                      <path
+                        d="M8.59 16.59L13.17 12L8.59 7.41L10 6l6 6l-6 6l-1.41-1.41z"
+                        fill="currentColor"></path>
+                    </svg>
+                  </NIcon>
+                </span>
+              )}
+            </div>
+
+            {forms.openTableList.length > 4 && (
+              <NSpace class={styles.swipeControll}>
+                <div>
+                  <NImage
+                    previewDisabled
+                    class={[styles.leftIcon]}
+                    src={iconSlideRight}
+                  />
+                </div>
+                <div>
+                  <NImage previewDisabled src={iconSlideRight} />
+                </div>
+              </NSpace>
+            )}
+          </div>
+
+          <NSpin show={forms.openLoading} class={styles.openLoading}>
+            <NCarousel
+              slidesPerView={4}
+              loop={false}
+              style={{ width: forms.bodyWidth }}
+              v-model:currentIndex={forms.carouselIndex}>
+              {forms.openTableList.map((item: any) => (
+                <div class={[styles.itemWrap, styles.itemBlock, 'row-nav']}>
+                  <div class={styles.itemWrapBox}>
+                    <CoursewareType
+                      isShowAdd
+                      item={item}
+                      onAdd={() => onAddCourseware(item)}
+                    />
+                  </div>
+                </div>
+              ))}
+            </NCarousel>
+          </NSpin>
+        </NScrollbar>
+
+        <NModal
+          v-model:show={forms.showRelatedClass}
+          preset="card"
+          showIcon={false}
+          class={['modalTitle background', styles.attendClassModal]}
+          title={'相关课件'}
+          blockScroll={false}>
+          <RelatedClass
+            tableList={forms.tableList}
+            subjectList={prepareStore.getSubjectList}
+            subjectId={prepareStore.getSubjectId as any}
+            coursewareDetailKnowledgeId={prepareStore.getSelectKey}
+            onClose={() => (forms.showRelatedClass = false)}
+            onAdd={(item: any) => onAddCourseware(item)}
+          />
+        </NModal>
+
+        <NModal
+          v-model:show={forms.editTitleVisiable}
+          preset="card"
+          class={['modalTitle', styles.removeVisiable1]}
+          title={'课件重命名'}>
+          <div class={styles.studentRemove}>
+            <NInput
+              placeholder="请输入课件名称"
+              v-model:value={forms.editTitle}
+              maxlength={15}
+              onKeyup={(e: any) => {
+                if (e.code === 'ArrowLeft' || e.code === 'ArrowRight') {
+                  e.stopPropagation();
+                }
+              }}
+            />
+
+            <NSpace class={styles.btnGroupModal} justify="center">
+              <NButton round onClick={() => (forms.editTitleVisiable = false)}>
+                取消
+              </NButton>
+              <NButton
+                round
+                type="primary"
+                onClick={onEditTitleSubmit}
+                loading={forms.editBtnLoading}>
+                确定
+              </NButton>
+            </NSpace>
+          </div>
+        </NModal>
+
+        <NModal
+          v-model:show={forms.preRemoveVisiable}
+          preset="card"
+          class={['modalTitle', styles.removeVisiable1]}
+          title={'保存预设'}>
+          <TheMessageDialog
+            content={`<p style="text-align: left;">请确认是否删除【${forms.selectItem.name}】,删除后不可恢复</p>`}
+            cancelButtonText="取消"
+            confirmButtonText="确认"
+            loading={forms.messageLoading}
+            onClose={() => (forms.preRemoveVisiable = false)}
+            onConfirm={() => onRemove()}
+          />
+        </NModal>
+      </div>
+    );
+  }
+});

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

@@ -0,0 +1,475 @@
+.btnGroup {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  margin-left: 22px !important;
+  margin-right: 22px !important;
+  // height: 40px;
+  padding-bottom: 25px;
+  border-bottom: 1px solid #F0F0F0;
+  margin-bottom: 10px;
+
+
+  .btnClassList {
+    :global {
+      .n-base-selection {
+        width: 200px !important;
+      }
+
+      .n-base-selection-label {
+        &::before {
+          margin-left: 12px;
+          content: ' ';
+          width: max(20px, 14Px);
+          height: max(20px, 14Px);
+          flex-shrink: 0;
+          background: url('../../../images/icon-class-name.png') no-repeat center;
+          background-size: contain;
+        }
+      }
+
+      .n-base-selection-input {
+        padding-left: 8px;
+      }
+
+      .n-base-selection.n-base-selection--disabled {
+        cursor: pointer;
+      }
+
+      .n-base-selection.n-base-selection--disabled .n-base-selection-label {
+        background-color: #fff;
+        cursor: pointer;
+      }
+
+      .n-base-selection.n-base-selection--disabled .n-base-selection-label .n-base-selection-input {
+        color: var(--n-text-color);
+        cursor: pointer;
+      }
+
+      .n-base-selection .n-base-selection-overlay {
+        left: 24px;
+      }
+    }
+  }
+
+
+  :global {
+
+    .n-input,
+    .n-base-selection {
+      --n-height: max(40px, 36Px) !important;
+      width: 200px !important;
+      font-size: 15px;
+      border-radius: 8px !important;
+    }
+
+    .n-base-selection-input__content {
+      font-size: max(15px, 13Px);
+    }
+
+    .n-button {
+      border-radius: 8px;
+      height: 38px;
+      font-size: max(18px, 13Px);
+      font-weight: 600 !important;
+      padding: 0 27px;
+    }
+
+    .n-button--default-type {
+      background: #E8F4FF;
+      color: #0378EC;
+
+      &:not(.n-button--disabled):hover {
+        background: #E8F4FF;
+      }
+
+      .n-button__border {
+        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;
+      }
+    }
+  }
+
+  .btnClassStart {
+    background: #F44541 !important;
+    color: #fff !important;
+
+    :global {
+      .n-button__border {
+        border: 1px solid #F44541;
+      }
+    }
+  }
+
+  .btnItem {
+    display: flex;
+    align-items: center;
+    height: max(40px, 36Px) !important;
+
+    .btnTitle {
+      flex-shrink: 0;
+      font-size: max(18px, 13Px);
+      font-weight: 600;
+
+      span {
+        color: #EA4132;
+      }
+    }
+
+    // &:last-child {
+    //   margin-left: 12px;
+    // }
+  }
+}
+
+.tipsContainer {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  flex: 1 auto;
+  margin-right: 30px;
+  height: 40px;
+  padding: 0 16px;
+  background: #EDF7FF;
+  border-radius: 9px;
+  max-width: 600px;
+
+  .tipsLeft {
+    display: flex;
+    align-items: center;
+    font-size: max(16px, 12Px);
+    color: rgba(0, 0, 0, 0.88);
+
+
+    .tips {
+      line-height: 38px;
+    }
+  }
+
+  .iconTips {
+    margin-right: 9px;
+    width: 18px;
+    height: 18px;
+  }
+
+  .btnNoTips {
+    font-size: max(16px, 12Px);
+    font-weight: 500;
+    color: #1677FF;
+    cursor: pointer;
+  }
+}
+
+.btnGroupClass {
+  padding-top: 10px;
+}
+
+.listContainer {
+  margin-top: 12px;
+  // // 52 + 28 + 38 + 38 多余10像素空间
+  // max-height: calc(var(--window-page-lesson-height) - 196px);
+
+  // .listSection {
+  //   min-height: calc(var(--window-page-lesson-height) - 196px);
+  // }
+
+  // &.listContainerDrag {
+  //   max-height: calc(var(--window-page-lesson-height) - 148px);
+
+  //   .listSection {
+  //     min-height: calc(var(--window-page-lesson-height) - 148px);
+  //   }
+  // }
+  max-height: calc(var(--window-page-lesson-height) - 184px);
+
+  .listSection {
+    min-height: calc(var(--window-page-lesson-height) - 184px);
+  }
+
+  .emptySection {
+    display: flex;
+    align-items: center;
+  }
+}
+
+
+.listItems {
+  position: relative;
+  background: #F8F9FC;
+  border-radius: 13px;
+  margin: 0 20px;
+
+  &+.listItems {
+    margin-top: 13px;
+  }
+}
+
+.knowledgePoint {
+  .btnItem {
+    padding: 13px 13px 0;
+    display: flex;
+    align-items: center;
+    // height: max(40px, 36Px) !important;
+
+    .btnTitle {
+      flex-shrink: 0;
+      font-size: max(18px, 13Px);
+      font-weight: 600;
+
+      span {
+        color: #EA4132;
+      }
+    }
+
+  }
+
+  :global {
+
+    .n-input,
+    .n-base-selection {
+      --n-height: max(40px, 36Px) !important;
+      width: 200px !important;
+      font-size: 15px;
+      border-radius: 8px !important;
+    }
+
+  }
+}
+
+.operationGroup {
+  position: absolute;
+  right: 13px;
+  top: 18px;
+
+  i {
+    display: inline-block;
+    width: max(27px, 24Px);
+    height: max(27px, 24Px);
+    cursor: pointer;
+  }
+
+  .iconCUp {
+    background: url('../../../images/icon-c-up.png') no-repeat center;
+    background-size: contain;
+  }
+
+  .iconCDown {
+    background: url('../../../images/icon-c-down.png') no-repeat center;
+    background-size: contain;
+  }
+
+  .iconCRemove {
+    background: url('../../../images/icon-c-delete.png') no-repeat center;
+    background-size: contain;
+  }
+}
+
+.addKnowledgePoint {
+  margin: 13px;
+  width: calc(100% - 26px) !important;
+  --n-border-radius: 7px !important;
+  --n-height: max(40px, 36Px) !important;
+  font-weight: 600 !important;
+  --n-font-size: max(14px, 12Px) !important;
+  color: #0378EC;
+
+  .iconCAdd {
+    width: max(13px, 11Px);
+    height: max(14px, 12Px);
+    margin-right: 8px;
+    background: url('../../../images/icon-c-add.png') no-repeat center;
+    background-size: contain;
+  }
+}
+
+
+.list {
+  display: flex;
+  flex-flow: row wrap;
+  justify-content: flex-start;
+
+  padding: 17px 7px 17px;
+  display: flex;
+  flex-flow: row wrap;
+  justify-content: flex-start;
+  gap: 20px 0;
+
+
+  .itemWrap {
+    width: calc(100% / 4);
+    padding-bottom: calc(100% / 4 * 0.73333);
+    position: relative;
+
+    .itemWrapBox {
+      position: absolute;
+      left: 0;
+      top: 0;
+      width: 100%;
+      height: 100%;
+      padding: 0 6px;
+    }
+  }
+
+  :global {
+    .card-section-container {
+      width: 100%;
+      height: 100%;
+    }
+  }
+}
+
+.attendClassModal {
+  width: 800px;
+  border-radius: 16px;
+  overflow: hidden;
+}
+
+.itemBlock {
+  position: relative;
+
+  .itemOperation {
+    position: absolute;
+    top: 0;
+    left: 10px;
+
+    width: calc(100% - 20px);
+    height: 100%;
+    text-align: right;
+    z-index: 98;
+    cursor: move;
+  }
+
+  .iconDelete {
+    width: 27px;
+    height: 27px;
+    margin-top: 8px;
+    margin-right: 8px;
+    cursor: pointer;
+  }
+}
+
+.removeVisiable {
+  width: 432px;
+
+  :global {
+    .n-card-header {
+      font-size: max(22px, 16Px);
+    }
+  }
+
+
+  .studentRemove {
+    padding: 20px 40px 0;
+
+    p {
+      font-size: max(18px, 14Px);
+      color: #777777;
+      line-height: 30px;
+      text-align: center;
+
+      span {
+        color: #EA4132;
+      }
+    }
+  }
+
+  .btnGroupModal {
+    padding: 32px 0;
+
+    :global {
+      .n-button {
+        height: 47px;
+        min-width: 156px;
+      }
+    }
+  }
+}
+
+.removeVisiable1 {
+  width: 462px;
+
+  :global {
+    .n-card-header {
+      font-size: max(22px, 16Px);
+    }
+  }
+
+  .studentRemove {
+    padding: 20px 40px 0;
+
+    p {
+      font-size: max(18px, 14Px);
+      color: #777777;
+      line-height: 30px;
+
+      span {
+        color: #EA4132;
+      }
+    }
+  }
+
+  .btnGroupModal {
+    padding: 32px 0;
+
+    :global {
+      .n-button {
+        height: 47px;
+        min-width: 156px;
+      }
+    }
+  }
+}
+
+.addMusicItem {
+  position: relative;
+  box-sizing: border-box;
+  width: 100%;
+  height: 100%;
+  border-radius: 14px;
+  background: #F9FAFD;
+  display: inline-flex;
+  transition: all .3s ease-in-out;
+  border: 1Px solid rgba(209, 216, 235, 1) !important;
+  cursor: pointer;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  flex-direction: column;
+
+  img {
+    width: 30Px;
+    height: 30Px;
+  }
+
+  .addMusicName {
+    padding-top: 16px;
+    font-size: max(16px, 13Px);
+    color: #131415;
+  }
+
+  // 鼠标经过时样式
+  &:hover {
+    transform: scale(1.01);
+    transition: all .3s ease-in-out;
+    border: 2px solid rgba(0, 122, 254, 1) !important;
+  }
+}
+
+
+.subjectSyncModal {
+  width: 1070px;
+}

+ 693 - 0
src/views/prepare-lessons/components/lesson-main/courseware/addCourseware.tsx

@@ -0,0 +1,693 @@
+import {
+  defineComponent,
+  nextTick,
+  onMounted,
+  onUnmounted,
+  reactive,
+  watch
+} from 'vue';
+import styles from './addCourseware.module.less';
+import {
+  NButton,
+  NModal,
+  NScrollbar,
+  NSelect,
+  NSpace,
+  NSpin,
+  useMessage,
+  useDialog,
+  NSwitch,
+  NInput,
+  NTooltip,
+  NImage,
+  NIcon
+} 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 {
+  api_add,
+  courseScheduleStart,
+  queryCourseware,
+  saveCourseware,
+  teacherKnowledgeMaterialDelete
+} from '../../../api';
+import Draggable from 'vuedraggable';
+import iconDelete from '../../../images/icon-delete.png';
+import iconAddMusic from '../../../images/icon-add-music.png';
+import { onBeforeRouteUpdate, useRoute, useRouter } from 'vue-router';
+import deepClone from '/src/helpers/deep-clone';
+import CardPreview from '/src/components/card-preview';
+import PreviewWindow from '/src/views/preview-window';
+import { state } from '/src/state';
+import SubjectSync from '../../../model/subject-sync';
+import { eventGlobal } from '/src/utils';
+import iconTips from '../../../images/icon-tips.png';
+import TheMessageDialog from '/src/components/TheMessageDialog';
+export default defineComponent({
+  name: 'courseware-modal',
+  emits: ['change'],
+  setup(props, { emit }) {
+    const catchStore = useCatchStore();
+    const prepareStore = usePrepareStore();
+    const route = useRoute();
+    const router = useRouter();
+    const dialog = useDialog();
+    const message = useMessage();
+
+    const forms = reactive({
+      subjects: [] as any,
+      name: '',
+      openFlag: false,
+      coursewareList: [
+        {
+          name: '',
+          list: [] as any
+        }
+      ] as any,
+      loadingStatus: false,
+      showAttendClass: false,
+      attendClassType: 'change', //
+      removeIds: [] as any, // 临时删除的编号
+      editSubjectIds: '', // 声部编号
+      removeVisiable: false,
+      addCoursewareItem: {} as any,
+      messageOperation: {
+        visiable: false,
+        type: 'delete' as 'delete' | 'addItem' | 'save',
+        contentDirection: 'center' as 'left' | 'center' | 'right',
+        title: '删除知识点',
+        content: '请确认是否删除该知识点,删除知识点后将同步删除知识点下的资源',
+        cancelButtonText: '取消',
+        confirmButtonText: '确认',
+        index: 0
+      },
+      subjectSyncVisiable: false, // 同步声部
+      show: false,
+      item: {} as any,
+      previewModal: false,
+      previewParams: {
+        type: '',
+        subjectId: '',
+        detailId: ''
+      } as any
+    });
+
+    // 获取列表
+    const getList = async () => {
+      forms.loadingStatus = true;
+      try {
+        // const { data } = await queryCourseware({
+        //   coursewareDetailKnowledgeId: prepareStore.getSelectKey,
+        //   subjectId: prepareStore.getSubjectId,
+        //   page: 1,
+        //   rows: 99
+        // });
+        // const tempRows = data.rows || [];
+        // const temp: any = [];
+        // tempRows.forEach((row: any) => {
+        //   temp.push({
+        //     id: row.id,
+        //     materialId: row.materialId,
+        //     coverImg: row.coverImg,
+        //     type: row.materialType,
+        //     title: row.materialName,
+        //     isCollect: !!row.favoriteFlag,
+        //     isSelected: row.source === 'PLATFORM' ? true : false,
+        //     content: row.content,
+        //     removeFlag: row.removeFlag
+        //   });
+        // });
+        // prepareStore.setCoursewareList(temp || []);
+        // const tempCourse: any = [];
+        // temp.forEach((item: any) => {
+        //   if (!forms.removeIds.includes(item.id)) {
+        //     tempCourse.push(item);
+        //   }
+        // });
+        // forms.coursewareList = tempCourse;
+      } catch {
+        //
+      }
+      forms.loadingStatus = false;
+    };
+
+    // 删除
+    const onDelete = (item: any) => {
+      //
+      forms.removeIds.push(item.id);
+      const index = forms.coursewareList.findIndex(
+        (c: any) => c.id === item.id
+      );
+      forms.coursewareList.splice(index, 1);
+    };
+
+    // 完成编辑
+    const onOverEdit = async () => {
+      try {
+        const temp: any = [];
+        forms.coursewareList.forEach((item: any) => {
+          temp.push({
+            materialName: item.name,
+            materialType: item.type,
+            materialId: item.materialId,
+            id: item.id
+          });
+        });
+        // 保存课件
+        // 判断是否编辑,如果编辑则取选择的声部
+        await saveCourseware({
+          coursewareDetailKnowledgeId: prepareStore.getSelectKey,
+          lessonCoursewareId: prepareStore.getLessonCoursewareId,
+          lessonCoursewareDetailId: prepareStore.getLessonCoursewareDetailId,
+          // subjectId: forms.isEdit
+          //   ? forms.editSubjectIds
+          //   : prepareStore.getSubjectId,
+          materialList: [...temp]
+        });
+
+        message.success('编辑成功');
+        forms.removeVisiable = false;
+        prepareStore.setIsEditResource(false);
+        // 重置临时删除编号
+        forms.removeIds = [];
+        await getList();
+      } catch {
+        //
+      }
+    };
+
+    const isPointInsideElement = (element: any, x: number, y: number) => {
+      const rect = element.getBoundingClientRect();
+      return (
+        x >= rect.left && x <= rect.right && y >= rect.top && y <= rect.bottom
+      );
+    };
+    const isPointOnLeft = (element: any, x: number) => {
+      const rect = element.getBoundingClientRect();
+      const elementCenterX = rect.left + rect.width / 2;
+      return x < elementCenterX;
+    };
+
+    // 操作
+    const onChangePoint = (type: string, index: number) => {
+      if (type === 'up') {
+        //
+      } else if (type === 'down') {
+        //
+      } else if (type === 'remove') {
+        forms.messageOperation = {
+          visiable: true,
+          type: 'delete',
+          contentDirection: 'left',
+          title: '删除知识点',
+          content:
+            '请确认是否删除该知识点,删除知识点后将同步删除知识点下的资源',
+          cancelButtonText: '取消',
+          confirmButtonText: '确认',
+          index
+        };
+      }
+    };
+
+    //
+    const onMessageConfirm = async () => {
+      const type = forms.messageOperation.type;
+      if (type === 'delete') {
+        forms.coursewareList.splice(forms.messageOperation.index, 1);
+      } else if (type === 'addItem') {
+        forms.coursewareList.push({ name: '', list: [] });
+        addCoursewareItem(forms.addCoursewareItem);
+      } else if (type === 'save') {
+        await onSaveCourseWare();
+      }
+      forms.messageOperation.visiable = false;
+    };
+
+    const addCoursewareItem = (item: any, point?: any) => {
+      nextTick(() => {
+        if (point) {
+          const dom = document.querySelectorAll('.row-nav');
+          let isAdd = false;
+          dom.forEach((child: any, index: number) => {
+            const status = isPointInsideElement(child, point.x, point.y);
+            if (status) {
+              const array: any = forms.coursewareList;
+              const left = isPointOnLeft(child, point.x);
+              if (!left) {
+                array.splice(index + 1, 0, item);
+              } else {
+                array.splice(index, 0, item);
+              }
+              isAdd = true;
+              forms.coursewareList[item.index || 0].list = array;
+              prepareStore.setCoursewareList(forms.coursewareList);
+            }
+          });
+          if (!isAdd) {
+            forms.coursewareList[item.index || 0].list.push(item);
+            prepareStore.setCoursewareList(forms.coursewareList);
+          }
+        } else {
+          forms.coursewareList[0].list.push(item);
+          console.log(forms.coursewareList);
+          prepareStore.setCoursewareList(forms.coursewareList);
+        }
+      });
+    };
+
+    // 提交
+    const onSubmit = async () => {
+      try {
+        if (!forms.name) {
+          message.error('请输入课件标题');
+          return;
+        }
+        if (forms.subjects.length <= 0) {
+          message.error('请选择声部');
+          return;
+        }
+
+        let isNotAdd = false;
+        for (const item of forms.coursewareList) {
+          if (!item.name) {
+            message.error('请输入知识点名称');
+            return;
+          }
+          if (Array.isArray(item.list) && item.list.length <= 0) {
+            isNotAdd = true;
+          }
+        }
+
+        if (isNotAdd) {
+          message.error('请至少添加一个资源');
+          return;
+        }
+        forms.messageOperation = {
+          visiable: true,
+          type: 'save',
+          contentDirection: 'center',
+          title: '保存课件',
+          content: '当前课件暂未保存,是否保存?',
+          cancelButtonText: '不保存',
+          confirmButtonText: '保存',
+          index: 0
+        };
+      } catch {
+        //
+      }
+    };
+    let isLock = false;
+    const onSaveCourseWare = async () => {
+      try {
+        if (isLock) return;
+        isLock = true;
+        const params = {
+          name: forms.name,
+          subjectIds: forms.subjects.join(','),
+          openFlag: forms.openFlag,
+          coursewareDetailKnowledgeId: prepareStore.getSelectKey,
+          chapterKnowledgeList: [] as any
+        };
+
+        forms.coursewareList.forEach((item: any) => {
+          let tempItem: any = [];
+          if (Array.isArray(item.list) && item.list.length > 0) {
+            tempItem = item.list.map((child: any) => {
+              return {
+                bizId: child.materialId,
+                type: child.type,
+                dataJson: ''
+              };
+            });
+          }
+          params.chapterKnowledgeList.push({
+            name: item.name,
+            chapterKnowledgeMaterialList: tempItem
+          });
+        });
+        await api_add(params);
+        message.success('添加成功');
+        emit('change', { status: false });
+        eventGlobal.emit('teacher-slideshow', false);
+        setTimeout(() => {
+          isLock = false;
+        }, 100);
+      } catch {
+        //
+      }
+    };
+
+    const addItem = (item: any, point?: any) => {
+      if (forms.coursewareList.length <= 0) {
+        // 添加到临时对象
+        forms.addCoursewareItem = item;
+        forms.messageOperation = {
+          visiable: true,
+          type: 'addItem',
+          contentDirection: 'center',
+          title: '添加到知识点',
+          content: '当前课件暂无知识点,请添加知识点后操作',
+          cancelButtonText: '取消',
+          confirmButtonText: '添加知识点',
+          index: 0
+        };
+      } else {
+        addCoursewareItem(item, point);
+      }
+    };
+    onMounted(async () => {
+      await getList();
+
+      // 动态添加数据
+      eventGlobal.on('onPrepareAddItem', addItem);
+    });
+
+    onUnmounted(() => {
+      eventGlobal.off('onPrepareAddItem', addItem);
+    });
+
+    return () => (
+      <div class={styles.coursewareModal}>
+        <div class={styles.btnGroup}>
+          <NSpace>
+            <div class={styles.btnItem}>
+              <span class={styles.btnTitle}>
+                <span>*</span>标题:
+              </span>
+              <NInput placeholder="请输入课件标题" v-model:value={forms.name} />
+            </div>
+            <div class={styles.btnItem}>
+              <span class={styles.btnTitle}>
+                <span>*</span>声部:
+              </span>
+              <NSelect
+                placeholder="请选择声部(可多选)"
+                class={styles.btnSubjectList}
+                options={prepareStore.getSubjectList}
+                labelField="name"
+                valueField="id"
+                multiple
+                maxTagCount={1}
+                size="small"
+                v-model:value={forms.subjects}
+              />
+            </div>
+            <div class={styles.btnItem}>
+              <span class={styles.btnTitle}>公开:</span>
+              <NSwitch v-model:value={forms.openFlag} />
+            </div>
+          </NSpace>
+
+          {/* 编辑 */}
+          <NSpace>
+            <NButton
+              type="error"
+              onClick={() => {
+                emit('change', { status: false });
+                eventGlobal.emit('teacher-slideshow', false);
+              }}>
+              取消
+            </NButton>
+            <NButton type="primary" onClick={onSubmit}>
+              保存课件
+            </NButton>
+          </NSpace>
+        </div>
+
+        <NScrollbar class={[styles.listContainer]} {...{ id: 'lessons-2' }}>
+          <NSpin show={forms.loadingStatus}>
+            <div class={[styles.listSection]}>
+              {forms.coursewareList.map((item: any, index: number) => (
+                <div
+                  class={styles.listItems}
+                  onDragenter={(e: any) => {
+                    e.preventDefault();
+                  }}
+                  onDragover={(e: any) => {
+                    e.preventDefault();
+                  }}
+                  onDrop={(e: any) => {
+                    let dropItem = e.dataTransfer.getData('text');
+                    dropItem = dropItem ? JSON.parse(dropItem) : {};
+                    // 判断是否有数据
+                    if (dropItem.id) {
+                      // 获取拖拽的目标元素
+                      eventGlobal.emit(
+                        'onPrepareAddItem',
+                        {
+                          materialId: dropItem.id,
+                          coverImg: dropItem.coverImg,
+                          type: dropItem.type,
+                          title: dropItem.name,
+                          isCollect: dropItem.isCollect,
+                          isSelected: dropItem.isSelected,
+                          content: dropItem.content,
+                          removeFlag: false,
+                          index
+                        },
+                        {
+                          x: e.clientX,
+                          y: e.clientY
+                        }
+                      );
+                    }
+                  }}>
+                  <div class={styles.knowledgePoint}>
+                    <div class={styles.btnItem}>
+                      <span class={styles.btnTitle}>
+                        <span>*</span>知识点名称:
+                      </span>
+                      <NInput
+                        placeholder="未命名知识点"
+                        v-model:value={item.name}
+                      />
+                    </div>
+                  </div>
+
+                  <NSpace class={styles.operationGroup}>
+                    {index > 0 && (
+                      <NTooltip>
+                        {{
+                          trigger: () => (
+                            <i
+                              class={styles.iconCUp}
+                              onClick={() => onChangePoint('up', index)}></i>
+                          ),
+                          default: '上移知识点'
+                        }}
+                      </NTooltip>
+                    )}
+                    {forms.coursewareList.length > 1 && (
+                      <NTooltip>
+                        {{
+                          trigger: () => (
+                            <i
+                              class={styles.iconCDown}
+                              onClick={() => onChangePoint('down', index)}></i>
+                          ),
+                          default: '下移知识点'
+                        }}
+                      </NTooltip>
+                    )}
+                    <NTooltip>
+                      {{
+                        trigger: () => (
+                          <i
+                            class={styles.iconCRemove}
+                            onClick={() => onChangePoint('remove', index)}></i>
+                        ),
+                        default: '删除知识点'
+                      }}
+                    </NTooltip>
+                  </NSpace>
+
+                  {item.list.length > 0 && (
+                    <Draggable
+                      v-model:modelValue={item.list}
+                      itemKey="id"
+                      componentData={{
+                        itemKey: 'id',
+                        tag: 'div',
+                        animation: 200,
+                        group: 'description',
+                        disabled: false
+                      }}
+                      class={styles.list}>
+                      {{
+                        item: (element: any) => {
+                          const item = element.element;
+                          return (
+                            <div
+                              data-id={item.id}
+                              class={[
+                                styles.itemWrap,
+                                styles.itemBlock,
+                                'row-nav'
+                              ]}>
+                              <div class={styles.itemWrapBox}>
+                                <CardType
+                                  class={[styles.itemContent]}
+                                  isShowCollect={false}
+                                  offShelf={item.removeFlag ? true : false}
+                                  // onOffShelf={() => onRemove(item)}
+                                  item={item}
+                                />
+                                <div class={styles.itemOperation}>
+                                  <img
+                                    src={iconDelete}
+                                    class={styles.iconDelete}
+                                    onClick={(e: MouseEvent) => {
+                                      e.stopPropagation();
+                                      onDelete(item);
+                                    }}
+                                  />
+                                </div>
+                              </div>
+                            </div>
+                          );
+                        },
+                        footer: () => (
+                          <div class={styles.itemWrap}>
+                            <div class={styles.itemWrapBox}>
+                              <div
+                                class={[
+                                  styles.itemContent,
+                                  styles.addMusicItem,
+                                  'handle'
+                                ]}
+                                onClick={() => {
+                                  // 直接跳转到制谱页面 (临时存储数据)
+                                  sessionStorage.setItem(
+                                    'notation-open-create',
+                                    '1'
+                                  );
+                                  router.push('/notation');
+                                }}>
+                                <img src={iconAddMusic} />
+
+                                <p class={styles.addMusicName}>添加功能</p>
+                              </div>
+                            </div>
+                          </div>
+                        )
+                      }}
+                    </Draggable>
+                  )}
+                  {item.list <= 0 && (
+                    <div class={styles.list}>
+                      <div class={styles.itemWrap}>
+                        <div class={styles.itemWrapBox}>
+                          <div
+                            class={[
+                              styles.itemContent,
+                              styles.addMusicItem,
+                              'handle'
+                            ]}
+                            onClick={() => {
+                              // 直接跳转到制谱页面 (临时存储数据)
+                              sessionStorage.setItem(
+                                'notation-open-create',
+                                '1'
+                              );
+                              router.push('/notation');
+                            }}>
+                            <img src={iconAddMusic} />
+
+                            <p class={styles.addMusicName}>添加功能</p>
+                          </div>
+                        </div>
+                      </div>
+                    </div>
+                  )}
+                </div>
+              ))}
+
+              <NButton
+                block
+                type="primary"
+                secondary
+                class={styles.addKnowledgePoint}
+                onClick={() => {
+                  forms.coursewareList.push({
+                    name: '',
+                    list: []
+                  });
+                }}>
+                <i class={styles.iconCAdd}></i>
+                添加知识点
+              </NButton>
+            </div>
+          </NSpin>
+        </NScrollbar>
+
+        {/* 弹窗查看 */}
+        <CardPreview v-model:show={forms.show} item={forms.item} />
+
+        <NModal
+          v-model:show={forms.removeVisiable}
+          preset="card"
+          class={['modalTitle', styles.removeVisiable]}
+          title={'提示'}>
+          <div class={styles.studentRemove}>
+            <p>是否完成编辑?</p>
+
+            <NSpace class={styles.btnGroupModal} justify="center">
+              <NButton round type="primary" onClick={onOverEdit}>
+                确定
+              </NButton>
+              <NButton round onClick={() => (forms.removeVisiable = false)}>
+                取消
+              </NButton>
+            </NSpace>
+          </div>
+        </NModal>
+
+        <NModal
+          v-model:show={forms.messageOperation.visiable}
+          preset="card"
+          class={['modalTitle', styles.removeVisiable1]}
+          title={forms.messageOperation.title}>
+          <TheMessageDialog
+            content={forms.messageOperation.content}
+            contentDirection={forms.messageOperation.contentDirection}
+            cancelButtonText={forms.messageOperation.cancelButtonText}
+            confirmButtonText={forms.messageOperation.confirmButtonText}
+            onClose={() => (forms.messageOperation.visiable = false)}
+            onConfirm={() => onMessageConfirm()}
+          />
+        </NModal>
+
+        <PreviewWindow
+          v-model:show={forms.previewModal}
+          type="attend"
+          params={forms.previewParams}
+        />
+
+        {/* 完成编辑时,选择声部 */}
+        <NModal
+          v-model:show={forms.subjectSyncVisiable}
+          preset="card"
+          class={['modalTitle background', styles.subjectSyncModal]}
+          title={'同步声部'}>
+          <SubjectSync
+            subjectId={prepareStore.getSubjectId as any}
+            onClose={() => (forms.subjectSyncVisiable = false)}
+            onConfirm={async (subjectIds: any) => {
+              //
+              try {
+                forms.editSubjectIds = subjectIds.join(',');
+                await onOverEdit();
+                forms.subjectSyncVisiable = false;
+              } catch {
+                //
+              }
+            }}
+          />
+        </NModal>
+      </div>
+    );
+  }
+});

+ 73 - 30
src/views/prepare-lessons/components/lesson-main/index.tsx

@@ -1,24 +1,37 @@
-import { defineComponent, reactive } from 'vue';
+import { defineComponent, nextTick, reactive, ref, watch } from 'vue';
 import styles from './index.module.less';
 import { NTabPane, NTabs } from 'naive-ui';
-import Courseware from './courseware';
+import Courseware from './courseware/addCourseware';
 import Train from './train';
 import { usePrepareStore } from '/src/store/modules/prepareLessons';
 import TrainPresets from './train-presets';
 import { eventGlobal } from '/src/utils';
+import CoursewarePresets from './courseware-presets';
 
 export default defineComponent({
   name: 'lesson-main',
   setup() {
     const prepareStore = usePrepareStore();
     const state = reactive({
+      editCoursewareShow: false, // 是否编辑课件
       editWorkShow: false, // 是否编辑预设
       editWork: {} as any // 预设模板编号
     });
+    const lessonMainRef = ref();
+
+    // watch(
+    //   () => [state.editWorkShow, state.editCoursewareShow],
+    //   () => {
+    //     nextTick(() => {
+    //       lessonMainRef.value?.syncBarPosition();
+    //     });
+    //   }
+    // );
 
     return () => (
       <div class={styles['lesson-main']}>
         <NTabs
+          ref={lessonMainRef}
           defaultValue="courseware"
           paneClass={styles.paneTitle}
           justifyContent="center"
@@ -30,29 +43,20 @@ export default defineComponent({
             prepareStore.setIsEditResource(false);
             prepareStore.setIsEditTrain(false);
 
-            eventGlobal.emit(
-              'teacher-slideshow',
-              val === 'train' ? false : true
-            );
+            eventGlobal.emit('teacher-slideshow', false);
             if (val !== 'train') {
               state.editWorkShow = false;
             }
           }}>
-          <NTabPane name="courseware" tab="课件" displayDirective="show">
-            <Courseware />
-          </NTabPane>
-          <NTabPane
-            name="train"
-            tab="作业"
-            displayDirective="if"
-            v-slots={{ tab: () => <span id="lessons-4">作业</span> }}
-            {...{ id: 'lessons-4' }}>
-            <div>
-              {state.editWorkShow ? (
-                <Train
-                  lessonPreTraining={state.editWork}
+          {!state.editWorkShow && (
+            <NTabPane
+              name="courseware"
+              tab={state.editCoursewareShow ? '编辑课件' : '课件'}
+              displayDirective="if">
+              {state.editCoursewareShow ? (
+                <Courseware
                   onChange={(val: any) => {
-                    state.editWorkShow = val.status;
+                    state.editCoursewareShow = val.status;
 
                     if (!val.status) {
                       eventGlobal.emit('teacher-slideshow', false);
@@ -60,20 +64,59 @@ export default defineComponent({
                   }}
                 />
               ) : (
-                <TrainPresets
+                <CoursewarePresets
                   onChange={(val: any) => {
-                    state.editWorkShow = val.status;
-                    state.editWork = {
-                      ...val.lessonPreTraining,
-                      title:
-                        val.lessonPreTraining?.title ||
-                        prepareStore.getSelectName + '课后作业'
-                    };
+                    state.editCoursewareShow = val.status;
                   }}
                 />
               )}
-            </div>
-          </NTabPane>
+            </NTabPane>
+          )}
+
+          {!state.editCoursewareShow && (
+            <NTabPane
+              name="train"
+              // tab="作业"
+              tab={state.editWorkShow ? '编辑作业' : '作业'}
+              displayDirective="if"
+              // v-slots={{
+              //   tab: () =>
+              //     state.editWorkShow ? (
+              //       <span id="lessons-4">编辑作业</span>
+              //     ) : (
+              //       <span id="lessons-4">作业</span>
+              //     )
+              // }}
+              // {...{ id: 'lessons-4' }}
+            >
+              <div>
+                {state.editWorkShow ? (
+                  <Train
+                    lessonPreTraining={state.editWork}
+                    onChange={(val: any) => {
+                      state.editWorkShow = val.status;
+
+                      if (!val.status) {
+                        eventGlobal.emit('teacher-slideshow', false);
+                      }
+                    }}
+                  />
+                ) : (
+                  <TrainPresets
+                    onChange={(val: any) => {
+                      state.editWorkShow = val.status;
+                      state.editWork = {
+                        ...val.lessonPreTraining,
+                        title:
+                          val.lessonPreTraining?.title ||
+                          prepareStore.getSelectName + '课后作业'
+                      };
+                    }}
+                  />
+                )}
+              </div>
+            </NTabPane>
+          )}
         </NTabs>
       </div>
     );

+ 27 - 60
src/views/prepare-lessons/components/resource-main/components/resource-item/index.tsx

@@ -18,14 +18,18 @@ const formatType = (type: string) => {
     return 3;
   } else if (type === 'myCollect') {
     return 4;
+  } else if (type === 'relateResources') {
+    return 5;
   }
 };
 export default defineComponent({
   name: 'share-resources',
   props: {
     type: {
-      type: String as PropType<'shareResources' | 'myResources' | 'myCollect'>,
-      default: 'shareResources'
+      type: String as PropType<
+        'shareResources' | 'myResources' | 'myCollect' | 'relateResources'
+      >,
+      default: 'relateResources'
     }
   },
   setup(props) {
@@ -53,14 +57,14 @@ export default defineComponent({
     });
     const getList = async () => {
       try {
-        if (!prepareStore.getSubjectId) return;
+        // if (!prepareStore.getSubjectId) return;
         if (state.pagination.page === 1) {
           state.loading = true;
         }
         const { data } = await materialQueryPage({
           ...state.searchGroup,
-          ...state.pagination,
-          subjectId: prepareStore.getSubjectId
+          ...state.pagination
+          // subjectId: prepareStore.getSubjectId
         });
         state.loading = false;
         const tempRows = data.rows || [];
@@ -76,8 +80,8 @@ export default defineComponent({
             title: row.name,
             isCollect: !!row.favoriteFlag,
             isSelected: row.sourceFrom === 'PLATFORM' ? true : false,
-            content: row.content,
-            exist: index !== -1 ? true : false // 是否存在
+            content: row.content
+            // exist: index !== -1 ? true : false // 是否存在
           });
         });
         if (state.pagination.page === 1) {
@@ -114,21 +118,21 @@ export default defineComponent({
         onSearch(state.searchGroup);
       }
     );
-    watch(
-      () => prepareStore.coursewareList,
-      () => {
-        state.tableList.forEach((item: any) => {
-          const index = prepareStore.getCoursewareList.findIndex(
-            (course: any) => course.materialId === item.id
-          );
-          item.exist = index !== -1 ? true : false; // 是否存在
-        });
-      },
-      {
-        deep: true,
-        immediate: true
-      }
-    );
+    // watch(
+    //   () => prepareStore.coursewareList,
+    //   () => {
+    //     state.tableList.forEach((item: any) => {
+    //       const index = prepareStore.getCoursewareList.findIndex(
+    //         (course: any) => course.materialId === item.id
+    //       );
+    //       item.exist = index !== -1 ? true : false; // 是否存在
+    //     });
+    //   },
+    //   {
+    //     deep: true,
+    //     immediate: true
+    //   }
+    // );
 
     const throttledFn = useThrottleFn(() => {
       state.pagination.page = state.pagination.page + 1;
@@ -137,23 +141,7 @@ export default defineComponent({
 
     // 添加资源
     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
-        //   });
-        // });
         eventGlobal.emit('onPrepareAddItem', {
           materialId: item.id,
           coverImg: item.coverImg,
@@ -164,31 +152,9 @@ export default defineComponent({
           content: item.content,
           removeFlag: false
         });
-
-        // 保存课件
-        // await saveCourseware({
-        //   coursewareDetailKnowledgeId: prepareStore.getSelectKey,
-        //   lessonCoursewareId: prepareStore.getLessonCoursewareId,
-        //   lessonCoursewareDetailId: prepareStore.getLessonCoursewareDetailId,
-        //   materialList: [
-        //     ...temp,
-        //     {
-        //       materialName: item.title,
-        //       materialType: item.type,
-        //       materialId: item.id
-        //     }
-        //   ],
-        //   subjectId: prepareStore.getSubjectId
-        // });
-
-        // message.success('添加成功');
-
-        // prepareStore.setIsAddResource(true);
       } catch {
         //
       }
-      // }
-      // });
     };
 
     // 收藏
@@ -214,6 +180,7 @@ export default defineComponent({
           type={props.type}
           onSearch={(item: any) => {
             state.searchGroup = Object.assign(state.searchGroup, item);
+            console.log('111');
             throttledFnSearch(item);
           }}
         />

+ 4 - 2
src/views/prepare-lessons/components/resource-main/components/resource-item/resource-search-group/index.tsx

@@ -10,8 +10,10 @@ export default defineComponent({
   emits: ['search'],
   props: {
     type: {
-      type: String as PropType<'shareResources' | 'myResources' | 'myCollect'>,
-      default: 'shareResources'
+      type: String as PropType<
+        'shareResources' | 'myResources' | 'myCollect' | 'relateResources'
+      >,
+      default: 'relateResources'
     }
   },
   setup(props, { emit }) {

+ 11 - 7
src/views/prepare-lessons/components/resource-main/index.tsx

@@ -29,7 +29,7 @@ export default defineComponent({
   setup(props, { expose }) {
     const prepareStore = usePrepareStore();
     const forms = reactive({
-      tabType: 'myResources',
+      tabType: 'relateResources',
       tabWorkType: 'myMusic',
       selectMusicStatus: false,
       selectResourceStatus: false,
@@ -121,12 +121,8 @@ export default defineComponent({
               ),
               default: () => (
                 <>
-                  <NTabPane
-                    name="myResources"
-                    tab="我的资源"
-                    // displayDirective="show:lazy"
-                  >
-                    <ResourceItem type="myResources" />
+                  <NTabPane name="relateResources" tab="相关资源">
+                    <ResourceItem type="relateResources" />
                   </NTabPane>
                   <NTabPane
                     name="shareResources"
@@ -136,6 +132,14 @@ export default defineComponent({
                     <ResourceItem type="shareResources" />
                   </NTabPane>
                   <NTabPane
+                    name="myResources"
+                    tab="我的资源"
+                    // displayDirective="show:lazy"
+                  >
+                    <ResourceItem type="myResources" />
+                  </NTabPane>
+
+                  <NTabPane
                     name="myCollect"
                     tab="我的收藏"
                     // displayDirective="show:lazy"

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


BIN
src/views/prepare-lessons/images/icon-c-add.png


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


BIN
src/views/prepare-lessons/images/icon-c-down.png


BIN
src/views/prepare-lessons/images/icon-c-up.png


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


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


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


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


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


BIN
src/views/prepare-lessons/images/icon-slide-right.png


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

@@ -21,7 +21,7 @@ export default defineComponent({
   name: 'prepare-lessons',
   setup() {
     const state = reactive({
-      sidebarShow: true
+      sidebarShow: false
     });
     const prepareStore = usePrepareStore();
     const resourceMainRef = ref();
@@ -56,13 +56,18 @@ export default defineComponent({
       state.sidebarShow = val;
       resourceMainRef.value?.resetTabPosition();
     };
+
     onMounted(() => {
       // 指引事件
       eventGlobal.on('teacher-guideInfo', async (name: string) =>
         onUpdate(name)
       );
 
+      // 作业预设事件
       eventGlobal.on('teacher-slideshow', onSlideChange);
+
+      // // 课件编辑事件
+      // eventGlobal.on('courseware-slideshow', onSlideChange);
     });
     onUnmounted(() => {
       eventGlobal.off('teacher-guideInfo', onUpdate);

+ 185 - 0
src/views/prepare-lessons/model/courseware-type/index.module.less

@@ -0,0 +1,185 @@
+.coursewareType {
+  position: relative;
+  background: #F5F6FA;
+  border-radius: 13px;
+  padding: 10px;
+  transition: all 0.3 ease;
+
+  &:hover {
+    transform: scale(1.01);
+    transition: all 0.3 ease;
+  }
+
+  &.coursewareTypeHover:hover {
+    .addBtn {
+      opacity: 1;
+      visibility: visible;
+    }
+  }
+
+  &.coursewareTypeHocoursewareTypeShow {
+    .addBtn {
+      opacity: 1;
+      visibility: visible;
+    }
+  }
+
+  .addBtn {
+    opacity: 0;
+    visibility: hidden;
+    position: absolute;
+    top: 15px;
+    right: 15px;
+    // width: 60px;
+    height: 27px;
+    background: #3D9EFF;
+    border-radius: 5px;
+    padding: 0 12px;
+
+    z-index: 1;
+    --n-border: none !important;
+  }
+
+  :global {
+    .n-button.n-button--disabled {
+      background: #ccc;
+      --n-border-disabled: none !important;
+    }
+  }
+
+  .cover {
+    cursor: pointer;
+    position: relative;
+    height: 153px;
+    border-radius: 10px;
+    overflow: hidden;
+    background-color: #fff;
+
+    .status {
+      position: absolute;
+      top: 0;
+      left: 0;
+      background: linear-gradient(226deg, #13BFFF 0%, #1183FF 100%);
+      border-radius: 4px 0px 10px 0px;
+      font-weight: 600;
+      font-size: max(12px, 11Px);
+      color: #FFFFFF;
+      padding: 3px 10px;
+    }
+
+    :global {
+      .n-image {
+        width: 100%;
+        height: 100%;
+      }
+    }
+
+    img {
+      width: 100%;
+    }
+  }
+
+  .coursewareText {
+    padding: 10px 0 0;
+
+    .name {
+      font-weight: 600;
+      font-size: max(13px, 12Px);
+      color: #000000;
+      display: flex;
+      align-items: center;
+
+      span {
+        max-width: 100%;
+        display: inline-block;
+        overflow: hidden;
+        text-overflow: ellipsis;
+        white-space: nowrap;
+      }
+
+      .iconEditName {
+        display: none;
+        width: max(13px, 12Px);
+        height: max(13px, 12Px);
+        background: url('../../images/icon-edit-name.png') no-repeat center center;
+        background-size: contain;
+      }
+    }
+
+    .subjectName {
+      max-width: 100%;
+      overflow: hidden;
+      text-overflow: ellipsis;
+      white-space: nowrap;
+    }
+
+    .editName {
+      display: flex;
+      align-items: center;
+      // justify-content: space-between;
+
+      span {
+        max-width: 94%;
+      }
+
+      &:hover {
+        span {
+          color: #1677FF;
+        }
+
+        .iconEditName {
+          margin-left: 6px;
+          cursor: pointer;
+          flex-shrink: 0;
+          display: inline-block;
+        }
+      }
+    }
+
+    .subjectName {
+      padding-top: 10px;
+      font-size: 11Px;
+      color: #777777;
+    }
+  }
+}
+
+.footer {
+  display: flex;
+  align-items: center;
+  padding-top: 10px;
+
+  .actionClass {
+    cursor: pointer;
+    flex: 1;
+    height: 30Px !important;
+    line-height: 30Px;
+    background: #FFFFFF;
+    border-radius: 7px;
+    text-align: center;
+    margin-right: 12Px;
+    font-size: max(14px, 13Px);
+    color: #484F59;
+    font-weight: 600;
+
+    &:hover {
+      background-color: #198CFE;
+      color: #fff;
+    }
+  }
+
+  .optons {
+    width: 20Px;
+    height: 20Px;
+    background: #FFFFFF;
+    border-radius: 7px;
+    padding: 5px;
+    box-sizing: content-box;
+    cursor: pointer;
+
+    img {
+      width: 20Px;
+      height: 20Px;
+    }
+  }
+}

+ 104 - 0
src/views/prepare-lessons/model/courseware-type/index.tsx

@@ -0,0 +1,104 @@
+import { defineComponent } from 'vue';
+import styles from './index.module.less';
+import { NButton, NImage, NSpace, NTooltip } from 'naive-ui';
+import iconEdit from '../../images/icon-courseware-edit.png';
+import iconDelete from '../../images/icon-courseware-delete.png';
+import iconEditName from '../../images/icon-edit-name.png';
+
+export default defineComponent({
+  name: 'courseware-type',
+  props: {
+    item: {
+      type: Object,
+      default: () => ({})
+    },
+    /** 是否鼠标滑动时显示显示添加按钮 */
+    isHoverShowAdd: {
+      type: Boolean,
+      default: true
+    },
+    /** 是否显示添加按钮 */
+    isShowAdd: {
+      type: Boolean,
+      default: false
+    },
+    /** 是否支持编辑名称 */
+    isEditName: {
+      type: Boolean,
+      default: false
+    },
+    /** 是否显示操作按钮 */
+    operate: {
+      type: Boolean,
+      default: false
+    }
+  },
+  /** add */
+  emits: ['add', 'editName', 'edit', 'delete', 'startClass'],
+  setup(props, { emit }) {
+    return () => (
+      <div
+        class={[
+          styles.coursewareType,
+          props.isHoverShowAdd
+            ? styles.coursewareTypeHover
+            : styles.coursewareTypeHocoursewareTypeShow
+        ]}>
+        {props.isShowAdd && (
+          <NButton
+            type="primary"
+            class={[styles.addBtn]}
+            disabled={props.item.isAdd ? true : false}
+            onClick={(e: MouseEvent) => {
+              e.stopPropagation();
+              e.preventDefault();
+              emit('add');
+            }}>
+            {props.item.isAdd ? '已添加' : '添加'}
+          </NButton>
+        )}
+        <div class={styles.cover}>
+          {props.item.openFlag && <span class={styles.status}>公开</span>}
+          <NImage objectFit="cover" previewDisabled src={props.item.coverImg} />
+        </div>
+        <div class={styles.coursewareText}>
+          <div class={[styles.name, props.isEditName && styles.editName]}>
+            <span>{props.item.name}</span>
+            <i class={styles.iconEditName} onClick={() => emit('editName')}></i>
+          </div>
+          <div class={styles.subjectName}>{props.item.subjectNames}</div>
+        </div>
+
+        {props.operate && (
+          <div class={styles.footer}>
+            <div class={styles.actionClass} onClick={() => emit('startClass')}>
+              开始上课
+            </div>
+            <NSpace>
+              <NTooltip>
+                {{
+                  trigger: () => (
+                    <div class={styles.optons} onClick={() => emit('edit')}>
+                      <NImage src={iconEdit} previewDisabled></NImage>
+                    </div>
+                  ),
+                  default: () => <div>编辑</div>
+                }}
+              </NTooltip>
+              <NTooltip>
+                {{
+                  trigger: () => (
+                    <div class={styles.optons} onClick={() => emit('delete')}>
+                      <NImage src={iconDelete} previewDisabled></NImage>
+                    </div>
+                  ),
+                  default: () => <div>删除</div>
+                }}
+              </NTooltip>
+            </NSpace>
+          </div>
+        )}
+      </div>
+    );
+  }
+});

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

@@ -0,0 +1,82 @@
+.relatedClass {
+  margin: 32px 0;
+}
+
+.attendClassSearch {
+  width: 100%;
+  display: flex;
+  align-items: center;
+  gap: 0 24px;
+  margin-bottom: 28px;
+  padding: 0 40px;
+
+  :global {
+
+    .n-base-selection,
+    .n-input {
+      // height: 52px;
+      // min-height: 52px;
+      // --n-height: 52px !important;
+      // font-size: 18px;
+      border-radius: 8px !important;
+
+    }
+
+    .n-input,
+    .n-select {
+      max-width: 250px;
+    }
+  }
+
+  .iconSearch {
+    width: 16px;
+    height: 17px;
+  }
+}
+
+.classList {
+  max-height: 60vh;
+  min-height: 60vh;
+  padding: 0 40px;
+
+  .listSection {
+    min-height: 60vh;
+  }
+
+  .emptySection {
+    display: flex;
+    align-items: center;
+  }
+}
+
+.list {
+  display: flex;
+  flex-flow: row wrap;
+  justify-content: flex-start;
+  padding: 0 0 12px;
+  gap: 20px 0;
+  margin: 0 -10px 0;
+  min-height: 300px;
+
+  &.listSame {
+    margin-top: 0;
+    padding-top: 0;
+  }
+
+  .itemWrap {
+    width: calc(100% / 4);
+    // padding-bottom: calc(100% / 3 * 0.73333);
+    // position: relative;
+
+    .itemWrapBox {
+      padding: 0 10px;
+    }
+  }
+
+  :global {
+    .card-section-container {
+      width: 100%;
+      height: 100%;
+    }
+  }
+}

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

@@ -0,0 +1,197 @@
+import { defineComponent, onMounted, reactive, watch } from 'vue';
+import styles from './index.module.less';
+import { NInput, NScrollbar, NSelect, NSpin } from 'naive-ui';
+import { useThrottleFn } from '@vueuse/core';
+import CoursewareType from '../courseware-type';
+import TheEmpty from '/src/components/TheEmpty';
+import { api_queryOpenCoursewareByPage } from '../../api';
+
+export default defineComponent({
+  name: 'related-class',
+  props: {
+    tableList: {
+      type: Array,
+      default: () => []
+    },
+    subjectList: {
+      type: Array,
+      default: () => []
+    },
+    subjectId: {
+      type: [String, Number],
+      default: ''
+    },
+    coursewareDetailKnowledgeId: {
+      type: [String, Number],
+      default: ''
+    }
+  },
+  emits: ['close', 'add'],
+  setup(props, { emit }) {
+    const forms = reactive({
+      loading: false,
+      finshed: false, // 是否加载完
+      pagination: {
+        page: 1,
+        rows: 20
+      },
+      tableList: [] as any,
+      searchGroup: {
+        subjectId: props.subjectId,
+        keyword: null
+      }
+    });
+    const getList = async () => {
+      try {
+        if (forms.pagination.page === 1) {
+          forms.loading = true;
+        }
+        // 查询公开课件列表
+        const { data } = await api_queryOpenCoursewareByPage({
+          coursewareDetailKnowledgeId: props.coursewareDetailKnowledgeId,
+          ...forms.searchGroup,
+          ...forms.pagination
+        });
+        const result = data.rows || [];
+        const tempList: any = [];
+        result.forEach((item: any) => {
+          const index = forms.tableList.findIndex(
+            (i: any) => i.fromChapterLessonCoursewareId === item.id
+          );
+          const firstItem: any =
+            item.chapterKnowledgeList[0]?.chapterKnowledgeMaterialList[0];
+          tempList.push({
+            id: item.id,
+            openFlag: item.openFlag,
+            openFlagEnable: item.openFlagEnable,
+            subjectNames: item.subjectNames,
+            fromChapterLessonCoursewareId: item.fromChapterLessonCoursewareId,
+            name: item.name,
+            coverImg: firstItem?.bizInfo.coverImg,
+            type: firstItem?.bizInfo.type,
+            isAdd: index !== -1 ? true : false
+          });
+        });
+
+        forms.loading = false;
+        forms.tableList.push(...tempList);
+
+        forms.finshed = data.pages <= data.current ? true : false;
+      } catch {
+        forms.loading = false;
+      }
+    };
+
+    // 检测数据是否存在
+    watch(
+      () => props.tableList,
+      () => {
+        // fromChapterLessonCoursewareId;
+        forms.tableList.forEach((item: any) => {
+          const index = props.tableList.findIndex(
+            (i: any) => i.fromChapterLessonCoursewareId === item.id
+          );
+          item.isAdd = index !== -1 ? true : false;
+        });
+      }
+    );
+
+    const throttleFn = useThrottleFn(() => {
+      forms.tableList = [];
+      getList();
+    }, 500);
+
+    onMounted(() => {
+      getList();
+    });
+    return () => (
+      <div class={styles.relatedClass}>
+        <div class={styles.attendClassSearch}>
+          <NSelect
+            placeholder="全部声部"
+            clearable
+            options={[
+              { name: '全部声部', id: '' },
+              ...(props.subjectList as any)
+            ]}
+            labelField="name"
+            valueField="id"
+            v-model:value={forms.searchGroup.subjectId}
+            onUpdate:value={() => throttleFn()}
+          />
+          <NInput
+            placeholder="请输课件标题关键词"
+            clearable
+            v-model:value={forms.searchGroup.keyword}
+            onKeyup={(e: KeyboardEvent) => {
+              if (e.code === 'Enter') {
+                throttleFn();
+              }
+            }}
+            onClear={() => throttleFn()}>
+            {{
+              prefix: () => (
+                <span
+                  class="icon-search-input"
+                  style={{ cursor: 'pointer' }}
+                  onClick={() => throttleFn()}></span>
+              )
+            }}
+          </NInput>
+        </div>
+
+        <NScrollbar
+          class={styles.classList}
+          style={{
+            'max-height': `60vh`
+          }}
+          onScroll={(e: any) => {
+            const clientHeight = e.target?.clientHeight;
+            const scrollTop = e.target?.scrollTop;
+            const scrollHeight = e.target?.scrollHeight;
+            // 是否到底,是否加载完
+            if (
+              clientHeight + scrollTop + 20 >= scrollHeight &&
+              !forms.finshed &&
+              !forms.loading
+            ) {
+              throttleFn();
+            }
+          }}>
+          <NSpin show={forms.loading} size={'small'}>
+            <div
+              style={{
+                'min-height': `60vh)`
+              }}
+              class={[
+                styles.listSection,
+                !forms.loading && forms.tableList.length <= 0
+                  ? styles.emptySection
+                  : ''
+              ]}>
+              {forms.tableList.length > 0 && (
+                <div class={[styles.list]}>
+                  {forms.tableList.map((item: any) => (
+                    <div class={[styles.itemWrap, styles.itemBlock, 'row-nav']}>
+                      <div class={styles.itemWrapBox}>
+                        <CoursewareType
+                          isHoverShowAdd={false}
+                          isShowAdd
+                          item={item}
+                          onAdd={() => {
+                            emit('add', item);
+                          }}
+                        />
+                      </div>
+                    </div>
+                  ))}
+                </div>
+              )}
+              {!forms.loading && forms.tableList.length <= 0 && <TheEmpty />}
+            </div>
+          </NSpin>
+        </NScrollbar>
+      </div>
+    );
+  }
+});

+ 2 - 2
vite.config.ts

@@ -23,8 +23,8 @@ function resolve(dir: string) {
 }
 // https://vitejs.dev/config/
 // https://github.com/vitejs/vite/issues/1930 .env
-// const proxyUrl = 'https://dev.kt.colexiu.com/';
-const proxyUrl = 'https://test.kt.colexiu.com';
+const proxyUrl = 'https://dev.kt.colexiu.com/';
+// const proxyUrl = 'https://test.kt.colexiu.com';
 // const proxyUrl = 'http://192.168.3.14:7989';
 const now = new Date().getTime();
 export default defineConfig(() => {