HarmonyOS鸿蒙Next中ArkUI路由/导航系列十二:Navigation路由拦截

HarmonyOS鸿蒙Next中ArkUI路由/导航系列十二:Navigation路由拦截

1、概述

路由拦截给开发者提供了一个能够在不修改原始路由跳转代码的情况下,额外重定向调整跳转逻辑的能力。

具体使用方式:给导航控制器NavPathStack设置回调函数,在navigation进行跳转前,跳转后,单双栏切换时触发对应的回调。 接口如下:

  • setInterception(interception: NavigationInterception): void 其中NavigationInterception三个成员及说明如下:
成员 说明
willShow 页面跳转前拦截,允许操作栈,在当前跳转中生效
didShow 页面跳转后回调。在该回调中操作栈在下一次跳转中刷新
modeChange Navigation单双栏显示状态发生变更时触发该回调

详细的接口文档见:Navigation-导航与切换-ArkTS组件-ArkUI(方舟UI框架)-应用框架 - 华为HarmonyOS开发者

2、案例分享

考虑这样一个场景:有一个网购App,用户可以在非登录状态浏览商品,但是一旦用户选中了某些商品并进行购买时,就需要处于登录状态,才能进行支付操作。 可能的执行流程大致如下图所示:

虽然商品页面在跳转之前可以检测是否已经登录,如果没有登录则主动跳转到登录页面,否则直接跳转到支付页面。

但是这样会导致商品页面需要增加登录检测的逻辑,任何会跳转到支付页面的地方都需要增加类似的代码,这样会出现大量重复代码并且导致模块耦合。

所以考虑利用Navigation的路由拦截能力对上述逻辑进行改造,改造成如下逻辑:

  • 对需要登陆后才能访问的页面添加拦截(对于上述案例来说就是支付页面)
  • 在路由拦截的willShow回调中检测是否已登录,如果未登录则将支付页面pop掉,然后转而跳转到登录页面,并且将原始的跳转信息转发给登录页面。
  • 登录页面进行登录操作,登录完成后,如果发现有原始的跳转信息,则继续跳转到原来的目的页面。

下面通过一个详细的示例,来展示如果进行路由拦截。

2.1 主页面

// src/main/ets/pages/Index.ets
import { LoginParam } from './Common';
@Component
struct Index {
  private stack: NavPathStack = new NavPathStack();
  aboutToAppear(): void {
    // 使用AppStorage记录是否当前已经登录
    AppStorage.setOrCreate('hasLogin', false);
    this.stack.setInterception({
      willShow: (from: NavDestinationContext | NavBar, to: NavDestinationContext | NavBar, operation: NavigationOperation, isAnimated: boolean) => {
        if (!to || to == 'navBar' || !to.pathInfo) {
          return;
        }
        // 这里检测是否是跳转到支付页面(支付页面必须要登录后才能访问)
        if (to.pathInfo.name == 'PaymentPage') {
          let hasLogin: boolean = AppStorage.get<boolean>('hasLogin');
          if (!hasLogin) {
            // 如果还未登录,则将支付页面移除,并跳转到登录页面
            this.stack.pop();
            // 将原始跳转信息传给登录页面,方便后续如果登录成功后,能够继续跳转到支付页面
            this.stack.pushPath({name: 'LoginPage', param: new LoginParam(to.pathInfo)});
          }
        }
      }
    })
    // 直接进入产品页
    this.stack.pushPath({name: 'ProductPage'})
  }
  build() {
    Navigation(this.stack) {}
    .hideNavBar(true)
    .height('100%')
    .width('100%')
  }
}

2.2 产品页

//src/main/ets/pages/ProductPage.ets
import { ProductInfo } from './Common';
@Component
struct CustomMenu {
  @StorageProp('hasLogin') hasLogin: boolean = false;
  build() {
    Stack({alignContent: Alignment.Center}) {
      Text(`${this.hasLogin ? '已登录' : '未登录'}`)
    }.width(100).height(56)
  }
}
@Component
struct ProductPage {
  private stack: NavPathStack | undefined = undefined;
  @Builder
  MyMenus() {
    CustomMenu()
  }
  build() {
    NavDestination() {
      Column() {
        Stack({alignContent: Alignment.Center}) {
          Text('产品图示')
        }.width('90%').height('40%').backgroundColor('#fff3e9c4')
        Text('产品介绍').align(Alignment.Start).margin(15)
        Text('xxxxxxx').margin(15)
        Button('购买该产品').onClick(() => {
          // 用户选择购买产品后,跳转到支付页面,并将购买的产品信息传给支付页面。
          this.stack?.pushPath({name: 'PaymentPage', param: new ProductInfo('苹果', 2, 3)})
        })
      }.width('100%').height('100%')
    }.title('产品页')
    // 产品页右上角menu显示当前登录状态
    .menus(this.MyMenus)
    .hideToolBar(true)
    .mode(NavDestinationMode.STANDARD)
    .onReady((ctx: NavDestinationContext) => {
      this.stack = ctx.pathStack;
    })
  }
}
@Builder
export function ProductPageBuilder() {
  ProductPage()
}

2.3 支付页面

