05 路由配置与 main_pages.json 详解:页面跳转与参数传递完全指南
05 路由配置与 main_pages.json 详解:页面跳转与参数传递完全指南
前言

图:05 路由配置与 main_pages.json 详解:页面跳转与参数传递完全指南 运行效果截图(HarmonyOS NEXT)
在 HarmonyOS NEXT Stage 模型中,应用的所有页面都必须在 main_pages.json 中注册,这是路由系统的"地图"。如果一个页面没有注册,调用 router.pushUrl({ url: 'pages/MyPage' }) 时会报 “Page not found” 错误。
本文将全面解析鸿蒙的页面路由系统——从静态配置到动态跳转,从简单的 pushUrl/replaceUrl 到带参数的跨页面数据传递,以及"鹿鹿"项目中 16 个页面的完整路由架构设计。
官方文档·页面路由:developer.huawei.com
项目源码仓库:harmony-app(GitHub)

图:HarmonyOS NEXT 页面路由系统——router 对象管理页面栈,支持 push/replace/back 操作
一、main_pages.json 配置
1.1 文件位置与引用关系
entry/src/main/resources/base/profile/
└── main_pages.json ← 路由配置文件
entry/src/main/module.json5
└── "pages": "$profile:main_pages" ← 通过 $profile: 引用
1.2 路由注册机制
main_pages.json 采用扁平数组结构,每个元素对应一个页面文件:
{
"src": [
"pages/LoginPage",
"pages/PhoneLoginPage",
"pages/HomePage",
"pages/CapturePage",
"pages/AnalyzingPage",
"pages/ReportDetailPage",
"pages/ArchivePage",
"pages/TrendPage",
"pages/EmptyPage",
"pages/MePage",
"pages/WritePage",
"pages/BindRelationPage",
"pages/CoupleHomePage",
"pages/DuetReportPage",
"pages/SharePage",
"pages/UnclassifiedArchivePage"
]
}
注意:路由字符串与
entry/src/main/ets/pages/目录下的.ets文件对应。例如"pages/LoginPage"对应entry/src/main/ets/pages/LoginPage.ets。
二、页面跳转 API 详解
2.1 获取 router 对象
HarmonyOS NEXT 推荐通过 getUIContext() 获取路由控制器:
// 方式一:通过 getUIContext()(推荐,上下文安全)
this.getUIContext().getRouter().pushUrl({ url: 'pages/CapturePage' });
// 方式二:直接引用(需要 import,旧写法)
import router from '@ohos.router';
router.pushUrl({ url: 'pages/CapturePage' });
2.2 四种导航方式
| 方法 | 效果 | 适用场景 | 能否返回 |
|---|---|---|---|
pushUrl() |
压栈(新页面叠加在上方) | 详情页、子页面 | ✅ 可以 back |
replaceUrl() |
替换当前页(当前页出栈) | TabBar 切换 | ❌ 无法 back 到被替换页 |
back() |
返回上一页(出栈) | 返回按钮 | — |
clear() |
清空页面栈 | 退出登录时清空历史 | — |
2.3 pushUrl 详细用法
// 基础用法:简单跳转
this.getUIContext().getRouter().pushUrl({ url: 'pages/CapturePage' });
// 带参数跳转
this.getUIContext().getRouter().pushUrl({
url: 'pages/ReportDetailPage',
params: {
reportId: this.currentReportId,
fromPage: 'archive'
}
});
// 带路由模式跳转
this.getUIContext().getRouter().pushUrl(
{ url: 'pages/ReportDetailPage' },
router.RouterMode.Standard // Standard: 多实例;Single: 单实例
);
// 跳转后的回调
this.getUIContext().getRouter().pushUrl(
{ url: 'pages/CapturePage' },
(err) => {
if (err) {
hilog.error(0x0000, TAG, '跳转失败: %{public}s', JSON.stringify(err));
}
}
);
2.4 replaceUrl 场景
replaceUrl 用于不需要返回的页面切换,常见于 TabBar 导航:
// HomePage.ets — TabBar 切换
.onClick(() => {
switch (index) {
case 1: // 档案 Tab
this.getUIContext().getRouter().replaceUrl({ url: 'pages/ArchivePage' });
break;
case 2: // 洞察/双人 Tab
if (this.isCoupleBound) {
this.getUIContext().getRouter().replaceUrl({ url: 'pages/CoupleHomePage' });
} else {
this.getUIContext().getRouter().replaceUrl({ url: 'pages/TrendPage' });
}
break;
case 3: // 我的 Tab
this.getUIContext().getRouter().replaceUrl({ url: 'pages/MePage' });
break;
}
})
三、路由参数传递
3.1 发送参数(发送方)
// 发送方:ReportDetailPage 传入 reportId
this.getUIContext().getRouter().pushUrl({
url: 'pages/ReportDetailPage',
params: {
reportId: 123,
source: 'archive',
fromDate: '2026-06-23'
}
});
3.2 接收参数(接收方)
// 接收方:ReportDetailPage 读取参数
@Entry
@Component
struct ReportDetailPage {
@State reportId: number = 0
aboutToAppear() {
// 方式一:从路由参数读取
try {
const params = this.getUIContext().getRouter().getParams() as Record<string, Object>;
if (params && typeof params['reportId'] === 'number') {
this.reportId = params['reportId'] as number;
}
} catch (e) {
hilog.warn(0x0000, TAG, '路由参数读取失败,使用 AppStorage 兜底');
}
// 方式二:AppStorage 兜底(双保险)
if (!this.reportId) {
this.reportId = AppStorage.get<number>('current_report_id') ?? 0;
}
}
}
3.3 路由参数 vs AppStorage 双保险策略
实战技巧:同时使用路由参数和 AppStorage 传递关键数据,是应对某些系统版本中路由参数丢失 bug 的防御性编程策略。
四、项目完整路由架构
4.1 页面跳转关系图
4.2 路由策略说明
| 跳转方向 | 使用方法 | 原因 |
|---|---|---|
| 首页 → 拍照/手写 | pushUrl |
需要返回首页 |
| 拍照 → 分析中 | replaceUrl |
不希望用户返回拍照页 |
| 分析中 → 报告详情 | replaceUrl |
分析完成后不需要返回分析页 |
| 首页 TabBar 切换 | replaceUrl |
Tab 切换不需要栈堆积 |
| 首页 → 档案/趋势 | replaceUrl |
TabBar 模式 |
| 报告 → 分享页 | pushUrl |
分享后可返回报告 |
五、底部 TabBar 导航
5.1 TabBar 实现模式
"鹿鹿"项目的首页 TabBar 通过自定义 @Builder 实现:
// HomePage.ets — 完整的 TabBar 实现
@Entry
@Component
struct HomePage {
@State currentTab: number = 0
@State isCoupleBound: boolean = false
// 读取伴侣绑定状态
aboutToAppear() {
this.isCoupleBound = AppStorage.get<boolean>('couple_bound') ?? false;
}
@Builder
TabItem(icon: string, label: string, index: number) {
Column() {
Text(icon).fontSize(22)
Text(label)
.fontSize(10)
.fontColor(this.currentTab === index ? '#A8907A' : '#B5A99A')
.margin({ top: 2 })
}
.layoutWeight(1)
.alignItems(HorizontalAlign.Center)
.onClick(() => this.switchTab(index))
}
private switchTab(index: number) {
this.currentTab = index;
switch (index) {
case 1:
this.getUIContext().getRouter().replaceUrl({ url: 'pages/ArchivePage' });
break;
case 2:
const target = this.isCoupleBound ? 'pages/CoupleHomePage' : 'pages/TrendPage';
this.getUIContext().getRouter().replaceUrl({ url: target });
break;
case 3:
this.getUIContext().getRouter().replaceUrl({ url: 'pages/MePage' });
break;
}
}
build() {
Column() {
// 页面主内容
Scroll() { /* 内容区 */ }
.layoutWeight(1)
// 底部 TabBar
Row() {
this.TabItem('⌂', '首页', 0)
this.TabItem('📁', '档案', 1)
this.TabItem(this.isCoupleBound ? '♡' : '📈', this.isCoupleBound ? '我们' : '洞察', 2)
this.TabItem('◔', '我的', 3)
}
.width('100%')
.height(56)
.backgroundColor('#FFFFFF')
.padding({ bottom: 8 })
}
.height('100%')
}
}
六、页面栈管理
6.1 页面栈的概念
路由系统维护一个页面栈(Page Stack),push 入栈,back 出栈:
页面栈示例(从底到顶):
[LoginPage] → [HomePage] → [ArchivePage] → [ReportDetailPage]
↑ 当前页面(栈顶)
6.2 栈管理方法
// 返回上一页
this.getUIContext().getRouter().back();
// 返回到指定页面(从栈中向下找到第一个匹配的页面)
this.getUIContext().getRouter().back({
url: 'pages/HomePage',
params: { needRefresh: true }
});
// 获取当前栈大小
const stackSize = this.getUIContext().getRouter().getLength();
// 获取当前路由状态
const state = this.getUIContext().getRouter().getState();
console.log('当前页面:' + state.name); // "pages/ReportDetailPage"
6.3 防止重复跳转
在高频交互场景(如快速点击)中,需要防止重复跳转:
// 防抖:使用标志位防止重复跳转
@State isNavigating: boolean = false;
private navigateToReport(reportId: number) {
if (this.isNavigating) return;
this.isNavigating = true;
AppStorage.setOrCreate('current_report_id', reportId);
this.getUIContext().getRouter().pushUrl(
{ url: 'pages/ReportDetailPage', params: { reportId } },
() => { this.isNavigating = false; }
);
}
七、常见路由问题排查
7.1 路由常见错误
| 错误 | 原因 | 解决方案 |
|---|---|---|
Page not found: pages/XXX |
页面未在 main_pages.json 注册 |
将页面路径添加到 main_pages.json |
| 路由参数丢失 | 某些系统版本 bug | 同时通过 AppStorage 传递数据 |
replaceUrl 后无法 back |
被替换的页面已出栈 | 这是预期行为,考虑是否应该用 pushUrl |
| 栈中页面过多导致内存问题 | 无节制地 push | 合理使用 replaceUrl,控制栈深度 |
7.2 调试路由状态
// 在任意页面中打印路由状态
const state = this.getUIContext().getRouter().getState();
hilog.info(0x0000, TAG, '当前路由: %{public}s, 栈深度: %{public}d',
state.name, this.getUIContext().getRouter().getLength());
八、注意事项与常见问题
8.1 开发注意事项
在实际开发过程中,需特别注意以下几点:
- API 兼容性:部分接口仅在特定 HarmonyOS NEXT 版本中可用,需做版本条件判断
- 权限模型:采用静态声明(module.json5)+ 动态申请(requestPermissionsFromUser)的两阶段授权
- 生命周期:合理使用
aboutToAppear()和aboutToDisappear()管理资源初始化与释放 - 状态同步:跨页面数据通过 AppStorage 共享,组件内状态使用
@State/@Prop/@Link装饰器
8.2 常见错误与解决方案
常见问题快速排查表:
| 问题类型 | 排查方向 | 参考方法 |
|---|---|---|
| 应用崩溃 | 查看 hilog 错误日志 | hilog.error(TAG, "...", e.message) |
| 状态丢失 | 检查 AppStorage 键名拼写 | 统一使用常量管理键名 |
| 动画不流畅 | 避免在 animateTo 回调中执行 I/O | 动画与数据操作分离 |
总结
本文从 main_pages.json 配置出发,全面解析了鸿蒙页面路由系统的核心机制:
- 路由注册:所有页面必须在
main_pages.json的src数组中注册 - 导航方式:
pushUrl(入栈可返回)vsreplaceUrl(不可返回,用于 Tab 切换) - 参数传递:路由参数 + AppStorage 双保险,防止参数丢失
- TabBar 导航:自定义
@Builder+replaceUrl实现标准底部导航 - 栈管理:合理使用 push/replace/back/clear 控制页面栈深度
下一篇预告:第06篇 项目目录结构与编码规范
如果这篇文章对你有帮助,欢迎点赞👍、收藏⭐、关注🔔,你的支持是我持续创作的动力!
相关资源:
更多推荐




所有评论(0)