- collabDoc.start 要 await 六个动态 import,房间可能在这期间就关了(机房首次 加载 y* 那几个 chunk 正是最慢的时候)。stop() 先跑完面对的是 doc === null, 什么也拆不到;import 回来之后 start 的后半段照样建文档、装 binaryHandler、 挂 yCollab、发 SyncStep1——给一个不存在的房间。加会话代号,过期就整个放弃。 - SyncCodeEditor 卸载(切走题目页、把语言切成流程图)原来只是悄悄 stop(), 房间和 active 都还留着,老师那边模态框照开、字照敲,一个也到不了;watch 没有 immediate,学生切回来也不会重建。改成明确 leave() 结束协作。 另外 @ready 也作为起点之一:学生排队时切走、老师这期间接了单,切回来能接上。 - 演示模式下 isTeacherOrAbove 被强制 false,服务端却按库里的 adminType 照样 把他算作在线老师:学生因此拿到 pending 而不是 no_teacher,排队等一个顶栏里 根本没有求助列表的人;他自己看到的求助按钮点下去,服务端回「教师不能发起求助」。 两头都不对,索性对演示模式整个关掉这个功能。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016DHxhKNxXfG89JnVzHbvgj
102 lines
3.6 KiB
Vue
102 lines
3.6 KiB
Vue
<script setup lang="ts">
|
||
import { darkTheme, dateZhCN, zhCN } from "naive-ui"
|
||
import "normalize.css"
|
||
import "./index.css"
|
||
import { useConfigStore } from "shared/store/config"
|
||
import { useConfigUpdate } from "shared/composables/configUpdate"
|
||
import { useMaxKB } from "shared/composables/maxkb"
|
||
import { useUserStore } from "shared/store/user"
|
||
import { useCollabStore } from "shared/store/collab"
|
||
|
||
const isDark = useDark()
|
||
const configStore = useConfigStore()
|
||
const userStore = useUserStore()
|
||
const collabStore = useCollabStore()
|
||
|
||
// 初始化配置和实时更新
|
||
onMounted(() => {
|
||
configStore.getConfig()
|
||
userStore.getMyProfile()
|
||
})
|
||
|
||
// 配置实时推送 + MaxKB 挂件。阶段 2 时因为后端还没有 /ws/config 而暂时关掉,
|
||
// 阶段 3 迁完之后一直没接回来 —— 表现是管理员改站点配置后学生要刷新才生效、
|
||
// 知识库挂件根本不出现,且没有任何报错。两者现在都走新后端的 /ws/config。
|
||
useConfigUpdate()
|
||
useMaxKB()
|
||
|
||
// 课堂求助通道。和 /ws/config 一样是全局常驻的:老师可能正在后台改题时
|
||
// 收到求助,学生也要在排队期间一直挂着,所以不放在题目页里起落。
|
||
//
|
||
// 演示模式下整个关掉。它是超管把界面伪装成学生用来投屏的,而服务端只认库里的
|
||
// 真实身份:连着的话这个人会被算进「在线老师」,学生因此拿到 pending 而不是
|
||
// no_teacher,排队等一个顶栏里根本没有求助列表的人;反过来他自己看到的求助
|
||
// 按钮点下去,服务端回的是「教师不能发起求助」。两头都不对,索性对演示模式
|
||
// 关闭这个功能(Form.vue 的按钮同步隐藏)。
|
||
watch(
|
||
() => userStore.isAuthed && !userStore.demoMode,
|
||
(available) => {
|
||
if (available) collabStore.connect()
|
||
else collabStore.disconnect()
|
||
},
|
||
{ immediate: true },
|
||
)
|
||
|
||
// 延迟加载 highlight.js,避免阻塞首屏
|
||
const hljsInstance = ref<any>(null)
|
||
const loadHighlightJS = async () => {
|
||
if (hljsInstance.value) return hljsInstance.value
|
||
|
||
// 逐个取 .default,不要在 Promise.all 后面 map —— 那样元组会塌成
|
||
// (HLJSApi | LanguageFn)[],每个元素都变成联合类型,hljs 上就找不到方法了
|
||
const [core, c, cpp, python, java, javascript, go, sql] = await Promise.all([
|
||
import("highlight.js/lib/core"),
|
||
import("highlight.js/lib/languages/c"),
|
||
import("highlight.js/lib/languages/cpp"),
|
||
import("highlight.js/lib/languages/python"),
|
||
import("highlight.js/lib/languages/java"),
|
||
import("highlight.js/lib/languages/javascript"),
|
||
import("highlight.js/lib/languages/go"),
|
||
import("highlight.js/lib/languages/sql"),
|
||
])
|
||
const hljs = core.default
|
||
|
||
hljs.registerLanguage("c", c.default)
|
||
hljs.registerLanguage("python", python.default)
|
||
hljs.registerLanguage("cpp", cpp.default)
|
||
hljs.registerLanguage("java", java.default)
|
||
hljs.registerLanguage("javascript", javascript.default)
|
||
hljs.registerLanguage("go", go.default)
|
||
hljs.registerLanguage("sql", sql.default)
|
||
|
||
hljsInstance.value = hljs
|
||
return hljs
|
||
}
|
||
|
||
// 在空闲时预加载
|
||
onMounted(() => {
|
||
if ("requestIdleCallback" in window) {
|
||
requestIdleCallback(() => loadHighlightJS())
|
||
} else {
|
||
setTimeout(() => loadHighlightJS(), 1000)
|
||
}
|
||
})
|
||
|
||
provide("hljs", hljsInstance)
|
||
</script>
|
||
|
||
<template>
|
||
<n-config-provider
|
||
:theme="isDark ? darkTheme : null"
|
||
:locale="zhCN"
|
||
:date-locale="dateZhCN"
|
||
:hljs="hljsInstance"
|
||
>
|
||
<n-dialog-provider>
|
||
<n-message-provider>
|
||
<router-view></router-view>
|
||
</n-message-provider>
|
||
</n-dialog-provider>
|
||
</n-config-provider>
|
||
</template>
|