Files
36-character-flower/docs/36字花-游戏模块数据与界面分层第一阶段实施稿.md
JiaJun 6aaf90a6ac docs(game): 添加游戏模块数据
- 新增 useGameBoardVm 数据层实施说明文档
- 添加 36字花核心玩法与前端规则摘要
- 创建游戏模块数据与界面分层第一阶段实施稿
- 定义四层架构:api/dto、store、view-model hooks、ui层
- 规范 PC 与 Mobile 共享业务逻辑的改造方案
- 明确各层职责边界和组件改造顺序
2026-05-09 17:52:30 +08:00

847 lines
23 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 接入