# 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 | 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 接入