diff --git a/apps/api/src/collab/handler.ts b/apps/api/src/collab/handler.ts index cb915f5..21f8269 100644 --- a/apps/api/src/collab/handler.ts +++ b/apps/api/src/collab/handler.ts @@ -69,8 +69,8 @@ export function handleCollabOpen(ws: CollabSocket) { // 学生(重)连:如果这个账号名下已经有一条请求(掉线重连回来,或者干脆是 // 同账号第二个标签页),把它迁移到这条新连接上,并把当前状态补发回去 —— // 前端 onConnected 时会先把本地状态清空等着这条补发,不发的话就永远卡在 - // idle;不迁移 socket 归属的话,旧连接一断,handleHelpCancel 的 - // `request.socket === ws` 校验就会认不出这条新连接发起的取消 + // idle;不迁移 socket 归属的话,sendHelpStatus/accept 等后续推送会发到一条 + // 已经不用的旧连接上,新连接(新标签页)什么都收不到 const request = getRequest(ws.data.userId) if (!request) return request.socket = ws @@ -234,9 +234,11 @@ async function handleHelpRequest(ws: CollabSocket, problemId: unknown) { function handleHelpCancel(ws: CollabSocket) { const request = getRequest(ws.data.userId) - // 同一账号可能开着两个标签页;只能取消自己这条连接发起的请求,不然 B 标签页 - // 能把 A 标签页排队中的求助顶掉 —— 和 handleCollabClose 排队分支同一类归属漏洞 - if (!request || request.socket !== ws || request.status === "active") return + // 不比对 socket 归属:取消的是这个学生自己的求助,不管从他哪个标签页发起都 + // 合法——getRequest(ws.data.userId) 已经把范围锁在这一个用户上了,不是跨用户 + // 操作。这里和 handleCollabClose 的排队分支不是同一类问题:那边关闭事件是 + // 「顺带」触发的,必须认出是不是本人这条连接;这里是用户主动点了取消 + if (!request || request.status === "active") return removeRequest(ws.data.userId) broadcastRequests() } diff --git a/apps/web/src/oj/problem/components/Form.vue b/apps/web/src/oj/problem/components/Form.vue index 7f183de..73e1dbe 100644 --- a/apps/web/src/oj/problem/components/Form.vue +++ b/apps/web/src/oj/problem/components/Form.vue @@ -3,8 +3,7 @@ import { storeToRefs } from "pinia" import { copyToClipboard, utoa } from "utils/functions" import { useCodeStore } from "oj/store/code" import { useProblemStore } from "oj/store/problem" -import { injectSyncStatus } from "oj/composables/syncStatus" -import { SYNC_MESSAGES } from "shared/composables/sync" +import { useCollabStore } from "shared/store/collab" import { ICON_SET, LANGUAGE_FORMAT_VALUE, @@ -27,17 +26,14 @@ const SubmitFlowchart = defineAsyncComponent( interface Props { storageKey: string - isConnected?: boolean // WebSocket 实际的连接状态(已建立/未建立) } -const { storageKey, isConnected = false } = defineProps() +const { storageKey } = defineProps() -// 注入同步状态 -const syncStatus = injectSyncStatus() +const collabStore = useCollabStore() const emit = defineEmits<{ changeLanguage: [v: LANGUAGE] - toggleSync: [v: boolean] }>() const message = useMessage() @@ -50,20 +46,42 @@ const { problem, languages } = storeToRefs(problemStore) const { isDesktop } = useBreakpoints() -const syncEnabled = ref(false) // 用户点击按钮后的意图状态(想要开启/关闭) const statisticPanel = ref(false) // 计算属性 const isContestMode = computed(() => route.name === "contest problem") const buttonSize = computed(() => (isDesktop.value ? "medium" : "small")) -const showSyncFeature = computed( +// 可见条件沿用原来的 showSyncFeature,再加上「不是教师」—— +// 教师端的入口在顶栏,不在题目页 +const showHelpButton = computed( () => isDesktop.value && userStore.isAuthed && + !userStore.isTeacherOrAbove && codeStore.code.language !== "Flowchart" && !isContestMode.value, ) +const helpButtonText = computed(() => { + if (collabStore.helpStatus === "active") return "老师正在帮你" + if (collabStore.helpStatus === "pending") return "取消求助" + return "求助" +}) + +const toggleHelp = () => { + if (collabStore.helpStatus === "pending") collabStore.cancelHelp() + else if (collabStore.helpStatus === "idle") + 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 @@ -191,17 +209,6 @@ const goEdit = () => { window.open(router.resolve(url).href, "_blank") } -const toggleSync = () => { - syncEnabled.value = !syncEnabled.value - emit("toggleSync", syncEnabled.value) -} - -defineExpose({ - resetSyncStatus: () => { - syncEnabled.value = false - }, -}) - onMounted(() => { if (!languages.value.includes(codeStore.code.language)) { // 回退到题目支持的第一种语言(如 SQL 题只有 "SQL",硬编码 Python3 会被后端拒绝) @@ -250,31 +257,22 @@ onMounted(() => { 更多操作 -