lsc 2 napja
szülő
commit
e03281520c
4 módosított fájl, 215 hozzáadás és 4 törlés
  1. 125 1
      package-lock.json
  2. 3 1
      package.json
  3. 2 0
      src/services/course.ts
  4. 85 2
      src/views/Student/index.vue

+ 125 - 1
package-lock.json

@@ -44,7 +44,9 @@
         "tinycolor2": "^1.6.0",
         "tippy.js": "^6.3.7",
         "vue": "^3.5.17",
-        "vuedraggable": "^4.1.0"
+        "vuedraggable": "^4.1.0",
+        "y-websocket": "^3.0.0",
+        "yjs": "^13.6.27"
       },
       "devDependencies": {
         "@commitlint/cli": "^18.4.3",
@@ -3647,6 +3649,15 @@
       "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
       "dev": true
     },
+    "node_modules/isomorphic.js": {
+      "version": "0.2.5",
+      "resolved": "https://registry.npmjs.org/isomorphic.js/-/isomorphic.js-0.2.5.tgz",
+      "integrity": "sha512-PIeMbHqMt4DnUP3MA/Flc0HElYjMXArsw1qwJZcm9sqR8mq3l8NYizFMty0pWwE/tzIGH3EKK5+jes5mAr85yw==",
+      "funding": {
+        "type": "GitHub Sponsors ❤",
+        "url": "https://github.com/sponsors/dmonad"
+      }
+    },
     "node_modules/jiti": {
       "version": "1.21.0",
       "resolved": "https://registry.npmmirror.com/jiti/-/jiti-1.21.0.tgz",
@@ -3795,6 +3806,26 @@
         "node": ">= 0.8.0"
       }
     },
+    "node_modules/lib0": {
+      "version": "0.2.114",
+      "resolved": "https://registry.npmjs.org/lib0/-/lib0-0.2.114.tgz",
+      "integrity": "sha512-gcxmNFzA4hv8UYi8j43uPlQ7CGcyMJ2KQb5kZASw6SnAKAf10hK12i2fjrS3Cl/ugZa5Ui6WwIu1/6MIXiHttQ==",
+      "dependencies": {
+        "isomorphic.js": "^0.2.4"
+      },
+      "bin": {
+        "0ecdsa-generate-keypair": "bin/0ecdsa-generate-keypair.js",
+        "0gentesthtml": "bin/gentesthtml.js",
+        "0serve": "bin/0serve.js"
+      },
+      "engines": {
+        "node": ">=16"
+      },
+      "funding": {
+        "type": "GitHub Sponsors ❤",
+        "url": "https://github.com/sponsors/dmonad"
+      }
+    },
     "node_modules/lie": {
       "version": "3.3.0",
       "resolved": "https://registry.npmmirror.com/lie/-/lie-3.3.0.tgz",
@@ -5693,6 +5724,45 @@
         "node": ">=12"
       }
     },
+    "node_modules/y-protocols": {
+      "version": "1.0.6",
+      "resolved": "https://registry.npmjs.org/y-protocols/-/y-protocols-1.0.6.tgz",
+      "integrity": "sha512-vHRF2L6iT3rwj1jub/K5tYcTT/mEYDUppgNPXwp8fmLpui9f7Yeq3OEtTLVF012j39QnV+KEQpNqoN7CWU7Y9Q==",
+      "dependencies": {
+        "lib0": "^0.2.85"
+      },
+      "engines": {
+        "node": ">=16.0.0",
+        "npm": ">=8.0.0"
+      },
+      "funding": {
+        "type": "GitHub Sponsors ❤",
+        "url": "https://github.com/sponsors/dmonad"
+      },
+      "peerDependencies": {
+        "yjs": "^13.0.0"
+      }
+    },
+    "node_modules/y-websocket": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/y-websocket/-/y-websocket-3.0.0.tgz",
+      "integrity": "sha512-mUHy7AzkOZ834T/7piqtlA8Yk6AchqKqcrCXjKW8J1w2lPtRDjz8W5/CvXz9higKAHgKRKqpI3T33YkRFLkPtg==",
+      "dependencies": {
+        "lib0": "^0.2.102",
+        "y-protocols": "^1.0.5"
+      },
+      "engines": {
+        "node": ">=16.0.0",
+        "npm": ">=8.0.0"
+      },
+      "funding": {
+        "type": "GitHub Sponsors ❤",
+        "url": "https://github.com/sponsors/dmonad"
+      },
+      "peerDependencies": {
+        "yjs": "^13.5.6"
+      }
+    },
     "node_modules/y18n": {
       "version": "5.0.8",
       "resolved": "https://registry.npmmirror.com/y18n/-/y18n-5.0.8.tgz",
@@ -5743,6 +5813,22 @@
         "node": ">=18.16.0"
       }
     },
