diff --git a/apps/web/src/App.vue b/apps/web/src/App.vue index 31f36d7..83eeda6 100644 --- a/apps/web/src/App.vue +++ b/apps/web/src/App.vue @@ -26,11 +26,17 @@ useConfigUpdate() useMaxKB() // 课堂求助通道。和 /ws/config 一样是全局常驻的:老师可能正在后台改题时 -// 收到求助,学生也要在排队期间一直挂着,所以不放在题目页里起落 +// 收到求助,学生也要在排队期间一直挂着,所以不放在题目页里起落。 +// +// 演示模式下整个关掉。它是超管把界面伪装成学生用来投屏的,而服务端只认库里的 +// 真实身份:连着的话这个人会被算进「在线老师」,学生因此拿到 pending 而不是 +// no_teacher,排队等一个顶栏里根本没有求助列表的人;反过来他自己看到的求助 +// 按钮点下去,服务端回的是「教师不能发起求助」。两头都不对,索性对演示模式 +// 关闭这个功能(Form.vue 的按钮同步隐藏)。 watch( - () => userStore.isAuthed, - (isAuthed) => { - if (isAuthed) collabStore.connect() + () => userStore.isAuthed && !userStore.demoMode, + (available) => { + if (available) collabStore.connect() else collabStore.disconnect() }, { immediate: true }, diff --git a/apps/web/src/oj/problem/components/Form.vue b/apps/web/src/oj/problem/components/Form.vue index 73e1dbe..bb62a4c 100644 --- a/apps/web/src/oj/problem/components/Form.vue +++ b/apps/web/src/oj/problem/components/Form.vue @@ -58,6 +58,8 @@ const showHelpButton = computed( isDesktop.value && userStore.isAuthed && !userStore.isTeacherOrAbove && + // 演示模式下协作通道是断开的(见 App.vue),按钮点了也没人收 + !userStore.demoMode && codeStore.code.language !== "Flowchart" && !isContestMode.value, ) @@ -74,14 +76,6 @@ const toggleHelp = () => { collabStore.requestHelp(problem.value!._id) } -// 服务端的一次性提示(没有老师在线、老师取消了求助) -watch( - () => collabStore.notice, - (text) => { - if (text) message.info(collabStore.consumeNotice()) - }, -) - const showGoSubmissionButton = computed(() => { if (isContestMode.value) return true else if (userStore.isAdminRole) return true diff --git a/apps/web/src/shared/components/SyncCodeEditor.vue b/apps/web/src/shared/components/SyncCodeEditor.vue index 090503b..989a9fd 100644 --- a/apps/web/src/shared/components/SyncCodeEditor.vue +++ b/apps/web/src/shared/components/SyncCodeEditor.vue @@ -68,8 +68,17 @@ interface EditorReadyPayload { // 项目里旧的 sync.ts 用的是裸变量,同一个道理,这里换成 shallowRef 规避。 const editorView = shallowRef(null) +const bind = (view: EditorView) => { + if (collabStore.isTeacher || !collabStore.room) return + // 学生端:当前编辑器内容就是内容源 + start({ editorView: view, seedContent: view.state.doc.toString() }) +} + const handleEditorReady = (payload: EditorReadyPayload) => { editorView.value = payload.view + // 也从这里起:学生排队时切去看提交记录、老师在这期间接了单, + // 等他切回来时 room 早就非空了,只靠下面的 watch 是等不到的 + bind(payload.view) } // 房间开了才建文档。学生点求助时什么都不做 —— 老师没来之前不该动他的编辑器。 @@ -82,19 +91,19 @@ const handleEditorReady = (payload: EditorReadyPayload) => { watch( () => collabStore.room, (room) => { - if (room && !collabStore.isTeacher && editorView.value) { - // 学生端:当前编辑器内容就是内容源 - start({ - editorView: editorView.value, - seedContent: editorView.value.state.doc.toString(), - }) - } else { - stop() - } + if (room && !collabStore.isTeacher && editorView.value) bind(editorView.value) + else stop() }, ) -onUnmounted(stop) +onUnmounted(() => { + stop() + // 这个组件卸载意味着编辑器没了(切走页面、或者把语言切成流程图), + // CRDT 会话没法接着用:回来时只能新建 Y.Doc,再拿编辑器内容当种子就会和 + // 老师那份合并成重复文本。所以不是"悄悄把绑定拆了",而是明确结束协作 —— + // 否则老师那边模态框照开、字照敲,一个也到不了学生那里 + if (collabStore.room && !collabStore.isTeacher) collabStore.leave() +})