新增复盘服务相关DTO、Controller和Service 实现复盘页面容器组件ReviewPageContainer 更新前端页面架构文档与开发计划 移除DemoGameController中的演示复盘接口 补充复盘服务单元测试
338 lines
8.5 KiB
Markdown
338 lines
8.5 KiB
Markdown
# H5 对局页信息架构与页面拆分方案
|
|
|
|
当前状态快照日期:`2026-03-20`
|
|
|
|
---
|
|
|
|
## 1. 文档目标
|
|
|
|
这份文档用于落实 `S1-08`,为 H5 原型页向正式页面演进提供可以直接执行的拆分输入。
|
|
|
|
它要解决的不是“最终视觉长什么样”,而是先把下面几件事固定下来:
|
|
|
|
- 页面边界怎么切
|
|
- 正式对局页内部区域怎么分
|
|
- 公共消息和私有消息分别落在哪个区
|
|
- 哪些状态应该留在页面容器,哪些应该沉到子组件
|
|
- 下一轮前端继续拆页时,如何避免再次把逻辑堆回 `App.vue`
|
|
|
|
这符合:
|
|
|
|
- `KISS`:先拆职责边界,不先追求完整视觉重写
|
|
- `YAGNI`:当前不引入路由级复杂壳层和状态库
|
|
- `SOLID`:页面容器、消息面板、动作面板、公共时间线分层明确
|
|
- `DRY`:共享类型与格式化函数抽离,避免多组件重复维护
|
|
|
|
---
|
|
|
|
## 2. 当前现状判断
|
|
|
|
当前 `frontend/src/App.vue` 已经承担了四类职责:
|
|
|
|
1. 房间流操作
|
|
2. 对局页状态与动作提交
|
|
3. WebSocket 订阅与消息清理
|
|
4. 消息卡片、结算卡片、时间线等展示
|
|
|
|
这说明它已经不再是“临时原型脚本”,而是一个事实上的页面容器。
|
|
|
|
当前如果继续在 `App.vue` 里叠加:
|
|
|
|
- 断线重连提示
|
|
- 教学开关
|
|
- 复盘入口
|
|
- 页面级切换
|
|
- 更多正式规则结算展示
|
|
|
|
后续维护成本会快速上升。
|
|
|
|
---
|
|
|
|
## 3. 正式页面拆分建议
|
|
|
|
### 3.1 页面层级
|
|
|
|
建议前端逐步演进为以下页面:
|
|
|
|
- `HomePage`
|
|
- 负责产品入口、快速建房、邀请码输入、最近房间入口
|
|
- `RoomPage`
|
|
- 负责建房后、开局前的房间管理与玩家准备
|
|
- `GamePage`
|
|
- 负责正式对局中的牌桌、动作、教学、公共事件
|
|
- `ReviewPage`
|
|
- 负责局后个人复盘、关键失误与建议回看
|
|
|
|
当前阶段不要求一次性引入完整路由,但组件组织和文档命名要按这个终态准备。
|
|
|
|
当前代码层已经落下对应的页面级容器命名:
|
|
|
|
- `frontend/src/pages/RoomPageContainer.vue`
|
|
- `frontend/src/pages/GamePageContainer.vue`
|
|
- `frontend/src/pages/ReviewPageContainer.vue`
|
|
|
|
其中 `ReviewPageContainer` 当前仍是占位实现,用于固定页面职责,不代表已经接入真实复盘数据。
|
|
|
|
当前还补了一条最小演示协议入口:
|
|
|
|
- 后端真实接口:`GET /api/games/{gameId}/review?userId={userId}`
|
|
|
|
它的目标不是替代真实局后数据,而是先把前后端在 `ReviewSummaryResponse` 这套字段上的语义对齐。
|
|
|
|
当前前端已经能在不接入正式路由的前提下,手动加载这条 demo 复盘数据并渲染 `ReviewPageContainer`。
|
|
|
|
### 3.2 当前这一轮的落地点
|
|
|
|
本轮先聚焦 `GamePage`。
|
|
|
|
原因:
|
|
|
|
- 房间流已经基本够用
|
|
- 当前复杂度主要集中在对局页
|
|
- 动作消息、教学消息、结算时间线都已经真实接入
|
|
- 正式血战计分规则后续迭代也主要会继续压在对局页
|
|
|
|
---
|
|
|
|
## 4. GamePage 信息分区
|
|
|
|
### 4.1 顶部概览区
|
|
|
|
职责:
|
|
|
|
- 显示对局阶段
|
|
- 显示当前视角
|
|
- 显示剩余牌墙
|
|
- 显示当前系统提示
|
|
|
|
说明:
|
|
|
|
- 顶部概览区只做“当前局势总览”
|
|
- 不承载具体动作按钮
|
|
- 不放长文教学解释,避免遮挡主桌面
|
|
|
|
### 4.2 自己手牌与当前动作区
|
|
|
|
职责:
|
|
|
|
- 展示自己的手牌与副露
|
|
- 承接定缺、主动回合动作
|
|
- 处理“点击手牌出牌”的高频交互
|
|
- 在手牌区局部高亮教学推荐牌
|
|
|
|
说明:
|
|
|
|
- 出牌仍保留“点击手牌直接触发”
|
|
- 杠、自摸胡等低频动作通过统一动作面板承接
|
|
- 这是为了保留 H5 的高频单手操作效率
|
|
|
|
### 4.3 响应动作区
|
|
|
|
职责:
|
|
|
|
- 响应窗口出现时展示 `PENG / GANG / HU / PASS`
|
|
- 清晰显示来源座位、目标牌、触发事件类型
|
|
|
|
说明:
|
|
|
|
- 该区域与“当前回合动作区”共用一个动作容器
|
|
- 但视觉上必须区分当前回合与响应窗口
|
|
- 当前项目已经实现统一动作消息结构,因此前端只需按 `actionScope` 切换视图
|
|
|
|
### 4.4 私有教学区
|
|
|
|
职责:
|
|
|
|
- 展示推荐动作
|
|
- 展示解释文案
|
|
- 展示候选牌、评分、原因标签
|
|
|
|
说明:
|
|
|
|
- 私有教学区必须与公共区域显著分离
|
|
- 教学候选列表属于私有信息,不得混入公共事件流
|
|
- 移动端下建议紧贴动作区,但与牌桌保持卡片边界
|
|
|
|
### 4.5 公共桌面区
|
|
|
|
职责:
|
|
|
|
- 展示其他座位的公开信息
|
|
- 展示弃牌、副露、积分、是否已胡
|
|
|
|
说明:
|
|
|
|
- 公共桌面区不显示其他玩家私有教学
|
|
- 这是消息边界在 UI 层的直接体现
|
|
|
|
### 4.6 公共事件与最近结算区
|
|
|
|
职责:
|
|
|
|
- 展示公共事件时间线
|
|
- 展示最近一次结算卡片
|
|
- 展示最近结算对应的分数变化
|
|
|
|
说明:
|
|
|
|
- 公共事件是调试和正式观感都需要保留的区域
|
|
- 但在正式页面中,它应当是“次主区”,不是最上方首屏主区
|
|
|
|
---
|
|
|
|
## 5. 组件拆分建议
|
|
|
|
### 5.1 页面容器组件
|
|
|
|
- `GamePageContainer`
|
|
- 负责请求、WebSocket、页面级状态聚合
|
|
- 负责“谁是当前视角用户”和“当前牌局状态”这两个页面主状态
|
|
|
|
当前阶段由 `App.vue` 暂代这个角色。
|
|
|
|
### 5.2 已建议先拆出的展示组件
|
|
|
|
本轮已经先按最小风险拆出三块:
|
|
|
|
- `GameActionDock.vue`
|
|
- 只负责主动动作 / 响应动作展示与按钮提交
|
|
- `GameMessageStack.vue`
|
|
- 只负责私有动作摘要卡与私有教学卡
|
|
- `PublicEventTimeline.vue`
|
|
- 只负责最近结算、分数变化与公共事件时间线
|
|
|
|
这样做的目的不是“拆得越碎越好”,而是先把最容易继续膨胀的三个展示区从页面容器中拔出来。
|
|
|
|
### 5.3 下一轮建议继续拆的组件
|
|
|
|
- `RoomControlPanel`
|
|
- 建房、入房、准备、开局入口
|
|
- `SelfHandPanel`
|
|
- 手牌、副露、推荐牌高亮
|
|
- `PublicSeatBoard`
|
|
- 其他玩家公开桌面
|
|
- `ViewSwitchPanel`
|
|
- 多玩家视角切换
|
|
|
|
---
|
|
|
|
## 6. 状态归属建议
|
|
|
|
### 6.1 必须保留在页面容器的状态
|
|
|
|
- `room`
|
|
- `game`
|
|
- `currentUserId`
|
|
- `publicEvents`
|
|
- `privateAction`
|
|
- `privateTeaching`
|
|
- `wsStatus`
|
|
- `busy`
|
|
- `error`
|
|
|
|
原因:
|
|
|
|
- 它们同时被多个区域消费
|
|
- 其中 `privateAction` 与公共事件清理之间有联动
|
|
- `currentUserId` 变化会触发重拉状态与重连私有 WebSocket
|
|
|
|
### 6.2 可下沉为共享工具的内容
|
|
|
|
- 接口响应类型
|
|
- 私有/公共消息 DTO
|
|
- UI 标签映射
|
|
- 事件摘要格式化函数
|
|
- 结算详情解析函数
|
|
|
|
本轮已经落地为:
|
|
|
|
- `frontend/src/types/game.ts`
|
|
- `frontend/src/utils/gameUi.ts`
|
|
|
|
### 6.3 当前不建议引入的东西
|
|
|
|
- Pinia 等全局状态库
|
|
- 过早的页面级缓存层
|
|
- 复杂消息归档服务
|
|
|
|
原因:
|
|
|
|
- 当前页面仍是单条主链,复杂状态库会提升理解成本
|
|
- 现阶段优先保证规则联调和 H5 可操作性
|
|
|
|
---
|
|
|
|
## 7. 移动端布局建议
|
|
|
|
### 7.1 布局优先级
|
|
|
|
移动端从上到下建议为:
|
|
|
|
1. 顶部提示与对局摘要
|
|
2. 自己手牌区
|
|
3. 动作区
|
|
4. 私有教学区
|
|
5. 公共桌面区
|
|
6. 公共事件时间线
|
|
|
|
### 7.2 关键原因
|
|
|
|
- 手牌和动作区必须靠前,保证高频操作路径最短
|
|
- 私有教学紧跟动作区,减少视线切换
|
|
- 公共事件时间线放在靠后位置,避免压缩手牌点击区域
|
|
|
|
### 7.3 H5 交互要求回落
|
|
|
|
- 关键按钮保持大点击热区
|
|
- 不依赖 hover
|
|
- 响应窗口出现时动作按钮必须明显聚焦
|
|
- 私有教学不能遮住手牌
|
|
|
|
---
|
|
|
|
## 8. 本轮已经落地的最小结构调整
|
|
|
|
为降低后续继续拆页的成本,本轮已经完成以下基础整理:
|
|
|
|
- 抽离共享类型文件,减少组件间类型重复
|
|
- 抽离 UI 格式化工具,减少多处重复的事件/结算渲染逻辑
|
|
- 抽离动作面板组件
|
|
- 抽离私有消息栈组件
|
|
- 抽离公共事件时间线组件
|
|
|
|
这一步仍然保持:
|
|
|
|
- `App.vue` 作为页面容器
|
|
- 行为逻辑不变
|
|
- WebSocket 订阅逻辑不变
|
|
- 动作提交链路不变
|
|
|
|
---
|
|
|
|
## 9. 下一轮前端任务建议
|
|
|
|
建议按以下顺序继续:
|
|
|
|
1. 把 `App.vue` 演进为 `GamePageContainer`
|
|
2. 把房间流区域抽成 `RoomPage` 或 `RoomControlPanel`
|
|
3. 把手牌区和公共桌面区拆成独立组件
|
|
4. 再决定是否引入路由和页面级导航
|
|
|
|
如果下一轮要优先做体验而不是结构,建议优先加:
|
|
|
|
- 教学开关入口
|
|
- WebSocket 断线提示
|
|
- 对局结束后的复盘入口占位
|
|
|
|
---
|
|
|
|
## 10. 验收结论
|
|
|
|
`S1-08` 完成的标准不是“页面完全重写”,而是:
|
|
|
|
- 正式对局页的职责边界已经明确
|
|
- 私有区、公共区、动作区层级已经固定
|
|
- 下一轮前端可以按文档和当前代码骨架继续拆,不必重新讨论页面结构
|
|
|
|
当前结论:已满足。
|