Files
OJ2/apps/web/package.json
yuetsh 86857dedf0 feat(web): 协作编辑走 collab 通道
Yjs sync/awareness 协议直接跑在 /ws/collab 上,服务端哑转发。
内容源是学生:学生端先把编辑器内容写进 ytext 再挂 yCollab,
教师端 seedContent 恒为 null —— 这条根治了老实现里谁的代码活下来看运气的问题。

订正 brief 里的一处疏漏:SyncCodeEditor.vue 挂在每个用户的题目页上,教师也不例外
(ProblemEditor.vue 只按语言是不是 Flowchart 分支,不看角色)。教师接单同样会让
collabStore.room 非空,若不按角色收窄,教师自己停在某道题页面上接单时,这个组件
会把教师自己的编辑器内容当成种子插入文档,还会跟 CollabModal 抢
setBinaryHandler 这个单例槽位。改为 `room && !collabStore.isTeacher` 才起协作,
教师端的协作只归 CollabModal 管。

另外两处修正:
- editorView 用 shallowRef 而非 ref —— CodeMirror 的 EditorView 是带 getter 的类
  实例,ref() 的深度 UnwrapRef 会把它拆成丢了原型方法的假类型,vue-tsc 报错。
- Header.vue 挂 CollabModal 放进根 n-flex 内部而不是同级 —— 组件一旦变成多根
  fragment,default.vue 里 `<Header class="header" />` 的 class 就没有单一根节点
  可以落地,header 行会丢掉居中样式(实测触发了 Vue 的
  Extraneous non-props attributes 警告)。n-modal 默认 teleport 到 body,塞在
  这里不影响其实际渲染位置。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K1d8B3f4SXJwDvUY625eQd
2026-08-28 07:43:08 -06:00

74 lines
2.2 KiB
JSON

{
"name": "@oj2/web",
"version": "1.9.0",
"type": "module",
"scripts": {
"dev": "vite",
"start": "vite",
"build": "vite build",
"build:staging": "vite build --mode staging",
"build:test": "vite build --mode test",
"fmt": "prettier --write src *.ts",
"type-check": "vue-tsc --noEmit -p tsconfig.app.json"
},
"dependencies": {
"@codemirror/autocomplete": "^6.20.3",
"@codemirror/lang-cpp": "^6.0.3",
"@codemirror/lang-python": "^6.2.1",
"@codemirror/lang-sql": "^6.10.0",
"@codemirror/language": "^6.12.4",
"@codemirror/state": "^6.7.1",
"@codemirror/view": "^6.43.9",
"@lezer/highlight": "^1.2.3",
"@oj2/contract": "workspace:*",
"@vue-flow/background": "^1.3.2",
"@vue-flow/controls": "^1.1.3",
"@vue-flow/core": "^1.48.2",
"@vue-flow/minimap": "^1.5.4",
"@vue-flow/node-resizer": "^1.5.1",
"@vue-flow/node-toolbar": "^1.1.1",
"@vueuse/core": "^14.4.0",
"@vueuse/router": "^14.4.0",
"@wangeditor-next/editor": "^6.2.0",
"@wangeditor-next/editor-for-vue": "^6.2.0",
"axios": "^1.19.0",
"canvas-confetti": "^1.9.4",
"chart.js": "^4.5.1",
"chartjs-chart-wordcloud": "^4.4.5",
"codemirror": "^6.0.2",
"copy-text-to-clipboard": "^3.2.2",
"date-fns": "^4.4.0",
"fflate": "^0.8.3",
"highlight.js": "^11.12.0",
"md-editor-v3": "^6.5.6",
"mermaid": "^11.17.2",
"mermaid-legacy": "npm:mermaid@^9.4.3",
"naive-ui": "^2.45.2",
"nanoid": "^6.0.1",
"normalize.css": "^8.0.1",
"pinia": "^4.0.3",
"skulpt": "^1.2.0",
"vue": "^3.5.41",
"vue-chartjs": "^5.3.4",
"vue-codemirror": "^6.1.1",
"vue-router": "^5.2.0",
"y-codemirror.next": "^0.3.6",
"y-protocols": "1.0.7",
"y-webrtc": "^10.3.0",
"yjs": "^13.6.32"
},
"devDependencies": {
"@iconify/vue": "^5.0.1",
"@types/canvas-confetti": "^1.9.0",
"@types/node": "^26.3.0",
"@vitejs/plugin-legacy": "^8.2.3",
"@vitejs/plugin-vue": "^6.0.8",
"@vue/tsconfig": "^0.9.1",
"prettier": "^3.9.6",
"unplugin-auto-import": "^21.1.0",
"unplugin-vue-components": "^32.1.0",
"vite": "^8.2.2",
"vue-tsc": "^3.3.11"
}
}