前后端分离开发之权限篇及 VUE路由守卫 next() / next({ ...to, replace: true }) / next(‘/‘) 说明

写项目过程中在权限部分遇到了不少有关next() 的问题,解决完之后发现对于权限篇的整体逻辑清晰不少,在这里记录一下。
首先是路由守卫,是不是感觉简简单单

beforeEach((to, from, next) => {
	to // 要去的路由
	from // 当前路由
	next() // 放行的意思
}

但是在看别的项目时常常能看到next('/logon')next(to) 或者 next({ ...to, replace: true }) 这又是啥意思呢

其实在路由守卫中,只有next()是放行,其他的诸如:next('/logon')next(to)或者 next({ ...to, replace: true })都不是放行,而是:中断当前导航,执行新的导航

可以这么理解:

next() 是放行,但是如果next()里有参数的话,next()就像被重载一样,就有了不同的功能。

而对于上面说的中断当前导航,执行新的导航打个比方:

现在我有一个守卫,在守卫中我使用next('/logon'),肯定有同学认为是会直接跳转到/logon路由:

beforeEach((to, from, next) => {
  next('/logon')
}

然而年轻人不讲武德,执行时需要这么看:

beforeEach((to, from, next) => {
  beforeEach(('/logon', from, next) => {
  	 beforeEach(('/logon', from, next) => {
  	 	 beforeEach(('/logon', from, next) => {
  	 	 	beforeEac...  // 一直循环下去...... , 因为我们没有使用 next() 放行
 		}
 	 }
  }
}

如果把这个守卫改一下,当我在地址栏输入/home

beforeEach((to, from, next) => {
   if(to.path === '/home') {
   	next('/logon')
   } else {
    // 如果要去的地方不是 /home , 就放行
   	next()
   }
}

我本来要去/home路由,因此执行了第一次 beforeEach((to, from, next)

但是这个路由守卫中判断了如果要去的地方是'/home',就执行next('/logon')

所以想要访问/home可以这么看

beforeEach((to, from, next) => {
   beforeEach(('/logon', from, next) => {
     next()  // 现在要去的地方不是 /home , 因此放行
   }
}
正以为如此很多人在使用动态添加路由addRoutes()会遇到下面的情况:

在addRoutes()之后第一次访问被添加的路由会白屏,这是因为刚刚addRoutes()就立刻访问被添加的路由,然而此时addRoutes()没有执行结束,因而找不到刚刚被添加的路由导致白屏。因此需要从新访问一次路由才行。

该如何解决这个问题 ?
此时就要使用next({ ...to, replace: true })来确保addRoutes()时动态添加的路由已经被完全加载上去。

next({ ...to, replace: true })中的replace: true只是一个设置信息,告诉VUE本次操作后,不能通过浏览器后退按钮,返回前一个路由。

因此next({ ...to, replace: true })可以写成next({ ...to }),不过你应该不希望用户在addRoutes()还没有完成的时候,可以点击浏览器回退按钮搞事情吧。

其实next({ ...to })的执行很简单,它会判断:

如果参数to不能找到对应的路由的话,就再执行一次beforeEach((to, from, next)直到其中的next({ ...to})能找到对应的路由为止。

也就是说此时addRoutes()已经完成啦,找到对应的路由之后,接下来将执行前往对应路由的beforeEach((to, from, next) ,因此需要用代码来判断这一次是否就是前往对应路由的beforeEach((to, from, next),如果是,就执行next()放行。

如果守卫中没有正确的放行出口的话,会一直next({ ...to})进入死循环 !!!

因此你还需要确保在当addRoutes()已经完成时,所执行到的这一次beforeEach((to, from, next)中有一个正确的next()方向出口。

因此想实现动态添加路由的操作的话,代码应该是这样的:
router.beforeEach((to, from, next) => {
 const token = sessionStorage.getItem('access_token')
 // 存在 token 说明已经登录
 if (token) {
   // 登录过就不能访问登录界面,需要中断这一次路由守卫,执行下一次路由守卫,并且下一次守卫的to是主页'
   if (to.path === '/login') {
     next({ path: '/' })
   }
   // 保存在store中路由不为空则放行 (如果执行了刷新操作,则 store 里的路由为空,此时需要重新添加路由)
   if (store.getters.getRoutes.length || to.name != null) {
     //放行
     next()
   } else {
     // 将路由添加到 store 中,用来标记已添加动态路由
     store.commit('ADD_ROUTER', '需要添加的路由')
     router.addRoutes('需要添加的路由')
     // 如果 addRoutes 并未完成,路由守卫会一层一层的执行执行,直到 addRoutes 完成,找到对应的路由
     next({ ...to, replace: true })
   }
 } else {
   // 未登录时,注意 :在这里也许你的项目不只有 logon 不需要登录 ,register 等其他不需要登录的页面也需要处理
   if (to.path !== '/logon') {
     next({ path: '/logon' })
   } else {
     next()
   }
 }

核心的权限判断方法到这里就基本已经实现了。逻辑大致如此,至于源码,贴到下面~

import router from './routers';
import store from '@/store';
import NProgress from 'nprogress'; // progress bar
import 'nprogress/nprogress.css';// progress bar style
import { getToken } from '@/utils/auth'; // getToken from cookie
import { buildMenus } from '@api/menu';
import { filterAsyncRouter } from '@/store/modules/permission';

NProgress.configure({ showSpinner: false });// NProgress Configuration

const whiteList = ['/login', '/register', '/about', '/page/home'];      // no redirect whitelist

router.beforeEach((to, from, next) => {
    NProgress.start();
    if (getToken()) {
    // 已登录且要跳转的页面是登录页
        if (to.path === '/login') {
            next({ path: '/' });
            NProgress.done();
        } else {
            if (judgeObj(store.getters.user)) { // 判断当前用户是否已拉取完user_info信息
                store.dispatch('user/GetInfo').then(res => { // 拉取user_info
                    // 动态路由,拉取菜单
                    loadMenus(next, to);
                }).catch((err) => {
                    console.log(err);
                    store.dispatch('user/LogOut').then(() => {
                        location.reload(); // 为了重新实例化vue-router对象 避免bug
                    });
                });
                // 登录时未拉取 菜单,在此处拉取
            } else if (store.getters.loadMenus) {
                // 修改成false,防止死循环
                store.dispatch('user/updateLoadMenus').then(res => {});
                loadMenus(next, to);
            } else {
                next();
            }
        }
    } else {
    /* has no token*/
        console.log('no token', to.path);
        if (whiteList.indexOf(to.path) !== -1) { // 在免登录白名单,直接进入
            next();
        } else {
            next(`/page/home?redirect=${to.fullPath}`); // 否则全部重定向到首页
            NProgress.done();
        }
    }
});

/**
 * 判断是否为空对象
 * @param {*} obj
 * @returns
 */
function judgeObj(obj) {
    if (obj && Object.keys(obj).length === 0) return true;
    else return false;
}

export const loadMenus = (next, to) => {
    // 通过接口动态获取数据库保存的路由表
    buildMenus().then(res => {
        if (res.data) {
        //filterAsyncRouter()  根据需求自己写的方法,遍历后台传来的路由字符串,转换为组件对象
            const asyncRouter = filterAsyncRouter(res.data);
            asyncRouter.push({ path: '*', redirect: '/404', hidden: true });
            store.dispatch('permission/GenerateRoutes', asyncRouter).then(() => { // 存储路由
                router.addRoutes(asyncRouter); // 动态添加可访问路由表
                next({ ...to, replace: true });
            });
        }
    });
};

router.afterEach(() => {
    NProgress.done(); // finish progress bar
});

export default router;

参考:
https://blog.csdn.net/qq_41912398/article/details/109231418

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值