复刻「腾答知识竞赛系统」核心功能的本地 Web 版:大屏端 + 控制端 + 选手端三端通过 WebSocket 实时联动。 零第三方依赖,只需 Node.js(>= 14)。
node server.js启动后打开(默认端口 3000,可用 PORT=8080 node server.js 换端口):
| 端 | 地址 | 用途 |
|---|---|---|
| 🎛️ 控制端 | http://localhost:3000/host.html | 主持人:出题、计时、判分、队伍与题库管理 |
| 🖥️ 大屏端 | http://localhost:3000/screen.html | 投影/电视:题目、倒计时、抢答、实时积分榜 |
| 📱 选手端 | http://localhost:3000/player.html | 选手用手机/电脑:入队、抢答、作答 |
局域网内其他设备(手机、参赛电脑)访问启动时控制台打印的 局域网地址 即可,例如 http://192.168.1.5:3000/player.html。
- 选择赛制:控制端 → 竞赛控制 → 右上角「👥 团队赛 / 🎯 个人赛」按钮切换。
- 个人赛:选手无需队伍,创建选手姓名(手动或 Excel 导入)即可开始同步比赛,个人计分、个人排名(积分榜/领奖台/抢答均按选手);
- 团队赛:以队伍为单位计分排名(默认)。
- 队伍管理(团队赛用):控制端 → 队伍管理 → 添加队伍(红队、蓝队……),可随时重命名/删除;每队显示「在线人数 / 账号数」。
- 选手管理(独立页面):控制端 → 选手管理。
- 选手登录只需姓名(选手端无队伍选择),账号创建与队伍分配全部在控制端(后端)完成;
- 预注册账号:输入姓名 + 选择所属队伍 → 「添加选手账号」,把姓名发给选手;选手输入相同姓名即登录该账号;
- Excel / CSV 批量导入:下载模板(
姓名/队伍两列)→ 填好花名册 → 解析预览 → 一键导入。姓名必填,队伍可留空(未分组,由管理员分配);队伍不存在会自动创建,同名选手自动更新队伍归属; - 支持:在线/离线状态、移动选手到其他队伍、改名、移除(在线选手会被踢出)、一键清空离线账号;
- 断线后账号保留为离线状态,方便赛前点名核对。
- 题库管理(题库体系参考腾答:每个大类单独绑定一个题库,题库不可重复使用):
- 🗂️ 大类管理:六大类(基础题/必答题/抢答题/风险题/简答题/加赛题),顺序可调(↑↓按钮),每个大类绑定一个题库(互斥——同一题库不能被多个大类重复使用,改绑自动解绑原大类);
- 📚 题库管理:创建任意名称的题库,支持启用/停用(停用自动解绑)、重命名、清空、删除;
- 📝 题目管理:每个题库内的题目支持五种题型——单选题/多选题/判断题/填空题/简答题;添加、编辑、删除,Excel/CSV 批量导入(模板含五种题型示例)、JSON 导入导出;
- 判分规则:单选/判断自动判分;多选全对才得分;填空忽略空格大小写(可用
|分隔多个可接受答案);简答题不自动判分(主持人手动判对/判错);风险题(大类为风险题)答错倒扣分。 六个大类的用法(顺序可在「大类管理」中自由调整): - 基础题:各队/各选手同时作答,主持人点「揭晓答案」自动判分;
- 必答题:轮流作答,主持人手动判对/判错加分;
- 抢答题:主持人「开放抢答」,选手在手机端点抢答,最先抢到的队/选手作答,主持人判分;
- 风险题:答错倒扣分;
- 简答题:选手输入文字作答,主持人看答案人工判分;
- 加赛题:平分加赛专用(用法同基础题)。
- 竞赛控制:选择题库题目(按大类分组,顺序与「大类管理」一致,可按题型筛选)→ 「推送题目到大屏」→ 按大类开始答题倒计时 / 开放抢答 → 判分或揭晓。
- 计分:判对自动加本题分值;也可用积分榜上的 +/- 按钮手动调分;「重置得分」清零重来。
knowledge-competition/
├── server.js # 服务端入口:HTTP + WebSocket + 竞赛状态机 + 消息分发
├── ws-server.js # 极简 WebSocket 协议实现(RFC 6455,零依赖)
├── lib/
│ ├── util.js # 通用工具:JSON 读写 / 唯一ID
│ └── quiz.js # 题库领域逻辑(纯函数):大类/题库绑定/题目规范化/判分/迁移
├── public/
│ ├── common.js # 前端共享模块(浏览器/Node 双环境):常量/Excel解析/判分
│ ├── host.html/js # 控制端(竞赛控制/队伍管理/选手管理/题库管理)
│ ├── screen.html/js # 大屏端
│ ├── player.html/js # 选手端
│ ├── style.css # 共享样式
│ └── vendor/ # 本地化 SheetJS(xlsx) 解析库(离线可用)
├── docs/ # 腾答官网参考文档备份
├── test/
│ ├── e2e.js # 端到端流程测试(协议级,模拟三端)
│ └── excel-import.js# 解析一致性测试(页面 common.js <-> 服务端 lib/quiz.js)
└── data/
├── questions.json # 题目(含题库归属与题型)
├── quiz-config.json # 大类顺序 + 题库列表 + 绑定关系
└── state.json # 运行时自动保存:标题/轮次/赛制/队伍/选手
分层原则:
lib/quiz.js是纯函数领域层(不依赖网络/状态),新增题型/判分规则改这里即可,前后端共用同一套判分语义;public/common.js是前端共享层(与 Node 测试共用同一份代码),新增导入格式/题型展示改这里,页面和测试自动同步;- 页面 JS 只做 DOM 与消息粘合,服务端只做状态与分发——各层单一职责。
协议约定(WebSocket JSON,字段名以 publicState 输出为准):
- 选手端 → 服务端:
hello/join{name}/leave/buzz/answer{choice|choices|text} - 控制端 → 服务端:
setTitle/setMode/addTeam|removeTeam|renameTeam/addPlayer|removePlayer|movePlayer|renamePlayer|clearOfflinePlayers|importPlayers/moveCategory|bindBank|unbindBank/addBank|renameBank|toggleBank|clearBank|deleteBank/addQuestion|updateQuestion|deleteQuestion|importQuestions/pushQuestion|startTimer|closeBuzz|acceptBuzz|rejectBuzz|judge|reveal|nextQuestion|score|resetGame - 服务端 → 所有端:
state{state}/toast{text};选手端被移除时收到kicked
常见扩展点:
- 新增题型:改
lib/quiz.js的normalizeQuestion+gradeAnswer、public/common.js的TYPE_TEXT+gradeAnswer、三个页面的渲染分支(搜索q.type ===); - 新增大类:改
lib/quiz.js的DEFAULT_CATEGORIES(页面自动适配,无需改前端); - 新增导入列:改
public/common.js的QUESTION_HEAD/PLAYER_HEAD与buildXxxRows; - 新增端(如独立排行端):新页面引用
common.js,订阅state即可,无需改服务端消息协议; - 测试:协议行为加在
test/e2e.js,解析/判分一致性加在test/excel-import.js。
- 三个端页面均做了平板/手机屏幕适配:控制端在手机上按钮自动换行全宽、表格可横滑;大屏端在小屏自动上下堆叠(题目在上、积分榜在下);选手端本身为手机优先设计,并适配了 iPhone 刘海/底部安全区与横屏答题。
- 所有状态由服务器统一维护(计时、抢答顺序、得分、选手账号),页面刷新/断线重连后自动恢复同步。
- 选手端收到的状态不含题目答案,避免"看包"。
- 数据保存在
data/目录,重启服务不丢失(含选手账号)。 docs/目录存放腾答官网功能参考文档(本地化备份),docs/fetch-docs.js可重新抓取。