【听见课堂 HarmonyOS NEXT 实战系列 05】12 个页面如何不迷路:RouteId、导航外壳与响应式路由
【听见课堂 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 使偏好恢复、埋点、测试和跨模块参数都能指向同一页面身份。

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,这些副作用很容易漏掉。未来接入 Navigation 或 NavPathStack 时,也可以先保留 RouteId 语义,再替换底层栈实现。
三、手机底部导航只放高频主流程
紧凑屏幕不适合把 12 个页面全部塞进底部栏。听见课堂在手机上保留五个高频入口:
- 首页
HOME; - 课堂
LIVE_CLASS; - 回顾
REVIEW; - 任务
TASK_CENTER; - 设置
SETTINGS。
课程编辑、扫描材料、OCR 复核、手语识别、任务确认、历史记录和状态组件页从主流程进入。这既控制了底部导航数量,也让用户能够从“课程—课堂—回顾—任务”形成清晰路径。
底部栏的选中状态必须基于稳定 RouteId,不能基于标题文字。点击已经选中的入口时,可以选择滚动到顶部或保持当前位置,但行为要统一。
四、大屏侧栏可以展示更完整的信息架构
当可用宽度达到项目定义的 840vp 断点时,页面外壳切换为侧栏布局:
.onAreaChange((_oldValue: Area, newValue: Area) => {
const width = Number(newValue.width)
this.isLarge = width >= AppSizes.LARGE_BREAKPOINT
})
侧栏拥有更多垂直空间,可以展示除隐私页之外的完整页面入口,并在右侧保留内容区。这里的关键不是简单把手机底部栏移到左边,而是重新分配导航密度:
- 手机:只展示五个主任务;
- 平板/2in1:展示更完整的页面集合;
- 路由 identity 不变,Service 与数据状态也不因外壳变化而重建。
响应式切换时要重点测试“宽—窄—宽”往返。用户当前页面不应因为窗口尺寸变化被重置到首页,表单草稿也不能无提示丢失。
五、沉浸式页面需要独立的外壳策略
实时课堂、扫描材料和 OCR 复核分别对应 P04、P05、P06。在手机上,它们需要更大的内容空间和明确的退出动作,因此会采用沉浸式布局,隐藏标准底部导航。手语识别作为独立的 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 个页面,再分别为手机和大屏提供合适的导航密度。
下一篇将进入主题系统,分析 AppColors、AppSizes 与极光背景如何避免页面散写颜色和尺寸,并支持暗色模式与多设备布局。
更多推荐



所有评论(0)