Browse Source

feat: 上课页面底部交互修改

TIANYONG 1 năm trước cách đây
mục cha
commit
e237d492f0

+ 3 - 3
src/views/attend-class/component/audio.module.less

@@ -46,14 +46,14 @@
 
 .controls {
   position: absolute;
-  bottom: 126px;
+  bottom: 0;
   left: 0;
   right: 0;
   width: 100%;
   background-color: rgba(0, 0, 0, 0.7);
   // backdrop-filter: blur(26px);
-  height: 86px;
-  padding: 10px 40px 0 !important;
+  height: 108px;
+  padding: 0 330px 0 40px !important;
   transition: all 0.301s;
   display: flex;
   align-items: center;

+ 3 - 4
src/views/attend-class/component/video.module.less

@@ -11,18 +11,17 @@
 
   .controls {
     position: absolute;
-    bottom: 126px;
+    bottom: 0;
     left: 0;
     right: 0;
     width: 100%;
     background-color: rgba(0, 0, 0, 0.7);
     // backdrop-filter: blur(26px);
-    height: 86px;
-    padding: 10px 40px 0 !important;
+    height: 108px;
+    padding: 0 330px 0 40px !important;
     display: flex;
     align-items: center;
     transition: all 0.301s;
-
     .time {
       display: flex;
       justify-content: space-between;

BIN
src/views/attend-class/image/bottom_icon1.png


BIN
src/views/attend-class/image/bottom_icon2.png


BIN
src/views/attend-class/image/bottom_icon3.png


BIN
src/views/attend-class/image/bottom_icon4.png


BIN
src/views/attend-class/image/right_hide_icon.png


BIN
src/views/attend-class/image/right_icon1.png


BIN
src/views/attend-class/image/right_icon2.png


BIN
src/views/attend-class/image/right_icon3.png


BIN
src/views/attend-class/image/right_icon4.png


BIN
src/views/attend-class/image/right_icon5.png


BIN
src/views/attend-class/image/right_icon6.png


BIN
src/views/attend-class/image/right_icon7.png


BIN
src/views/attend-class/image/right_icon8.png


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

@@ -622,4 +622,161 @@
     z-index: 9;
     background: transparent;
   }
+}
+
+.rightColumn {
+  position: absolute;
+  right: 0;
+  top: 50%;
+  transform: translateY(-50%);
+  background: rgba(0, 0, 0, 0.3);
+  border-radius: 10px;
+  width: 76px;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  padding: 10px 0;
+  transition: all 0.5s;
+  .rightItem {
+    width: 54px;
+    height: 54px;
+    margin-bottom: 12px;
+    cursor: pointer;
+    position: relative;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    z-index: 9;
+    &.active, &:hover {
+      background: rgba(0, 0, 0, 0.16);
+      border-radius: 8px;
+      z-index: 9;
+    }
+    &::after {
+      content: "";
+      position: absolute;
+      left: 50%;
+      bottom: -6px;
+      transform: translateX(-50%);
+      width: 44px;
+      height: 1px;
+      background: rgba(255, 255, 255, 0.2);
+    }
+    img {
+      width: 34px;
+      height: 34px;
+      margin: auto;
+      z-index: 9;
+    }
+    .rightTips {
+      position: absolute;
+      right: -500%;
+      top: 50;
+      transform: translateX(-50%);
+      background: rgba(19, 20, 21, 1);
+      border-radius: 6px;
+      width: 88px;
+      height: 34px;
+      line-height: 34px;
+      text-align: center;
+      font-size: 16px;
+      font-family: PingFangSC, PingFang SC;
+      font-weight: 500;
+      color: #FFFFFF;
+      transition: all 0.8s;
+      z-index: 1;
+      opacity: 0;
+    }
+    &:hover {
+      .rightTips {
+        right: 24px;
+        opacity: 1;
+      }
+    }
+  }
+  .rightItem:last-child {
+    width: 54px;
+    height: 36px;
+    img {
+      width: 18px;
+      height: 18px;
+    }
+    &::after {
+      display: none;
+    }
+  }
+  .itemDisabled {
+    opacity: 0.7;
+    cursor: not-allowed;
+  }
+} 
+.rightColumnHide {
+  transform: translate(76px,-50%);
+}
+.rightHideIcon {
+  width: 30px;
+  height: 60px;
+  position: absolute;
+  right: 0;
+  top: 50%;
+  transform: translateY(60px,-50%);
+  z-index: 10;
+  cursor: pointer;
+  transition: all 0.5s;
+}
+.rightIconShow {
+  transform: translate(0,-50%);
+}
+.bottomColumn {
+  position: fixed;
+  right: 40px;
+  bottom: 26px;
+  display: flex;
+  z-index: 2;
+  .bottomItem {
+    width: 54px;
+    height: 54px;
+    background: rgba(0, 0, 0, 0.3);
+    border-radius: 10px;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    margin-right: 16px;
+    position: relative;
+    cursor: pointer;
+    .bottomTips {
+      position: absolute;
+      left: 50%;
+      top: -40px;
+      transform: translateX(-50%);
+      opacity: 0;
+      background: rgba(19, 20, 21, 1);
+      border-radius: 6px;
+      width: 88px;
+      height: 34px;
+      line-height: 34px;
+      text-align: center;
+      font-size: 16px;
+      font-family: PingFangSC, PingFang SC;
+      font-weight: 500;
+      color: #FFFFFF;
+    }
+    &.active, &:hover {
+      background: rgba(0, 0, 0, 0.5);
+      .bottomTips {
+        opacity: 1;
+      }
+    }
+    img {
+      width: 40px;
+      height: 40px;
+    }
+  }
+  .bottomItem:last-child {
+    margin-right: 0;
+  }
+  .itemDisabled {
+    opacity: 0.7;
+    cursor: not-allowed;
+  }
 }

+ 254 - 12
src/views/attend-class/index.tsx

@@ -6,7 +6,8 @@ import {
   ref,
   Transition,
   computed,
-  nextTick
+  nextTick,
+  watch
 } from 'vue';
 import styles from './index.module.less';
 import 'plyr/dist/plyr.css';
@@ -73,6 +74,20 @@ import iconUp from './new-image/icon-up.png';
 import iconWhite from './new-image/icon-white.png';
 import iconWork from './new-image/icon-work.png';
 
+import rightIconEnd from './image/right_icon1.png';
+import rightIconArrange from './image/right_icon2.png';
+import rightIconPostil from './image/right_icon3.png';
+import rightIconWhiteboard from './image/right_icon4.png';
+import rightIconMetronome from './image/right_icon5.png';
+import rightIconTuner from './image/right_icon6.png';
+import rightIconTimer from './image/right_icon7.png';
+import rightIconPackUp from './image/right_icon8.png';
+import bottomIconSwitch from './image/bottom_icon1.png';
+import bottomIconResource from './image/bottom_icon2.png';
+import bottomIconPre from './image/bottom_icon3.png';
+import bottomIconNext from './image/bottom_icon4.png';
+import rightHideIcon from './image/right_hide_icon.png';
+
 export type ToolType = 'init' | 'pen' | 'whiteboard';
 export type ToolItem = {
   type: ToolType;
@@ -186,6 +201,10 @@ export default defineComponent({
       timer: null as any,
       item: null as any
     });
+
+    // 键盘事件监听状态
+    const listenerKeyUpState = ref(false);
+
     const getDetail = async () => {
       try {
         const res = await queryCourseware({
@@ -279,7 +298,6 @@ export default defineComponent({
     };
 
     onMounted(() => {
-      // debugger
       // initMoveable();
       const query = route.query;
       console.log(query, props.preStudentNum, '学生人数');
@@ -291,10 +309,12 @@ export default defineComponent({
       data.classGroupId = props.classGroupId || query.classGroupId;
       data.classId = props.classId || query.classId;
       data.preStudentNum = props.preStudentNum || query.preStudentNum;
-
       window.addEventListener('message', iframeHandle);
       getDetail();
       getLessonCoursewareDetail();
+      if (data.type === 'preview') {
+        rightList.splice(1,1)
+      }
     });
 
     const onFullScreen = () => {
@@ -824,8 +844,6 @@ export default defineComponent({
         setModelOpen();
       }
     };
-
-    // 监听页面键盘事件 - 上下切换
     document.body.addEventListener('keyup', (e: KeyboardEvent) => {
       // console.log(e, 'e');
       if (e.code === 'ArrowLeft') {
@@ -839,6 +857,39 @@ export default defineComponent({
       }
     });
 
+    const toggleListenerKeyUp = (type: string) => {
+      if (type === 'remove') {
+        document.body.removeEventListener('keyup', () => {
+          listenerKeyUpState.value = false
+        })
+      } else {
+        // 监听页面键盘事件 - 上下切换
+        document.body.addEventListener('keyup', (e: KeyboardEvent) => {
+          // console.log(e, 'e');
+          if (e.code === 'ArrowLeft') {
+            // if (popupData.activeIndex === 0) return;
+            setModalOpen();
+            handlePreAndNext('up');
+          } else if (e.code === 'ArrowRight') {
+            // if (popupData.activeIndex === data.itemList.length - 1) return;
+            setModalOpen();
+            handlePreAndNext('down');
+          }
+        });
+        listenerKeyUpState.value = true
+      }
+    }
+
+    // 监听切换到ppt课件时,手动移除键盘监听器
+    // watch(() => popupData.activeIndex, () => {
+    //   const activeItem = data.itemList[popupData.activeIndex];
+    //   if (activeItem?.type === 'PPT') {
+    //     toggleListenerKeyUp('remove')
+    //   } else {
+    //     !listenerKeyUpState.value && toggleListenerKeyUp('add')
+    //   }
+    // })
+
     const setModalOpen = (status = true) => {
       clearTimeout(activeData.timer);
       activeData.model = status;
@@ -1053,6 +1104,161 @@ export default defineComponent({
       }
       return {}
     })
+
+    // 右侧菜单栏
+    const rightList = reactive([
+      {
+        name: '结束课程',
+        name2: '结束预览',
+        icon: rightIconEnd,
+        id: 1,
+      },
+      {
+        name: '布置作业',
+        icon: rightIconArrange,
+        id: 2,
+      },
+      {
+        name: '批注',
+        icon: rightIconPostil,
+        id: 3,
+      },
+      {
+        name: '白板',
+        icon: rightIconWhiteboard,
+        id: 4,
+      },
+      {
+        name: '节拍器',
+        icon: rightIconMetronome,
+        id: 5,
+      },
+      {
+        name: '调音器',
+        icon: rightIconTuner,
+        id: 6,
+      },
+      {
+        name: '计时器',
+        icon: rightIconTimer,
+        id: 7,
+      },
+      {
+        name: '收起',
+        icon: rightIconPackUp,
+        id: 8,
+      },
+    ]);
+
+    // 底部菜单栏
+    const bottomList = reactive([
+      {
+        name: '切换章节',
+        icon: bottomIconSwitch,
+        id: 1,
+      },
+      {
+        name: '资源列表',
+        icon: bottomIconResource,
+        id: 2,
+      },
+      {
+        name: '上一张',
+        icon: bottomIconPre,
+        id: 3,
+      },
+      {
+        name: '下一张',
+        icon: bottomIconNext,
+        id: 4,
+      }
+    ]);
+    const rightColumnShow = ref(true);
+ 
+
+    // 右边栏操作
+    const operateRightBtn = async (id: number) => {
+      switch (id) {
+        case 1:
+          if (data.type === 'preview') {
+            handleStop();
+            data.removeVisiable = true;
+            data.removeTitle = '结束预览';
+            data.removeContent = '请确认是否结束预览?';
+          } else {
+            data.removeVisiable = true;
+            data.removeTitle = '结束课程';
+            data.removeContent = '请确认是否结束课程?';
+          }
+          break;
+        case 2:
+          // 学生人数必须大于0,才可以布置作业
+          if (data.preStudentNum <= 0) return;
+          const res = await lessonPreTrainingPage({
+            coursewareKnowledgeDetailId: data.detailId,
+            subjectId: data.subjectId,
+            page: 1,
+            rows: 99
+          });
+          if (res.data.rows && res.data.rows.length) {
+            data.modalAttendMessage =
+              '本节课已设置课后作业,是否布置?';
+          }
+          data.modelAttendStatus = true;          
+          break;
+        case 3:
+          openStudyTool({
+            type: 'pen',
+            icon: iconNote,
+            name: '批注'
+          })          
+          break;
+        case 4:
+          openStudyTool({
+            type: 'whiteboard',
+            icon: iconWhite,
+            name: '白板'
+          })          
+          break;
+        case 5:
+          startShowModal('beatIcon')
+          break;
+        case 6:
+          startShowModal('toneIcon')
+          break;
+        case 7:
+          startShowModal('setTimeIcon')
+          break;
+        case 8:
+          rightColumnShow.value = false
+          break;                                                                  
+        default:
+          break;
+      }
+    }
+
+    // 底部悬浮按钮操作
+    const operateBottomBtn = (id: number) => {
+      switch (id) {
+        case 1:
+          popupData.chapterOpen = true
+          break;
+        case 2:
+          popupData.open = true
+          break;
+        case 3:
+          if (!isUpArrow.value) return;
+          handlePreAndNext('up');
+          break;
+        case 4:
+          if (!isDownArrow.value) return;
+          handlePreAndNext('down');
+          break;      
+        default:
+          break;
+      }
+    }
+
     return () => (
       <div id="playContent" class={[styles.playContent, 'wrap']}>
         <div
@@ -1225,10 +1431,11 @@ export default defineComponent({
                           }}
                         />
                       ) 
-                      : m.type === 'PPT' ? <div class={styles.iframePpt}>
-                        <div class={styles.pptBox}></div>
-                        <iframe src={`https://view.officeapps.live.com/op/embed.aspx?src=${encodeURIComponent(m.content)}`} width='100%' height='100%' frameborder='1'></iframe>
-                      </div>
+                      // : m.type === 'PPT' ? <div class={styles.iframePpt}>
+                      //   <div class={styles.pptBox}></div>
+                      //   <iframe src={`https://view.officeapps.live.com/op/embed.aspx?src=${encodeURIComponent(m.content)}`} width='100%' height='100%' frameborder='1'></iframe>
+                      // </div>
+                      : m.type === 'PPT' ? <iframe src={`https://view.officeapps.live.com/op/embed.aspx?src=${encodeURIComponent(m.content)}`} width='100%' height='100%' frameborder='1'></iframe>                     
                       : (
                         <MusicScore
                           activeModel={activeData.model}
@@ -1251,14 +1458,14 @@ export default defineComponent({
         </div>
 
         {/* 头部样式 */}
-        <div
+        {/* <div
           style={{ transform: activeData.model ? '' : 'translateY(-100%)' }}
           class={styles.headerContainer}>
           <div class={styles.menu}>{activeName.value}</div>
-        </div>
+        </div> */}
         {/* 布置作业按钮 */}
 
-        <div
+        {/* <div
           onClick={(e: any) => {
             e.stopPropagation();
             if (activeData.timer){
@@ -1392,6 +1599,41 @@ export default defineComponent({
               <p>下一个</p>
             </div>
           </NSpace>
+        </div> */}
+
+        {/* 右边操作栏 */}
+        <div class={[styles.rightColumn, !rightColumnShow.value ? styles.rightColumnHide : '']}>
+          {rightList.map((item: any, index: number) => (
+              <div 
+                class={[
+                  styles.rightItem,
+                  (item.id === 2 && data.preStudentNum <= 0) ? styles.itemDisabled : ''
+                ]} 
+                onClick={() => operateRightBtn(item.id)}>
+                <img src={item.icon} />
+                <div class={styles.rightTips}>{index === 0 && data.type === 'preview' ? item.name2 : item.name}</div>
+              </div>
+            ))}
+        </div> 
+        {
+          !rightColumnShow.value && <img 
+            class={[styles.rightHideIcon, !rightColumnShow.value ? styles.rightIconShow : '']} 
+            src={rightHideIcon}
+            onClick={() => rightColumnShow.value = true } />
+        }  
+        {/* 右下角悬浮按钮 */}
+        <div class={styles.bottomColumn}>
+          {bottomList.map((item: any, index: number) => (
+              <div 
+                class={[
+                  styles.bottomItem,
+                  ((item.id === 3 && !isUpArrow.value) || (item.id === 4 && !isDownArrow.value)) ? styles.itemDisabled : ''
+                ]} 
+                onClick={() => operateBottomBtn(item.id)}>
+                <img src={item.icon} />
+                <div class={styles.bottomTips}>{item.name}</div>
+              </div>
+          ))}
         </div>
 
         {/* 显示列表 */}