|
|
@@ -1178,14 +1178,8 @@ const handleLaserMove = (e: MouseEvent) => {
|
|
|
sendRafPending = true
|
|
|
requestAnimationFrame(() => {
|
|
|
sendRafPending = false
|
|
|
+ // sendMessage 中已经直接更新 Map,不需要重复更新
|
|
|
sendMessage({ type: 'laser_move', x: lastSent.x, y: lastSent.y, courseid: props.courseid })
|
|
|
- // 更新共享 Map 的位置(节流后每帧最多一次)
|
|
|
- if (yLaserState.value) {
|
|
|
- docSocket.value?.transact(() => {
|
|
|
- yLaserState.value.set('x', lastSent.x)
|
|
|
- yLaserState.value.set('y', lastSent.y)
|
|
|
- })
|
|
|
- }
|
|
|
})
|
|
|
}
|
|
|
|
|
|
@@ -2542,6 +2536,8 @@ const messageInit = () => {
|
|
|
yMessage.value = docSocket.value.getArray('message')
|
|
|
yMessage.value.observe((e: any) => {
|
|
|
e.changes.added.forEach((i: any) => {
|
|
|
+ console.log('yMessage', yMessage.value.length)
|
|
|
+ console.log('yMessage', yMessage.value)
|
|
|
const message = i.content.getContent()[0]
|
|
|
console.log('yMessage', message)
|
|
|
if (message.mId !== mId.value) {
|
|
|
@@ -2555,14 +2551,24 @@ const messageInit = () => {
|
|
|
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.observe((e: any) => {
|
|
|
- // 检查数组长度,如果超过2000条,清理旧数据
|
|
|
- if (yTimerMessages.value && yTimerMessages.value.length > 2000 && isCreator.value && docSocket.value) {
|
|
|
- const excessCount = yTimerMessages.value.length - 2000
|
|
|
- docSocket.value.transact(() => {
|
|
|
- yTimerMessages.value.delete(0, excessCount)
|
|
|
- })
|
|
|
- console.log(`🧹 清理了 ${excessCount} 条计时器消息,保留最新2000条`)
|
|
|
+ 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) => {
|
|
|
@@ -2578,19 +2584,30 @@ const messageInit = () => {
|
|
|
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.observe((e: any) => {
|
|
|
- // 检查数组长度,如果超过2000条,清理旧数据
|
|
|
- if (yLaserMessages.value && yLaserMessages.value.length > 2000 && isCreator.value && docSocket.value) {
|
|
|
- const excessCount = yLaserMessages.value.length - 2000
|
|
|
- docSocket.value.transact(() => {
|
|
|
- yLaserMessages.value.delete(0, excessCount)
|
|
|
- })
|
|
|
- console.log(`🧹 清理了 ${excessCount} 条激光笔消息,保留最新2000条`)
|
|
|
+ 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]
|
|
|
- if (message.mId !== mId.value) {
|
|
|
+ // 只处理开关消息,位置消息直接通过 Map 同步
|
|
|
+ if (message.type === 'laser_toggle' && message.mId !== mId.value) {
|
|
|
getMessages(message)
|
|
|
}
|
|
|
})
|
|
|
@@ -2601,14 +2618,24 @@ const messageInit = () => {
|
|
|
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.observe((e: any) => {
|
|
|
- // 检查数组长度,如果超过2000条,清理旧数据
|
|
|
- if (yWritingBoardMessages.value && yWritingBoardMessages.value.length > 2000 && isCreator.value && docSocket.value) {
|
|
|
- const excessCount = yWritingBoardMessages.value.length - 2000
|
|
|
- docSocket.value.transact(() => {
|
|
|
- yWritingBoardMessages.value.delete(0, excessCount)
|
|
|
- })
|
|
|
- console.log(`🧹 清理了 ${excessCount} 条画图消息,保留最新2000条`)
|
|
|
+ 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) => {
|
|
|
@@ -2726,20 +2753,89 @@ const messageInit = () => {
|
|
|
|
|
|
/**
|
|
|
* 判断是否为特殊类型的消息(计时器、激光笔、画图)
|
|
|
+ * 注意:laser_move 不通过消息数组,直接通过 Map 同步,所以不包含在这里
|
|
|
*/
|
|
|
const isSpecialMessageType = (type: string): boolean => {
|
|
|
const timerTypes = ['timer_start', 'timer_pause', 'timer_reset', 'timer_stop', 'timer_finish', 'timer_update']
|
|
|
- const laserTypes = ['laser_toggle', 'laser_move']
|
|
|
+ const laserTypes = ['laser_toggle'] // laser_move 直接通过 Map,不通过消息数组
|
|
|
const writingBoardTypes = ['writing_board_update', 'writing_board_close', 'writing_board_blackboard']
|
|
|
return timerTypes.includes(type) || laserTypes.includes(type) || writingBoardTypes.includes(type)
|
|
|
}
|
|
|
|
|
|
+/**
|
|
|
+ * 判断是否为开关类型的消息(需要保留的状态消息)
|
|
|
+ */
|
|
|
+const isToggleMessageType = (type: string, category: 'timer' | 'laser' | 'writingBoard'): boolean => {
|
|
|
+ if (category === 'laser') {
|
|
|
+ return type === 'laser_toggle'
|
|
|
+ }
|
|
|
+ if (category === 'writingBoard') {
|
|
|
+ return type === 'writing_board_close'
|
|
|
+ }
|
|
|
+ if (category === 'timer') {
|
|
|
+ return ['timer_start', 'timer_stop', 'timer_reset'].includes(type)
|
|
|
+ }
|
|
|
+ return false
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 智能清理消息数组,保留最新的开关消息
|
|
|
+ */
|
|
|
+const smartCleanupMessages = (messageArray: any, maxLength: number, category: 'timer' | 'laser' | 'writingBoard', docSocket: Y.Doc) => {
|
|
|
+ if (!messageArray || messageArray.length <= maxLength) return
|
|
|
+
|
|
|
+ const allMessages = messageArray.toArray()
|
|
|
+
|
|
|
+ // 找到所有开关类型的消息及其索引
|
|
|
+ const toggleMessages: Array<{ index: number; message: any }> = []
|
|
|
+ for (let i = 0; i < allMessages.length; i++) {
|
|
|
+ const msg = allMessages[i]
|
|
|
+ if (msg && typeof msg === 'object' && msg.type && isToggleMessageType(msg.type, category)) {
|
|
|
+ toggleMessages.push({ index: i, message: msg })
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果有关键的开关消息,需要确保它们被保留
|
|
|
+ if (toggleMessages.length > 0) {
|
|
|
+ // 找到最新的开关消息索引
|
|
|
+ const latestToggleIndex = toggleMessages[toggleMessages.length - 1].index
|
|
|
+
|
|
|
+ // 如果最新的开关消息在最后500条内,直接保留最后500条
|
|
|
+ if (latestToggleIndex >= allMessages.length - maxLength) {
|
|
|
+ const excessCount = allMessages.length - maxLength
|
|
|
+ docSocket.transact(() => {
|
|
|
+ messageArray.delete(0, excessCount)
|
|
|
+ })
|
|
|
+ console.log(`🧹 清理了 ${excessCount} 条${category}消息,保留最新${maxLength}条(包含最新开关状态)`)
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ // 如果最新的开关消息不在最后500条内,需要特殊处理
|
|
|
+ // 保留从最新开关消息开始到末尾的所有消息
|
|
|
+ const keepFromIndex = Math.max(0, latestToggleIndex)
|
|
|
+ const excessCount = keepFromIndex
|
|
|
+ docSocket.transact(() => {
|
|
|
+ messageArray.delete(0, excessCount)
|
|
|
+ })
|
|
|
+ console.log(`🧹 清理了 ${excessCount} 条${category}消息,保留从最新开关状态开始的所有消息`)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ // 没有开关消息,直接保留最后500条
|
|
|
+ const excessCount = allMessages.length - maxLength
|
|
|
+ docSocket.transact(() => {
|
|
|
+ messageArray.delete(0, excessCount)
|
|
|
+ })
|
|
|
+ console.log(`🧹 清理了 ${excessCount} 条${category}消息,保留最新${maxLength}条`)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
/**
|
|
|
* 获取消息类型对应的数组
|
|
|
+ * 注意:laser_move 不通过消息数组,直接通过 Map 同步
|
|
|
*/
|
|
|
const getMessageArrayByType = (type: string): any | null => {
|
|
|
const timerTypes = ['timer_start', 'timer_pause', 'timer_reset', 'timer_stop', 'timer_finish', 'timer_update']
|
|
|
- const laserTypes = ['laser_toggle', 'laser_move']
|
|
|
+ const laserTypes = ['laser_toggle'] // laser_move 直接通过 Map,不通过消息数组
|
|
|
const writingBoardTypes = ['writing_board_update', 'writing_board_close', 'writing_board_blackboard']
|
|
|
|
|
|
if (timerTypes.includes(type)) {
|
|
|
@@ -2765,6 +2861,18 @@ const sendMessage = (obj: any) => {
|
|
|
message.mId = mId.value
|
|
|
|
|
|
const messageType = message.type
|
|
|
+
|
|
|
+ // laser_move 消息直接通过 Map 同步,不通过消息数组(减少延迟)
|
|
|
+ if (messageType === 'laser_move') {
|
|
|
+ if (yLaserState.value) {
|
|
|
+ docSocket.value.transact(() => {
|
|
|
+ yLaserState.value.set('x', message.x)
|
|
|
+ yLaserState.value.set('y', message.y)
|
|
|
+ })
|
|
|
+ }
|
|
|
+ return // 不存储到消息数组
|
|
|
+ }
|
|
|
+
|
|
|
const isSpecial = isSpecialMessageType(messageType)
|
|
|
|
|
|
docSocket.value.transact(() => {
|
|
|
@@ -2847,15 +2955,7 @@ const getMessages = (msgObj: any) => {
|
|
|
laserMoveRafId = requestAnimationFrame(updateLaserDotPosition)
|
|
|
}
|
|
|
}
|
|
|
- // 激光笔:老师广播的位置
|
|
|
- if (props.type == '2' && msgObj.type === 'laser_move' && msgObj.courseid === props.courseid) {
|
|
|
- if (laserPenOverlay.value.visible) {
|
|
|
- laserPenOverlay.value.xPct = Number(msgObj.x || 0)
|
|
|
- laserPenOverlay.value.yPct = Number(msgObj.y || 0)
|
|
|
- if (laserMoveRafId) cancelAnimationFrame(laserMoveRafId)
|
|
|
- laserMoveRafId = requestAnimationFrame(updateLaserDotPosition)
|
|
|
- }
|
|
|
- }
|
|
|
+ // 注意:laser_move 现在直接通过 Map 同步,不通过消息数组,所以这里不需要处理
|
|
|
|
|
|
// 画图:老师广播的画图数据
|
|
|
if (props.type == '2' && msgObj.type === 'writing_board_update' && msgObj.courseid === props.courseid) {
|
|
|
@@ -3613,17 +3713,21 @@ const applyTimerUpdate = (payload: { durationSec: number; startAt?: string }) =>
|
|
|
// 应用激光笔共享状态(任意端)
|
|
|
const applyLaserStateSnapshot = (snap: any) => {
|
|
|
if (!snap) return
|
|
|
- const enabled = !!snap.enabled
|
|
|
+ const enabled = snap.enabled !== undefined ? !!snap.enabled : laserPenOverlay.value.visible
|
|
|
const x = typeof snap.x === 'number' ? snap.x : null
|
|
|
const y = typeof snap.y === 'number' ? snap.y : null
|
|
|
+
|
|
|
if (props.type == '2') {
|
|
|
- laserPenOverlay.value.visible = enabled
|
|
|
- if (enabled) {
|
|
|
+ // 更新开关状态
|
|
|
+ if (snap.enabled !== undefined) {
|
|
|
+ laserPenOverlay.value.visible = enabled
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果激光笔是开启状态,更新位置
|
|
|
+ if (laserPenOverlay.value.visible && x != null && y != null) {
|
|
|
+ laserPenOverlay.value.xPct = x
|
|
|
+ laserPenOverlay.value.yPct = y
|
|
|
refreshLaserOverlayRect()
|
|
|
- if (x != null && y != null) {
|
|
|
- laserPenOverlay.value.xPct = x
|
|
|
- laserPenOverlay.value.yPct = y
|
|
|
- }
|
|
|
if (laserMoveRafId) cancelAnimationFrame(laserMoveRafId)
|
|
|
laserMoveRafId = requestAnimationFrame(updateLaserDotPosition)
|
|
|
}
|