lsc 1 giorno fa
parent
commit
6c559a1ad6
2 ha cambiato i file con 243 aggiunte e 8 eliminazioni
  1. 46 0
      src/services/course.ts
  2. 197 8
      src/views/Student/index.vue

+ 46 - 0
src/services/course.ts

@@ -65,14 +65,60 @@ export const selectWorksStudent = (oid: string, cid: string): Promise<any> => {
   })
 }
 
+/**
+ * 获取HTML内容
+ * @param url 目标URL
+ * @returns Promise<any>
+ */
 export const getHTML = (url: string): Promise<any> => {
   return axios.get(`${url}`)
 }
 
+/**
+ * 开启/关闭课程跟随模式
+ * @param sopen 1否 2是
+ * @param cid 课程ID
+ * @returns Promise<any>
+ */
+export const updateCourseFollow = (sopen: number, cid: string): Promise<any> => {
+  return axios.post(`${API_URL}updateCourseFollow`, [{
+    sopen,
+    cid,
+  }])
+}
+
+/**
+ * 新增:开启/关闭课程跟随模式(带第几张参数)
+ * @param sopen 1否 2是
+ * @param page 第几张
+ * @param cid 课程ID
+ * @returns Promise<any>
+ */
+export const updateCourseFollowC = (sopen: number, cid: string): Promise<any> => {
+  return axios.post(`${API_URL}updateCourseFollowC`, [{
+    sopen,
+    cid,
+  }])
+}
+
+/**
+ * 查看课程跟随状态
+ * @param cid 课程ID
+ * @returns Promise<any>
+ */
+export const selectCourseSLook = (cid: string): Promise<any> => {
+  return axios.get(`${API_URL}selectCourseSLook`, {
+    params: { cid },
+  })
+}
+
 export default {
   getCourseDetail,
   submitWork,
   selectSWorks,
   selectWorksStudent,
   getHTML,
+  updateCourseFollow,
+  updateCourseFollowC,
+  selectCourseSLook,
 }

+ 197 - 8
src/views/Student/index.vue

@@ -34,10 +34,10 @@
       <div class="viewer-header" :class="{ 'hidden': isFullscreen }">
         <div class="slide-title">幻灯片 {{ slideIndex + 1 }}</div>
         <div class="viewer-controls">
-          <button @click="previousSlide" :disabled="slideIndex === 0" title="上一页">
+          <button @click="previousSlide" :disabled="slideIndex === 0" title="上一页" v-if="!isFollowModeActive || props.type == '1'">
             <IconLeftTwo class="control-icon" />
           </button>
-          <button @click="nextSlide" :disabled="slideIndex === slides.length - 1" title="下一页">
+          <button @click="nextSlide" :disabled="slideIndex === slides.length - 1" title="下一页" v-if="!isFollowModeActive || props.type == '1'">
             <IconRightTwo class="control-icon" />
           </button>
           <!-- <button @click="resetZoom" title="重置缩放">
@@ -46,6 +46,14 @@
           <button @click="enterFullscreen" title="全屏">
             <IconFullScreenOne class="control-icon" />
           </button>
+          <!-- 只有创建人才显示跟随模式按钮 -->
+          <button 
+            v-if="isCreator" 
+            @click="toggleFollowMode" 
+            :class="{ 'follow-active': isFollowModeActive }"
+          >
+            {{ isFollowModeActive ? '关闭跟随模式' : '开启跟随模式' }}
+          </button>
           <!-- <button @click="backToEditor" class="back-btn" title="返回编辑">
                         <IconEdit class="control-icon" />
                     </button> -->
@@ -127,10 +135,11 @@
           </button>
         </div>
         
-        <!-- 收缩状态下的切换按钮 -->
-        <div v-show="workPanelCollapsed" class="collapsed-tabs">
+        <!-- 侧边导航标签 - 无论展开还是收缩都显示在左侧 -->
+        <div class="side-nav-tabs">
           <button 
-            class="collapsed-tab-btn" 
+            v-if="currentSlideHasIframe"
+            class="side-nav-btn" 
             :class="{ active: rightPanelMode === 'homework' }"
             @click="switchToHomework"
             title="作业"
@@ -138,7 +147,7 @@
             <img :src="rightPanelMode === 'homework' ? homeworkActiveIcon : homeworkIcon" alt="作业">
           </button>
           <button 
