# 讲解流拆分为独立页面设计 日期:2026-07-10 ## 背景 当前讲解流中的展厅列表、业务单元列表、讲解点列表都承载在 `src/pages/explain/list.vue` 的同一个页面内,通过 `explainStage` 在 `hall / unit / stop` 三种状态间切换。该结构在普通 H5 中可以通过页面内自绘标题栏和返回按钮维持体验,但在嵌入微信小程序 web-view 时存在明显问题: 1. 小程序宿主只能稳定感知“页面路由变化”,不能天然理解同一 H5 页面内部的状态切换。 2. 隐藏 H5 自绘标题栏后,业务单元列表和讲解点列表缺少可被宿主导航栏接管的真实页面标题。 3. 小程序宿主返回按钮更接近页面级返回,而不是单页内部 `stage` 状态回退,导致返回链路与讲解流层级不一致。 因此,本设计将讲解流拆成真实多页面结构,让标题和返回都回到页面路由语义上解决,而不再依赖单页状态机和 history 补丁。 ## 目标 将讲解流调整为真实页面路由: - 展厅列表页:标题固定为“讲解” - 业务单元列表页:标题为当前展厅名 - 讲解点列表页:标题为当前业务单元名 - 返回链路:讲解点列表 → 业务单元列表 → 展厅列表 → 讲解首页/入口页 并满足以下要求: - 嵌入小程序时,宿主标题和返回行为与页面层级一致。 - 普通 H5 中现有讲解流视觉风格和交互尽量保持不变。 - 继续复用当前 explain use case / repository 数据边界,不引入新的页面直接数据耦合。 - 讲解点进入 `pages/exhibit/detail` 的既有详情能力保持可用。 ## 非目标 本次设计不包含以下内容: - 不重做讲解详情页的数据模型或播放器架构。 - 不改造 explain 数据来源边界。 - 不新增小程序宿主桥接协议。 - 不重构 `ExplainHallSelect.vue` 为多个纯展示组件,除非实现时为降低复杂度必须做最小拆分。 ## 方案比较 ### 方案 A:保留单页状态机,继续补宿主桥接 继续使用 `pages/explain/list.vue` 承载全部层级,通过 `uni.setNavigationBarTitle`、`document.title`、`window.history.pushState` 与未来的小程序桥接能力修补宿主标题/返回问题。 优点:改动集中,短期页面数不增加。 缺点:本质上仍让小程序宿主面对单页内部状态机;标题和返回仍依赖额外桥接与补丁,稳定性和可理解性都较差。 ### 方案 B:拆成真实多页面,复用现有列表组件(推荐) 将讲解流拆为多个真实页面路由,但短期继续复用 `ExplainHallSelect.vue`,每个页面只使用其中一种模式。 优点: - 宿主标题与返回自然跟随页面路由。 - 页面职责变清晰。 - 改动面比“页面 + 组件同时重构”小。 - 后续还可继续把列表组件分拆为更清晰的纯展示组件。 缺点:`ExplainHallSelect.vue` 仍暂时保留多模式能力,组件层职责还不够彻底收敛。 ### 方案 C:完整拆页并同步拆分组件 同时新增多页面和多展示组件,分别为 HallList / BusinessUnitList / GuideStopList。 优点:结构最纯粹。 缺点:一次性改动面过大,风险更高,不适合作为当前问题的第一步修复。 ## 结论 采用方案 B:**页面层拆开,组件层先复用。** ## 页面结构设计 ### 1. 展厅列表页 保留 `src/pages/explain/list.vue`,职责收敛为: - 加载展厅列表数据 - 标题固定为“讲解” - 点击展厅后跳转业务单元列表页 - 页面返回时回讲解首页/入口页 该页不再维护 `unit` 或 `stop` 两级状态,也不再承担业务单元和讲解点的数据恢复职责。 ### 2. 业务单元列表页 新增 `src/pages/explain/business-unit-list.vue`。 职责: - 接收 `hallId`,必要时带 `hallName` - 根据 `hallId` 调用 `explainUseCase.loadTemporaryBusinessUnitsByHall(hallId)` 加载业务单元 - 标题显示当前展厅名 - 点击业务单元后跳转讲解点列表页 - 返回时回展厅列表页 当路由参数缺失或展厅数据加载失败时,页面应给出可理解的错误态,并允许返回展厅列表页。 ### 3. 讲解点列表页 新增 `src/pages/explain/guide-stop-list.vue`。 职责: - 接收 `hallId`、`unitId`,必要时带 `unitName` - 根据 `hallId` 加载业务单元,再从中定位目标 `unitId`,或通过现有 use case 能力直接恢复业务单元 - 标题显示当前业务单元名 - 点击讲解点后进入 `pages/exhibit/detail` - 返回时回业务单元列表页 ### 4. 讲解详情页 保留 `src/pages/exhibit/detail.vue`。 当前设计下不强制改造详情页结构,只要求: - 从讲解点列表进入详情页时,浏览器 / uni 页面返回能自然回到讲解点列表页 - 现有 `navigateBack` 与 fallback 逻辑继续工作 ## 路由与参数设计 ### 展厅列表 → 业务单元列表 参数: - `hallId`:必需 - `hallName`:可选,用于首屏标题占位和回退兜底 ### 业务单元列表 → 讲解点列表 参数: - `hallId`:必需 - `hallName`:可选 - `unitId`:必需 - `unitName`:可选,用于首屏标题占位和回退兜底 ### 讲解点列表 → 讲解详情页 沿用当前详情参数: - `id` - `tab=explain` - `targetType` - `targetId` ## 标题策略 每个独立页面都在 `onLoad` 后设置页面标题: - 展厅列表页:`讲解` - 业务单元列表页:当前展厅名 - 讲解点列表页:当前业务单元名 页面层统一执行: - `uni.setNavigationBarTitle({ title })` - H5 场景下同步 `document.title = title` 这样可以同时兼容: - uni H5 页面标题 - 普通浏览器标签页标题 - 小程序宿主在页面级别可感知的标题变化 ## 返回策略 返回一律按照真实页面栈处理,不再用单页 `stage` 模拟: - 讲解点列表页返回:`navigateBack()`,失败则带参数跳业务单元列表页 - 业务单元列表页返回:`navigateBack()`,失败则回展厅列表页 - 展厅列表页返回:`navigateBack()`,失败则回讲解首页/入口页 这样嵌入小程序时,宿主返回按钮和页面内返回按钮都会走同一个页面级链路。 ## 组件策略 短期继续复用 `src/components/explain/ExplainHallSelect.vue`,但每个页面只传入一种模式: - 展厅列表页:只传 `stage="hall"` - 业务单元列表页:只传 `stage="unit"` - 讲解点列表页:只传 `stage="stop"` 同时,父页面负责页面级标题和页面级返回,组件只保留内容展示和列表内部点击事件发射。 如果实现中发现多模式分支继续妨碍可读性,可做最小拆分,但目标仍应保持“本次先解决页面路由问题,而不是顺手做大规模组件重构”。 ## 数据流设计 - 展厅列表页:`explainUseCase.loadExplainHalls()` + `loadExplainHallSummaries()` - 业务单元列表页:`explainUseCase.loadTemporaryBusinessUnitsByHall(hallId)` - 讲解点列表页: - 先通过 `hallId` 载入业务单元集合 - 再根据 `unitId` 选中目标业务单元 - 从目标业务单元中映射出讲解点列表 页面消费的仍是 view-model 化后的轻量数据,不直接依赖底层源字段。 ## 错误处理 ### 业务单元列表页 当 `hallId` 缺失、展厅不存在或业务单元加载失败时: - 标题使用 `hallName` 或兜底值 - 页面显示错误态 - 返回按钮仍可回展厅列表页 ### 讲解点列表页 当 `hallId` / `unitId` 缺失、业务单元不存在或讲解点为空时: - 标题使用 `unitName` 或兜底值 - 页面显示空态或错误态 - 返回按钮仍可回业务单元列表页 ## 测试与验证 实现后至少验证: 1. 普通 H5: - 展厅列表 → 业务单元列表 → 讲解点列表 → 详情页 - 浏览器返回逐级回退 - 标题与页面内容一致 2. 嵌入小程序: - 宿主标题按页面层级变化 - 宿主返回逐级回退 - H5 自绘头部隐藏后仍有清晰导航语义 3. 回退兜底: - 直接打开业务单元列表页 / 讲解点列表页缺少参数时有合理错误态与返回路径 4. 回归: - 讲解详情进入与返回不被破坏 - `pnpm type-check` - `pnpm lint` - `pnpm build:h5` ## 实施边界 本次实施应优先关注: - 页面拆分 - 路由参数 - 标题设置 - 返回链路 - 现有讲解流回归 不应顺手做: - explain 数据源重构 - 详情页播放器逻辑重写 - 小程序宿主专有桥接协议设计 - 与当前任务无关的全局导航大改