feat(课堂求助): 教师端接单直接跳题目页协作,删掉 CollabModal
Some checks failed
Deploy / deploy (push) Has been cancelled
Some checks failed
Deploy / deploy (push) Has been cancelled
点求助列表里的一条 = 跳到 /problem/:id,在页面自带的那个编辑器里和学生同步。
原来是弹一个 CollabModal(弹框里再挂一个 CodeMirror + 一份只读题面):
- 按一下 Esc 弹框就关、协作跟着结束(`show` 的 setter 里 leave 是唯一的关闭语义,
Esc 和点遮罩都走它),上课太容易误触;
- 弹框里那份题面不能跑测试、不能提交,老师还是得另开标签页对照。
现在结束协作只有两条明确的路:工具栏的「结束协作」按钮、离开这一页。教师端也少了
一个 CodeMirror 实例 —— setBinaryHandler 那个单例槽位只剩 SyncCodeEditor 一个使用者。
改造中撞出两个真问题,根因是同一个:`/problem/1002` → `/problem/1001` 是同一条路由
换 params,**组件被复用、不卸载**。
- 老师在别的题上接单跳过来,`@ready` 不会再触发,而 room 早在导航前就 open 了 ——
老师停在一个空编辑器上干等。watch 的源从 room 一个变成 [room, roomIsHere] 两个。
- 反过来老师协作中切去别的题,onUnmounted 不触发 → 不发 leave,学生一直挂着
「老师正在帮你」而老师的字一个也过不去。加 `bound` 标记区分「房间从我这儿挪走了」
(补发 leave)和「房间在别处开起来了」(跟我无关,接单那一瞬间就是这种)。
`leave` 分两种语义(协议加 reason):
| 前端 | 求助记录 | 对面看到 |
|---|---|---|
| leave("done") 点「结束协作」 | 删除 | 老师已结束这次帮忙 |
| leave("left") 离开这道题的页面 | 教师走→退回排队;学生走→删除 | 老师暂时离开,已重新排队 |
"left" 复用的是老师掉线那条路(requeueAfterTeacherGone),两件事语义相同。原来两者
都按 done 处理 —— 老师点一下「提交信息」,学生就得重新举手,而他看到的是
「协作已结束」,会以为被处理完了。发起方收到的 reason 单独一份(self_left/done),
不然学生自己切走了却看到「老师已结束这次帮忙」。
其余为这套交互补的闸:
- 学生排队期间离开那道题 → 自动取消求助。那条求助说的是「我卡在这道题」,人走了
就不成立;原来它会一直挂在队列里,老师接进来时学生的编辑器不在这道题上、根本
不会绑,老师对着空编辑器敲字,两边都没提示。
- 协作中**教师的语言选择跟着 room.language 走**(写 codeStore,不只是改高亮),
结束后还原。只改高亮的话老师会拿自己那档语言提交学生的代码(学生写 C、老师选的
是 Python,当场 CE),工具栏还可能显示「提交流程图」。学生端一个字不动 ——
服务端的 room_language 只发给教师,学生本地那份停在建房那一刻。
- 协作中不给「重置代码」:v-model 一写回就顺着 Yjs 同步过去,等于一键清空学生的作业。
- 协作中「提交信息」走新标签 —— 那是教师工具栏上唯一会跳路由的按钮。
- 接单时 resetScreenMode():分屏在「题目」「自测」两档时右侧编辑器根本没挂出来,
停在同一道题上接单会落进一个没有编辑器的页面(跳到别的题时 detail.vue 的 init()
会重置,同页不会)。
- 老师已在一个房间里时前端也拦住接下一单(服务端本来就拦),不拦的话前端已经跳到
新题目上,等于把手上那场协作断掉。
- 文案:协议层那四条英文(Invalid problemId 之类)会被原样弹成 toast,改成中文;
「请先退出当前协作」统一成「结束」;room_closed 的提示按 reason × 角色展开。
验证:本机起 api + web,三个账号两两组合实跑 —— 跨题接单 / 同页接单、双向编辑、
连按 Esc、结束协作、老师跳走后再接、学生跳走、学生排队中切题、学生协作中切语言、
分屏在「题目」档接单、协作中重复接单,以及上面每一句提示的实际文案。
api typecheck / check:routes、前端 type-check / build、fmt 全过。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -216,7 +216,7 @@ teacherSockets: Set<socket> // 在线老师,用于推 requests 与判断 no_
|
||||
|---|---|
|
||||
| `shared/store/collab.ts` | pinia store:持有 WS 连接、求助列表(老师)、自身求助状态(学生)、当前房间 |
|
||||
| `shared/components/HelpRequestList.vue` | 顶栏红点 + 下拉列表,含同题聚合 |
|
||||
| `shared/components/CollabModal.vue` | 老师端协作模态框 |
|
||||
| ~~`shared/components/CollabModal.vue`~~ | 老师端协作模态框。**2026-09-16 删除**,见文末的变更 |
|
||||
| `shared/composables/collab.ts` | Y.Doc 与 yCollab 绑定,取代 `sync.ts` |
|
||||
|
||||
### 5.2 改动
|
||||
@@ -298,3 +298,89 @@ teacherSockets: Set<socket> // 在线老师,用于推 requests 与判断 no_
|
||||
的 Django 后端),其中「Yjs + y-webrtc for collaborative editing **in the flowchart
|
||||
editor**」一句本就是错的(协作在代码编辑器,流程图被显式排除)。本次至少要把
|
||||
实时特性与环境变量两节改对,整份文档的翻新另计。
|
||||
|
||||
## 变更记录
|
||||
|
||||
### 2026-09-16:教师端不再用弹框,接单直接跳题目页
|
||||
|
||||
老师点求助列表里的一条 → 跳到 `/problem/:id`,就在**页面自带的那个编辑器**里
|
||||
和学生同步。`CollabModal.vue` 删除。
|
||||
|
||||
为什么改:
|
||||
|
||||
- **弹框按一下 Esc 就关,协作跟着结束。** 上课时太容易误触 —— `show` 的 setter
|
||||
里 `leave()` 是唯一的关闭语义,n-modal 的 Esc / 点遮罩都会走到它。
|
||||
现在结束协作只有两条明确的路:工具栏的「结束协作」按钮、离开这一页。
|
||||
- 弹框里那份题面是只读复制品(不能跑测试、不能提交),老师还是得另开一个标签页。
|
||||
跳到真页面之后,老师看到的就是学生看到的那一页。
|
||||
- 少一个 CodeMirror 实例:`setBinaryHandler` 那个单例槽位现在只有
|
||||
`SyncCodeEditor` 一个使用者,教师端也不用再为了首屏体积把整套编辑器拆成
|
||||
异步 chunk(页面本来就要加载它)。
|
||||
|
||||
连带的口径:
|
||||
|
||||
- `SyncCodeEditor` 多一个 `problemId` prop,**房间的题号对不上就不绑**(老师接单时
|
||||
可能停在别的题上);种子内容仍然只来自学生端(教师端传 `null`)。
|
||||
- 协作期间教师那个编辑器的语言跟着 `room.language` 走,语言选择器禁用;
|
||||
草稿不写 localStorage(那会儿编辑器里是学生的代码),协作结束后读回教师自己的草稿。
|
||||
- 协作中的题目页强制走代码编辑器分支,不受教师自己选的「流程图」影响。
|
||||
|
||||
### 2026-09-16(同日,第二轮):`leave` 分两种语义
|
||||
|
||||
「老师走开」和「老师处理完了」原来都走 `leave` → `teardownRoom("done")` → 删掉求助
|
||||
记录,于是老师点一下「提交信息」,学生就得重新举手,而且他看到的是「协作已结束」,
|
||||
会以为自己被处理完了。
|
||||
|
||||
协议加一个 reason:
|
||||
|
||||
| 前端 | 服务端 | 求助记录 | 对面看到 |
|
||||
|---|---|---|---|
|
||||
| `leave("done")`(点「结束协作」) | `teardownRoom("done")` | 删除 | 协作已结束 |
|
||||
| `leave("left")`(离开这道题的页面) | `teardownRoom("peer_left", side, ws)` | 教师走→**退回排队**;学生走→删除 | 老师暂时离开,已重新排队 |
|
||||
|
||||
`"left"` 复用的是老师掉线那条路(`requeueAfterTeacherGone`)—— 两件事语义相同。
|
||||
**发起方收到的 reason 是 `done`**(`teardownRoom` 的 `initiator` 参数),他不该看到
|
||||
一句「对方离开了」。
|
||||
|
||||
配套:
|
||||
|
||||
- 协作中的「提交信息」改成新标签打开 —— 那是教师工具栏上唯一会跳路由的按钮,
|
||||
而「看看这学生都交了什么」恰好是协作时最常点的一个;
|
||||
- **学生排队期间离开那道题 → 自动取消求助**(`SyncCodeEditor` 的
|
||||
`cancelPendingIfLeaving`)。那条求助说的是「我卡在这道题」,人走了就不成立;
|
||||
原来它会一直挂在队列里,老师接进来时学生的编辑器不在这道题上、根本不会绑,
|
||||
老师对着空编辑器敲字,两边都没有提示。只认「离开」这件事、不跟房间状态挂钩:
|
||||
老师走开时服务端刚补上的那条 pending 不能被它顺手取消掉;
|
||||
- **协作中教师的语言选择跟着 `room.language` 走**(写 codeStore,不只是改编辑器高亮),
|
||||
协作结束后还原成他自己那档。只改高亮的话,老师会拿着自己那档语言提交学生的代码
|
||||
(学生写 C、老师选的是 Python,当场 CE),工具栏还可能显示「提交流程图」。
|
||||
- 「重置代码」协作中不出现在菜单里:`v-model` 一写回就顺着 Yjs 同步过去,
|
||||
等于一键清空学生的作业。
|
||||
- 接单时 `resetScreenMode()`:分屏模式是「题目」或「自测」时右侧编辑器根本没挂出来,
|
||||
停在同一道题上接单会落进一个没有编辑器的页面(跳到别的题时 `detail.vue` 的
|
||||
`init()` 会重置,同页不会)。
|
||||
- 老师已经在一个房间里时,前端也拦住接下一单(服务端本来就拦)—— 不拦的话前端
|
||||
已经跳到新题目上,等于把手上那场协作断掉。
|
||||
|
||||
### 2026-09-16(同日,第三轮):文案
|
||||
|
||||
`type: "error"` 的 message **会被前端原样弹成 toast**(store 的 `case "error"` →
|
||||
`setNotice` → `message.info`),所以服务端那几条协议层错误从英文改成中文
|
||||
(`Invalid problemId` → 「题号不对,请刷新页面重试」等四条)。**以后往这个通道加
|
||||
错误消息一律写中文、写成学生看得懂的话。**
|
||||
|
||||
「请先**退出**当前协作」改成「请先**结束**当前协作」,和按钮「结束协作」、前端那道
|
||||
拦截统一成一个词。
|
||||
|
||||
`room_closed` 的提示语按 reason × 角色展开,原来四种情形共用一句「协作已结束」:
|
||||
|
||||
| reason | 谁收到 | 老师看到 | 学生看到 |
|
||||
|---|---|---|---|
|
||||
| `done` | 对面(有人点了「结束协作」) | 已结束这次协作 | 老师已结束这次帮忙 |
|
||||
| `self_left` | **发起方**(自己离开了这道题) | 你已离开这道题,求助退回队列了 | 你已离开这道题,协作结束 |
|
||||
| `peer_left` | 对面(对方离开了这道题) | 学生离开了这道题,协作已结束 | 老师暂时离开,你的求助已重新排队 |
|
||||
| `peer_offline` | 对面(掉线 / 转发失败) | 对方已断开连接 | 对方已断开连接 |
|
||||
|
||||
`self_left` 是这一轮新加的:发起方原来跟对面收同一条 `done`,于是学生自己切走了
|
||||
却看到「老师已结束这次帮忙」—— 一句假话。发起方收到的 reason 由 `teardownRoom`
|
||||
的 `initiator` 决定。
|
||||
|
||||
Reference in New Issue
Block a user