# 工程优化审查报告(2026-03-29) ## 说明 - 扫描范围:`src/`、`scripts/`、`docs/`、`package.json`、`vite.config.ts`、`tsconfig.json` - 已执行校验:`npm run lint`、`npm run build`、`npm run check:content` - 本报告只从工程角度讨论结构、边界、质量门禁、可维护性与可扩展性 - 按仓库说明,暂不讨论中文乱码本身 ## 当前结论 项目当前**可构建、可运行、内容校验可通过**,说明基础功能链路是通的;但从工程视角看,已经出现明显的“单点过重、边界混杂、质量门禁偏弱、编辑器与运行时耦合”问题。继续叠需求会越来越依赖人工记忆和局部经验,回归风险会持续上升。 当前最值得优先处理的不是单个 UI 细节,而是以下四个工程主题: 1. 运行时主链路的职责拆分还不够,核心 hook / 组件已经过载 2. 缺少真正的工程质量门禁,`lint` 目前本质上只是 `tsc` 3. 编辑器、运行时、类后端能力都混在同一个 Vite 配置里 4. 持久化、AI 调用、编辑器保存等基础设施仍然是“分散手写” ## 运行状态快照 - `npm run lint` 通过 - `npm run build` 通过 - `npm run check:content` 通过 - 应用代码下未发现测试文件:`src/`、`scripts/`、`docs/` 内没有 `*.test.*` / `*.spec.*` - 构建产物已出现较大 chunk - `dist/assets/App-*.js` 约 `407 KB` - `dist/assets/itemCatalog-*.js` 约 `414 KB` - `dist/assets/PresetEditor-*.js` 约 `109 KB` ## 代码体征 下列文件已经明显进入“超大模块”区间: | 文件 | 行数 | 观察 | | --- | ---: | --- | | `src/hooks/useStoryGeneration.ts` | 3304 | 同时管理剧情、NPC 交互、交易、送礼、招募、任务、角色聊天、道具/锻造接入 | | `src/components/PresetEditor.tsx` | 2244 | 多编辑器入口聚合在一个巨型组件中 | | `src/hooks/useCombatFlow.ts` | 1791 | 同时承担战斗推演、动画时序、逃跑演出、状态落地 | | `src/components/GameShell.tsx` | 1592 | 入口 UI、选角、世界选择、自定义世界、场景切换、浮层控制全部集中 | | `src/types.ts` | 663 | 运行时、AI、编辑器、自定义世界、背包、任务类型集中在一个总文件 | 补充信号: - `src/components/GameShell.tsx` 内有 16 个 `useState`、10 个 `useEffect`、13 个 `useMemo` - `src/hooks/useStoryGeneration.ts` 虽然只有少量 React state,但内部累计 40+ 个函数,已经是“巨型流程控制器” - `src/hooks/useCombatFlow.ts` 内有大量时间常量、动画常量、`sleep + setGameState` 过程式循环,测试成本很高 ## 优先级问题 ## P0:运行时主链路职责过度集中 证据: - `src/hooks/useStoryGeneration.ts:868-930` 进入 hook 后立即开始定义交易、送礼、招募、角色聊天等子流程 - `src/hooks/useStoryGeneration.ts:3191-3303` 返回对象同时暴露剧情、任务、NPC UI、角色聊天 UI、背包/锻造 UI - `src/components/GameShell.tsx:293-360` 组件 props 很多,内部 state 也很多,承担“壳层 + 流程 + 浮层 + 自定义世界生成 + 场景切换” - `src/hooks/useCombatFlow.ts:559-1787` 将战斗计算和战斗演出揉在同一层里 影响: - 任何一个新需求都容易同时碰到剧情、UI、战斗、背包、NPC 关系四五条链路 - 代码 review 很难聚焦,改动一处时往往需要脑内跟完整条大流程 - 单元测试难写,因为逻辑不是纯函数,而是大量闭包 + 过程式状态推进 - 长期会形成“只有熟悉历史上下文的人才能安全修改”的隐性门槛 建议: - 将 `useStoryGeneration` 拆为“剧情推进”“NPC 交互”“角色聊天”“任务结算”“模态框控制”几个子域 - 将 `useCombatFlow` 拆成“纯战斗结算引擎”和“战斗播放适配层” - 让 `GameShell` 回到壳层职责,只负责路由态、页面态、模态挂载与 props 编排 - 以“领域职责”拆分,而不是按“文件太长了随便切一刀”拆分 ## P0:缺少真正的工程质量门禁 证据: - `package.json:11` 的 `lint` 实际只有 `tsc --noEmit` - `package.json` 中没有 `test`、`format`、`lint:fix` 等基础脚本 - 根目录未发现 `.eslintrc*`、`.prettierrc*`、`.editorconfig` - 代码目录下没有测试文件 影响: - 当前项目的“能过 lint”只代表类型没炸,不代表风格一致、依赖正确、Hooks 规则正确、死代码已清理 - 大型 hook / 大型组件的重构几乎没有自动回归保护 - 运行时行为、编辑器行为、AI fallback 行为主要依赖人工回归 建议: - 补齐 ESLint、Prettier、EditorConfig,至少覆盖 React Hooks、import、unused code、复杂度基线 - 引入 Vitest,先覆盖纯数据层与纯规则层 - 为 `useCombatFlow`、`stateFunctions`、`npcInteractions`、`questFlow` 增加单元测试 - 为“开局 -> 选世界 -> 选角色 -> 进入剧情 -> 战斗 -> 存档恢复”补最小 E2E smoke - CI 中至少串联:类型检查 + 单测 + build + 内容校验 ## P1:编辑器、运行时、类后端能力全部耦合在 Vite 配置里 证据: - `vite.config.ts:151-203` 在 Vite 插件里实现了 LLM 代理 - `vite.config.ts:206-269` 在 Vite 插件里实现了通用 JSON 文件读写 API - `vite.config.ts:253` 直接写回 `src/data/*.json` - `vite.config.ts:265-266` 和 `vite.config.ts:400-401` 在 `preview` 阶段也挂了这些接口 - `vite.config.ts:425-434` 启动时默认把这些“编辑器后端能力”全部注册进去 影响: - 本地编辑器能力与运行时能力没有清晰边界 - `preview` 环境仍可写源码文件,发布边界不清晰 - 未来如果要做独立部署、多人协作、远程编辑、权限控制,会非常难迁移 - Vite 配置同时扮演构建配置、代理层、文件服务层、编辑器后端,职责失衡 建议: - 将编辑器读写 API 从 `vite.config.ts` 抽到独立的本地工具服务或独立脚本 - 至少区分 `dev-only write api` 与 `preview/prod read-only api` - 对编辑器保存接口建立统一客户端 SDK,避免组件直接散落 `fetch('/api/...')` - LLM 代理也建议独立成 `server/` 或 `scripts/dev-server/`,不要继续长在构建配置里 ## P1:持久化策略分散,且直接序列化大状态对象 证据: - `src/hooks/useGamePersistence.ts:152-167` 会在状态变化时自动把完整快照写入 `localStorage` - `src/hooks/useGamePersistence.ts:157-163` 快照包含 `gameState + bottomTab + currentStory` - `src/hooks/useGamePersistence.ts:68-116` 恢复逻辑已经开始承担大量 schema 纠偏职责 - `src/data/customWorldLibrary.ts:1-282` 自定义世界库单独维护一套 `localStorage` 读写与 normalize - `src/hooks/useGameSettings.ts` 也单独维护一套本地设置持久化 影响: - 状态结构一旦继续膨胀,快照写入频率和反序列化成本都会增加 - schema 迁移会越来越依赖手工 normalize 补丁 - 不同持久化入口各写一套 parser / normalizer,风格和鲁棒性难统一 - 当前保存的是“运行中大对象”,而不是“稳定领域快照”,长期会放大兼容成本 建议: - 建立统一的 persistence 层,集中管理 key、version、migration、节流、序列化策略 - 对 `GameState` 做“可持久化切片”和“运行时临时切片”分层 - 自动保存增加节流/去抖,避免每次状态波动都全量落盘 - 如果继续扩展角色聊天、自定义世界、编辑器草稿,建议评估 IndexedDB 替代 `localStorage` ## P1:运行时与编辑器仍在同一个前端入口体系中,包体继续膨胀 证据: - `src/main.tsx:21-34` 通过 `window.location.pathname` 手写分发页面 - `src/main.tsx:60` 只有“游戏”和“PresetEditor”两个大入口 - `PresetEditor`、`ItemCatalogEditor`、`StateFunctionEditor` 都属于重型模块 - 构建产物已经出现 `App` 约 `407 KB`、`itemCatalog` 约 `414 KB` 的 chunk 影响: - 游戏端与编辑器端的演进节奏被绑定在一个 SPA 入口上 - 编辑器相关数据和静态资源容易继续抬高构建体积 - 未来增加更多编辑器页、更多世界模板、更多资源目录后,冷启动成本会更明显 建议: - 将编辑器拆成独立入口,至少做成独立 route module,而不是单个 `PresetEditor` - 继续下钻按 tab 做懒加载,尤其是 `items/functions/npcs` - 将静态大数据、资源目录索引、编辑器专用预览逻辑做更细的 chunk 拆分 - 如果项目后续会长期保留编辑器,建议直接分成 game app / editor app 两个 entry ## P2:编辑器基础设施重复实现较多 证据: - `src/components/PresetEditor.tsx:111-181` 自己实现 `cloneValue`、`saveJsonObject` - `src/components/StateFunctionEditor.tsx:113-130` 再次实现 `cloneValue`、`SectionCard` - `src/components/ItemCatalogEditor.tsx:94` 再次实现保存请求 - `src/hooks/useInventoryFlow.ts:8`、`src/hooks/useEquipmentFlow.ts:10`、`src/hooks/useForgeFlow.ts:12`、`src/hooks/useTreasureFlow.ts:10` 重复声明 `CommitGeneratedState` 影响: - 修改保存行为、错误处理、深拷贝策略时需要多处同步 - 编辑器 UI 风格与交互行为容易逐步漂移 - 公共契约没有收拢到共享层,维护成本会逐步抬高 建议: - 抽 `editor/shared/` 层,集中放保存 SDK、表单字段、卡片容器、克隆工具、错误处理 - 抽通用的 `CommitGeneratedState` 类型定义 - 将编辑器请求和覆盖保存逻辑统一走一个 client ## P2:类型系统已经出现“总文件过载” 证据: - `src/types.ts` 共 663 行 - `src/types.ts:1-260` 同时包含世界、动画、技能、对话、自定义世界、物品等类型 - `src/types.ts:536-663` 又继续承接剧情、聊天、任务、`GameState`、AI 响应 影响: - 任一领域类型变化都会增加总文件冲突概率 - 新人理解类型边界成本高 - 编辑器类型、运行时类型、AI 传输类型被放在一起,不利于演化 建议: - 按领域拆分:`types/combat.ts`、`types/story.ts`、`types/item.ts`、`types/customWorld.ts`、`types/persistence.ts` - `GameState` 相关类型与 editor override 类型分开 - AI request/response contract 单独收口,避免继续堆进总类型文件 ## P2:AI 客户端层过厚,且重复了多套请求与解析逻辑 证据: - `src/services/ai.ts` 共 1153 行 - `src/services/ai.ts:540-605`、`608-678`、`745-790` 分别手写了 JSON completion、纯文本 completion、流式 completion - `src/services/ai.ts:680-697` 手写了多段 JSON 解析兜底 - `src/services/ai.ts:76-78`、`591-594`、`662-666` 主要依赖 `console.*` 打日志 影响: - LLM 行为扩展时容易继续复制请求模板、错误处理、超时逻辑 - 错误分类不够稳定,观测主要停留在 console 层 - prompt、transport、fallback、parse 被放在一起,后续测试和替换模型都不够轻 建议: - 抽 `llmClient`,统一 transport、timeout、stream、error taxonomy - 抽 `llmParsers`,将 JSON parse / plain text parse / suggestion parse 独立 - 为关键 prompt 输出建立 fixture 测试,至少覆盖 fallback 与异常响应 - 如果后续要接多个模型,尽早把 provider 层和 prompt 层解耦 ## P2:手写路由与死代码开始累积 证据: - `src/main.tsx:21-34` 采用手写 `pathname.startsWith(...)` - `src/components/GameShell.tsx:1511` 存在 `false && showTeamModal` 影响: - 路由能力不具备可扩展性,也不利于后续加 404、重定向、权限判断、嵌套路由 - 死代码继续堆积后,会误导维护者对真实入口和真实 UI 状态的判断 建议: - 引入正式路由层,哪怕只做轻量路由也比手写分发更清晰 - 清理已经废弃的 UI 分支和不可达逻辑 - 对“临时下线的功能”改为 feature flag 或明确注释,不要用 `false &&` ## 建议落地顺序 ### 第一阶段:先补工程底座 - 增加 ESLint / Prettier / EditorConfig - 增加 `test` 脚本与 Vitest - 把 CI 最小闭环搭起来:类型检查、单测、build、内容校验 ### 第二阶段:先拆边界,再拆大文件 - 先把 Vite 中的编辑器写文件接口、LLM 代理抽走 - 再把 `GameShell`、`useStoryGeneration`、`useCombatFlow` 按职责拆域 - 拆分时优先保持外部接口稳定,避免一次性全仓大改 ### 第三阶段:收敛基础设施 - 统一 persistence 层 - 统一 editor shared 层 - 统一 AI client 层 - 拆分 `types.ts` ### 第四阶段:降低发布成本 - 将 editor 与 game 做更明确的入口拆分 - 优化 chunk 边界 - 评估是否把编辑器做成独立 app ## 一句话结论 这个仓库当前最需要优化的不是“再补几个功能”,而是**把已经验证有效的玩法与工具链,从“靠大文件和经验串起来”升级为“靠清晰边界、统一基础设施和自动化门禁支撑起来”**。只要这一步不做,后续每次加内容、加编辑器能力、加 AI 流程,工程成本都会持续上升。