App左上角返回逻辑解析

428 阅读9分钟

前言

这篇文章是我关于webview记录的第四篇文章,前三篇是针对webview的一个通用介绍。 而这篇文章是我个人在项目中针对业务所提出的页面返回需求所给出的最终解决方案。可能不具备通用性,但也是自己的一点理解,所以决定作为webview有关的第四篇记录下来。

项目背景介绍

项目框架及版本

angular 1.8.2

别惊讶,我没打错字也没打错符号,别问我为什么,我来之前也没想到,只能说成年人的生活就是这样的。招聘时候面试的时候是react进来做angular,虽然以前没做过但是框架间都是互通的,也就凑乎干了起来。

但是由于版本太低,很多新兴技术和方法都不具备,造成了开发的不便和问题。和这篇文章相关的就是 我们有项目路由配置文件,但是只支持router.state.go(), 以及跳转时携带参数到页面解析参数。

不具备返回以及常规history所具备的方法。

业务背景

业务共分了两个迭代开发,第一个迭代将所有的页面开发完毕后交付给业务。业务不满意想按照他的想法调整,于是第二个迭代我做了页面返回逻辑的设计实现。

在第一个迭代中共做了四个页面,分别是home(保险主页)insure(投保页)record(领取记录)result(投保结果)四个页面。业务逻辑大概如下:

page.JPEG

进入页面判断卡类型

打开时首先会进入 home ,调用查询卡类型接口。卡类型有三种主卡(Master)副卡(ACCESSORY)交叉持卡(BOTH),其中如果是纯卡人(MASTER | ACCESSORY)直接调用下一个开通状态接口,如果是BOTH则弹出选择弹窗,让用户选择其中一种。

判断开通状态

然后将用户状态传入第二个查询开通状态的接口,如果是OPEN,则显示当前页面(HOME)。如果是NOT_OPEN则采用路由跳转到insure投保页。

所以保险主页home做了三件事:

  1. 调用两个接口
  2. 显示both时的弹窗
  3. 显示已开通页面home

页面之间跳转逻辑

如图在保险主页(home)有一个 添加被保人/保险 的按钮,通过他可以跳转到投保(insure),是否展示这个按钮是根据后端返回字段来决定的。除此之外,从投保页(insure)可以跳转到结果页(result)、从保险主页可以跳转到领取记录页面(record)。

因为第一个迭代开发业务未明确返回需求,所以做了以上四个页面以及流程,各个页面之间都是使用路由 state.go()的方式跳转。因此无论处于哪个页面,点击左上角返回按钮都是关闭整个页面,回到最初进入页面的位置。

之所以最开始这么做,是因为大部分其他项目都是采用这种解决方式。

业务需求

在当前场景下,无论是在四个页面中的哪一个,点击返回都是全部关闭,业务认为非常影响体验。所以提了以下需求:

  1. 从领取记录页(record)可以返回到保险主页(home);
  2. 如果是从保险主页(home)点击 添加被保人/保险 跳转到投保页(insure),可以返回到home;
  3. 投保成功时(result)点返回可以回到保险主页(home),投保失败时(result)点返回回到投保页(insure)重复投保;
  4. 针对交叉持卡both,在弹窗选择身份进入对应页面后,可以返回到弹窗重新选择身份;
  5. 交叉持卡选择身份查询开通状态进入 home | insure 后,后续逻辑与 1、2、3条相同。

解决主要思路

在当前业务背景下,我主要利用了以下几点做了改造:

  1. 在一个webview中打开另一个webview时,会覆盖到其上面。当关闭这个webview时位于他下方的webview不会有任何变化,仍然展示打开之前的状态**(不会重复刷新接口)**。

webview.JPEG

  1. 一个SPA内的所有路由都位于一个webview内,不同webview之间的同一路由不存在相互关联性。

SPA.JPEG

  1. 利用全屏组件对返回按钮的返回逻辑做劫持处理。

quan.JPEG

  1. 利用webviewDidShow处理返回接口刷新

webviewDidShow方法是在本层webview注册此方法后,当本层webview的上层webview被关闭后会触发这个方法所注册的事件。所以他通常用于从一个webview返回到另一个webview时,刷新当前页面的接口。

具体解决方式

拆分保险主页home

根据第4、5条需求可知当卡类型为both时,在对应开通页面可以回到弹窗。因此弹窗不能和home绑定在一起,而是要单独独立出来成为一个新的页面,我把这个页面称为交叉页(both)。

拆分后home只需调用查询已开通状态的初始化接口渲染已开通的页面,而both负责根据用户类型决定是否展示弹窗和根据开通状态决定跳转到home还是insure。

home到record、insure跳转方式修改

home到recordhome到insure 这两种方式由原来的 state.go()变更为webview跳转,这样在点击返回时默认关闭当前webview就可以显示出原来的页面,而不会重新刷新home的接口。

result变更为全屏组件

全屏组件就类似于一个覆盖全屏的弹窗,在投保页insure调用投保接口成功后显示出来,点击左上角返回时做劫持后关闭。之所以这样做是因为以下几点:

  1. 结果页result是最后一个页面,他所涉及到的前后跳转逻辑较为简单;
  2. 失败时返回insure不希望刷新insure页面,而从insure到result重开一个webview过于浪费,变为组件的显隐则简单很多;
  3. 投保结果是在投保页跳转到结果页之前获取到的,然后通过路由方式传递给result,result页面没有任何的接口调用和复杂逻辑。不涉及数据双向流动和复杂交互,是一个纯纯的html展示页面;
  4. 做成全屏组件对左上角返回事件进行拦截,对于处理成功和失败分别返回不同页面非常方便。