-            class="collapsed-tab-btn" 
+            class="side-nav-btn" 
             :class="{ active: rightPanelMode === 'dialogue' }"
             @click="switchToDialogue"
             title="对话"
@@ -147,7 +156,7 @@
           </button>
           <button 
             v-if="isChoiceQuestion"
-            class="collapsed-tab-btn" 
+            class="side-nav-btn" 
             :class="{ active: rightPanelMode === 'choice' }"
             @click="switchToChoice"
             title="统计"
@@ -156,6 +165,8 @@
           </button>
         </div>
         
+
+        
         <!-- 作业区内容 -->
         <div v-show="!workPanelCollapsed && rightPanelMode === 'homework'" class="panel-content">
           <div class="homework-title">已提交</div>
@@ -318,6 +329,10 @@ const workUpdateInterval = 5000 // 5秒更新一次
 const courseDetail = ref<any>({})
 const studentArray = ref<any>([])
 
+// 跟随模式相关状态
+const isCreator = ref(false) // 是否为创建人
+const isFollowModeActive = ref(false) // 跟随模式是否开启
+
 // 计算未提交作业的学生
 const unsubmittedStudents = computed(() => {
   if (!studentArray.value || !workArray.value) return []
@@ -355,6 +370,26 @@ const switchToChoice = () => {
   }
 }
 
+// 自动切换到可用的面板
+const autoSwitchToAvailablePanel = () => {
+  // 如果当前在作业区但没有iframe,自动切换到其他可用面板
+  if (rightPanelMode.value === 'homework' && !currentSlideHasIframe.value) {
+    if (isChoiceQuestion.value) {
+      rightPanelMode.value = 'choice'
+      console.log('自动切换到统计面板')
+    }
+    else {
+      rightPanelMode.value = 'dialogue'
+      console.log('自动切换到对话面板')
+    }
+  }
+  // 如果当前在统计面板但不是选择题,自动切换到对话面板
+  else if (rightPanelMode.value === 'choice' && !isChoiceQuestion.value) {
+    rightPanelMode.value = 'dialogue'
+    console.log('自动切换到对话面板')
+  }
+}
+
 // 启动作业更新定时器
 const startWorkTimer = () => {
   if (workTimer.value) {
@@ -561,9 +596,21 @@ watch(() => slideIndex.value, (newIndex, oldIndex) => {
       console.log('当前页面无iframe,停止作业更新定时器')
       stopWorkTimer()
     }
+    if (isFollowModeActive.value && isCreator.value) {
+      api.updateCourseFollowC(newIndex, props.courseid as string)
+    }
+    // 自动切换到可用的面板
+    autoSwitchToAvailablePanel()
   }
 }, { immediate: false, deep: false })
 
+// 监听iframe状态变化,自动切换面板
+watch(() => currentSlideHasIframe.value, (hasIframe) => {
+  if (!hasIframe) {
+    autoSwitchToAvailablePanel()
+  }
+}, { immediate: false })
+
 // 全屏
 const enterFullscreen = () => {
   if (document.fullscreenElement) {
@@ -782,6 +829,7 @@ const importJSON = (jsonData: any) => {
             getWork()
             startWorkTimer()
           }
+          selectCourseSLook()
           console.log('组件重新渲染完成')
         }, 500)
       })
@@ -1161,6 +1209,7 @@ const getCourseDetail = async () => {
     const courseData = res[0][0]
     courseDetail.value = courseData
     selectWorksStudent()
+    checkIsCreator()
     const pptJSONUrl = JSON.parse(courseData.chapters).pptData ? JSON.parse(courseData.chapters).pptData : ''
     console.log(pptJSONUrl)
     
@@ -1291,6 +1340,60 @@ const checkWorkArrayChanged = (oldArray: WorkItem[], newArray: WorkItem[]): bool
   return false
 }
 