//src/main/ets/pages/PaymentPage.ets
import { ProductInfo } from './Common';
@Component
struct PaymentPage {
  private stack: NavPathStack | undefined = undefined;
  private productInfo: ProductInfo | undefined = undefined;
  build() {
    NavDestination() {
      Column() {
        Column() {
          Text(`名称:${this.productInfo?.name}`).margin(15)
          Text(`数量:${this.productInfo?.count}`).margin(15)
          Text(`价格:¥${this.productInfo?.price}`).margin(15)
        }.margin(15)
        Button('付款').onClick(() => {
          // 支付逻辑
        })
      }.width('100%').height('100%')
    }.title('支付')
    .hideToolBar(true)
    .mode(NavDestinationMode.STANDARD)
    .onReady((ctx: NavDestinationContext) => {
      this.productInfo = ctx.pathInfo.param as ProductInfo;
      this.stack = ctx.pathStack;
    })
  }
}
@Builder
export function PaymentPageBuilder() {
  PaymentPage()
}

2.4 登录页面

//src/main/ets/pages/LoginPage.ets
import { promptAction } from '@kit.ArkUI';
import { LoginParam } from './Common';
@Component
struct LoginPage {
  private stack: NavPathStack | undefined = undefined;
  @State isLoggingIn: boolean = false;
  private originInfo: NavPathInfo | undefined = undefined;
  build() {
    NavDestination() {
      Column() {
        Row() {
          Text('用户名').width('30%')
          TextInput({placeholder: '请输入用户名'}).width('70%')
        }.margin(15)
        Row() {
          Text('密码').width('30%')
          TextInput({placeholder: '请输入密码'}).width('70%')
        }.margin(15)
        Button(`${this.isLoggingIn ? '登录中...' : '登录'}`).onClick(() => {
          // 当前正在登录时不允许操作
          this.isLoggingIn = true;
          setTimeout(() => {
            // 用定时任务模拟登录过程,并且登录成功后更新全局的登录标识
            AppStorage.setOrCreate('hasLogin', true);
            promptAction.openToast({message: '登录成功!'});
            // 登录成功后将当前的登录页面移除
            this.stack?.pop()
            if (this.originInfo && this.originInfo.name) {
              // 如果有原始的目的页面,这里继续跳转到原目的页面。
              this.stack?.pushPath({name: this.originInfo.name, param: this.originInfo.param})
            }
          }, 1000);
        }).enabled(this.isLoggingIn ? false : true).margin(15)
      }.width('100%').margin({top: 50})
    }.title('登录')
    .hideToolBar(true)
    .mode(NavDestinationMode.STANDARD)
    .onReady((ctx: NavDestinationContext) => {
      let param = ctx?.pathInfo?.param as LoginParam;
      // 保存原始的跳转页面信息,等到登录成功后继续跳转到原目的页面
      this.originInfo = param?.originInfo;
      this.stack = ctx?.pathStack;
    })
  }
}
@Builder
export function LoginPageBuilder() {
  LoginPage()
}

上述demo运行效果如下:

3、附件

上述案例完整示例代码如下:

(代码链接待补充)

(未完待续……)

Navigation一镜到底转场开发

Navigation页面复用

Navigation应用开发实战

Navigation常见问题的Q&A


【系列其他文章】

【ArkUI路由/导航系列】一:ArkUI路由原理简介,认识Navigation组件

【ArkUI路由/导航系列】二:Navigation基础路由操作,让页面跳转起来

【ArkUI路由/导航系列】三:NavDestination标题栏和工具栏,丰富页面信息

【ArkUI路由/导航系列】四:Navigation页面信息查询

【ArkUI路由/导航系列】五:Navigation生命周期管理

【ArkUI路由/导航系列】六:Navigation组件的无感监听

【ArkUI路由/导航系列】七:Navigation自定义转场动画,让页面切换炫起来

【ArkUI路由/导航系列】八:Navigation跨包路由,迈入高级路由能力

【ArkUI路由/导航系列】九:Navigation分栏开发,开启多设备开发之旅

【ArkUI路由/导航系列】十:Navigation嵌套开发

【ArkUI路由/导航系列】十一:Navigation弹窗页面开发

【ArkUI路由/导航系列】十二:Navigation路由拦截


更多关于HarmonyOS鸿蒙Next中ArkUI路由/导航系列十二:Navigation路由拦截的实战教程也可以访问 https://www.itying.com/category-93-b0.html

2 回复

在HarmonyOS Next中,ArkUI的Navigation组件可通过路由拦截控制页面跳转。使用onInterceptRoute回调函数,在路由切换前进行拦截处理。该回调接收RouteInfo参数,包含目标页面的路由信息。通过返回true可拦截路由,阻止跳转;返回false则允许继续导航。拦截逻辑可基于业务需求(如权限验证、数据校验等)动态控制。拦截后可通过router.pushrouter.replace手动触发新路由。该机制不依赖Java或C,纯ArkTS实现。

更多关于HarmonyOS鸿蒙Next中ArkUI路由/导航系列十二:Navigation路由拦截的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


这是一个很好的HarmonyOS Next中关于Navigation路由拦截的案例分享。我来总结几个关键点:

  1. 路由拦截的核心是通过NavPathStack的setInterception方法设置拦截回调,主要使用willShow在跳转前进行拦截处理。

  2. 案例中巧妙利用willShow回调实现了登录状态的统一校验:

  • 检测到跳转支付页面时检查登录状态
  • 未登录则移除支付页面并跳转登录页
  • 登录成功后恢复原始跳转
  1. 技术实现要点:
  • 使用AppStorage管理全局登录状态
  • 通过NavPathInfo传递原始跳转信息
  • 在登录成功后执行原始跳转
  1. 这种设计模式的优点:
  • 避免在各页面重复登录校验代码
  • 保持业务逻辑解耦
  • 提供统一的权限控制机制

这个案例展示了HarmonyOS Next中Navigation组件强大的路由拦截能力,为应用开发提供了灵活的权限控制方案。

回到顶部