- 新增 useGameBoardVm 数据层实施说明文档 - 添加 36字花核心玩法与前端规则摘要 - 创建游戏模块数据与界面分层第一阶段实施稿 - 定义四层架构:api/dto、store、view-model hooks、ui层 - 规范 PC 与 Mobile 共享业务逻辑的改造方案 - 明确各层职责边界和组件改造顺序
847 lines
23 KiB
Markdown
847 lines
23 KiB
Markdown
# 36字花游戏模块数据与界面分层第一阶段实施稿
|
||
|
||
## 1. 目标
|
||
|
||
第一阶段的目标不是做一次彻底重构,而是先把当前项目中的“业务数据”和“界面渲染”拆出清晰边界,让同一套数据和交互逻辑可以同时服务 `PC` 界面与 `Mobile` 界面。
|
||
|
||
本阶段只解决以下问题:
|
||
|
||
- 一套业务数据,供 `PC` 与 `Mobile` 共同消费
|
||
- 统一业务交互逻辑,避免双端各自维护一份
|
||
- 保持 `PC` 与 `Mobile` 布局、样式、视觉独立
|
||
- 不大范围推翻现有组件,优先在现有代码上增量改造
|
||
|
||
本阶段暂不追求:
|
||
|
||
- 所有组件完全抽象成跨端共享组件
|
||
- 一次性去掉所有本地 `useState`
|
||
- 一次性重写所有弹窗和表单
|
||
- 大规模目录迁移导致整仓成本过高
|
||
|
||
|
||
## 2. 当前代码现状
|
||
|
||
当前仓库中已经存在一部分良好的基础设施:
|
||
|
||
- 数据请求与 DTO 归一化:
|
||
- [src/features/game/api/game-api.ts](/Users/jiaunun/Desktop/36-character-flower/src/features/game/api/game-api.ts)
|
||
- 共享领域类型与派生逻辑:
|
||
- [src/features/game/shared/types.ts](/Users/jiaunun/Desktop/36-character-flower/src/features/game/shared/types.ts)
|
||
- [src/features/game/shared/selectors.ts](/Users/jiaunun/Desktop/36-character-flower/src/features/game/shared/selectors.ts)
|
||
- 回合与会话状态:
|
||
- [src/store/game/game-round-store.ts](/Users/jiaunun/Desktop/36-character-flower/src/store/game/game-round-store.ts)
|
||
- [src/store/game/game-session-store.ts](/Users/jiaunun/Desktop/36-character-flower/src/store/game/game-session-store.ts)
|
||
- 页面入口已统一在:
|
||
- [src/features/game/entry/entry-page.tsx](/Users/jiaunun/Desktop/36-character-flower/src/features/game/entry/entry-page.tsx)
|
||
|
||
当前主要问题有 4 个:
|
||
|
||
1. `Mobile` 界面直接复用了 `Desktop` 组件
|
||
例如 [src/features/game/entry/mobile-entry.tsx](/Users/jiaunun/Desktop/36-character-flower/src/features/game/entry/mobile-entry.tsx) 当前直接引用了 `DesktopAnimal`、`DesktopGameHistory`、`DesktopTitle`。这会导致移动端无法形成独立布局层。
|
||
|
||
2. 业务状态和界面状态混杂
|
||
一些状态是业务性的,例如当前选中筹码、是否允许下注;另一些只是表现性的,例如按钮点击动画、过渡效果。当前这两种状态没有被清晰拆开。
|
||
|
||
3. 组件层直接拼 store 数据或自己维护业务数据
|
||
例如 [src/features/game/components/desktop/desktop-control.tsx](/Users/jiaunun/Desktop/36-character-flower/src/features/game/components/desktop/desktop-control.tsx) 内部自己维护了部分核心控制状态,导致未来 `Mobile` 难以复用。
|
||
|
||
4. 弹窗与 UI 业务状态分散在各组件本地 `useState`
|
||
比如 `auto setting`、`notice`、`user info`、`procedures` 等弹窗,当前都在各自文件内部自管 `open` 或 tab 状态,不利于双端共享和统一调度。
|
||
|
||
---
|
||
|
||
## 3. 第一阶段总体原则
|
||
|
||
第一阶段采用四层结构:
|
||
|
||
1. `api / dto / normalize`
|
||
2. `store / domain actions`
|
||
3. `view-model hooks`
|
||
4. `pc ui` 与 `mobile ui`
|
||
|
||
每层职责如下:
|
||
|
||
### 3.1 API 层
|
||
|
||
职责:
|
||
|
||
- 调用后端接口
|
||
- DTO 转前端领域模型
|
||
- 不参与 UI 展示逻辑
|
||
|
||
保留现状:
|
||
|
||
- [src/features/game/api/game-api.ts](/Users/jiaunun/Desktop/36-character-flower/src/features/game/api/game-api.ts)
|
||
|
||
### 3.2 Store 层
|
||
|
||
职责:
|
||
|
||
- 保存当前页面共享的真实业务状态
|
||
- 提供业务动作
|
||
- 不直接处理视觉布局
|
||
|
||
### 3.3 View-model hooks 层
|
||
|
||
职责:
|
||
|
||
- 从 store 中读取数据
|
||
- 聚合派生字段
|
||
- 封装业务事件
|
||
- 返回给 `PC` 与 `Mobile` 可直接渲染的数据结构
|
||
|
||
这层是本次改造的核心。
|
||
|
||
### 3.4 UI 层
|
||
|
||
职责:
|
||
|
||
- 只负责布局、视觉、交互表现
|
||
- `PC` 与 `Mobile` 各自独立
|
||
- 不直接拼底层 store
|
||
|
||
---
|
||
|
||
## 4. 第一阶段目录改造清单
|
||
|
||
建议目录结构如下。第一阶段以“新增”为主,不要求立刻大规模迁移原文件。
|
||
|
||
```text
|
||
src/features/game/
|
||
api/
|
||
game-api.ts
|
||
index.ts
|
||
types.ts
|
||
|
||
shared/
|
||
constants.ts
|
||
mock-data.ts
|
||
selectors.ts
|
||
types.ts
|
||
|
||
hooks/
|
||
use-game-header-vm.ts
|
||
use-game-status-vm.ts
|
||
use-game-board-vm.ts
|
||
use-game-history-vm.ts
|
||
use-game-control-vm.ts
|
||
use-game-announcement-vm.ts
|
||
use-game-auto-spin-vm.ts
|
||
index.ts
|
||
|
||
views/
|
||
pc/
|
||
pc-entry.tsx
|
||
mobile/
|
||
mobile-entry.tsx
|
||
|
||
components/
|
||
shared/
|
||
game-board.tsx
|
||
game-history-list.tsx
|
||
game-status-bar.tsx
|
||
game-control-panel.tsx
|
||
desktop/
|
||
mobile/
|
||
|
||
modal/
|
||
desktop/
|
||
mobile/
|
||
```
|
||
|
||
Store 目录建议补齐成:
|
||
|
||
```text
|
||
src/store/game/
|
||
game-round-store.ts
|
||
game-session-store.ts
|
||
game-ui-store.ts
|
||
index.ts
|
||
```
|
||
|
||
说明:
|
||
|
||
- 第一阶段可以继续保留现有 `components/desktop` 与 `entry/` 目录
|
||
- `views/pc` 与 `views/mobile` 可以先作为新目录开始承接之后的容器入口
|
||
- `components/shared` 第一阶段只抽少量共用展示结构,不强行全部收拢
|
||
|
||
---
|
||
|
||
## 5. 第一阶段 Store 设计
|
||
|
||
### 5.1 `game-round-store`
|
||
|
||
文件:
|
||
|
||
- [src/store/game/game-round-store.ts](/Users/jiaunun/Desktop/36-character-flower/src/store/game/game-round-store.ts)
|
||
|
||
该 store 保留为“回合与下注核心业务状态”。
|
||
|
||
#### 现有字段
|
||
|
||
- `cells`
|
||
- `chips`
|
||
- `history`
|
||
- `round`
|
||
- `selections`
|
||
- `trends`
|
||
- `activeChipId`
|
||
|
||
#### 现有动作
|
||
|
||
- `hydrateRound`
|
||
- `selectChip`
|
||
- `placeBet`
|
||
- `removeSelectionsForCell`
|
||
- `clearSelections`
|
||
- `setPhase`
|
||
- `syncRound`
|
||
- `upsertSelections`
|
||
|
||
#### 第一阶段建议新增动作
|
||
|
||
- `setActiveChip(chipId: string)`
|
||
- `replaceHistory(history: HistoryEntry[])`
|
||
- `replaceTrends(trends: TrendEntry[])`
|
||
- `replaceCells(cells: GameCell[])`
|
||
|
||
说明:
|
||
|
||
- 如果不想增加重复动作,也可以只统一命名 `selectChip`
|
||
- 新增 `replaceHistory / replaceTrends / replaceCells` 是为了后续轮询、增量同步更清晰
|
||
|
||
#### 不应该放在这里的内容
|
||
|
||
- 各类 modal 的打开关闭
|
||
- 用户信息 tab
|
||
- auto-spin 面板设置
|
||
- 登录注册表单
|
||
- 按钮 hover / 点击动画态
|
||
|
||
---
|
||
|
||
### 5.2 `game-session-store`
|
||
|
||
文件:
|
||
|
||
- [src/store/game/game-session-store.ts](/Users/jiaunun/Desktop/36-character-flower/src/store/game/game-session-store.ts)
|
||
|
||
该 store 保留为“会话、连接、公告、面板摘要信息”。
|
||
|
||
#### 现有字段
|
||
|
||
- `announcements`
|
||
- `connection`
|
||
- `dashboard`
|
||
|
||
#### 现有动作
|
||
|
||
- `hydrateSession`
|
||
- `dismissAnnouncement`
|
||
- `markAnnouncementRead`
|
||
- `setConnectionLatency`
|
||
- `setConnectionStatus`
|
||
- `syncConnection`
|
||
- `syncDashboard`
|
||
|
||
#### 第一阶段建议新增字段
|
||
|
||
- `serverTimeIso: string | null`
|
||
- `lastBootstrapAt: string | null`
|
||
|
||
#### 第一阶段建议新增动作
|
||
|
||
- `setServerTime(serverTimeIso: string)`
|
||
- `setLastBootstrapAt(iso: string)`
|
||
|
||
说明:
|
||
|
||
- [src/features/game/components/desktop/desktop-header.tsx](/Users/jiaunun/Desktop/36-character-flower/src/features/game/components/desktop/desktop-header.tsx) 当前展示系统时间,后续建议统一从这里拿
|
||
- `lastBootstrapAt` 方便后续做重连、刷新、数据同步判断
|
||
|
||
---
|
||
|
||
### 5.3 新增 `game-ui-store`
|
||
|
||
新文件建议:
|
||
|
||
- `src/store/game/game-ui-store.ts`
|
||
|
||
该 store 专门管理“共享的 UI 业务状态”。
|
||
|
||
#### 建议字段
|
||
|
||
- `activeModal: GameModalType | null`
|
||
- `modalPayload: Record<string, unknown> | null`
|
||
- `selectedUserInfoTab: 'profile' | 'message'`
|
||
- `isBgmEnabled: boolean`
|
||
- `isMuted: boolean`
|
||
- `autoSpinEnabled: boolean`
|
||
- `autoSpinSettings`
|
||
- `stopIfBalanceLowerThan: string`
|
||
- `stopIfSingleWinExceeds: string`
|
||
- `stopOnAnyJackpot: boolean`
|
||
- `authForm`
|
||
- `loginAccount: string`
|
||
- `loginPassword: string`
|
||
- `registerAccount: string`
|
||
- `registerPassword: string`
|
||
- `registerConfirmPassword: string`
|
||
- `proceduresType: 'withdraw' | 'topup' | null`
|
||
|
||
#### 建议动作
|
||
|
||
- `openModal(type, payload?)`
|
||
- `closeModal()`
|
||
- `setSelectedUserInfoTab(tab)`
|
||
- `toggleBgm()`
|
||
- `setAutoSpinEnabled(enabled)`
|
||
- `setAutoSpinSetting(key, value)`
|
||
- `setAuthField(field, value)`
|
||
- `setProceduresType(type)`
|
||
- `resetAuthForm()`
|
||
- `resetAutoSpinSettings()`
|
||
|
||
#### 第一阶段要接管的现有状态来源
|
||
|
||
- [src/features/game/modal/desktop/desktop-auto-setting-modal.tsx](/Users/jiaunun/Desktop/36-character-flower/src/features/game/modal/desktop/desktop-auto-setting-modal.tsx)
|
||
- [src/features/game/modal/desktop/desktop-notice-modal.tsx](/Users/jiaunun/Desktop/36-character-flower/src/features/game/modal/desktop/desktop-notice-modal.tsx)
|
||
- [src/features/game/modal/desktop/desktop-userInfo-modal.tsx](/Users/jiaunun/Desktop/36-character-flower/src/features/game/modal/desktop/desktop-userInfo-modal.tsx)
|
||
- [src/features/game/modal/desktop/desktop-procedures-modal.tsx](/Users/jiaunun/Desktop/36-character-flower/src/features/game/modal/desktop/desktop-procedures-modal.tsx)
|
||
- [src/features/game/modal/desktop/desktop-login-modal.tsx](/Users/jiaunun/Desktop/36-character-flower/src/features/game/modal/desktop/desktop-login-modal.tsx)
|
||
- [src/features/game/modal/desktop/desktop-register-modal.tsx](/Users/jiaunun/Desktop/36-character-flower/src/features/game/modal/desktop/desktop-register-modal.tsx)
|
||
- [src/features/game/modal/desktop/desktop-withdraw-topup-modal.tsx](/Users/jiaunun/Desktop/36-character-flower/src/features/game/modal/desktop/desktop-withdraw-topup-modal.tsx)
|
||
|
||
#### 不要放到 `game-ui-store` 的状态
|
||
|
||
- `confirmClicked`
|
||
- `clickedId`
|
||
- `hidingId`
|
||
|
||
这些状态目前位于 [src/features/game/components/desktop/desktop-control.tsx](/Users/jiaunun/Desktop/36-character-flower/src/features/game/components/desktop/desktop-control.tsx),属于纯动画与表现状态,继续留在本地组件即可。
|
||
|
||
---
|
||
|
||
## 6. 第一阶段 Hook 设计
|
||
|
||
本阶段重点不是“先抽 shared 组件”,而是先抽统一的 `view-model hooks`。
|
||
|
||
这些 hook 的目标:
|
||
|
||
- 统一封装 store 读取
|
||
- 统一组织派生字段
|
||
- 统一输出 PC/Mobile 都能消费的 props
|
||
|
||
---
|
||
|
||
### 6.1 `useGameHeaderVm`
|
||
|
||
建议文件:
|
||
|
||
- `src/features/game/hooks/use-game-header-vm.ts`
|
||
|
||
服务组件:
|
||
|
||
- [src/features/game/components/desktop/desktop-header.tsx](/Users/jiaunun/Desktop/36-character-flower/src/features/game/components/desktop/desktop-header.tsx)
|
||
- 未来的 `mobile-header.tsx`
|
||
|
||
#### 建议暴露字段
|
||
|
||
- `latencyMs: number | null`
|
||
- `latencyLabel: string`
|
||
- `connectionStatus: 'connected' | 'reconnecting' | 'offline'`
|
||
- `systemTimeLabel: string`
|
||
- `username: string`
|
||
- `balanceLabel: string`
|
||
- `avatarUrl: string | null`
|
||
- `unreadMessageCount: number`
|
||
- `isBgmEnabled: boolean`
|
||
|
||
#### 建议暴露动作
|
||
|
||
- `onOpenRules()`
|
||
- `onOpenMessages()`
|
||
- `onToggleBgm()`
|
||
- `onOpenProfile()`
|
||
|
||
#### 说明
|
||
|
||
- `DesktopHeader` 未来不再直接读取 store 或写死文案来源
|
||
- header 只消费“最终展示值”与“用户点击后的动作”
|
||
|
||
---
|
||
|
||
### 6.2 `useGameStatusVm`
|
||
|
||
建议文件:
|
||
|
||
- `src/features/game/hooks/use-game-status-vm.ts`
|
||
|
||
服务组件:
|
||
|
||
- [src/features/game/components/desktop/desktop-status.tsx](/Users/jiaunun/Desktop/36-character-flower/src/features/game/components/desktop/desktop-status.tsx)
|
||
|
||
#### 建议暴露字段
|
||
|
||
- `roundId: string`
|
||
- `oddsLabel: string`
|
||
- `streakLabel: string`
|
||
- `limitLabel: string`
|
||
- `countdownSeconds: number`
|
||
- `countdownMs: number`
|
||
- `phase: RoundPhase`
|
||
- `phaseLabel: string`
|
||
- `phaseTone: 'open' | 'locked' | 'settled'`
|
||
- `acceptingBets: boolean`
|
||
|
||
#### 说明
|
||
|
||
- 倒计时展示逻辑、回合阶段文案逻辑统一在这里处理
|
||
- `PC` 和 `Mobile` 不各自重复拼 countdown 与 round phase
|
||
|
||
---
|
||
|
||
### 6.3 `useGameBoardVm`
|
||
|
||
建议文件:
|
||
|
||
- `src/features/game/hooks/use-game-board-vm.ts`
|
||
|
||
服务组件:
|
||
|
||
- [src/features/game/components/desktop/desktop-animal.tsx](/Users/jiaunun/Desktop/36-character-flower/src/features/game/components/desktop/desktop-animal.tsx)
|
||
- 未来 `mobile-board.tsx`
|
||
|
||
#### 建议暴露字段
|
||
|
||
- `cells`
|
||
- `id`
|
||
- `imageUrl`
|
||
- `status`
|
||
- `isSelected`
|
||
- `isWinningCell`
|
||
- `selectionAmount`
|
||
- `selectionCount`
|
||
- `hitCount`
|
||
- `currentStreak`
|
||
- `activeCellId: number | null`
|
||
- `canPlaceBets: boolean`
|
||
- `totalSelectedCount: number`
|
||
- `totalSelectedAmount: number`
|
||
|
||
#### 建议暴露动作
|
||
|
||
- `onCellPress(cellId: number)`
|
||
- `onCellLongPress?(cellId: number)`
|
||
- `onCellClear(cellId: number)`
|
||
|
||
#### 说明
|
||
|
||
- 优先基于 [src/features/game/shared/selectors.ts](/Users/jiaunun/Desktop/36-character-flower/src/features/game/shared/selectors.ts) 中的:
|
||
- `buildGameCellViewModels`
|
||
- `groupSelectionsByCell`
|
||
- `getSelectionTotal`
|
||
- `DesktopAnimal` 不应该自己管理业务选择逻辑,只负责渲染 cell grid
|
||
|
||
---
|
||
|
||
### 6.4 `useGameHistoryVm`
|
||
|
||
建议文件:
|
||
|
||
- `src/features/game/hooks/use-game-history-vm.ts`
|
||
|
||
服务组件:
|
||
|
||
- [src/features/game/components/desktop/desktop-game-history.tsx](/Users/jiaunun/Desktop/36-character-flower/src/features/game/components/desktop/desktop-game-history.tsx)
|
||
- 未来 `mobile-history.tsx`
|
||
|
||
#### 建议暴露字段
|
||
|
||
- `items`
|
||
- `orderNo`
|
||
- `roundId`
|
||
- `winningCellId`
|
||
- `betAmountLabel`
|
||
- `totalAmountLabel`
|
||
- `winAmountLabel`
|
||
- `statusLabel`
|
||
- `createdAtLabel`
|
||
- `isEmpty: boolean`
|
||
- `emptyText: string`
|
||
- `recentWinningCellIds: number[]`
|
||
|
||
#### 说明
|
||
|
||
- 当前 [src/features/game/components/desktop/desktop-game-history.tsx](/Users/jiaunun/Desktop/36-character-flower/src/features/game/components/desktop/desktop-game-history.tsx) 里还是本地 fake data,第一阶段应切换为 VM 数据
|
||
- `PC` 与 `Mobile` 历史列表的数据格式统一,只是布局不同
|
||
|
||
---
|
||
|
||
### 6.5 `useGameControlVm`
|
||
|
||
建议文件:
|
||
|
||
- `src/features/game/hooks/use-game-control-vm.ts`
|
||
|
||
服务组件:
|
||
|
||
- [src/features/game/components/desktop/desktop-control.tsx](/Users/jiaunun/Desktop/36-character-flower/src/features/game/components/desktop/desktop-control.tsx)
|
||
- 未来 `mobile-control.tsx`
|
||
|
||
#### 建议暴露字段
|
||
|
||
- `chips`
|
||
- `id`
|
||
- `value`
|
||
- `amount`
|
||
- `color`
|
||
- `isSelected`
|
||
- `isDefault`
|
||
- `selectedChipId: string | null`
|
||
- `selectedChipAmountLabel: string`
|
||
- `selectionTotalLabel: string`
|
||
- `selectedCountLabel: string`
|
||
- `actionButtons`
|
||
- `id`
|
||
- `label`
|
||
- `disabled`
|
||
- `canConfirm: boolean`
|
||
- `canClear: boolean`
|
||
- `canRepeat: boolean`
|
||
- `canOpenAutoSpin: boolean`
|
||
|
||
#### 建议暴露动作
|
||
|
||
- `onChipSelect(chipId: string)`
|
||
- `onAddChip()`
|
||
- `onReduceChip()`
|
||
- `onClearSelections()`
|
||
- `onRepeatLastRound()`
|
||
- `onOpenAutoSpin()`
|
||
- `onConfirmBet()`
|
||
|
||
#### 说明
|
||
|
||
- [src/features/game/components/desktop/desktop-control.tsx](/Users/jiaunun/Desktop/36-character-flower/src/features/game/components/desktop/desktop-control.tsx) 内部仍可保留点击动画和短暂视觉反馈
|
||
- 但“当前选中筹码、是否可确认、操作是否禁用”等必须来自 VM,而不是继续散在组件内部
|
||
|
||
---
|
||
|
||
### 6.6 `useGameAnnouncementVm`
|
||
|
||
建议文件:
|
||
|
||
- `src/features/game/hooks/use-game-announcement-vm.ts`
|
||
|
||
服务组件:
|
||
|
||
- [src/features/game/components/shared/game-announcement-modal.tsx](/Users/jiaunun/Desktop/36-character-flower/src/features/game/components/shared/game-announcement-modal.tsx)
|
||
- header 消息入口
|
||
- notice modal
|
||
|
||
#### 建议暴露字段
|
||
|
||
- `activeAnnouncement: AnnouncementItem | null`
|
||
- `visibleAnnouncements: AnnouncementItem[]`
|
||
- `unreadCount: number`
|
||
- `hasUnread: boolean`
|
||
- `isOpen: boolean`
|
||
|
||
#### 建议暴露动作
|
||
|
||
- `onOpenAnnouncement(id?: string)`
|
||
- `onDismissAnnouncement(id: string)`
|
||
- `onMarkRead(id: string)`
|
||
- `onCloseAnnouncement()`
|
||
|
||
#### 说明
|
||
|
||
- 这样公告逻辑不会散落在 [src/features/game/entry/entry-page.tsx](/Users/jiaunun/Desktop/36-character-flower/src/features/game/entry/entry-page.tsx) 和多个具体 modal 中
|
||
|
||
---
|
||
|
||
### 6.7 `useGameAutoSpinVm`
|
||
|
||
建议文件:
|
||
|
||
- `src/features/game/hooks/use-game-auto-spin-vm.ts`
|
||
|
||
服务组件:
|
||
|
||
- [src/features/game/modal/desktop/desktop-auto-setting-modal.tsx](/Users/jiaunun/Desktop/36-character-flower/src/features/game/modal/desktop/desktop-auto-setting-modal.tsx)
|
||
- 未来 `mobile-auto-setting-modal.tsx`
|
||
|
||
#### 建议暴露字段
|
||
|
||
- `open: boolean`
|
||
- `enabled: boolean`
|
||
- `stopIfBalanceLowerThan: string`
|
||
- `stopIfSingleWinExceeds: string`
|
||
- `stopOnAnyJackpot: boolean`
|
||
- `canSubmit: boolean`
|
||
|
||
#### 建议暴露动作
|
||
|
||
- `onOpen()`
|
||
- `onClose()`
|
||
- `onToggleEnabled(enabled: boolean)`
|
||
- `onChangeBalanceThreshold(value: string)`
|
||
- `onChangeSingleWinThreshold(value: string)`
|
||
- `onToggleStopOnJackpot(enabled: boolean)`
|
||
- `onSubmit()`
|
||
|
||
#### 说明
|
||
|
||
- 这是最适合第一阶段打样的一类场景:数据结构固定、PC/Mobile 表现不同、交互规则相同
|
||
|
||
---
|
||
|
||
## 7. 第一阶段共享展示组件建议
|
||
|
||
本阶段不要一口气把所有 `Desktop` 组件变成跨端共享。
|
||
|
||
建议只优先抽 2 到 3 个 shared 展示组件:
|
||
|
||
- `src/features/game/components/shared/game-board.tsx`
|
||
- `src/features/game/components/shared/game-history-list.tsx`
|
||
- `src/features/game/components/shared/game-status-bar.tsx`
|
||
|
||
### 7.1 `game-board`
|
||
|
||
职责:
|
||
|
||
- 只接收 `cells`
|
||
- 只接收 `onCellPress`
|
||
- 不直接读 store
|
||
|
||
这样:
|
||
|
||
- `PC` 可以包一层桌面 grid 样式
|
||
- `Mobile` 可以包一层移动端布局样式
|
||
|
||
### 7.2 `game-history-list`
|
||
|
||
职责:
|
||
|
||
- 只负责历史列表渲染
|
||
- 不关心历史数据怎么来
|
||
|
||
### 7.3 `game-status-bar`
|
||
|
||
职责:
|
||
|
||
- 渲染赔率、回合、倒计时、当前状态
|
||
- 由 `useGameStatusVm` 驱动
|
||
|
||
不建议第一阶段就抽 shared 的内容:
|
||
|
||
- header
|
||
- control panel
|
||
- 大多数 modal
|
||
|
||
因为这些区域 `PC/Mobile` 差异通常更大,先共用 hook 比先共用视觉组件更稳。
|
||
|
||
---
|
||
|
||
## 8. 第一阶段 UI 层改造边界
|
||
|
||
本阶段只定 3 条规则:
|
||
|
||
### 8.1 `EntryPage` 可以直接处理 hydration
|
||
|
||
文件:
|
||
|
||
- [src/features/game/entry/entry-page.tsx](/Users/jiaunun/Desktop/36-character-flower/src/features/game/entry/entry-page.tsx)
|
||
|
||
保留它作为:
|
||
|
||
- bootstrap 获取入口
|
||
- store hydrate 入口
|
||
- PC/Mobile 分流入口
|
||
|
||
### 8.2 容器组件可以调用 VM hooks
|
||
|
||
例如:
|
||
|
||
- `PcEntry`
|
||
- `MobileEntry`
|
||
- 各 section 容器
|
||
- modal host
|
||
|
||
这些组件可以:
|
||
|
||
- `useGameBoardVm()`
|
||
- `useGameControlVm()`
|
||
- `useGameStatusVm()`
|
||
|
||
### 8.3 纯展示组件不直接碰 store
|
||
|
||
比如:
|
||
|
||
- [src/features/game/components/desktop/desktop-animal.tsx](/Users/jiaunun/Desktop/36-character-flower/src/features/game/components/desktop/desktop-animal.tsx)
|
||
- [src/features/game/components/desktop/desktop-game-history.tsx](/Users/jiaunun/Desktop/36-character-flower/src/features/game/components/desktop/desktop-game-history.tsx)
|
||
- [src/features/game/components/desktop/desktop-status.tsx](/Users/jiaunun/Desktop/36-character-flower/src/features/game/components/desktop/desktop-status.tsx)
|
||
- [src/features/game/components/desktop/desktop-header.tsx](/Users/jiaunun/Desktop/36-character-flower/src/features/game/components/desktop/desktop-header.tsx)
|
||
|
||
未来都应该尽量演进为:
|
||
|
||
- 只吃 props
|
||
- 不主动拼底层 store
|
||
|
||
---
|
||
|
||
## 9. 第一阶段实施顺序
|
||
|
||
建议严格按这个顺序执行,避免改动面过大。
|
||
|
||
### Step 1:新增 UI Store 与 Hooks 目录
|
||
|
||
新增:
|
||
|
||
- `src/store/game/game-ui-store.ts`
|
||
- `src/features/game/hooks/`
|
||
|
||
这一步只建壳子,不急着接业务。
|
||
|
||
### Step 2:实现 `useGameBoardVm` 与 `useGameControlVm`
|
||
|
||
这两块是主玩法交互核心,优先收益最大。
|
||
|
||
优先接的现有文件:
|
||
|
||
- [src/features/game/components/desktop/desktop-animal.tsx](/Users/jiaunun/Desktop/36-character-flower/src/features/game/components/desktop/desktop-animal.tsx)
|
||
- [src/features/game/components/desktop/desktop-control.tsx](/Users/jiaunun/Desktop/36-character-flower/src/features/game/components/desktop/desktop-control.tsx)
|
||
|
||
### Step 3:让 `DesktopAnimal` 只吃 props
|
||
|
||
目标:
|
||
|
||
- 不直接读 store
|
||
- 不自己处理业务选择逻辑
|
||
|
||
保留字段形态:
|
||
|
||
- `cells`
|
||
- `activeId`
|
||
- `onSelect`
|
||
|
||
### Step 4:让 `DesktopControl` 业务数据来自 VM
|
||
|
||
目标:
|
||
|
||
- 当前筹码列表、已选筹码、总下注额等来自 `useGameControlVm`
|
||
- 点击动画状态仍然保留本地
|
||
|
||
### Step 5:停止 `Mobile` 直接复用 `Desktop` 组件
|
||
|
||
文件:
|
||
|
||
- [src/features/game/entry/mobile-entry.tsx](/Users/jiaunun/Desktop/36-character-flower/src/features/game/entry/mobile-entry.tsx)
|
||
|
||
当前问题:
|
||
|
||
- 直接 import `DesktopAnimal`
|
||
- 直接 import `DesktopGameHistory`
|
||
- 直接 import `DesktopTitle`
|
||
|
||
第一阶段目标:
|
||
|
||
- 改成 `MobileBoardSection`
|
||
- 改成 `MobileHistorySection`
|
||
- 共享 hook,不共享 `Desktop` 布局组件
|
||
|
||
### Step 6:实现 `useGameStatusVm` 与 `useGameHeaderVm`
|
||
|
||
接入文件:
|
||
|
||
- [src/features/game/components/desktop/desktop-status.tsx](/Users/jiaunun/Desktop/36-character-flower/src/features/game/components/desktop/desktop-status.tsx)
|
||
- [src/features/game/components/desktop/desktop-header.tsx](/Users/jiaunun/Desktop/36-character-flower/src/features/game/components/desktop/desktop-header.tsx)
|
||
|
||
### Step 7:实现 `game-ui-store` + `useGameAutoSpinVm`
|
||
|
||
先接一个最合适的 modal:
|
||
|
||
- [src/features/game/modal/desktop/desktop-auto-setting-modal.tsx](/Users/jiaunun/Desktop/36-character-flower/src/features/game/modal/desktop/desktop-auto-setting-modal.tsx)
|
||
|
||
跑通后,再接:
|
||
|
||
- notice
|
||
- user info
|
||
- procedures
|
||
- login/register
|
||
|
||
---
|
||
|
||
## 10. 第一阶段优先改造文件清单
|
||
|
||
第一阶段最值得优先改的 6 个文件如下:
|
||
|
||
1. [src/features/game/entry/entry-page.tsx](/Users/jiaunun/Desktop/36-character-flower/src/features/game/entry/entry-page.tsx)
|
||
2. [src/features/game/entry/mobile-entry.tsx](/Users/jiaunun/Desktop/36-character-flower/src/features/game/entry/mobile-entry.tsx)
|
||
3. [src/features/game/components/desktop/desktop-animal.tsx](/Users/jiaunun/Desktop/36-character-flower/src/features/game/components/desktop/desktop-animal.tsx)
|
||
4. [src/features/game/components/desktop/desktop-control.tsx](/Users/jiaunun/Desktop/36-character-flower/src/features/game/components/desktop/desktop-control.tsx)
|
||
5. [src/features/game/components/desktop/desktop-status.tsx](/Users/jiaunun/Desktop/36-character-flower/src/features/game/components/desktop/desktop-status.tsx)
|
||
6. [src/features/game/modal/desktop/desktop-auto-setting-modal.tsx](/Users/jiaunun/Desktop/36-character-flower/src/features/game/modal/desktop/desktop-auto-setting-modal.tsx)
|
||
|
||
原因:
|
||
|
||
- 它们覆盖了主玩法、主状态、移动端入口和共享 modal 模式
|
||
- 先把这 6 个改通,数据与界面分层的骨架就建立起来了
|
||
|
||
---
|
||
|
||
## 11. 第一阶段完成后的验收标准
|
||
|
||
做到以下几点,即可视为第一阶段完成:
|
||
|
||
- `PC` 与 `Mobile` 都不直接拼 API / store 的底层结构
|
||
- `Mobile` 不再直接 import `DesktopXxx`
|
||
- 至少 `board / control / status` 这 3 类区域中的 2 类已经改为 “VM 驱动 + 展示组件消费 props”
|
||
- modal 开关与 tab 这类共享 UI 业务状态已进入 `game-ui-store`
|
||
- 视觉动画状态与业务状态分层清晰
|
||
|
||
---
|
||
|
||
## 12. 风险与边界说明
|
||
|
||
### 12.1 不要在第一阶段做的事情
|
||
|
||
- 不要一次性把所有桌面组件都改成共享组件
|
||
- 不要同时把全部 modal 状态都收进 store
|
||
- 不要同时重做 API 调用方式
|
||
- 不要试图在第一阶段解决所有命名和目录历史包袱
|
||
|
||
### 12.2 第一阶段最容易犯的错误
|
||
|
||
- 把所有状态都塞进 store
|
||
结果动画、hover、点击反馈这些纯表现状态也被全局化,反而变复杂
|
||
|
||
- 先抽 shared UI,而不是先抽 VM
|
||
这样容易做出一堆仍然耦合 store 的“伪共享组件”
|
||
|
||
- Mobile 继续复用 Desktop 组件
|
||
这会让后面任何布局调整都越来越痛苦
|
||
|
||
---
|
||
|
||
## 13. 结论
|
||
|
||
本项目第一阶段的正确方向不是:
|
||
|
||
- “PC 一套状态,Mobile 一套状态”
|
||
|
||
而是:
|
||
|
||
- 一套 `domain state`
|
||
- 一套 `domain actions`
|
||
- 一套 `view-model hooks`
|
||
- 两套 `presentation`
|
||
|
||
按本实施稿推进后,后续才适合继续做:
|
||
|
||
- 第二阶段:shared UI 组件整理
|
||
- 第三阶段:modal host 统一
|
||
- 第四阶段:实时数据同步、轮询或 websocket 接入
|
||
|