lsc 2 週間 前
親
コミット
310990d911
1 ファイル変更、51 行追加、16 行削除
  1. 51 16
      src/views/Student/index.vue

+ 51 - 16
src/views/Student/index.vue

@@ -3240,6 +3240,7 @@ const messageInit = () => {
   // y-websocket 内部重连复用同一个 doc → observe 仍有效,直接返回(避免重复 observe)
   // 只有 doc 被销毁重建(full reconnect)时才全部重新绑定
   const boundDoc = (yMessage.value as any)?.doc
+  console.log('🔗 messageInit: doc.guid=', doc.guid, 'boundDoc=', boundDoc?.guid, 'same=', boundDoc === doc)
   if (boundDoc === doc) {
     // 同 doc 重连:补发上线消息即可
     if (props.type === '2') {
@@ -3267,9 +3268,10 @@ const messageInit = () => {
   yMessage.value.observe((e: any) => {
     // 同时兼容 keysChanged 和 changes.keys(不同 Yjs 版本/事件结构)
     const changed = e.keysChanged || e.changes?.keys
+    console.log('🔗 yMessage observe 触发, changed keys:', changed ? Array.from(changed) : 'null')
     if (!changed || !changed.has('latest')) return
     const message = yMessage.value!.get('latest')
-    console.log('yMessage', message)
+    console.log('🔗 yMessage latest:', message?.type, 'mId:', message?.mId, '当前 mId:', mId.value)
     if (message && message.mId !== mId.value) {
       getMessages(message)
     }
@@ -4077,7 +4079,10 @@ onMounted(() => {
   document.addEventListener('visibilitychange', handleVisibilityChange)
   
   // pagehide 事件(作为补充,某些浏览器中比 beforeunload 更可靠)
-  window.addEventListener('pagehide', handlePageUnload)
+  window.addEventListener('pagehide', () => {
+    console.log('pagehide')
+    handlePageUnload()
+  })
   
   // 存储清理函数,方便在 onUnmounted 中移除
   ;(window as any).__pptistStudentUnloadHandlers = {
@@ -4252,6 +4257,7 @@ const cleanupWebSocketConnection = () => {
   if (docSocket.value) {
     // 注意:doc 会被 provider.destroy() 自动销毁,不需要手动 destroy
     // 但如果 provider 已经被销毁,这里置空即可
+    docSocket.value.destroy()
     docSocket.value = null
   }
 
@@ -4310,23 +4316,17 @@ const createWebSocketConnection = async (type = 1) => {
       docSocket.value,
       {
         params: { yauth: authToken.value },
-        // maxBackoffTime: 5000, // 重连最长等 5 秒
+        resyncInterval: 5000, // 每 5 秒用标准协议请求服务器状态(代替畸形心跳)
+        maxBackoffTime: 5000, // 重连最长退避 5 秒
       }
     )
     providerSocket.value.awareness.setLocalState(null)
     providerSocket.value.awareness.destroy() // 完全禁用 awareness
-    // ★ 心跳(用 providerSocket.value,断开时清除避免泄漏)
-    if (heartbeatTimer.value) clearInterval(heartbeatTimer.value)
-    heartbeatTimer.value = setInterval(() => {
-      if (providerSocket.value?.ws?.readyState === WebSocket.OPEN) {
-        try {
-          providerSocket.value.ws.send(new Uint8Array([0, 0]))
-        }
-        catch (e) {
-          console.error('heartbeatTimer', e)
-        }
-      }
-    }, 30000)
+    // 清理可能残留的旧心跳定时器
+    if (heartbeatTimer.value) {
+      clearInterval(heartbeatTimer.value)
+      heartbeatTimer.value = null
+    }
     // 启动定期更新 token
     // 30分钟后再次更新
     if (authTokenUpdateTimer.value) {
@@ -4381,10 +4381,45 @@ const createWebSocketConnection = async (type = 1) => {
     providerSocket.value.on('connection-error', (error: any) => {
       console.error('👉 WebSocket 连接错误(将自动重连):', error)
     })
-    
+
     providerSocket.value.on('connection-close', (event: any) => {
       console.log('👉 WebSocket 连接关闭(将自动重连):', event)
     })
+
+    // ★ synced 事件:在服务器状态同步完成(sync step 2 已应用到 Y.Doc)后触发
+    // 'connected' 在 onopen 时就触发,此时 sync 尚未完成,messageInit 的 catch-up 读不到数据
+    // 'synced' 才是做 catch-up 的正确时机——确保服务器上的 latest 已写入本地 Y.Doc
+    providerSocket.value.on('sync', (isSynced: boolean) => {
+      if (!isSynced) return
+      console.log('🔗 Yjs sync 完成,开始 catch-up')
+
+      // catch-up:读取服务器同步过来的 latest 消息
+      if (yMessage.value) {
+        const latest = yMessage.value.get('latest')
+        console.log('🔗 synced catch-up latest:', latest)
+        if (latest && latest.mId !== mId.value) {
+          console.log('🔗 synced catch-up: 处理 latest 消息', latest.type)
+          getMessages(latest)
+        }
+      }
+
+      // catch-up:读取计时器、激光笔、画图状态
+      if (yTimerState.value) {
+        const snapshot = yTimerState.value.toJSON()
+        applyTimerStateSnapshot(snapshot)
+      }
+      if (yLaserState.value) {
+        const laserSnap = yLaserState.value.toJSON()
+        if (laserSnap.visible !== undefined) {
+          laserPenOverlay.value.visible = !!laserSnap.visible
+        }
+      }
+
+      // 学生端补发上线消息,让老师知道学生已就绪
+      if (props.type === '2') {
+        sendMessage({ type: 'class_begin', courseid: props.courseid })
+      }
+    })
     // 监听连接错误
     // providerSocket.value.on('connection-error', (error: any) => {
     //   console.error('👉 WebSocket连接错误:', error)