浏览代码

Merge branch 'video-tcplayer' into iteration-class

lex 1 年之前
父节点
当前提交
d0e82777cd
共有 26 个文件被更改,包括 702 次插入203 次删除
  1. 8 10
      src/components/layout/index.module.less
  2. 2 2
      src/components/layout/layoutTop.tsx
  3. 23 1
      src/components/layout/modals/update-password.module.less
  4. 44 9
      src/components/layout/modals/update-password.tsx
  5. 1 1
      src/router/routes/index.ts
  6. 二进制
      src/views/attend-class/image/icon-change.png
  7. 二进制
      src/views/attend-class/image/icon-down.png
  8. 二进制
      src/views/attend-class/image/icon-up.png
  9. 12 2
      src/views/attend-class/index.module.less
  10. 266 170
      src/views/attend-class/index.tsx
  11. 二进制
      src/views/attend-class/model/chapter/images/arrow-active.png
  12. 二进制
      src/views/attend-class/model/chapter/images/arrow-default.png
  13. 39 0
      src/views/attend-class/model/chapter/images/icon-custom.svg
  14. 二进制
      src/views/attend-class/model/chapter/images/icon-d-active.png
  15. 二进制
      src/views/attend-class/model/chapter/images/icon-d.png
  16. 二进制
      src/views/attend-class/model/chapter/images/icon-upload-bg.png
  17. 40 0
      src/views/attend-class/model/chapter/images/icon_default.svg
  18. 93 0
      src/views/attend-class/model/chapter/index.module.less
  19. 145 0
      src/views/attend-class/model/chapter/index.tsx
  20. 6 1
      src/views/attend-class/model/train-settings/index.tsx
  21. 3 1
      src/views/classList/modals/Gotoclass.tsx
  22. 3 1
      src/views/home/modals/chioseModal.tsx
  23. 8 0
      src/views/prepare-lessons/api.ts
  24. 1 1
      src/views/prepare-lessons/components/directory-main/index.module.less
  25. 4 2
      src/views/prepare-lessons/components/lesson-main/courseware/index.tsx
  26. 4 2
      src/views/prepare-lessons/model/attend-class/index.tsx

+ 8 - 10
src/components/layout/index.module.less

@@ -133,16 +133,6 @@
         margin-right: 24px;
 
 
-
-        &.messageBadgeNo {
-          :global {
-            .n-badge-sup {
-              visibility: hidden;
-            }
-          }
-
-        }
-
         .messageIcon {
           width: 32px;
           height: 32px;
@@ -157,6 +147,14 @@
         }
       }
 
