【听见课堂 HarmonyOS NEXT 实战系列 05】12 个页面如何不迷路:RouteId、导航外壳与响应式路由

当 HarmonyOS NEXT 应用只有首页和设置页时,在点击事件里直接写一个页面字符串似乎没有问题。随着页面增加到 12 个,手机使用底部导航、平板使用侧栏、部分页面又需要沉浸式布局,分散路由字符串就会迅速失控。

听见课堂把页面标识集中到 common-core,由 entry 的页面外壳决定手机、平板和 2in1 如何呈现同一组路由。这里不讨论“动画够不够炫”,而是关注导航语义如何保持稳定。

听见课堂手机与大屏响应式路由

一、12 个页面先有稳定身份,再有显示文案

项目使用 RouteId 表达页面身份,并维护对应的 PAGE_META

export enum RouteId {
  PRIVACY = 'P01',
  HOME = 'P02',
  COURSE_EDIT = 'P03',
  LIVE_CLASS = 'P04',
  SCAN = 'P05',
  OCR_REVIEW = 'P06',
  REVIEW = 'P07',
  TASK_CONFIRM = 'P08',
  TASK_CENTER = 'P09',
  HISTORY = 'P10',
  SETTINGS = 'P11',
  STATE_GALLERY = 'P12',
  SIGN_LANGUAGE = 'S01'
}

页面显示名称可以从“任务”改成“任务中心”,但 P08 不应该随文案变化。稳定 key 使偏好恢复、埋点、测试和跨模块参数都能指向同一页面身份。

集中式 RouteId 与 P01 至 P12 页面图谱

PAGE_META 统一保存 RouteId、完整标题和短标题;手机底栏的五个图标则由页面外壳单独映射。页面层不再重复维护同一组标题字符串,也更容易检查是否存在没有入口的孤立页面。

二、统一的 navigate 处理跨页面副作用

听见课堂的页面外壳维护当前路由,并通过统一方法切换:

private navigate(route: RouteId): void {
  if (route !== RouteId.LIVE_CLASS &&
    this.currentPage === RouteId.LIVE_CLASS &&
    this.liveState === LiveSessionState.LISTENING) {
    this.liveService.pause();
  }
  if (route !== this.currentPage) {
    this.contentScroller.scrollEdge(Edge.Top);
  }
  this.currentPage = route;
  this.preferences.lastPage = route;
  PersistenceV2.save(UserPreferences);
}

这段代码展示了集中导航入口的三个价值:

  • 离开实时课堂时统一暂停相关会话,避免页面不可见后仍占用资源;
  • 切换页面时将滚动位置复位,避免新页面继承上一页的偏移;
  • 记录最后页面,为下次启动恢复提供依据。

如果每个按钮都直接修改 currentPage,这些副作用很容易漏掉。未来接入 NavigationNavPathStack 时,也可以先保留 RouteId 语义,再替换底层栈实现。

三、手机底部导航只放高频主流程

紧凑屏幕不适合把 12 个页面全部塞进底部栏。听见课堂在手机上保留五个高频入口:

  1. 首页 HOME
  2. 课堂 LIVE_CLASS
  3. 回顾 REVIEW
  4. 任务 TASK_CENTER
  5. 设置 SETTINGS

课程编辑、扫描材料、OCR 复核、手语识别、任务确认、历史记录和状态组件页从主流程进入。这既控制了底部导航数量,也让用户能够从“课程—课堂—回顾—任务”形成清晰路径。

底部栏的选中状态必须基于稳定 RouteId,不能基于标题文字。点击已经选中的入口时,可以选择滚动到顶部或保持当前位置,但行为要统一。

四、大屏侧栏可以展示更完整的信息架构

当可用宽度达到项目定义的 840vp 断点时,页面外壳切换为侧栏布局:

.onAreaChange((_oldValue: Area, newValue: Area) => {
  const width = Number(newValue.width)
  this.isLarge = width >= AppSizes.LARGE_BREAKPOINT
})

侧栏拥有更多垂直空间,可以展示除隐私页之外的完整页面入口,并在右侧保留内容区。这里的关键不是简单把手机底部栏移到左边,而是重新分配导航密度:

  • 手机:只展示五个主任务;
  • 平板/2in1:展示更完整的页面集合;
  • 路由 identity 不变,Service 与数据状态也不因外壳变化而重建。

响应式切换时要重点测试“宽—窄—宽”往返。用户当前页面不应因为窗口尺寸变化被重置到首页,表单草稿也不能无提示丢失。

五、沉浸式页面需要独立的外壳策略

实时课堂、扫描材料和 OCR 复核分别对应 P04P05P06。在手机上,它们需要更大的内容空间和明确的退出动作,因此会采用沉浸式布局,隐藏标准底部导航。手语识别作为独立的 S01 能力页处理。

沉浸模式下尤其要处理:

  • 状态栏与底部安全区;
  • 相机或麦克风权限被拒绝后的降级;
  • 返回键和页面内关闭按钮的一致行为;
  • 页面离开时释放相机、识别会话和定时器;
  • 横竖屏或分屏变化时预览区域不拉伸变形。

“隐藏导航栏”不等于“没有导航”。用户必须始终知道如何退出,并且退出动作要经过统一 navigate,否则资源清理逻辑可能失效。

六、隐私页是启动门禁,不是普通 Tab

项目通过持久化用户偏好决定启动页面:未接受隐私说明时进入 P01,接受后才恢复 lastPage。这类门禁不能只靠视觉隐藏主导航,还应保证用户无法从其他入口绕过。

偏好对象通过 PersistenceV2 连接:

const PERSISTED_PREFERENCES: UserPreferences = PersistenceV2.connect(
  UserPreferences,
  'heard-classroom-preferences',
  () => new UserPreferences()
) ?? new UserPreferences();

@Local private currentPage: string = this.preferences.privacyAccepted
  ? this.preferences.lastPage
  : RouteId.PRIVACY;

恢复最后页面时还要做合法性校验。如果旧版本保存了已经删除的 RouteId,应回退到首页,而不是渲染空白内容。

七、集中路由需要怎样验证

12 个页面的导航验证不能只靠逐个点击一次。建议建立路由矩阵:

验证项 重点检查
页面可达性 每个 RouteId 至少有一个合法入口
返回路径 详情、编辑、沉浸页能回到正确上级
侧栏/底栏一致性 同一路由的选中状态和标题一致
隐私门禁 未同意时不能绕过,已同意后可恢复
尺寸切换 当前页面与草稿态不丢失
滚动复位 新页面不继承上一页面偏移
资源释放 离开课堂、相机、手语页后能力停止
异常路由 未知 key 安全回退并记录错误

当前听见课堂采用单页面外壳加集中 RouteId 的方式,适合现阶段固定页面集合。若后续需要复杂深链、多层详情栈或跨模块动态页面,可以逐步迁移到 Navigation/NavPathStack,但仍应保留稳定 RouteId 和类型化参数。

总结

路由设计的目标不是让每个按钮都能跳转,而是让页面身份、入口、返回、响应式外壳和生命周期副作用保持一致。听见课堂通过集中式 RouteId 管理 12 个页面,再分别为手机和大屏提供合适的导航密度。

下一篇将进入主题系统,分析 AppColorsAppSizes 与极光背景如何避免页面散写颜色和尺寸,并支持暗色模式与多设备布局。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