both到home、insure跳转修改

跳转方式由查询开通状态后通过state.go()跳转home | insure,变更为当纯卡时state.go()、当交叉卡时改为webview跳转。

当投保成功后跳转回both刷新开通状态接口后,跳转方式都变为state.go()。之所以这么处理:

是因为纯卡时的返回只在一个webview内,而交叉卡因为要返回到弹窗所以必须要新开一个webview。而当从结果页返回时前面的webview层级不应该被改变,所以使用路由跳转。

用户行为枚举

enum.JPEG

行为注解:

  1. 左侧both表示both页面,没带弹窗的表示纯卡,只调用了卡类型查询和开通状态查询接口,不展示弹窗;both弹窗表示卡类型为both,展示交叉卡选择弹窗。
  2. (无被保人/保险)表示不可以从home跳转到insure,有被保人/保险表示可以。
  3. 红色文字表示页面间传递的参数。
  4. webviewDidShow为关闭上层webview时本层webview事件的回调

参数注解:

  1. resultType表示当前用户的类型,因为投保成功后可以拿到当前用户的类型。在返回到both后不需要重复调用查询类型接口,因此在进入both时,通过resultType来选择是调用卡类型查询还是直接调开通状态查询。
  2. fromHome表示当前用户是否由home页进入到insure页,如果是的话在投保成功后只需要关闭webview就可以回到home,而如果不是的话就要采用state.go()跳转。
  3. fromBoth表示是否是通过交叉卡弹窗进入的home | insure,如果是的话跳转需要新开webview,如果不是则采用state,go()跳转。

代码实现

主入口页面both: both.js

$onInit() {
    // 从路由中将resultType解出来,判断是否从结果页返回
    this.resultType = $stateParams.userType;
    if(this.resultType) {
        // 存在表示从结果页返回,直接调用查询开通状态接口
        this.userType = this.resultType;
        this.checkUserQualification(this.userType);
    } else {
        // 不存在表示第一次进入调用查询卡类型接口,调用成功后再调开通状态接口
        this.checkUserAccountType();
    }
}

/**
 * 跳转到目标页面  保险主页home | 投保页insure
 * @param userType 用户类型
 * @param openStatus 开通状态
 * @param fromBoth  是否是从both弹窗选择后调用的接口 true | false
 */
goTargetPage(userType, openStatus, fromBoth) {
    if(fromBoth) {
        // 如果是从both弹窗跳转(交叉卡)则新开webview
        let env = isUat ? UAT : PRO;
        let routerPage = openStatus === 'OPEN' ? '保险主页' : '投保页';
        this.util.openWebview(`${env}${routerPage}?userType=${userType}`);
    } else {
        // 如果是纯卡则直接路由跳转
        this.$state.go(
            openStatus === 'OPEN' ? '保险主页' : '投保页',
            {userType}
        );
    }
}

/*
* 当纯卡用户第一次进入页面查询开通状态后调用goTargetPage,fromBoth传入false;
* 当交叉卡用户通过both弹窗选完卡类型后调用开通状态接口时,fromBoth传入true;
* 当用户投保成功返回both页面重新调用开通状态查询接口时,fromBoth传入false;
*/

保险主页: home.js

// 上层webview关闭返回时,本层webview的回调。用于从结果页返回到保险主页时刷新页面初始化接口
callbackInit = () => {
    this.$window.webViewDidShow = () => {
        // 如果投保页的变量不是true则不是从投保页返回的直接return,避免事件重复触发
        if(!this.toInsure) return;
        // 刷新初始化接口
        this.LoginV2Service.clientLogin(loginSuccess => {
            if (loginSuccess) {
                console.log('--开始刷新初始化接口--')
                this.getHomePageInfo(this.userType);
                this.toInsure = false;
            }
        }, true);
    };
    this.NativeLink.registerWebViewDidShow();
}
        
 // 添加被保人/保险点击事件
addInsurePerson() {
    // 跳转insure前将变量置为true
    this.toInsure = true;
    const env = isUat ? UAT : PRO;
    // 当点击按钮时从home跳转到insure,将fromHome传入true
    this.util.openWebview(`${env}投保页?userType=${this.userType}&fromHome=true`);
}

投保页: insure.js

在insure页面解析出home页传入的fromHome,在调用投保接口成功后打开结果页时再传递给结果页,这里就不展示了。

结果页: result.js

$onInit() {
   // 从路由中拿到是否是从home页面进入到insure,又进入到result
  this.fromHome = this.params.fromHome;
}

// 左上角返回事件劫持
registerBack = () => {
    if(this.result === 'SUCCESS') {
        // 投保成功时回到保险主页home
        if(this.fromHome) {
            // 如果是从home页来的则关闭当前webview,这样就回到了从home到insure新开的webview之前,同时触发home的callbackInit函数刷新
            this.NativeLink.closeWebview();
        } else {
            // 将当前投保成功的卡类型传回给both页,重新调用开通状态接口进入home | insure
            this.$state.go('both', {
                userType: this.userType,
            });
        }
    } else {
        // 投保失败直接关闭全屏组件回到投保页insure
        if (this.showResult) {
            // 如果当前全屏组件是打开状态说明在结果页,就把全屏组件关掉
            this.showResult = false;
            this.$scope.$apply();
        } else{
            // 如果当前全屏组件没打开就说明在insure页面,执行默认的关闭当前webview方法
            this.NativeLink.closeWebview();
        }
    }
}