+    "node_modules/yjs": {
+      "version": "13.6.27",
+      "resolved": "https://registry.npmjs.org/yjs/-/yjs-13.6.27.tgz",
+      "integrity": "sha512-OIDwaflOaq4wC6YlPBy2L6ceKeKuF7DeTxx+jPzv1FHn9tCZ0ZwSRnUBxD05E3yed46fv/FWJbvR+Ud7x0L7zw==",
+      "dependencies": {
+        "lib0": "^0.2.99"
+      },
+      "engines": {
+        "node": ">=16.0.0",
+        "npm": ">=8.0.0"
+      },
+      "funding": {
+        "type": "GitHub Sponsors ❤",
+        "url": "https://github.com/sponsors/dmonad"
+      }
+    },
     "node_modules/yocto-queue": {
       "version": "0.1.0",
       "resolved": "https://registry.npmmirror.com/yocto-queue/-/yocto-queue-0.1.0.tgz",
@@ -8386,6 +8472,11 @@
       "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
       "dev": true
     },
+    "isomorphic.js": {
+      "version": "0.2.5",
+      "resolved": "https://registry.npmjs.org/isomorphic.js/-/isomorphic.js-0.2.5.tgz",
+      "integrity": "sha512-PIeMbHqMt4DnUP3MA/Flc0HElYjMXArsw1qwJZcm9sqR8mq3l8NYizFMty0pWwE/tzIGH3EKK5+jes5mAr85yw=="
+    },
     "jiti": {
       "version": "1.21.0",
       "resolved": "https://registry.npmmirror.com/jiti/-/jiti-1.21.0.tgz",
@@ -8501,6 +8592,14 @@
         "type-check": "~0.4.0"
       }
     },
+    "lib0": {
+      "version": "0.2.114",
+      "resolved": "https://registry.npmjs.org/lib0/-/lib0-0.2.114.tgz",
+      "integrity": "sha512-gcxmNFzA4hv8UYi8j43uPlQ7CGcyMJ2KQb5kZASw6SnAKAf10hK12i2fjrS3Cl/ugZa5Ui6WwIu1/6MIXiHttQ==",
+      "requires": {
+        "isomorphic.js": "^0.2.4"
+      }
+    },
     "lie": {
       "version": "3.3.0",
       "resolved": "https://registry.npmmirror.com/lie/-/lie-3.3.0.tgz",
@@ -9959,6 +10058,23 @@
       "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==",
       "dev": true
     },