+// 查询课程跟随状态
+const selectCourseSLook = async () => {
+  const res = await api.selectCourseSLook(props.courseid as string)
+  console.log('selectCourseSLook', res)
+  if (res[0][0].follow == 2) {
+    if (props.type == '2') {
+      goToSlide(Number(res[0][0].followC))
+    }
+    isFollowModeActive.value = true
+  }
+  else {
+    isFollowModeActive.value = false
+  }
+}
+
+// 切换跟随模式
+const toggleFollowMode = async () => {
+  try {
+    const newFollowState = !isFollowModeActive.value
+    const sopen = newFollowState ? 2 : 1
+    
+    // 调用API更新跟随状态
+    const res = await api.updateCourseFollow(sopen, props.courseid as string)
+    console.log('更新跟随模式状态:', res)
+    
+    if (res) {
+      isFollowModeActive.value = newFollowState
+      message.success(newFollowState ? '跟随模式已开启' : '跟随模式已关闭')
+      
+      // 如果开启跟随模式,设置当前幻灯片为跟随目标
+      if (newFollowState) {
+        await api.updateCourseFollowC(slideIndex.value, props.courseid as string)
+        console.log('设置当前幻灯片为跟随目标:', slideIndex.value)
+      }
+    }
+    else {
+      message.error('操作失败,请重试')
+    }
+  }
+  catch (error) {
+    console.error('切换跟随模式失败:', error)
+    message.error('操作失败,请重试')
+  }
+}
+
+// 检查是否为创建人
+const checkIsCreator = () => {
+  // 这里可以根据实际业务逻辑判断是否为创建人
+  // 比如通过props中的userid与课程创建者ID比较
+  if (courseDetail.value && props.userid) {
+    isCreator.value = courseDetail.value.userid === props.userid
+  }
+}
+
 onMounted(() => {
   document.addEventListener('keydown', handleKeydown)
 
@@ -1319,6 +1422,9 @@ onMounted(() => {
 
     // 处理iframe链接
     processIframeLinks()
+    
+    // 初始化时检查并自动切换到可用面板
+    autoSwitchToAvailablePanel()
   })
 
   // 监听窗口大小变化
@@ -1433,6 +1539,12 @@ onUnmounted(() => {
   border-left: 1px solid #e0e0e0;
   overflow-y: auto;
   transition: width .2s ease;
+  position: relative;
+}
+
+.panel-content {
+  margin-right: 48px;
+  padding: 0 8px;
 }
 .layout-content-right.collapsed {
   width: 48px;
@@ -1443,6 +1555,71 @@ onUnmounted(() => {
   justify-content: space-between;
 }
 
+/* 侧边导航标签样式 */
+.side-nav-tabs {
+  position: absolute;
+  right: 0;
+  top: 60px;
+  bottom: 0;
+  width: 48px;
+  display: flex;
+  flex-direction: column;
+  gap: 8px;
+  padding: 8px 0;
+  align-items: center;
+  background: #fff;
+  border-left: 1px solid #e0e0e0;
+  z-index: 10;
+}
+
+.side-nav-btn {
+  width: 40px;
+  height: 40px;
+  min-height: 40px;
+  border: 1px solid #d9d9d9;
+  background: #fff;
+  border-radius: 6px;
+  cursor: pointer;
+  transition: all 0.2s;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  padding: 8px;
+  gap: 8px;
+  overflow: hidden;
+  
+  &:hover {
+    border-color: #1890ff;
+    transform: scale(1.02);
+  }
+  
+  &.active {
+    border-color: #3681fc;
+    background: #3681fc;
+    box-shadow: 0 0 0 2px rgba(54, 129, 252, 0.2);
+  }
+  
+  img {
+    width: 20px;
+    height: 20px;
+    object-fit: contain;
+    flex-shrink: 0;
+  }
+  
+  span {
+    font-size: 12px;
+    font-weight: 500;
+    color: #333;
+    white-space: nowrap;
+    overflow: hidden;
+    text-overflow: ellipsis;
+  }
+  
+  &.active span {
+    color: #fff;
+  }
+}
+
 /* 收缩时头部仅显示按钮,并保持按钮在可用宽度内水平居中 */
 .layout-content-right.collapsed .right-panel-header {
   justify-content: center;
@@ -1676,7 +1853,7 @@ onUnmounted(() => {
   display: flex;
   align-items: center;
   justify-content: space-between;
-  padding: 0 24px;
+  padding: 0 8px;
   transition: transform 0.3s ease;
 
   &.hidden {
@@ -1728,6 +1905,18 @@ onUnmounted(() => {
         }
       }
 
+      &.follow-active {
+        background-color: #3681fc;
+        color: #fff !important;
+        border-color: #3681fc;
+
+        &:hover {
+          background-color: #2d6fd9;
+          border-color: #2d6fd9;
+          color: #fff !important;
+        }
+      }
+
       .control-icon {
         font-size: 16px;
       }