前几天做vue-router的动态路由做的焦头烂额,最后还是解决了,故记录下来。
我这边通过后端接口给过来的菜单+角色来判断用户的路由添加,我先定义两种路由:需要权限校验的路由和基础路由
import layout from '@/layout/layout'
/**
* 需要权限校验的路由
*/
export const authRouters = [
{
path: '/layout',
name: 'layout',
component: layout,
redirect :'',
meta: {
},
children: [
{
path: '/detail',
name: 'detail',
meta: {
role: ['admin','project']
},
component: () => import('@/pages/detail')
},
{
path: '/list',
name: 'list',
meta: {
role: ['admin',]
},
component: () => import('@/pages/list')
}
]
},
// 404 注意:使用动态路由时,这个404跳转的路由一定要放在最后添加,不然会无限跳转到404
{
path: '*', redirect: '/404', hidden: true
}
]
/**
* 基础路由
*/
export const constantRouters = [
{
path: '/',
redirect: '/login'
},
{
path: '/login',
name: 'login',
component: () => import('@/pages/login')
},
//这里才是定义404页面的地方
{
path: '/404',
name: '404',
component: () => import('@/pages/error-page-404')
}
]
然后在vuex 定义一个action 用于装配路由: /store/permission.js
import {constantRouters, authRouters} from '@/router/routes'
import { cloneDeep } from 'lodash'
/**
* 过滤账户是否拥有某一个权限,并将菜单从加载列表移除
*/
function hasPermission(roles, route) {
if (route.meta && route.meta.role) {
let flag = -1
for (let i = 0, len = roles.length; i < len; i++) {
flag = route.meta.role.indexOf(roles[i])
if (flag >= 0) {
return true
}
}
return false
}
return true
}
/**
* 通过角色过滤路由
*/
function filterAsyncRouter(routerMap, roles) {
const accessedRouters = routerMap.filter(route => {
if (hasPermission(roles, route)) {
if (route.children && route.children.length) {
route.children = filterAsyncRouter(route.children, roles)
//默认指向第一个子路由
if(route.children.length > 0){
route.redirect = route.children[0].path
}
}
return true
}
return false
})
return accessedRouters
}
export default {
namespaced: true,
state: {
routers: constantRouters,
addRouters: [],
roles:[],
//用于判断用户是不是刷新过
routesAdded:false
},
mutations: {
SET_ROUTERS: (state, data) => {
state.addRouters = data;
state.routers = constantRouters.concat(data);
//路由加载好后将标识设置一下
state.routesAdded = true;
console.log('-----mutations---SET_ROUTERS----', data)
},
SET_ROLES: (state, data) => {
state.roles = data;
console.log('-----mutations---SET_ROLES----', data)
},
},
actions: {
/**
* @description 装配路由
*/
configRouter({ commit },userInfo){
return new Promise(resolve => {
let roles = ['admin'] //用户的角色列表,后端给或者自己逻辑判断都可以
//使用lodash 深拷贝方法,防止在验证过程中改变了 定义好的路由
const appRouter = filterAsyncRouter(cloneDeep(authRouters),roles)
commit('SET_ROUTERS', appRouter);
commit('SET_ROLES', roles);
// end
resolve(appRouter)
})
}
}
重点来了,我们会发现页面每次刷新都跳到404页面,那是因为刷新使整个页面状态重置了包括vue-router、vuex等。
我们可以在vue-router的前置守卫中加入一些逻辑来阻止这种问题发生 /router/index.js :
import Vue from 'vue'
import VueRouter from 'vue-router'
import store from '@/store/index'
import util from '@/libs/util.js'
// 路由数据
import { constantRouters } from './routes'
Vue.use(VueRouter)
// 导出路由 在 main.js 里使用
const router = new VueRouter({
mode: 'history',
routes: constantRouters
})
/**
* 路由拦截
* 权限验证
*/
router.beforeEach((to, from, next) => {
// 这里暂时将cookie里是否存有token作为验证是否登录的条件
if (util.cookies.get('token')) {
// 动态添加可访问路由表
let routesAdded = store.getters.routesAdded;
//判断用户是否刷新了页面
if(routesAdded){
if(to.path === '/login'){
next({ path: '/dashboard' });
}
next();
}else{
//获取用户信息
store.dispatch('/user/load').then(()=>{
//重新生成路由并装载
store.dispatch('/permission/configRouter',store.getters.userInfo).then(()=>{
router.addRoutes(store.getters.addRouters);
const redirect = decodeURIComponent(from.query.redirect || to.path)
if (to.path === redirect) {
// hack方法 确保addRoutes已完成 ,set the replace: true so the navigation will not leave a history record
next({ ...to, replace: true })
} else {
// 跳转到目的路由
next({ path: redirect})
}
})
}).catch((e) =>{
next({ path: '/' });
})
}
}else{
if(to.path !== '/login'){
next({ path: '/' });
}
next();
}
});
router.afterEach(to => {
// 更改标题
util.title(to.meta.title)
})
export default router
以上的思路就是每次路由跳转的时候就做一次判断,先判断用户登录的标示,再我们设置在vuex里的变量有没有被重置,以此来判断需不需要重新装载路由。当然这个逻辑只是一个初步的跑的通的例子,后面需要再加上一些判断逻辑也行。
===================================分割线-=======================================
后面实践发现,退出登录并没有做路由的清空,导致后面用户居然有前面用户的路由,这就很bug了,解决方法是每次登出,将路由重置:
//创建路由方法
const createRouter = () => new VueRouter({
mode: 'history',
routes: constantRouters
})
//初始化路由
const router = createRouter();
//重置路由:登出时调用
export function resetRouter() {
const newRouter = createRouter()
router.matcher = newRouter.matcher // the relevant part
}
export default router