鸿蒙Navigation拦截器实现页面跳转登录鉴权方案

我们在进行页面跳转时,很多情况下都得考虑登录状态问题,比如进入个人信息页面,下单交易页面等等。在这些场景下,通常在页面跳转前,会先判断下用户是否已经登录,若已登录,则跳转到相应的目标页面,若没有登录,则先跳转到登录页面,然后等着获取登录状态,若登录页面关闭时,能获取到已登录,则继续跳转到目标页,若用户取消了登录,则终止后面的行为。这样的处理通常会存在一些问题,例如很多页面都与登录状态相关,这样需要在大量的入口处增加登录逻辑判断。即使封装成一个方法,也需要关心是否登录成功,增加了逻辑的复杂性,而且登录页面先关闭,再打开新页面,页面切换动画也很不协调。

那么我们有没有一种更好的方案来处理登录鉴权问题呢?首先我们先梳理一下我们想要的效果,我们的目的是要跳转到相应的目标页,目标页是否需要先登录,我们是不太愿意关注的,最好是内部自己处理掉,,若没有登录,就先进行登录,登录成功后,继续后面的行为,外面使用的地方尽量做到无感知。总结一下就是进行页面跳转时,内部先判断一下状态,然后再进行后续的行为,而这恰好是Navigation拦截器的功能。

NavPathStack提供了setInterception方法,用于设置Navigation页面跳转拦截回调。该方法需要传一个NavigationInterception对象,该对象包含三个回调函数willShow,didShow和modeChange,我们在willShow页面即将显示时,进行拦截处理。先判断是否登录,没有登录,就重定向到登录页面,若已登录,则继续后续行为,不做拦截。示例如下

@Entry
@ComponentV2
struct Index {
  nav: NavPathStack = new NavPathStack()
  isLogin: boolean = false

  aboutToAppear(): void {
    this.nav.setInterception({
      willShow: (from: NavDestinationContext | NavBar, to: NavDestinationContext | NavBar,
        operation: NavigationOperation, isAnimated: boolean) => {
        if (typeof to === 'object') {
          if (isLogin) {
            AppRouter.popPage()
            AppRouter.jumpPage('login', undefined)
          }
        }
      }
    })
  }

  build() {
    Navigation(this.nav)
    .hideToolBar(true)
    .hideTitleBar(true)
    .height('100%')
    .width('100%')
  }
}

拦截器细节优化

如何判断是否需要进行拦截

在拦截器中,虽然我们可以进行拦截重定向跳转,但需要考虑的一个问题是什么情况下进行拦截,也就是哪些页面跳转时需要先判断下登录状态。首先想到的是弄一个数组,所有需要登录校验的页面都放到这个数组中。页面跳转时,我们只需要判断下目标页是否在数组中,就可以知道是否需要进行拦截校验登录了。其实思想是对的,只是我们有更简单的实现方式。在系统路由表中,有一个data字段,可以在这个字段中增加一个字段,是否需要登录,在拦截器中先获取目标页中这个参数,只要所有需要登录的页面,都添加了这个字段就可以了。我们以用户信息页为例,配置如下

{
  "routerMap": [
    {
      "name": "login",
      "pageSourceFile": "src/main/ets/pages/login/LoginPage.ets",
      "buildFunction": "loginBuilder"
    },
    {
      "name": "user_info",
      "pageSourceFile": "src/main/ets/pages/user/UserInfoPage.ets",
      "buildFunction": "userInfoBuilder",
      "data": {
        "needLogin": "1"
      }
    }
  ]
}

拦截器中获取该字段的方式如下

this.nav.setInterception({
  willShow: (from: NavDestinationContext | NavBar, to: NavDestinationContext | NavBar,
    operation: NavigationOperation, isAnimated: boolean) => {
    if (typeof to === 'object') {
      const data = (to as NavDestinationContext).getConfigInRouteMap()?.data
      if (data !== undefined && (data as object)['needLogin'] === '1' && !AppConstant.hasLogin) {
        AppRouter.popPage()
        AppRouter.jumpPage(Pages.login, undefined)
      }
    }
  }
})

登录成功后如何获取目标页和页面参数

登录成功后,我们如何知道要跳转到哪个目标页,以及跳转到目标页时所需要的参数呢?我们在跳转到登录页时可以增加2个参数targetPage和targetParam,分别表示要处理的目标页以及相应的参数,若targetPage的值为undefined,则说明登录成功后没有后续操作,若有值,则跳转到这个页面并把相应的参数传过去。在拦截器中,可以通过to.pathInfo.name获取到目标页的名称name以及通过to.pathInfo.param获取到目标页所需要的参数,并把它们赋值给登录页面的targetPage和targetParam就行了。

我们可以发现使用拦截器这种方式,完全符合我们最初的设想,外部调用时不用考虑是否要校验登录状态,由拦截器内部自己处理。登录后也是直接跳转到目标也,没有页面关闭效果。而且是否需要判断登录,只需配置一个字段就行了,非常方便。