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 双保险策略

ReportDetailPage AppStorage ArchivePage ReportDetailPage AppStorage ArchivePage alt [路由参数正常] [路由参数丢失(某些系统版本 bug)] AppStorage.set('current_report_id', 42) router.pushUrl({ url: '...', params: { reportId: 42 } }) try { params = router.getParams() } reportId = params.reportId ✅ reportId = AppStorage.get('current_report_id') ✅

实战技巧:同时使用路由参数和 AppStorage 传递关键数据,是应对某些系统版本中路由参数丢失 bug 的防御性编程策略。


四、项目完整路由架构

4.1 页面跳转关系图

LoginPage

PhoneLoginPage

HomePage

CapturePage

WritePage

ArchivePage

TrendPage

CoupleHomePage

MePage

AnalyzingPage

ReportDetailPage

SharePage

UnclassifiedArchivePage

DuetReportPage

BindRelationPage

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 配置出发,全面解析了鸿蒙页面路由系统的核心机制:

  1. 路由注册:所有页面必须在 main_pages.jsonsrc 数组中注册
  2. 导航方式pushUrl(入栈可返回)vs replaceUrl(不可返回,用于 Tab 切换)
  3. 参数传递:路由参数 + AppStorage 双保险,防止参数丢失
  4. TabBar 导航:自定义 @Builder + replaceUrl 实现标准底部导航
  5. 栈管理:合理使用 push/replace/back/clear 控制页面栈深度

下一篇预告第06篇 项目目录结构与编码规范

如果这篇文章对你有帮助,欢迎点赞👍、收藏⭐、关注🔔,你的支持是我持续创作的动力!


相关资源:

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