+    "y-protocols": {
+      "version": "1.0.6",
+      "resolved": "https://registry.npmjs.org/y-protocols/-/y-protocols-1.0.6.tgz",
+      "integrity": "sha512-vHRF2L6iT3rwj1jub/K5tYcTT/mEYDUppgNPXwp8fmLpui9f7Yeq3OEtTLVF012j39QnV+KEQpNqoN7CWU7Y9Q==",
+      "requires": {
+        "lib0": "^0.2.85"
+      }
+    },
+    "y-websocket": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/y-websocket/-/y-websocket-3.0.0.tgz",
+      "integrity": "sha512-mUHy7AzkOZ834T/7piqtlA8Yk6AchqKqcrCXjKW8J1w2lPtRDjz8W5/CvXz9higKAHgKRKqpI3T33YkRFLkPtg==",
+      "requires": {
+        "lib0": "^0.2.102",
+        "y-protocols": "^1.0.5"
+      }
+    },
     "y18n": {
       "version": "5.0.8",
       "resolved": "https://registry.npmmirror.com/y18n/-/y18n-5.0.8.tgz",
@@ -9997,6 +10113,14 @@
       "resolved": "https://registry.npmmirror.com/yerror/-/yerror-8.0.0.tgz",
       "integrity": "sha512-FemWD5/UqNm8ffj8oZIbjWXIF2KE0mZssggYpdaQkWDDgXBQ/35PNIxEuz6/YLn9o0kOxDBNJe8x8k9ljD7k/g=="
     },
