lsc vor 2 Wochen
Ursprung
Commit
f27df88d9c
2 geänderte Dateien mit 767 neuen und 459 gelöschten Zeilen
  1. 190 197
      src/views/Student/index.vue
  2. 577 262
      src/views/Student/index2.vue

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

@@ -419,7 +419,7 @@
 </template>
 
 <script lang="ts" setup>
-import { computed, ref, onMounted, onUnmounted, nextTick, inject, watch, provide } from 'vue'
+import { computed, ref, onMounted, onUnmounted, nextTick, inject, watch, provide, shallowRef } from 'vue'
 import { storeToRefs } from 'pinia'
 import { useSlidesStore } from '@/store'
 import { ElementTypes } from '@/types/slides'
@@ -1068,6 +1068,7 @@ onUnmounted(() => {
     aiBtn.removeEventListener('pointerup', handleAiBtnPointerUp)
     aiBtn.removeEventListener('pointercancel', handleAiBtnPointerCancel)
   }
+  cleanupWebSocketConnection()
 })
 
 const visibleAIChat = ref(false)
@@ -1119,8 +1120,15 @@ const currentIsResultArray = computed(() => {
 // 跳转到指定幻灯片
 const goToSlide = (index: number) => {
   console.log('goToSlide 被调用,目标索引:', index)
-  console.log('当前索引:', slideIndex.value)
-  
+  console.log('当前索引:', slideIndex.value, 'slides长度:', slides.value.length)
+
+  // slides 还没加载完时,暂存目标索引,等 slides 就绪后再跳转
+  if (slides.value.length === 0) {
+    console.warn('goToSlide: slides 未加载,暂存目标索引:', index)
+    pendingSlideIndex.value = index
+    return
+  }
+
   if (index >= 0 && index < slides.value.length) {
     slidesStore.updateSlideIndex(index)
     console.log('更新后的索引:', slideIndex.value)
@@ -1130,6 +1138,20 @@ const goToSlide = (index: number) => {
   }
 }
 
+// 暂存的目标幻灯片索引(slides 未就绪时使用)
+const pendingSlideIndex = ref<number | null>(null)
+// slides 加载完成后应用暂存的跳转
+watch(() => slides.value.length, (len) => {
+  if (len > 0 && pendingSlideIndex.value !== null) {
+    const idx = pendingSlideIndex.value
+    pendingSlideIndex.value = null
+    if (idx >= 0 && idx < len) {
+      console.log('🔄 slides 就绪,应用暂存的跳转索引:', idx)
+      slidesStore.updateSlideIndex(idx)
+    }
+  }
+})
+
 // 上一页
 const previousSlide = () => {
   if (slideIndex.value > 0) {
@@ -3182,7 +3204,7 @@ const setCan = (Array: any[]) => {
 }
 
 const forceLogout = () => {
-  // cleanupWebSocketConnection()
+  cleanupWebSocketConnection()
   sendMessage({ type: 'logout' })
 }
 
@@ -3211,158 +3233,141 @@ const checkIsCreator = () => {
  * 初始化消息监听
  */
 const messageInit = () => {
-  if (!docSocket.value) return
+  const doc = docSocket.value
+  if (!doc) return
+
+  // ★ 关键:判断当前绑定的共享类型是否属于这个 doc
+  // y-websocket 内部重连复用同一个 doc → observe 仍有效,直接返回(避免重复 observe)
+  // 只有 doc 被销毁重建(full reconnect)时才全部重新绑定
+  const boundDoc = (yMessage.value as any)?.doc
+  if (boundDoc === doc) {
+    // 同 doc 重连:补发上线消息即可
+    if (props.type === '2') {
+      sendMessage({ type: 'class_begin', courseid: props.courseid })
+    }
+    return
+  }
 
-  // 初始化普通消息数组(只保留最后一条)
-  if (!yMessage.value) {
-    console.log('初始化普通消息Map')
-    // 必须使用 messageV2:服务器上旧的 message 是 Array 根类型,
-    // 同名再 getMap 会导致根类型冲突、后注册的被改名,observe 间歇性失效
-    yMessage.value = docSocket.value.getMap('messageV2')
-    yMessage.value.observe((e: any) => {
-      // Map 的 observe 事件中 keysChanged 包含被更新的 key
-      if (e.keysChanged.has('latest')) {
-        const message = yMessage.value.get('latest')
-        console.log('yMessage', message)
-        if (message && message.mId !== mId.value) {
-          getMessages(message)
-        }
-      }
-    })
+  console.log('🔗 检测到新 Y.Doc,重新绑定所有共享数据监听')
+
+  // ★ 先把旧 doc 上的引用全部置空,杜绝守卫误判指向旧 doc
+  yMessage.value = null
+  yTimerState.value = null
+  yLaserState.value = null
+  yWritingBoardState.value = null
+  yTimerMessages.value = null
+  yLaserMessages.value = null
+  yWritingBoardMessages.value = null
+  yIsResultArrayMessages.value = null
+
+  // 普通消息(Map,只保留最后一条)
+  // 必须使用 messageV2:服务器上旧的 message 是 Array 根类型,
+  // 同名再 getMap 会导致根类型冲突、后注册的被改名,observe 间歇性失效
+  yMessage.value = doc.getMap('messageV2')
+  yMessage.value.observe((e: any) => {
+    // 同时兼容 keysChanged 和 changes.keys(不同 Yjs 版本/事件结构)
+    const changed = e.keysChanged || e.changes?.keys
+    if (!changed || !changed.has('latest')) return
+    const message = yMessage.value!.get('latest')
+    console.log('yMessage', message)
+    if (message && message.mId !== mId.value) {
+      getMessages(message)
+    }
+  })
+
+  // ★ 追平:连接建立后立即读取当前 latest,
+  // 防止老师在学生 observe 注册之前发的消息丢失(尤其是当前所在幻灯片)
+  const latest = yMessage.value.get('latest')
+  if (latest && latest.mId !== mId.value) {
+    console.log('🔗 追平当前 latest 消息:', latest)
+    getMessages(latest)
   }
 
-  // 初始化计时器消息数组
-  if (!yTimerMessages.value) {
-    console.log('初始化计时器消息数组')
-    yTimerMessages.value = docSocket.value.getArray('timerMessages')
-    
-    // 初始化时检查一次是否需要清理
-    if (yTimerMessages.value && yTimerMessages.value.length > 500 && isCreator.value && docSocket.value) {
-      smartCleanupMessages(yTimerMessages.value, 500, 'timer', docSocket.value)
+  // 计时器消息数组
+  yTimerMessages.value = doc.getArray('timerMessages')
+  // 初始化时检查一次是否需要清理
+  if (yTimerMessages.value.length > 500 && isCreator.value) {
+    smartCleanupMessages(yTimerMessages.value, 500, 'timer', doc)
+  }
+  yTimerMessages.value.observe((e: any) => {
+    if (yTimerMessages.value.length > 500 && isCreator.value) {
+      setTimeout(() => {
+        if (yTimerMessages.value && yTimerMessages.value.length > 500) {
+          smartCleanupMessages(yTimerMessages.value, 500, 'timer', doc)
+        }
+      }, 0)
     }
-    
-    yTimerMessages.value.observe((e: any) => {
-      console.log('yTimerMessages', yTimerMessages.value.length)
-      console.log('yTimerMessages', yTimerMessages.value)
-      
-      // 每次有新消息添加后,检查是否需要清理
-      if (yTimerMessages.value && yTimerMessages.value.length > 500 && isCreator.value && docSocket.value) {
-        // 使用 setTimeout 确保在 observe 回调执行完成后再清理
-        setTimeout(() => {
-          if (docSocket.value && yTimerMessages.value && yTimerMessages.value.length > 500) {
-            smartCleanupMessages(yTimerMessages.value, 500, 'timer', docSocket.value)
-          }
-        }, 0)
+    e.changes.added.forEach((i: any) => {
+      const message = i.content.getContent()[0]
+      if (message.mId !== mId.value) {
+        getMessages(message)
       }
-      
-      e.changes.added.forEach((i: any) => {
-        const message = i.content.getContent()[0]
-        if (message.mId !== mId.value) {
-          getMessages(message)
-        }
-      })
     })
-  }
+  })
 
-  // 初始化激光笔消息数组
-  if (!yLaserMessages.value) {
-    console.log('初始化激光笔消息数组')
-    yLaserMessages.value = docSocket.value.getArray('laserMessages')
-    
-    // 初始化时检查一次是否需要清理
-    if (yLaserMessages.value && yLaserMessages.value.length > 500 && isCreator.value && docSocket.value) {
-      smartCleanupMessages(yLaserMessages.value, 500, 'laser', docSocket.value)
+  // 激光笔消息数组
+  yLaserMessages.value = doc.getArray('laserMessages')
+  if (yLaserMessages.value.length > 500 && isCreator.value) {
+    smartCleanupMessages(yLaserMessages.value, 500, 'laser', doc)
+  }
+  yLaserMessages.value.observe((e: any) => {
+    if (yLaserMessages.value.length > 500 && isCreator.value) {
+      setTimeout(() => {
+        if (yLaserMessages.value && yLaserMessages.value.length > 500) {
+          smartCleanupMessages(yLaserMessages.value, 500, 'laser', doc)
+        }
+      }, 0)
     }
-    
-    yLaserMessages.value.observe((e: any) => {
-      console.log('yLaserMessages', yLaserMessages.value.length)
-      console.log('yLaserMessages', yLaserMessages.value)
-      
-      // 每次有新消息添加后,检查是否需要清理
-      if (yLaserMessages.value && yLaserMessages.value.length > 500 && isCreator.value && docSocket.value) {
-        // 使用 setTimeout 确保在 observe 回调执行完成后再清理
-        setTimeout(() => {
-          if (docSocket.value && yLaserMessages.value && yLaserMessages.value.length > 500) {
-            smartCleanupMessages(yLaserMessages.value, 500, 'laser', docSocket.value)
-          }
-        }, 0)
+    e.changes.added.forEach((i: any) => {
+      const message = i.content.getContent()[0]
+      // 只处理开关消息,位置消息直接通过 Map 同步
+      if (message.type === 'laser_toggle' && message.mId !== mId.value) {
+        getMessages(message)
       }
-      
-      e.changes.added.forEach((i: any) => {
-        const message = i.content.getContent()[0]
-        // 只处理开关消息,位置消息直接通过 Map 同步
-        if (message.type === 'laser_toggle' && message.mId !== mId.value) {
-          getMessages(message)
-        }
-      })
     })
-  }
+  })
 
-  // 初始化画图消息数组
-  if (!yWritingBoardMessages.value) {
-    console.log('初始化画图消息数组')
-    yWritingBoardMessages.value = docSocket.value.getArray('writingBoardMessages')
-    
-    // 初始化时检查一次是否需要清理
-    if (yWritingBoardMessages.value && yWritingBoardMessages.value.length > 500 && isCreator.value && docSocket.value) {
-      smartCleanupMessages(yWritingBoardMessages.value, 500, 'writingBoard', docSocket.value)
+  // 画图消息数组
+  yWritingBoardMessages.value = doc.getArray('writingBoardMessages')
+  if (yWritingBoardMessages.value.length > 500 && isCreator.value) {
+    smartCleanupMessages(yWritingBoardMessages.value, 500, 'writingBoard', doc)
+  }
+  yWritingBoardMessages.value.observe((e: any) => {
+    if (yWritingBoardMessages.value.length > 500 && isCreator.value) {
+      setTimeout(() => {
+        if (yWritingBoardMessages.value && yWritingBoardMessages.value.length > 500) {
+          smartCleanupMessages(yWritingBoardMessages.value, 500, 'writingBoard', doc)
+        }
+      }, 0)
     }
-    
-    yWritingBoardMessages.value.observe((e: any) => {
-      console.log('yWritingBoardMessages', yWritingBoardMessages.value.length)
-      console.log('yWritingBoardMessages', yWritingBoardMessages.value)
-      
-      // 每次有新消息添加后,检查是否需要清理
-      if (yWritingBoardMessages.value && yWritingBoardMessages.value.length > 500 && isCreator.value && docSocket.value) {
-        // 使用 setTimeout 确保在 observe 回调执行完成后再清理
-        setTimeout(() => {
-          if (docSocket.value && yWritingBoardMessages.value && yWritingBoardMessages.value.length > 500) {
-            smartCleanupMessages(yWritingBoardMessages.value, 500, 'writingBoard', docSocket.value)
-          }
-        }, 0)
+    e.changes.added.forEach((i: any) => {
+      const message = i.content.getContent()[0]
+      if (message.mId !== mId.value) {
+        getMessages(message)
       }
-      
-      e.changes.added.forEach((i: any) => {
-        const message = i.content.getContent()[0]
-        if (message.mId !== mId.value) {
-          getMessages(message)
-        }
-      })
     })
-  }
-
-  if (!yIsResultArrayMessages.value) {
-    console.log('初始化isResultArray消息数组')
-    yIsResultArrayMessages.value = docSocket.value.getArray('isResultArrayMessages')
+  })
 
-    // 初始化时检查一次是否需要清理
-    if (yIsResultArrayMessages.value && yIsResultArrayMessages.value.length > 1 && isCreator.value && docSocket.value) {
-      smartCleanupMessages(yIsResultArrayMessages.value, 1, 'isResultArray', docSocket.value)
+  // isResultArray 消息数组
+  yIsResultArrayMessages.value = doc.getArray('isResultArrayMessages')
+  if (yIsResultArrayMessages.value.length > 1 && isCreator.value) {
+    smartCleanupMessages(yIsResultArrayMessages.value, 1, 'isResultArray', doc)
+  }
+  yIsResultArrayMessages.value.observe((e: any) => {
+    if (yIsResultArrayMessages.value.length > 1 && isCreator.value) {
+      setTimeout(() => {
+        if (yIsResultArrayMessages.value && yIsResultArrayMessages.value.length > 1) {
+          smartCleanupMessages(yIsResultArrayMessages.value, 1, 'isResultArray', doc)
+        }
+      }, 0)
     }
-
-    yIsResultArrayMessages.value.observe((e: any) => {
-      console.log('yIsResultArrayMessages', yIsResultArrayMessages.value.length)
-      console.log('yIsResultArrayMessages', yIsResultArrayMessages.value)
-      
-      // 每次有新消息添加后,检查是否需要清理
-      if (yIsResultArrayMessages.value && yIsResultArrayMessages.value.length > 1 && isCreator.value && docSocket.value) {  
-        // 使用 setTimeout 确保在 observe 回调执行完成后再清理
-        setTimeout(() => {
-          if (docSocket.value && yIsResultArrayMessages.value && yIsResultArrayMessages.value.length > 1) {
-            smartCleanupMessages(yIsResultArrayMessages.value, 1, 'isResultArray', docSocket.value)
-          }
-        }, 0)
+    e.changes.added.forEach((i: any) => {
+      const message = i.content.getContent()[0]
+      if (message.mId !== mId.value) {
+        getMessages(message)
       }
-      
-      e.changes.added.forEach((i: any) => {
-        const message = i.content.getContent()[0]
-        if (message.mId !== mId.value) {
-          getMessages(message)
-        }
-      })
     })
-    
-  }
+  })
 
   // 如果是首次进入且是创建者,清空所有同步状态
   if (isFirstEnter.value && isCreator.value && docSocket.value) {
@@ -3407,66 +3412,54 @@ const messageInit = () => {
     // 标记已不再是首次进入
     isFirstEnter.value = false
   }
-  // 初始化计时器状态 Map 并监听
-  if (docSocket.value && !yTimerState.value) {
-    yTimerState.value = docSocket.value.getMap('timerState')
-    // 初始状态同步(后加入用户会立即拿到当前 map 值)
-    const snapshot = yTimerState.value.toJSON()
-    applyTimerStateSnapshot(snapshot)
-    // 监听变化
-    yTimerState.value.observe((event: any) => {
-      const snap = yTimerState.value.toJSON()
-      applyTimerStateSnapshot(snap)
-    })
-  }
-  // 初始化激光笔状态 Map 并监听
-  if (docSocket.value && !yLaserState.value) {
-    yLaserState.value = docSocket.value.getMap('laserState')
-    const lsnap = yLaserState.value.toJSON()
-    applyLaserStateSnapshot(lsnap)
-    yLaserState.value.observe(() => {
-      const s = yLaserState.value.toJSON()
-      applyLaserStateSnapshot(s)
-    })
-  }
-  // 初始化画图状态 Map 并监听
-  if (docSocket.value && !yWritingBoardState.value) {
-    yWritingBoardState.value = docSocket.value.getMap('writingBoardState')
-    const wsnap = yWritingBoardState.value.toJSON()
-    console.log('📝 初始化画图状态Map,快照:', wsnap, '当前幻灯片:', currentSlide.value?.id)
-    // 延迟应用,确保 currentSlide 已初始化
-    nextTick(() => {
-      // 如果 currentSlide 还没准备好,再等一帧
-      if (currentSlide.value && currentSlide.value.id) {
-        applyWritingBoardStateSnapshot(wsnap)
-      }
-      else {
-        // 如果还没准备好,等待 currentSlide 变化(最多等待3秒)
-        let timeoutId: any = null
-        const unwatch = watch(() => currentSlide.value?.id, (slideId) => {
-          if (slideId) {
-            applyWritingBoardStateSnapshot(wsnap)
-            unwatch()
-            if (timeoutId) clearTimeout(timeoutId)
-          }
-        }, { immediate: true })
-        // 3秒后如果还没准备好,强制应用一次
-        timeoutId = setTimeout(() => {
-          if (currentSlide.value && currentSlide.value.id) {
-            applyWritingBoardStateSnapshot(wsnap)
-          }
+  // 计时器状态 Map 并监听
+  yTimerState.value = doc.getMap('timerState')
+  // 初始状态同步(后加入用户会立即拿到当前 map 值)
+  applyTimerStateSnapshot(yTimerState.value.toJSON())
+  yTimerState.value.observe(() => {
+    applyTimerStateSnapshot(yTimerState.value!.toJSON())
+  })
+
+  // 激光笔状态 Map 并监听
+  yLaserState.value = doc.getMap('laserState')
+  applyLaserStateSnapshot(yLaserState.value.toJSON())
+  yLaserState.value.observe(() => {
+    applyLaserStateSnapshot(yLaserState.value!.toJSON())
+  })
+
+  // 画图状态 Map 并监听
+  yWritingBoardState.value = doc.getMap('writingBoardState')
+  const wsnap = yWritingBoardState.value.toJSON()
+  console.log('📝 初始化画图状态Map,快照:', wsnap, '当前幻灯片:', currentSlide.value?.id)
+  nextTick(() => {
+    if (currentSlide.value && currentSlide.value.id) {
+      applyWritingBoardStateSnapshot(wsnap)
+    }
+    else {
+      let timeoutId: any = null
+      const unwatch = watch(() => currentSlide.value?.id, (slideId) => {
+        if (slideId) {
+          applyWritingBoardStateSnapshot(wsnap)
           unwatch()
-        }, 3000)
-      }
-    })
-    yWritingBoardState.value.observe(() => {
-      const s = yWritingBoardState.value.toJSON()
-      if (currentSlide.value && currentSlide.value.id) {
-        applyWritingBoardStateSnapshot(s)
-      }
-    })
-  }
+          if (timeoutId) clearTimeout(timeoutId)
+        }
+      }, { immediate: true })
+      timeoutId = setTimeout(() => {
+        if (currentSlide.value && currentSlide.value.id) {
+          applyWritingBoardStateSnapshot(wsnap)
+        }
+        unwatch()
+      }, 3000)
+    }
+  })
+  yWritingBoardState.value.observe(() => {
+    const s = yWritingBoardState.value!.toJSON()
+    if (currentSlide.value && currentSlide.value.id) {
+      applyWritingBoardStateSnapshot(s)
+    }
+  })
 
+  // 新 doc 绑定完成,补发上线消息
   if (props.type === '2') {
     sendMessage({ type: 'class_begin', courseid: props.courseid })
   }
@@ -4263,7 +4256,7 @@ const cleanupWebSocketConnection = () => {
   }
 
   // ★ 关键:doc 已销毁,必须把绑定在旧 doc 上的共享类型引用全部置空,
-  // 否则重连后 messageInit() 的 if (!yXxx.value) 守卫会跳过绑定,
+  // 否则重连后 messadocSocket.value.guid === yMessazeInit() 的 if (!yXxx.value) 守卫会跳过绑定,
   // 新 doc 收到更新时 observe 回调不会触发(表现为突然收不到消息)
   yMessage.value = null
   yTimerState.value = null

Datei-Diff unterdrückt, da er zu groß ist
+ 577 - 262
src/views/Student/index2.vue


Einige Dateien werden nicht angezeigt, da zu viele Dateien in diesem Diff geändert wurden.