前几天做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 = -1for (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);// endresolve(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 recordnext({ ...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