本篇代码示例:github
前提:不关注移动端浏览器的前进事件
涵盖功能:
1,管理路由的历史记录
2,切页动画的实现
3,处理流程类页面的回退事件
描述:
流程类页面的回退事件的解释:
以注册页面举例, 为了拉长整个流程,把输入项分开。以下是整个流程(懒得画图,大家将就看)
1.首页 - 点击首页注册按钮可进入注册输入页
2.注册输入页 - 输入姓名
3.注册输入页 - 输入电话
4.提交注册页 - 点击提交按钮,获取注册状态(成功或者失败),如果成功,则跳转到指定页面(假定是产品页)
5.产品页
以上是一个简单的注册的流程,现在写一下业务场景和需求
1,注册未完成时
注册未完成时,页面可能处于 1,2,3,4 这几个页面,此时,点击返回(无论是页面本身自带的返回,还是浏览器或者设备-[如安卓]的返回键触发的返回) 均可回到上一页
2.注册完成时
指注册成功并已经进入 5 页面 ,此时,点击返回(浏览器或者设备-[如安卓]的返回键触发的返回),回到 4 页面之后,要求直接跳回 1 页面
准备工作
1.切页动画的实现
切页动画使用vue自带的 过渡效果 transition 实现, 这个是附带的,这里不讲,大家自己看代码,代码如下
<template> <div class="wapper"> <transition :name="transition"> <router-view class="child-view"></router-view> </transition> </div> </template> <script> export default { name: 'tableDemo', data () { return { transition: 'slide-left' } }, methods: { doSomething (e) { this.$router.delLastRouter() // 页面回退时,路由做对应的处理 } }, created () { window.addEventListener('popstate', this.doSomething, false) // 监听回退事件 }, beforeRouteUpdate (to, from, next) { window.document.title = to.meta.title let isBack = this.$router.isBack if (isBack) { this.transition = 'slide-right' next() } else { // 进入页面之前判断是否是进入流程页 if (from.meta.isStartPage) { this.$router.setProcessStatus(to.meta.group, false) } this.transition = 'slide-left' next() } } } </script> <style lang="scss"> .wapper { position: relative; width: 100%; height: 100%;} .child-view { position: absolute; width:100%; height: 100%;} .slide-left-leave { -webkit-transform: translate(0, 0); transform: translate(0, 0);} .slide-left-leave-active { transition: all .4s cubic-bezier(.55,0,.1,1);} .slide-left-leave-to { -webkit-transform: translate(100%, 0); transform: translate(100%, 0);} .slide-left-enter { -webkit-transform: translate(-100%, 0); transform: translate(-100%, 0);} .slide-left-enter-active { transition: all .4s cubic-bezier(.55,0,.1,1);} .slide-left-enter-to { -webkit-transform: translate(0, 0); transform: translate(0, 0);} .slide-right-leave { -webkit-transform: translate(0, 0); transform: translate(0, 0);} .slide-right-leave-active { transition: all .4s cubic-bezier(.55,0,.1,1);} .slide-right-leave-to { -webkit-transform: translate(-100%, 0); transform: translate(-100%, 0);} .slide-right-enter { -webkit-transform: translate(100%, 0); transform: translate(100%, 0);} .slide-right-enter-active { transition: all .4s cubic-bezier(.55,0,.1,1);} .slide-right-enter-to { -webkit-transform: translate(0, 0); transform: translate(0, 0);} </style>
2.路由的管理
1. 为vue-router设置一些状态
Router.prototype.routerArray = []; // 存储过往的路由信息
Router.prototype.processArray = []; // 存储流程页的状态
Router.prototype.isBack = false; // 是否是返回,动画使用
2. 使用 beforeRouteUpdate 来检查路由的变化 这里要注意的事情是 beforeRouteUpdate 只针对子路由生效 ,所以这段代码放在父路由指向的页面中
在这个页面,我们通过 this.$router.isBack 来区分页面是前进还是后退,并调整对应的动画效果。
beforeRouteUpdate (to, from, next) { window.document.title = to.meta.title let isBack = this.$router.isBack if (isBack) { this.transition = 'slide-right' next() } else { // 进入页面之前判断是否是进入流程页 if (from.meta.isStartPage) { this.$router.setProcessStatus(to.meta.group, false) } this.transition = 'slide-left' next() } }
3. 重写路由的push方法 在push方法中 如果push指向的页面已经存在于历史记录中,则当做返回处理, 前进则为 历史记录添加一条数据
// 修改 Router本身的push, 跳转之前做一些判断 let lPush = Router.prototype.push; Router.prototype.push = function (location, onComplete, onAbort) { let lGoCount = this.havRouterHistory(location) if (lGoCount < 0) { this.isBack = true this.go(lGoCount); } else { this.isBack = false this.routerArray.push(this.history.current) lPush.call(this, location, onComplete, onAbort) } };
4.重写路由的go方法
// 重写 Router 本身的 go, 回退的同时要清空记录中的数据 let lGo = Router.prototype.go Router.prototype.go = function (n) { if (n > 0) { lGo.call(this, n); return; } let lLen = 0 - n; lLen = this.routerArray.length > lLen ? lLen : this.routerArray.length; let lStart = this.routerArray.length - lLen; this.routerArray.splice(lStart, lLen); lGo.call(this, n); };
5.判断 push 时 指向的页面是否存在于历史记录中
// 判断历史记录中是否存在将要跳转的路由,如果有,执行回退操作 Router.prototype.havRouterHistory = function (location) { // debugger let lPath = location.path; let lName = location.name; let lLen = this.routerArray.length; for (let i = 0; i < lLen; i++) { if (this.routerArray[i].path === lPath || this.routerArray[i].name === lName ) { return i - lLen; // 存在则返回应该回退的步数,注意回退的步数是负值 } } return 1 // 如果存在则必然小于0,所以这里写返回1代表不存在没有问题 };
6.浏览器或者设备物理键触发回退
在父路由页面设置全局的事件监听,在触发事件的时候处理路由
methods: { doSomething (e) { this.$router.delLastRouter() // 页面回退时,路由做对应的处理 } }, created () { window.addEventListener('popstate', this.doSomething, false) // 监听回退事件 },
// 浏览器回退时,清除最后一条路由数据 这串代码在 路由配置文件 中 Router.prototype.delLastRouter = function () { this.routerArray.pop() }
7. 路由本身的回退事件 无需对路由的back事件做处理,因为vue-router本身的back 调用的 go(-1) go函数我们已经重写过了
8.根据流程相关页面,对路由做相对应的配置
isStartPage: true // 表示这是一个流程入口页,相当于上面的 页面1
group // group分组,代表了一组页面同属于一个流程中
/** * 返回效果的Demo */ export default [ { path: '/backDemoIndex', name: 'backDemoIndex', component: r => require.ensure([], () => r(require('../../Page/BackDemo/index')), 'BackDemo/index'), meta: { title: '入口选择页面' } }, { path: '/backAPage', name: 'backAPage', component: r => require.ensure([], () => r(require('../../Page/BackDemo/aPage')), 'BackDemo/aPage'), meta: { title: '操作起始页面aaaaa', isStartPage: true } }, { path: '/backAPageA', name: 'backAPageA', component: r => require.ensure([], () => r(require('../../Page/BackDemo/aPagea')), 'BackDemo/aPagea'), meta: { title: '操作起始页面bbb', isStartPage: true } }, { path: '/backAPageB', name: 'backAPageB', component: r => require.ensure([], () => r(require('../../Page/BackDemo/aPageb')), 'BackDemo/aPageb'), meta: { title: '操作起始页面CCC', isStartPage: true } }, { path: '/backBPage', name: 'backBPage', component: r => require.ensure([], () => r(require('../../Page/BackDemo/bPage')), 'BackDemo/bPage'), meta: { title: '第一个输入页面', group: 'backDemo' } }, { path: '/backCPage', name: 'backCPage', component: r => require.ensure([], () => r(require('../../Page/BackDemo/cPage')), 'BackDemo/cPage'), meta: { title: '第二个输入页面', group: 'backDemo' } }, { path: '/backDPage', name: 'backDPage', component: r => require.ensure([], () => r(require('../../Page/BackDemo/dPage')), 'BackDemo/dPage'), meta: { title: '结果页面', group: 'backDemo' } }, { path: '/other', name: 'other', component: r => require.ensure([], () => r(require('../../Page/BackDemo/other')), 'BackDemo/other'), meta: { title: '结果页面' } } ]
9.在流程起始页进入流程页是,修改流程的状态
this.$router.setProcessStatus( ‘backDemo’, false) // groupName 流程分组名称 status 流程状态 true为已经完成 false 为 未完成
// 修改一个流程的状态 Router.prototype.setProcessStatus = function (groupName, status) { this.processArray[groupName] = status };
10.在每个流程页 检查流程的完成情况
this.$router.onPageLoad()
// 找到最近的入口页面 Router.prototype.findInsterPage = function () { let lLen = this.routerArray.length; for (let i = lLen - 1; i >= 0; i--) { if (this.routerArray[i].meta.isStartPage) { return i - lLen } } return 1; // 如果存在则必然小于0,所以这里写返回1代表不存在没有问题 } // 如果进入一个流程页,并且该页面的状态已经是完成,则应该回退到最近的流程入口页面,或者 跳转到指定的页面 Router.prototype.onPageLoad = function (query) { // debugger if (this.history.current.meta.group) { let lNowRouter = this.history.current.meta.group let lStatus = this.processArray[lNowRouter] if (lStatus) { let lRes = this.findInsterPage() if (lRes < 0) { this.go(lRes) } } } };
11. 在流程结果处理页面,更改流程状态为完成 this.$router.setProcessStatus( ‘backDemo’, true)