本篇代碼示例: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)