耳畔三国 HarmonyOS 设计篇(27):MainFrame 听读、地图与人物模块拆分规划
一、拆分对象是真实巨型页面拆分结果仍是规划当前MainFrame同时持有听读播放、TTS、媒体会话、后台任务、地图缩放、人物与事件选择、收藏笔记、主题和多端布局状态文件已超过五千行。大量 Builder 与业务函数共用同一组页面字段修改一个区域时很容易触碰另一个区域的生命周期。这能证明“需要拆”不能证明“已经拆完”。本文规划把听读、地图和人物三个领域移出主框架同时让MainFrame退回到导航、主题和页面组合层。迁移期间必须保持现有行为可回滚尤其不能因为整理代码破坏后台朗读、地图手势或本地数据。当前集中职责目标所有者MainFrame 保留TTS、进度、媒体会话、后台任务AudioFeature选中入口与页面装配年份、阵营、缩放、全屏地图MapFeature导航到地图区域人物选择、标签、关联跳转PeopleFeature跨模块路由协调收藏、笔记、主题Shared / AppShell主题与顶层生命周期二、模块边界按状态所有权划分拆文件不是把 Builder 剪切到三个目录。每个模块必须拥有自己的状态、命令和持久化端口。听读模块拥有播放意图和媒体资源地图模块拥有视口变换人物模块拥有选择与内容投影。跨模块只传稳定 id 和事件不共享对方内部对象。export interface FeatureModuleState, Command, Event { snapshot(): State dispatch(command: Command): Promisevoid subscribe(listener: (state: State) void): () void onEvent(listener: (event: Event) void): () void activate(): Promisevoid deactivate(): Promisevoid } export interface FeatureRoute { feature: audio | map | people targetId?: string source: home | search | related | favorite }激活和停用是显式生命周期。切走人物页只解除 UI 订阅切走听读页时是否继续播放由播放策略决定不能随着组件销毁被动停止。三、听读模块先隔离意图再搬迁平台资源听读是风险最高的模块因为它同时连接 TTS、AVSession、后台任务、定时器和应用生命周期。第一阶段不移动平台对象只提取AudioState、AudioCommand和 reducer让现有方法通过命令更新状态。状态稳定后再把媒体会话与 TTS 放入 runtime adapter。export interface AudioState { selectedId?: string phase: idle | preparing | playing | paused | failed progressSeconds: number durationSeconds: number loopMode: list | single | once backgroundActive: boolean errorCode?: string } export type AudioCommand | { type: select; audioId: string } | { type: play } | { type: pause } | { type: seek; seconds: number } | { type: next } | { type: lifecycle; state: foreground | background } export interface AudioRuntime { prepare(text: string): Promisenumber play(fromSeconds: number): Promisevoid pause(): Promisevoid release(): Promisevoid }迁移阶段允许变化必须保持提取状态与命令调用入口变为 dispatch播放、暂停、进度表现提取 runtime adapter平台对象移出页面AVSession 与后台行为提取页面组件Builder 独立手机和平板布局四、地图模块把视口变换与业务选择分开地图的年份、阵营属于业务选择缩放与偏移属于视口状态。两者混在一起时切换年份可能意外沿用不适合新地图的偏移。地图模块应让业务模型决定可见区域让视口控制器负责夹取比例、手势增量和复位。export interface MapSelection { year: string factionId: string } export interface MapViewport { scale: number offsetX: number offsetY: number fullscreen: boolean } export interface MapController { selectYear(year: string): void selectFaction(factionId: string): void applyPinch(scaleDelta: number, centerX: number, centerY: number): void applyPan(deltaX: number, deltaY: number): void resetViewport(): void snapshot(): { selection: MapSelection; viewport: MapViewport } }年份变化后如果地图资源尺寸或可见阵营集合变化视口回到安全默认值仅打开全屏时保留当前中心点。手势算法通过纯函数测试不依赖完整页面渲染。五、人物模块通过 id 建立跨域链接人物详情会跳到相关人物、事件或阵营。模块间不能互相 import 页面状态而是发布领域事件由 AppShell 解析为路由。人物模块只知道personId事件模块只知道eventId共享目录服务负责名称到 id 的解析和不存在时的降级。export type PeopleEvent | { type: open_person; personId: string } | { type: open_event; eventId: string } | { type: open_faction; factionId: string } | { type: play_person_audio; personId: string } export interface PeopleState { selectedPersonId: string selectedTab: 生平 | 关系 | 评价 textMode: 原文 | 译文 relatedPersonIds: string[] } export interface HistoricalDirectory { personById(id: string): Person | undefined eventById(id: string): HistoryEvent | undefined factionById(id: string): Faction | undefined }找不到关联对象时保持当前页面并显示行内提示不能跳到默认人物制造错觉。播放人物内容通过事件交给听读模块人物模块不持有 TTS 实例。六、AppShell 只做装配和跨模块协调目标MainFrame只保留当前主标签、主题、多端布局、模块装配和事件路由。它不再计算听读进度、不再夹取地图坐标也不直接读写人物收藏。共享的 Preferences 端口由依赖注入传给各 Repository模块之间通过事件总线或显式协调器通信。交互事件来源协调结果人物页点击朗读PeopleEventAudioFeature 选择对应内容地图点击阵营MapEventPeopleFeature 打开阵营人物收藏中打开人物FavoriteEventAppShell 切页并传 personId应用进入后台生命周期AudioFeature 决定继续或暂停这种结构允许手机底部导航和平板侧栏复用同一模块状态只替换页面组合不复制业务逻辑。七、迁移失败必须可以按模块回退一次性改写五千行风险过高。每个模块都通过 feature flag 控制新旧实现状态序列化格式暂时保持兼容。新模块启动失败时回到旧 Builder持久化迁移失败时只读旧数据不覆盖听读 runtime 初始化失败时退回文本阅读地图资源缺失时退回列表视图。迁移期间最危险的是双写。首阶段只让旧实现写、新实现读影子状态比对稳定后切为新实现单写。任何时刻都不能让两个播放器同时占用媒体会话也不能让新旧地图控制器同时处理手势。八、实施顺序和验收门槛顺序建议为先建立共享类型与模块合同再提取人物模块因为平台依赖最少随后提取地图纯状态和手势最后处理听读状态、runtime 与后台生命周期三个模块稳定后再缩减 AppShell并删除旧路径。验收必须比较拆分前后的行为冷启动与主题切换一致人物、事件、阵营互跳不丢目标地图缩放、全屏和年份切换可预测朗读播放、暂停、拖动、后台继续和回前台恢复一致收藏笔记不丢失手机与平板入口一致模块停用后订阅、定时器和平台资源都释放。证据计划包括新旧路径录屏、状态快照对比、媒体会话日志、内存和资源释放检查。没有这些结果前文章保持设计定位。参考HarmonyOS ArkTS MVVM 开发指导。九、总结巨型页面的拆分单位不是视觉区块而是状态所有权和生命周期。听读、地图、人物各自拥有状态与平台端口AppShell 只负责装配和路由才能真正降低修改半径。以可回滚的阶段迁移替代一次性重写也能让后台播放和本地数据这些高风险能力在结构调整中保持可验证。