+      .messageBadgeNo {
+        :global {
+          .n-badge-sup {
+            visibility: hidden;
+          }
+        }
+      }
+
       @keyframes Tada {
         0% {
           transform: scale(1);

+ 2 - 2
src/components/layout/layoutTop.tsx

@@ -64,7 +64,7 @@ export default defineComponent({
       if (evt.data.api === 'onImClose') {
         showImGroup.value = false;
       } else if (evt.data.api === 'getNoReadMessageCount') {
-        // console.log(evt, 'onMessage');
+        console.log(evt, 'onMessage');
         noReadCount.value = evt.data.count || 0;
       }
     };
@@ -92,7 +92,7 @@ export default defineComponent({
                 max={99}
                 class={[
                   styles.messageBadge,
-                  noReadCount.value <= 0 ? styles.messageBadgeNo : ''
+                  noReadCount.value > 0 ? '' : styles.messageBadgeNo
                 ]}
                 {...{ id: 'home-2' }}
                 color={'#FF1036'}>

+ 23 - 1
src/components/layout/modals/update-password.module.less

@@ -1,3 +1,18 @@
+.no-pwd {
+  font-family: 'dotfont';
+
+  :global {
+    .n-input__input-el {
+      -webkit-text-security: disc !important;
+      -moz-text-security: disc !important;
+    }
+
+    .n-input__placeholder span {
+      margin-top: -5px;
+    }
+  }
+}
+
 .updatePassword {
   padding: 0px 40px 30px;
 
@@ -7,6 +22,13 @@
     color: #777777;
   }
 
+
+  .pwdIcon {
+    width: 24px;
+    height: 24px;
+    cursor: pointer;
+  }
+
   .phoneContainer {
     :global {
       .n-form-item-feedback-wrapper {
@@ -60,4 +82,4 @@
       line-height: 62px;
     }
   }
-}
+}

+ 44 - 9
src/components/layout/modals/update-password.tsx

@@ -13,7 +13,9 @@ import { useRouter } from 'vue-router';
 import { useUserStore } from '/src/store/modules/users';
 import { gradeToCN } from '/src/utils/contants';
 import { sendSms } from '/src/views/login/api';
-import { updatePassword } from '@/views/home/api'
+import { updatePassword } from '@/views/home/api';
+import openEye from '/src/views/login/images/openEye.png';
+import closeEye from '/src/views/login/images/closeEye.png';
 export default defineComponent({
   name: 'train-update',
   emits: ['close', 'submit'],
@@ -28,6 +30,11 @@ export default defineComponent({
       code: null
     });
 
+    const password = reactive({
+      passowrdStatus: false,
+      rePasswordStatus: false
+    });
+
     const validatePass2 = (rule: any, value: any, callback: any): any => {
       const reg = /^(?![0-9]+$)(?![a-zA-Z]+$)[0-9A-Za-z]{6,20}$/;
       if (value === '' || !value) {
@@ -143,10 +150,24 @@ export default defineComponent({
             <NInput
               v-model:value={forms.password}
               clearable
-              type="password"
-              show-password-on="click"
-              placeholder={'请输入新密码'}
-            />
+              type="text"
+              showPasswordOn="click"
+              inputProps={{ autocomplete: 'off' }}
+              class={[password.passowrdStatus ? '' : styles['no-pwd']]}
+              placeholder={'请输入新密码'}>
+              {{
+                suffix: () => (
+                  <img
+                    src={password.passowrdStatus ? openEye : closeEye}
+                    class={styles.pwdIcon}
+                    alt=""
+                    onClick={() => {
+                      password.passowrdStatus = !password.passowrdStatus;
+                    }}
+                  />
+                )
+              }}
+            </NInput>
           </NFormItem>
           <NFormItem
             path="rePassword"
@@ -161,10 +182,24 @@ export default defineComponent({
             <NInput
               v-model:value={forms.rePassword}
               clearable
-              type="password"
-              show-password-on="click"
-              placeholder={'再次输入新密码'}
-            />
+              type="text"
+              showPasswordOn="click"
+              inputProps={{ autocomplete: 'off' }}
+              class={[password.rePasswordStatus ? '' : styles['no-pwd']]}
+              placeholder={'再次输入新密码'}>
+              {{
+                suffix: () => (
+                  <img
+                    src={password.rePasswordStatus ? openEye : closeEye}
+                    class={styles.pwdIcon}
+                    alt=""
+                    onClick={() => {
+                      password.rePasswordStatus = !password.rePasswordStatus;
+                    }}
+                  />
+                )
+              }}
+            </NInput>
           </NFormItem>
           <NFormItem
             path="code"

+ 1 - 1
src/router/routes/index.ts

@@ -21,7 +21,7 @@ export const constantRoutes: RouteRecordRaw[] = [
         name: 'Home',
         component: () => import('@/views/home/index'),
         meta: {
-          title: '页',
+          title: '页',
           singleLayout: 'blank'
         }
       },

二进制
src/views/attend-class/image/icon-change.png


二进制
src/views/attend-class/image/icon-down.png


二进制
src/views/attend-class/image/icon-up.png


+ 12 - 2
src/views/attend-class/index.module.less

@@ -220,7 +220,7 @@
   overflow: hidden;
 
   &.point {
-    margin: 20px 0;
+    margin: 20px 0 0;
   }
 
   img {
@@ -232,9 +232,15 @@
     opacity: 0.8;
   }
 
+  &.iconUp,
+  &.iconDown {
+    margin-top: 20px;
+  }
+
   &.btnsDisabled {
     opacity: 0;
     pointer-events: none;
+    display: none;
   }
 }
 
@@ -287,13 +293,17 @@
 .drawerContainer {
   width: 360px !important;
 
+  .cardContainer {
+    margin-bottom: 24px;
+  }
+
   :global {
     .n-drawer-body-content-wrapper {
       padding: 8px 0px 0px !important;
       text-align: center;
 
       &>div {
-        margin-bottom: 24px;
+        // margin-bottom: 24px;
       }
     }
 

+ 266 - 170
src/views/attend-class/index.tsx

@@ -11,6 +11,7 @@ import {
 import styles from './index.module.less';
 import 'plyr/dist/plyr.css';
 import MusicScore from './component/musicScore';
+import iconChange from './image/icon-change.png';
 import iconMenu from './image/icon-menu.png';
 import iconUp from './image/icon-up.png';
 import iconDown from './image/icon-down.png';
@@ -39,7 +40,12 @@ import Pen from './component/tools/pen';
 import AudioPay from './component/audio-pay';
 import TrainSettings from './model/train-settings';
 import { useRoute } from 'vue-router';
-import { lessonPreTrainingPage, queryCourseware } from '../prepare-lessons/api';
+import {
+  courseScheduleUpdate,
+  lessonCoursewareDetail,
+  lessonPreTrainingPage,
+  queryCourseware
+} from '../prepare-lessons/api';
 import Attentguide from '@/custom-plugins/guide-page/attent-guide';
 import { vaildUrl } from '/src/utils/urlUtils';
 import TimerMeter from '/src/components/timerMeter';
@@ -51,6 +57,7 @@ import toneIcon from '/src/components/layout/images/toneIcon.png';
 import { px2vw } from '/src/utils';
 import PlaceholderTone from '/src/components/layout/modals/placeholderTone';
 import { state as globalState } from '/src/state';
+import Chapter from './model/chapter';
 export type ToolType = 'init' | 'pen' | 'whiteboard';
 export type ToolItem = {
   type: ToolType;
@@ -69,13 +76,24 @@ export default defineComponent({
       type: [String, Number],
       default: ''
     },
+    // 教材编号
+    lessonCourseId: {
+      type: [String, Number],
+      default: ''
+    },
     detailId: {
       type: String,
       default: ''
     },
+    // 班级编号
     classGroupId: {
       type: String,
       default: ''
+    },
+    // 上课记录编号
+    classId: {
+      type: String,
+      defaault: ''
     }
   },
   emits: ['close'],
@@ -120,8 +138,10 @@ export default defineComponent({
     const data = reactive({
       type: 'class' as '' | 'preview' | 'class', // 预览类型
       subjectId: '' as any, // 声部编号
+      lessonCourseId: '' as any, // 教材编号
       detailId: '' as any, // 编号 - 章节编号
       classGroupId: '' as any, // 上课时需要 班级编号
+      classId: '' as any, // 上课编号
       // detail: null,
       knowledgePointList: [] as any,
       itemList: [] as any,
@@ -360,7 +380,9 @@ export default defineComponent({
       data.type = props.type || (query.type as any);
       data.subjectId = props.subjectId || query.subjectId;
       data.detailId = props.detailId || query.detailId;
+      data.lessonCourseId = props.lessonCourseId || query.lessonCourseId;
       data.classGroupId = props.classGroupId || query.classGroupId;
+      data.classId = props.classId || query.classId;
 
       const subdEl = document.getElementById(`moveNPopoverA`) as HTMLDivElement;
       initBoundaryWrap(subdEl, boxBoundaryInfo);
@@ -368,6 +390,7 @@ export default defineComponent({
 
       window.addEventListener('message', iframeHandle);
       getDetail();
+      getLessonCoursewareDetail();
       window.addEventListener('resize', resetSize);
     });
 
@@ -523,9 +546,35 @@ export default defineComponent({
     const popupData = reactive({
       open: false,
       activeIndex: 0,
-      toolOpen: false // 工具弹窗控制
+      toolOpen: false, // 工具弹窗控制
+      chapterOpen: false, // 切换章节
+      chapterDetails: {} as any,
+      chapterLoading: false // 加载数据
     });
 
+    /** 获取章节 */
+    const getLessonCoursewareDetail = async () => {
+      try {
+        const res = await lessonCoursewareDetail({
+          id: data.lessonCourseId
+        });
+
+        popupData.chapterDetails = res.data.lessonList || [];
+      } catch {
+        //
+      }
+    };
+    /** 更新上课记录 */
+    const classCourseScheduleUpdate = async () => {
+      try {
+        if (!data.classId) return;
+        await courseScheduleUpdate({
+          lessonCoursewareKnowledgeDetailId: data.detailId,
+          id: data.classId
+        });
+      } catch {}
+    };
+
     const activeName = computed(() => {
       let name = '';
       data.knowledgePointList.forEach((item: any, index: number) => {
@@ -850,79 +899,117 @@ export default defineComponent({
 
     return () => (
       <div id="playContent" class={[styles.playContent, 'wrap']}>
-        <div
-          onClick={() => {
-            clearTimeout(activeData.timer);
-            activeData.model = !activeData.model;
-            Object.values(data.videoRefs).map((n: any) =>
-              n.toggleHideControl(activeData.model)
-            );
-            Object.values(data.audioRefs).map((n: any) =>
-              n.toggleHideControl(activeData.model)
-            );
-          }}>
+        {!popupData.chapterLoading ? (
           <div
-            class={styles.coursewarePlay}
-            style={{ width: parentContainer.width }}
-            onClick={(e: Event) => {
-              e.stopPropagation();
-              setModelOpen();
+            onClick={() => {
+              clearTimeout(activeData.timer);
+              activeData.model = !activeData.model;
+              Object.values(data.videoRefs).map((n: any) =>
+                n.toggleHideControl(activeData.model)
+              );
+              Object.values(data.audioRefs).map((n: any) =>
+                n.toggleHideControl(activeData.model)
+              );
             }}>
-            <div class={styles.wraps}>
-              {data.itemList.map((m: any, mIndex: number) => {
-                const isRender =
-                  m.isRender || Math.abs(popupData.activeIndex - mIndex) < 2;
-                const isEmtry = Math.abs(popupData.activeIndex - mIndex) > 4;
-                if (isRender) {
-                  m.isRender = true;
-                }
-                return isRender ? (
-                  <div
-                    key={'index' + mIndex}
-                    class={[
-                      styles.itemDiv,
-                      popupData.activeIndex === mIndex && styles.itemActive,
-                      activeData.isAnimation && styles.acitveAnimation,
-                      Math.abs(popupData.activeIndex - mIndex) < 2
-                        ? styles.show
-                        : styles.hide
-                    ]}
-                    style={
-                      mIndex < popupData.activeIndex
-                        ? effects[effectIndex.value].prev
-                        : mIndex > popupData.activeIndex
-                        ? effects[effectIndex.value].next
-                        : {}
-                    }
-                    onClick={(e: Event) => {
-                      e.stopPropagation();
-                      clearTimeout(activeData.timer);
-                      if (Date.now() - activeData.nowTime < 300) {
-                        handleDbClick(m);
-                        return;
+            <div
+              class={styles.coursewarePlay}
+              style={{ width: parentContainer.width }}
+              onClick={(e: Event) => {
+                e.stopPropagation();
+                setModelOpen();
+              }}>
+              <div class={styles.wraps}>
+                {data.itemList.map((m: any, mIndex: number) => {
+                  const isRender =
+                    m.isRender || Math.abs(popupData.activeIndex - mIndex) < 2;
+                  const isEmtry = Math.abs(popupData.activeIndex - mIndex) > 4;
+                  if (isRender) {
+                    m.isRender = true;
+                  }
+                  return isRender ? (
+                    <div
+                      key={'index' + mIndex}
+                      class={[
+                        styles.itemDiv,
+                        popupData.activeIndex === mIndex && styles.itemActive,
+                        activeData.isAnimation && styles.acitveAnimation,
+                        Math.abs(popupData.activeIndex - mIndex) < 2
+                          ? styles.show
+                          : styles.hide
+                      ]}
+                      style={
+                        mIndex < popupData.activeIndex
+                          ? effects[effectIndex.value].prev
+                          : mIndex > popupData.activeIndex
+                          ? effects[effectIndex.value].next
+                          : {}
                       }
-                      activeData.nowTime = Date.now();
-                      activeData.timer = setTimeout(() => {
-                        activeData.model = !activeData.model;
-                        Object.values(data.videoRefs).map((n: any) =>
-                          n.toggleHideControl(activeData.model)
-                        );
-                        Object.values(data.audioRefs).map((n: any) =>
-                          n.toggleHideControl(activeData.model)
-                        );
-                        if (activeData.model) {
-                          setModelOpen();
+                      onClick={(e: Event) => {
+                        e.stopPropagation();
+                        clearTimeout(activeData.timer);
+                        if (Date.now() - activeData.nowTime < 300) {
+                          handleDbClick(m);
+                          return;
                         }
-                      }, 300);
-                    }}>
-                    {m.type === 'VIDEO' ? (
-                      <>
-                        <VideoPlay
-                          ref={(v: any) => (data.videoRefs[mIndex] = v)}
+                        activeData.nowTime = Date.now();
+                        activeData.timer = setTimeout(() => {
+                          activeData.model = !activeData.model;
+                          Object.values(data.videoRefs).map((n: any) =>
+                            n.toggleHideControl(activeData.model)
+                          );
+                          Object.values(data.audioRefs).map((n: any) =>
+                            n.toggleHideControl(activeData.model)
+                          );
+                          if (activeData.model) {
+                            setModelOpen();
+                          }
+                        }, 300);
+                      }}>
+                      {m.type === 'VIDEO' ? (
+                        <>
+                          <VideoPlay
+                            ref={(v: any) => (data.videoRefs[mIndex] = v)}
+                            item={m}
+                            isEmtry={isEmtry}
+                            onLoadedmetadata={(videoItem: any) => {
+                              m.videoEle = videoItem;
+                              m.isprepare = true;
+                            }}
+                            onTogglePlay={(paused: boolean) => {
+                              m.autoPlay = false;
+                              if (paused || popupData.open) {
+                                clearTimeout(activeData.timer);
+                              } else {
+                                setModelOpen();
+                              }
+                            }}
+                            onReset={() => {
+                              if (!m.videoEle?.paused) {
+                                setModelOpen();
+                              }
+                            }}
+                            onError={() => {
+                              console.log('video error');
+                              m.error = true;
+                            }}
+                          />
+                          <Transition name="van-fade">
+                            {!m.isprepare && (
+                              <div class={styles.loadWrap}>
+                                <Vue3Lottie
+                                  animationData={playLoadData}></Vue3Lottie>
+                              </div>
+                            )}
+                          </Transition>
+                        </>
+                      ) : m.type === 'IMG' ? (
+                        <img src={m.content} />
+                      ) : m.type === 'SONG' ? (
+                        <AudioPay
                           item={m}
-                          isEmtry={isEmtry}
-                          onLoadedmetadata={(videoItem: any) => {
-                            m.videoEle = videoItem;
+                          ref={(v: any) => (data.audioRefs[mIndex] = v)}
+                          onLoadedmetadata={(audioItem: any) => {
+                            m.audioEle = audioItem;
                             m.isprepare = true;
                           }}
                           onTogglePlay={(paused: boolean) => {
@@ -933,115 +1020,91 @@ export default defineComponent({
                               setModelOpen();
                             }
                           }}
+                          onEnded={() => {
+                            const _index = popupData.activeIndex + 1;
+                            if (_index < data.itemList.length) {
+                              handleSwipeChange(_index);
+                            }
+                          }}
                           onReset={() => {
-                            if (!m.videoEle?.paused) {
+                            if (!m.audioEle?.paused) {
                               setModelOpen();
                             }
                           }}
-                          onError={() => {
-                            console.log('video error');
-                            m.error = true;
+                        />
+                      ) : (
+                        <MusicScore
+                          activeModel={activeData.model}
+                          data-vid={m.id}
+                          music={m}
+                          onSetIframe={(el: any) => {
+                            m.iframeRef = el;
                           }}
                         />
-                        <Transition name="van-fade">
-                          {!m.isprepare && (
-                            <div class={styles.loadWrap}>
-                              <Vue3Lottie
-                                animationData={playLoadData}></Vue3Lottie>
-                            </div>
-                          )}
-                        </Transition>
-                      </>
-                    ) : m.type === 'IMG' ? (
-                      <img src={m.content} />
-                    ) : m.type === 'SONG' ? (
-                      <AudioPay
-                        item={m}
-                        ref={(v: any) => (data.audioRefs[mIndex] = v)}
-                        onLoadedmetadata={(audioItem: any) => {
-                          m.audioEle = audioItem;
-                          m.isprepare = true;
-                        }}
-                        onTogglePlay={(paused: boolean) => {
-                          m.autoPlay = false;
-                          if (paused || popupData.open) {
-                            clearTimeout(activeData.timer);
-                          } else {
-                            setModelOpen();
-                          }
-                        }}
-                        onEnded={() => {
-                          const _index = popupData.activeIndex + 1;
-                          if (_index < data.itemList.length) {
-                            handleSwipeChange(_index);
-                          }
-                        }}
-                        onReset={() => {
-                          if (!m.audioEle?.paused) {
-                            setModelOpen();
-                          }
-                        }}
-                      />
-                    ) : (
-                      <MusicScore
-                        activeModel={activeData.model}
-                        data-vid={m.id}
-                        music={m}
-                        onSetIframe={(el: any) => {
-                          m.iframeRef = el;
-                        }}
-                      />
-                    )}
-                  </div>
-                ) : null;
-              })}
-            </div>
-            <Transition name="right">
-              {activeData.model && (
-                <div
-                  class={styles.rightFixedBtns}
-                  onClick={(e: Event) => {
-                    e.stopPropagation();
-                    clearTimeout(activeData.timer);
-                  }}>
+                      )}
+                    </div>
+                  ) : null;
+                })}
+              </div>
+              <Transition name="right">
+                {activeData.model && (
                   <div
-                    class={[
-                      styles.fullBtn,
-                      popupData.activeIndex === 0 ? styles.btnsDisabled : ''
-                    ]}
-                    onClick={() => {
-                      if (popupData.activeIndex === 0) return;
-                      handlePreAndNext('up');
+                    class={styles.rightFixedBtns}
+                    onClick={(e: Event) => {
+                      e.stopPropagation();
+                      clearTimeout(activeData.timer);
                     }}>
-                    <img src={iconUp} />
-                  </div>
-                  <div id="attent-0">
                     <div
-                      class={[styles.fullBtn, styles.point]}
-                      onClick={() => (popupData.open = true)}>
-                      <img src={iconMenu} />
+                      class={[styles.fullBtn]}
+                      onClick={() => (popupData.chapterOpen = true)}>
+                      <img src={iconChange} />
                     </div>
-
                     <div
                       class={[
                         styles.fullBtn,
-                        popupData.activeIndex === data.itemList.length - 1
-                          ? styles.btnsDisabled
-                          : ''
+                        styles.iconUp,
+                        popupData.activeIndex === 0 ? styles.btnsDisabled : ''
                       ]}
                       onClick={() => {
-                        if (popupData.activeIndex === data.itemList.length - 1)
-                          return;
-                        handlePreAndNext('down');
+                        if (popupData.activeIndex === 0) return;
+                        handlePreAndNext('up');
                       }}>
-                      <img src={iconDown} />
+                      <img src={iconUp} />
+                    </div>
+                    <div id="attent-0">
+                      <div
+                        class={[styles.fullBtn, styles.point]}
+                        onClick={() => (popupData.open = true)}>
+                        <img src={iconMenu} />
+                      </div>
+
+                      <div
+                        class={[
+                          styles.fullBtn,
+                          styles.iconDown,
+                          popupData.activeIndex === data.itemList.length - 1
+                            ? styles.btnsDisabled
+                            : ''
+                        ]}
+                        onClick={() => {
+                          if (
+                            popupData.activeIndex ===
+                            data.itemList.length - 1
+                          )
+                            return;
+                          handlePreAndNext('down');
+                        }}>
+                        <img src={iconDown} />
+                      </div>
                     </div>
                   </div>
-                </div>
-              )}
-            </Transition>
+                )}
+              </Transition>
+            </div>
           </div>
-        </div>
+        ) : (
+          ''
+        )}
 
         <div
           style={{ transform: activeData.model ? '' : 'translateY(-100%)' }}
@@ -1161,20 +1224,53 @@ export default defineComponent({
           showMask={false}>
           <NDrawerContent title="资源列表" closable>
             {data.knowledgePointList.map((item: any, index: number) => (
-              <CardType
-                item={item}
-                isActive={popupData.activeIndex === index}
-                isCollect={false}
-                isShowCollect={false}
-                onClick={(item: any) => {
-                  popupData.open = false;
-                  toggleMaterial(item.id);
-                }}
-              />
+              <div class={styles.cardContainer}>
+                <CardType
+                  item={item}
+                  isActive={popupData.activeIndex === index}
+                  isCollect={false}
+                  isShowCollect={false}
+                  onClick={(item: any) => {
+                    popupData.open = false;
+                    toggleMaterial(item.id);
+                  }}
+                />
+              </div>
             ))}
           </NDrawerContent>
         </NDrawer>
 
+        {/* 显示列表 */}
+        <NDrawer
+          v-model:show={popupData.chapterOpen}
+          class={styles.drawerContainer}
+          onAfterLeave={handleClosePopup}
+          showMask={false}>
+          <NDrawerContent title="切换章节" closable>
+            <Chapter
+              treeList={popupData.chapterDetails}
+              itemActive={data.detailId as any}
+              onHandleSelect={async (val: any) => {
+                popupData.chapterLoading = true;
+
+                try {
+                  data.detailId = val.itemActive;
+                  // 更新上课记录 上课的时候才更新
+                  if (data.type !== 'preview') {
+                    await classCourseScheduleUpdate();
+                  }
+                  await getDetail();
+                  popupData.activeIndex = 0;
+                  popupData.chapterOpen = false;
+                } catch {
+                  //
+                }
+                popupData.chapterLoading = false;
+              }}
+            />
+          </NDrawerContent>
+        </NDrawer>
+
         {/* 批注 */}
         {studyData.penShow && (
           <Pen

二进制
src/views/attend-class/model/chapter/images/arrow-active.png


二进制
src/views/attend-class/model/chapter/images/arrow-default.png


+ 39 - 0
src/views/attend-class/model/chapter/images/icon-custom.svg

@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="80px" height="82px" viewBox="0 0 80 82" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <title>编组 6</title>
+    <defs>
+        <linearGradient x1="16.0585307%" y1="48.1812553%" x2="88.7815146%" y2="51.655599%" id="linearGradient-1">
+            <stop stop-color="#308AFF" offset="0%"></stop>
+            <stop stop-color="#60A9FF" offset="100%"></stop>
+        </linearGradient>
+        <path d="M26.1421356,-9.86533591e-13 L77.8578644,-9.86533591e-13 C80.5100293,-9.92349856e-13 83.0535684,1.0535684 84.9289322,2.92893219 L104,22 L104,22 L-8.56952355e-13,22 L19.0710678,2.92893219 C20.9464316,1.0535684 23.4899707,-9.86046396e-13 26.1421356,-9.86533591e-13 Z" id="path-2"></path>
+        <linearGradient x1="50%" y1="13.692199%" x2="50%" y2="69.4941756%" id="linearGradient-4">
+            <stop stop-color="#FFFFFF" stop-opacity="0.404392483" offset="0%"></stop>
+            <stop stop-color="#FFFFFF" stop-opacity="0" offset="100%"></stop>
+        </linearGradient>
+    </defs>
+    <g id="所有页面" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="46、选择教材" transform="translate(-1010.000000, -322.000000)">
+            <g id="编组-4" transform="translate(385.000000, 127.000000)">
+                <g id="编组-2备份-2" transform="translate(605.000000, 179.703907)">
+                    <g id="编组-6" transform="translate(3.703907, 0.000000)">
+                        <g id="编组-5" transform="translate(48.777762, 48.777762) rotate(-45.000000) translate(-48.777762, -48.777762) translate(-4.075266, 32.648617)">
+                            <path d="M0.577507392,31.6130682 L-1.44486575e-13,24.5356078 L-1.44486575e-13,24.5356078 C0.150435792,23.0883 1.1854755,22.0762681 3.10511912,21.4995122 C5.02476274,20.9227562 7.59556321,20.1716791 10.8175205,19.2462809 L11.1122213,21.7991185 L1.08841361,31.8010336 C0.971128318,31.9180628 0.781178938,31.9178551 0.664149799,31.8005698 C0.613985136,31.7502954 0.583283393,31.6838541 0.577507392,31.6130682 Z" id="路径-33" fill="#1E3DCC"></path>
+                            <path d="M95.1713422,31.6130682 L94.5938348,24.5356078 L94.5938348,24.5356078 C94.7442706,23.0883 95.7793103,22.0762681 97.698954,21.4995122 C99.6185976,20.9227562 102.189398,20.1716791 105.411355,19.2462809 L105.706056,21.7991185 L95.6822485,31.8010336 C95.5649632,31.9180628 95.3750138,31.9178551 95.2579846,31.8005698 C95.20782,31.7502954 95.1771182,31.6838541 95.1713422,31.6130682 Z" id="路径-33备份" fill="#1E3DCC" transform="translate(100.149945, 25.752285) scale(-1, 1) translate(-100.149945, -25.752285) "></path>
+                            <g id="矩形" transform="translate(0.845434, 0.711632)">
+                                <mask id="mask-3" fill="white">
+                                    <use xlink:href="#path-2"></use>
+                                </mask>
+                                <use id="蒙版" fill="url(#linearGradient-1)" transform="translate(52.000000, 11.000000) rotate(-360.000000) translate(-52.000000, -11.000000) " xlink:href="#path-2"></use>
+                                <rect fill="url(#linearGradient-4)" mask="url(#mask-3)" transform="translate(30.713441, 12.039373) rotate(-315.000000) translate(-30.713441, -12.039373) " x="24.713441" y="-13.960627" width="12" height="52"></rect>
+                            </g>
+                            <text id="自定义" transform="translate(52.499475, 11.000000) rotate(-360.000000) translate(-52.499475, -11.000000) " font-family="PingFangSC-Semibold, PingFang SC" font-size="16" font-weight="500" letter-spacing="1" fill="#FFFFFF">
+                                <tspan x="26.9994747" y="17">自定义</tspan>
+                            </text>
+                        </g>
+                    </g>
+                </g>
+            </g>
+        </g>
+    </g>
+</svg>

二进制
src/views/attend-class/model/chapter/images/icon-d-active.png


二进制
src/views/attend-class/model/chapter/images/icon-d.png


二进制
src/views/attend-class/model/chapter/images/icon-upload-bg.png


+ 40 - 0
src/views/attend-class/model/chapter/images/icon_default.svg

@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="51px" height="49px" viewBox="0 0 51 49" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <title>编组@2x</title>
+    <defs>
+        <linearGradient x1="60.3790157%" y1="14.1179897%" x2="60.3790157%" y2="99.1367692%" id="linearGradient-1">
+            <stop stop-color="#D8D9DB" offset="0%"></stop>
+            <stop stop-color="#D3D4D6" offset="100%"></stop>
+        </linearGradient>
+        <linearGradient x1="47.928727%" y1="88.2137009%" x2="47.928727%" y2="-1.32949207e-12%" id="linearGradient-2">
+            <stop stop-color="#D0D1D3" offset="0%"></stop>
+            <stop stop-color="#CACBCD" offset="100%"></stop>
+        </linearGradient>
+        <linearGradient x1="57.5601934%" y1="1.66592618%" x2="56.4312509%" y2="99.1367692%" id="linearGradient-3">
+            <stop stop-color="#D8D9DB" offset="0%"></stop>
+            <stop stop-color="#D1D2D4" offset="100%"></stop>
+        </linearGradient>
+        <linearGradient x1="47.928727%" y1="88.2137009%" x2="47.928727%" y2="-1.32949207e-12%" id="linearGradient-4">
+            <stop stop-color="#D0D1D3" offset="0%"></stop>
+            <stop stop-color="#C8C9CB" offset="100%"></stop>
+        </linearGradient>
+        <linearGradient x1="47.928727%" y1="88.2137009%" x2="47.928727%" y2="-1.32949207e-12%" id="linearGradient-5">
+            <stop stop-color="#CFD0D2" offset="0%"></stop>
+            <stop stop-color="#CBCCCE" offset="100%"></stop>
+        </linearGradient>
+    </defs>
+    <g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="67、AI学练" transform="translate(-1720.000000, -737.000000)" fill-rule="nonzero">
+            <g id="编组" transform="translate(1720.000000, 737.000000)">
+                <g id="编组-6" transform="translate(0.600000, 0.666667)">
+                    <path d="M0.755328486,15.0252525 C-0.830540243,19.9313215 0.11107891,25.262202 3.29524979,29.4047571 C6.47942068,33.5473122 11.5319763,36.0147566 16.9240016,36.0606061 C24.4286025,36.09296 31.0759035,31.4573623 33.3061224,24.6361115 L0.755328486,15.0252525 Z" id="路径" fill="url(#linearGradient-1)"></path>
+                    <path d="M17.7262393,31.0568351 C8.4521233,30.9803529 0.940551474,23.8601879 0.813375057,15.0252525 L0.754723889,15.0252525 C-0.829875444,19.9313026 0.110989998,25.2621626 3.29261214,29.4047017 C6.47423428,33.5472408 11.5227456,36.0146758 16.9104549,36.060355 C24.4276023,36.0999738 31.0862959,31.4479077 33.3061224,24.6055962 L31.7758601,24.1535015 C28.5683718,28.5148198 23.3157927,31.0956941 17.7262393,31.0568351 L17.7262393,31.0568351 Z" id="路径" fill="url(#linearGradient-2)"></path>
+                    <path d="M14.7677422,5.37825957 C14.7677422,5.37825957 18.3328036,4.44948812 20.0004184,7.54187485 C15.9970737,11.1250329 13.586515,16.8823604 15.6870684,21.3679043 C16.5048411,23.1304592 18.0548677,24.5341705 18.6962581,26.4392074 C20.6791709,32.3335664 18.6004257,38.818554 13.5437557,42.5132859 L13.4849616,42.5555028 C13.4849616,42.5555028 12.9504696,42.951286 12.1113173,43.5370453 C14.650154,45.1782267 18.4236672,47.1360346 21.8711403,47.7956734 C30.2359393,49.4051921 38.8305699,44.0067081 42.0642462,36.4921028 C42.3367119,35.8608728 42.5652914,35.2120441 42.7483959,34.5501262 C43.5795078,31.4293042 43.8097647,28.1821626 43.4272007,24.9774478 C43.202714,23.0407483 42.6147729,19.4681445 45.3674064,19.0354214 C46.4363903,18.8665539 47.4626149,19.4048192 48.2803876,20.0539038 C48.4033207,20.1488917 49.5364437,21.067109 49.4562699,21.2518078 C51.2735425,16.9351315 48.2964223,5.51018733 38.3869416,1.64734247 C31.0376772,-1.32367072 20.3692179,-0.389622168 14.7677422,5.37825957 Z" id="路径" fill="url(#linearGradient-3)"></path>
+                    <path d="M26.6124657,47.6072826 C21.4912819,46.636432 18.7337213,44.7527707 16.9716508,43.1698621 C17.8393774,42.600015 18.3557545,42.2148405 18.3557545,42.2148405 L18.4143128,42.1726296 C23.5265567,38.5761859 25.72439,32.1283454 23.8602286,26.1958052 C23.2533524,24.2752094 21.7308383,22.8505916 20.9589343,21.077734 C18.9626308,16.5558917 21.459341,10.8415915 25.5105062,7.33281071 C23.9134634,4.20920435 20.3414111,5.07452773 20.3414111,5.07452773 C23.237382,2.18835767 27.421634,0.584343589 31.7922078,0.114747363 C25.5850348,-0.47092883 18.8188969,1.17001978 14.7570848,5.39110946 C14.7570848,5.39110946 18.3078432,4.46774609 19.9687677,7.55441792 C15.9814843,11.1370678 13.5805966,16.8988552 15.6727227,21.383763 C16.4872145,23.1407916 18.0310225,24.5495802 18.6698396,26.4490706 C20.6447971,32.3425937 18.5743917,38.8266616 13.5380088,42.5208695 L13.4794506,42.5630804 C13.4794506,42.5630804 12.947103,42.9640839 12.1113173,43.5444838 C14.6399683,45.1907088 18.3983423,47.1429627 21.8319843,47.8077844 C23.7881287,48.171816 25.7955452,48.171816 27.7516896,47.8077844 C27.3524289,47.744468 26.9797855,47.6811517 26.6124657,47.6072826 Z" id="路径" fill="url(#linearGradient-4)"></path>
+                    <path d="M45.0423476,14.0093804 C46.200789,13.854566 48.445269,13.5716291 48.445269,13.5716291 C46.707607,9.89878877 42.4247158,7.65130943 39.7402509,6.83452952 C39.7068343,6.83452952 38.3200464,6.42347036 38.3311852,6.43948565 C33.2852822,5.3717995 29.0190992,6.29000959 25.799969,9.25283865 C22.37477,12.4025128 21.6340358,17.9598192 24.3964728,21.7340897 C25.154529,22.6993831 26.100114,23.5151123 27.1811875,24.1363836 C25.0592349,38.8384218 37.0335082,40.6855189 37.1560356,40.562735 C39.1777385,38.2405176 41.2105802,35.939654 41.2105802,28.0601302 C41.2105802,25.9247579 40.2916244,22.4120704 40.1579581,21.5045372 C39.5230431,17.1750699 40.8652756,14.5966078 45.0423476,14.0093804 Z" id="路径" fill="#F4F5F7"></path>
+                    <ellipse id="椭圆形" fill="url(#linearGradient-5)" cx="35.5769944" cy="14.2739899" rx="2.27087199" ry="2.25378788"></ellipse>
+                </g>
+            </g>
+        </g>
+    </g>
+</svg>

+ 93 - 0
src/views/attend-class/model/chapter/index.module.less

@@ -0,0 +1,93 @@
+.scrollBar {
+  margin: 0 20px;
+  width: calc(100% - 40px);
+}
+
+.treeParent {
+  transition: height 1s ease-in-out;
+}
+
+.treeChild {
+  line-height: 54px;
+}
+
+.treeItem {
+  display: flex;
+  align-items: center;
+  line-height: 54px;
+  border-radius: 10px;
+  padding: 0 5px;
+  cursor: pointer;
+  border-radius: 10px;
+  font-size: max(17px, 13px);
+
+  &:hover {
+    background: #F5F6FA;
+  }
+
+  .title {
+    padding-left: 8px;
+    overflow: hidden;
+    white-space: nowrap;
+    text-overflow: ellipsis;
+    max-width: 280px !important;
+    color: rgba(0, 0, 0, .5);
+    display: flex;
+    align-items: center;
+
+    .dir {
+      flex-shrink: 1;
+      display: inline-block;
+      width: 16px;
+      height: 18px;
+      background: url('./images/icon-d.png') no-repeat center;
+      background-size: contain;
+      margin-right: 6px;
+    }
+
+    &.titleSelect {
+      color: #198CFE;
+      font-weight: bold;
+
+      .dir {
+        background: url('./images/icon-d-active.png') no-repeat center;
+        background-size: contain;
+      }
+    }
+  }
+
+  .arrow {
+    display: inline-block;
+    width: 14px;
+    height: 15px;
+    background: url('./images/arrow-default.png') no-repeat center;
+    background-size: contain;
+
+    &.arrowSelect {
+      background: url('./images/arrow-active.png') no-repeat center;
+      background-size: contain;
+    }
+  }
+
+  .childArrow {
+    width: 12px;
+  }
+
+  &.childItem {
+    padding-left: 30px;
+    font-size: 15px;
+
+    .title {
+      color: #131415;
+    }
+  }
+
+  &.childSelect {
+    background: #F5F6FA;
+
+    .title {
+      color: #198CFE;
+      font-weight: bold;
+    }
+  }
+}

+ 145 - 0
src/views/attend-class/model/chapter/index.tsx

@@ -0,0 +1,145 @@
+import { defineComponent, onMounted, reactive, toRefs, watch } from 'vue';
+import styles from './index.module.less';
+import { NScrollbar, useMessage } from 'naive-ui';
+
+export default defineComponent({
+  name: 'chapter-modal',
+  props: {
+    treeList: {
+      type: Array,
+      default: () => []
+    },
+    itemActive: {
+      type: String,
+      default: ''
+    }
+  },
+  emits: ['handleSelect'],
+  setup(props, { emit }) {
+    const message = useMessage();
+    const { treeList, itemActive } = toRefs(props);
+
+    const formatParentId = (id: any, list: any, ids = [] as any) => {
+      for (const item of list) {
+        if (item.knowledgeList && item.knowledgeList.length > 0) {
+          const cIds: any = formatParentId(id, item.knowledgeList, [
+            ...ids,
+            item.id
+          ]);
+          if (cIds.includes(id)) {
+            return cIds;
+          }
+        }
+        if (item.id === id) {
+          return [...ids, id];
+        }
+      }
+      return ids;
+    };
+
+    watch(
+      () => props.itemActive,
+      () => {
+        const ids = formatParentId(itemActive.value, treeList.value);
+        if (ids.length > 0) {
+          treeList.value.forEach((tree: any) => {
+            if (tree.id == ids[0]) {
+              tree.selected = true;
+            } else {
+              tree.selected = false;
+            }
+          });
+        }
+      }
+    );
+
+    onMounted(() => {
+      const ids = formatParentId(itemActive.value, treeList.value);
+      if (ids.length > 0) {
+        treeList.value.forEach((tree: any) => {
+          if (tree.id == ids[0]) {
+            tree.selected = true;
+          }
+        });
+      }
+    });
+    return () => (
+      <NScrollbar class={styles.scrollBar}>
+        <div class={[styles.listSection]}>
+          {treeList.value.map((item: any, index: number) => (
+            <div class={styles.treeParent} key={'parent' + index}>
+              <div
+                class={[styles.treeItem, styles.parentItem]}
+                onClick={() => {
+                  treeList.value.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.title,
+                    item.selected ? styles.titleSelect : ''
+                  ]}>
+                  <span
+                    class={[
+                      styles.dir,
+                      item.selected ? styles.dirSelect : ''
+                    ]}></span>
+                  {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,
+                      itemActive.value === child.id ? styles.childSelect : ''
+                    ]}
+                    onClick={() => {
+                      // 判断是否选择的同一个课件
+                      if (itemActive.value == child.id) {
+                        return;
+                      }
+                      if (!child.containMaterial) {
+                        message.error('该章节暂无课件');
+                        return;
+                      }
+                      emit('handleSelect', {
+                        itemActive: child.id,
+                        itemName: child.name
+                      });
+                      // emit('handleSelect', {});
+                      // 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>
+      </NScrollbar>
+    );
+  }
+});

+ 6 - 1
src/views/attend-class/model/train-settings/index.tsx

@@ -133,6 +133,10 @@ export default defineComponent({
 
     const onSubmit = async () => {
       // 训练内容不能为空
+      if (!trainForms.expireDate) {
+        message.error('请选择截止日期');
+        return;
+      }
       if (trainForms.trainList.length <= 0) {
         message.error('训练内容不能为空');
         return;
@@ -188,7 +192,6 @@ export default defineComponent({
               placeholder="请选择截止日期"
               v-model:formatted-value={trainForms.expireDate}
               type="date"
-              clearable
               valueFormat="yyyy-MM-dd"
               isDateDisabled={(ts: number) => {
                 return ts < trainForms.currentTime;
@@ -267,6 +270,7 @@ export default defineComponent({
             type="homework"
             onClose={() => (trainForms.editStatus = false)}
             onConfirm={(item: any) => {
+              console.log(item, 'update', trainForms);
               const tList = typeFormat(
                 item.trainingType,
                 item.trainingConfigJson
@@ -276,6 +280,7 @@ export default defineComponent({
                 trainForms.trainList.forEach((train: any) => {
                   if (train.id === item.id) {
                     train.trainingType = item.trainingType;
+                    train.trainingConfigJson = item.trainingConfigJson;
                     train.typeList = tList;
                   }
                 });

+ 3 - 1
src/views/classList/modals/Gotoclass.tsx

@@ -56,7 +56,7 @@ export default defineComponent({
           rows: 99
         });
         if (data.rows && data.rows.length > 0) {
-          await courseScheduleStart({
+          const res = await courseScheduleStart({
             lessonCoursewareKnowledgeDetailId: forms.chapter,
             classGroupId: props.activeRow.id
           });
@@ -64,6 +64,8 @@ export default defineComponent({
           emit('close');
           emit('preview', {
             type: 'class',
+            classId: res.data, // 上课编号
+            lessonCourseId: forms.category,
             classGroupId: props.activeRow.id,
             subjectId: forms.subjectId,
             detailId: forms.chapter

+ 3 - 1
src/views/home/modals/chioseModal.tsx

@@ -53,14 +53,16 @@ export default defineComponent({
           rows: 99
         });
         if (data.rows && data.rows.length > 0) {
-          await courseScheduleStart({
+          const res = await courseScheduleStart({
             lessonCoursewareKnowledgeDetailId: forms.chapter,
             classGroupId: forms.currentClass
           });
           emit('close');
           emit('preview', {
             type: 'class',
+            classId: res.data, // 上课编号
             classGroupId: forms.currentClass,
+            lessonCourseId: forms.category, // 册别编号
             subjectId: forms.subjectId,
             detailId: forms.chapter
           });

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

@@ -151,3 +151,11 @@ export const tagUseCourseware = (params?: any) => {
     data: params
   });
 };
+/**
+ * 备课 - 更新上课记录
+ */
+export const courseScheduleUpdate = (params?: any) => {
+  return request.post('/edu-app/courseSchedule/update', {
+    data: params
+  });
+};

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

@@ -131,4 +131,4 @@
 
 .coursewareModal {
   width: 1150px;
-}
+}

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

@@ -206,7 +206,8 @@ export default defineComponent({
         forms.previewParams = {
           type: 'preview',
           subjectId: prepareStore.getSubjectId,
-          detailId: prepareStore.getSelectKey
+          detailId: prepareStore.getSelectKey,
+          lessonCourseId: prepareStore.getBaseCourseware.id
         };
       } else {
         const { href } = router.resolve({
@@ -214,7 +215,8 @@ export default defineComponent({
           query: {
             type: 'preview',
             subjectId: prepareStore.getSubjectId,
-            detailId: prepareStore.getSelectKey
+            detailId: prepareStore.getSelectKey,
+            lessonCourseId: prepareStore.getBaseCourseware.id
           }
         });
         window.open(href, +new Date() + '');

+ 4 - 2
src/views/prepare-lessons/model/attend-class/index.tsx

@@ -59,7 +59,7 @@ export default defineComponent({
           return;
         }
 
-        await courseScheduleStart({
+        const res = await courseScheduleStart({
           lessonCoursewareKnowledgeDetailId: prepareStore.selectKey,
           classGroupId: item.id
         });
@@ -67,9 +67,11 @@ export default defineComponent({
         emit('close');
         emit('preview', {
           type: 'class',
+          classId: res.data, // 上课编号
           classGroupId: item.id,
           subjectId: prepareStore.getSubjectId,
-          detailId: prepareStore.getSelectKey
+          detailId: prepareStore.getSelectKey,
+          lessonCourseId: prepareStore.getBaseCourseware.id
         });
         if (window.matchMedia('(display-mode: standalone)').matches) {
           state.application = window.matchMedia(