架构概览
文档日期:2026-04
技术栈
| 层 | 技术 |
|---|---|
| 框架 | Next.js 16 App Router |
| UI 库 | React 19 + Tailwind CSS 4 + framer-motion |
| 语言 | TypeScript 5(strict 模式) |
| 实时通信 | 原生 WebSocket API |
| 状态管理 | React Context + useReducer |
| 认证 | HTTP-only Cookie(JWT) |
| 测试 | Playwright E2E |
核心设计原则
1. 单页游戏路由
/game 页面根据服务端推送的 phase 字段动态渲染对应阶段的 UI,无需多路由切换。这样避免了路由跳转时的状态丢失问题,也使得阶段切换动画更加流畅。
/game → GamePhaseRenderer → WaitingRoom | SpeakingPhase | TeamSelectPhase | ...2. 服务端单向数据流
WebSocket 消息
↓
useWebSocket (解析 + dispatch)
↓
gameReducer (纯函数状态转换)
↓
GameContext (状态分发)
↓
各 Phase 组件 (只读 state,调用 sendMessage)前端不持有任何业务逻辑判断(如"谁是好人"、"任务能否通过"),完全由服务端决策并推送结果。
3. 输入锁由服务端托管
所有交互按钮的 disabled 状态由 GameState.inputLocked 驱动。服务端通过 INPUT_LOCKED / INPUT_UNLOCKED 消息精确控制何时允许用户操作,杜绝重复提交。
目录结构
app/
├── game/
│ ├── page.tsx # 游戏主页入口(GameProvider + GameInner)
│ ├── context/
│ │ ├── GameContext.tsx # React Context + Provider + useGame hook
│ │ └── gameReducer.ts # 状态 reducer(处理所有 WS 消息)
│ ├── hooks/
│ │ └── useWebSocket.ts # WebSocket 连接、重连、sendMessage 注入
│ ├── types/
│ │ └── index.ts # 所有 TypeScript 类型定义
│ └── components/
│ ├── GamePhaseRenderer.tsx # 根据 phase 渲染对应阶段组件
│ ├── PhaseAnnouncement.tsx # 阶段切换全屏动画公告
│ ├── RoleRevealModal.tsx # 游戏开始时角色公示弹窗
│ ├── phases/ # 各阶段 UI 组件
│ │ ├── WaitingRoom.tsx
│ │ ├── SpeakingPhase.tsx
│ │ ├── TeamSelectPhase.tsx
│ │ ├── TeamVotePhase.tsx
│ │ ├── MissionPhase.tsx
│ │ ├── AssassinPhase.tsx
│ │ └── GameOver.tsx
│ └── ui/ # 常驻 UI 组件
│ ├── PlayerCard.tsx
│ ├── PlayerList.tsx
│ ├── MissionTracker.tsx
│ ├── RoleCard.tsx
│ ├── PhaseHeader.tsx
│ ├── ToastNotification.tsx
│ ├── VoteResult.tsx
│ ├── MissionResult.tsx
│ └── SpeechHistorySidebar.tsx
├── home/ # 大厅页面(房间列表)
├── login/ # 登录页
├── register/ # 注册页
├── api/ # Next.js API Routes(反向代理)
│ ├── [...path]/route.ts # 通用后端 API 代理
│ └── logout/route.ts # 登出接口
└── components/ # 全局共享组件
├── WolfLogo.tsx
└── PageBackground.tsx数据流详解
GameState 初始化
ts
// gameReducer.ts
export const initialGameState: GameState = {
connected: false,
phase: "waiting",
// ...所有字段均有明确初始值
};消息处理流水线
useWebSocket接收MessageEvent,解析为ServerMessagedispatch({ type: "SERVER_MESSAGE", message: parsed })gameReducer中SERVER_MESSAGEcase 再 switchmsg.type- 返回不可变的新 state
E2E 测试钩子
非生产环境下,以下对象被挂载到 window:
window.__gameDispatch__— 直接 dispatch action(用于模拟各种游戏状态)window.__gameState__— 读取当前完整 state
快速开始
bash
# 安装依赖
pnpm install
# 启动开发服务器(默认 localhost:3000)
pnpm dev
# 构建生产版本
pnpm build文档预览
pnpm docs:dev # 本地预览(含 TypeDoc 自动生成) pnpm docs:build # 生产构建 pnpm docs:preview # 预览生产构建结果