+    "yjs": {
+      "version": "13.6.27",
+      "resolved": "https://registry.npmjs.org/yjs/-/yjs-13.6.27.tgz",
+      "integrity": "sha512-OIDwaflOaq4wC6YlPBy2L6ceKeKuF7DeTxx+jPzv1FHn9tCZ0ZwSRnUBxD05E3yed46fv/FWJbvR+Ud7x0L7zw==",
+      "requires": {
+        "lib0": "^0.2.99"
+      }
+    },
     "yocto-queue": {
       "version": "0.1.0",
       "resolved": "https://registry.npmmirror.com/yocto-queue/-/yocto-queue-0.1.0.tgz",

+ 3 - 1
package.json

@@ -51,7 +51,9 @@
     "tinycolor2": "^1.6.0",
     "tippy.js": "^6.3.7",
     "vue": "^3.5.17",
-    "vuedraggable": "^4.1.0"
+    "vuedraggable": "^4.1.0",
+    "y-websocket": "^3.0.0",
+    "yjs": "^13.6.27"
   },
   "devDependencies": {
     "@commitlint/cli": "^18.4.3",

+ 2 - 0
src/services/course.ts

@@ -1,6 +1,7 @@
 import axios from './config'
 
 export const API_URL = 'https://pbl.cocorobo.cn/api/pbl/'
+export const yweb_socket = 'wss://yjs.cocorobo.cn'
 
 /**
  * 获取课程详情
@@ -121,4 +122,5 @@ export default {
   updateCourseFollow,
   updateCourseFollowC,
   selectCourseSLook,
+  yweb_socket,
 }

+ 85 - 2
src/views/Student/index.vue

@@ -237,6 +237,9 @@ import ChoiceWorkModal from './components/ChoiceWorkModal.vue'
 import AIWorkModal from './components/AIWorkModal.vue'
 import DialoguePanel from './components/DialoguePanel.vue'
 import ChoiceStatistics from './components/ChoiceStatistics.vue'
+import * as Y from 'yjs'
+import { WebsocketProvider } from 'y-websocket'
+
 
 // 导入图片资源
 import homeworkIcon from '@/assets/img/homework.png'
@@ -344,6 +347,10 @@ const unsubmittedStudents = computed(() => {
   return studentArray.value.filter((student: any) => !submittedNames.includes(student.name))
 })
 
+const docSocket = ref<Y.Doc | null>(null)
+const yMessage = ref<any | null>(null)
+const providerSocket = ref<WebsocketProvider | null>(null)
+const mId = ref<string | null>(null)
 
 
 // 切换到作业区
@@ -596,11 +603,13 @@ watch(() => slideIndex.value, (newIndex, oldIndex) => {
       console.log('当前页面无iframe,停止作业更新定时器')
       stopWorkTimer()
     }
-    if (isFollowModeActive.value && isCreator.value) {
+    if (props.type == '1' && isFollowModeActive.value && isCreator.value) {
       api.updateCourseFollowC(newIndex, props.courseid as string)
+      sendMessage({slideIndex: newIndex, courseid: props.courseid, type: 'slideIndex'})
     }
     // 自动切换到可用的面板
     autoSwitchToAvailablePanel()
+
   }
 }, { immediate: false, deep: false })
 
@@ -711,6 +720,7 @@ const processIframeLinks = async () => {
               // 检查是否是iframe元素
               if (element.type === ElementTypes.FRAME && element.url) {
                 const iframeSrc = element.url
+                const toolType = element.toolType
 
                 if (iframeSrc.includes('workPage')) {
                   hasIframe = true
@@ -756,7 +766,7 @@ const processIframeLinks = async () => {
                     return element
                   }
                 } 
-                else if (iframeSrc.includes('.html') || iframeSrc.includes('.htm')) {
+                else if (toolType == 45) {
                   hasIframe = true
                   
                   // 先尝试获取iframe的contentWindow,如果获取不到再使用HTML方式
@@ -1403,6 +1413,7 @@ const toggleFollowMode = async () => {
     // 调用API更新跟随状态
     const res = await api.updateCourseFollow(sopen, props.courseid as string)
     console.log('更新跟随模式状态:', res)
+    sendMessage({sopen: newFollowState, courseid: props.courseid, type: 'sopen'})
     
     if (res) {
       isFollowModeActive.value = newFollowState
@@ -1433,6 +1444,60 @@ const checkIsCreator = () => {
   }
 }
 
+
+/**
+ * 初始化消息监听
+ */
+const messageInit = () => {
+  if (docSocket.value && !yMessage.value) {
+    console.log('获取message', docSocket.value, yMessage.value)
+    yMessage.value = docSocket.value.getArray('message')
+    yMessage.value.observe((e: any) => {
+      e.changes.added.forEach((i: any) => {
+        const message = i.content.getContent()[0]
+        console.log('yMessage', message)
+        const _nowTime = new Date()
+        const _msgTime = new Date(message.timestamp)
+        if (
+          (_nowTime as any) - (_msgTime as any) <= 1000 * 10 &&
+          message.mId !== mId.value
+        ) {
+          // 10秒内且不是自己发的消息
+          getMessages(message)
+        }
+      })
+    })
+  }
+}
+
+/**
+ * 发送消息
+ */
+const sendMessage = (obj: any) => {
+  if (docSocket.value && yMessage.value) {
+    const message = obj
+    obj.timestamp = new Date().toISOString()
+    obj.mId = mId.value
+    docSocket.value.transact(() => {
+      yMessage.value.push([message])
+    })
+  }
+}
+
+/**
+ * 处理收到的消息
+ */
+const getMessages = (msgObj: any) => {
+  console.log('message', msgObj)
+  if (props.type == '2' && msgObj.slideIndex && msgObj.type === 'slideIndex') {
+    goToSlide(msgObj.slideIndex)
+  }
+  if (props.type == '2' && msgObj.type === 'sopen') {
+    selectCourseSLook()
+  }
+}
+
+
 onMounted(() => {
   document.addEventListener('keydown', handleKeydown)
 
@@ -1497,6 +1562,24 @@ onMounted(() => {
 
   console.log('PPTist Student View 已加载,可通过 window.PPTistStudent 访问功能')
   console.log('URL参数:', { courseid: props.courseid, type: props.type })
+
+  if (api.yweb_socket && !docSocket.value) {
+			
+    docSocket.value = new Y.Doc()
+    providerSocket.value = new WebsocketProvider(api.yweb_socket,
+      'PPT' + props.courseid,
+      docSocket.value
+    )
+
+    providerSocket.value.on('status', (event: any) => {
+      console.log('👉', event.status)
+      if (event.status === 'connected') {
+        console.log('👉连接成功websocket(teachingMode)')
+        mId.value = Math.random().toString(36).substr(2, 9)
+        messageInit()
+      }
+    })
+  }
 })