一,情景再现
做前端开发的同学,大多都遇到过这种需求:页面菜单根据用户权限动态生成,一个常见的解决方案是:
前端初始化的时候,只挂载不需要权限路由,如登陆,注册等页面路由,然后等用户登录之后,后端返回当前用户的权限表,前端根据这个权限表遍历前端路由表,动态生成用户权限路由,然后使用vue-router提供的addRoutes,将权限路由表动态添加到路由实例中,整个过程大致如下:
import Vue from 'vue'
import VueRouter from 'vue-router'
import store from '@/store'
import axios from 'axios'
import baseAPIs from '@/api/common'
import { Message } from 'element-ui'
// 引入组件
const login = () => import('@/views/login/index.vue') // 登录页
Vue.use(VueRouter)
//避免vue-router在3.0版本以上重复点击菜单报错的问题
const originalPush = VueRouter.prototype.push
VueRouter.prototype.push = function push(location) {
return originalPush.call(this, location).catch(err => err)
}
let routes = [ //先把基本的登陆路由写好
{
path: '/',
redirect: '/login',
hidden: true,
},
{
path: '/login',
component: login,
name: '登录',
hidden: true,
},
]
let router = new VueRouter({
routes
})
/**
* 判断当前是否登录,未登录不能跳转路由
* 防止未登录状态下直接输入路由跳转
*/
/* eslint-disable no-new */
router.beforeEach((to, from, next) => {
if (to.path == '/login') {
localStorage.removeItem('USERINFO')
next()
}else{
if (!JSON.parse(localStorage.getItem('USERINFO'))) {
next({
path: '/login'
})
next()
} else { // 登录成功
// 如果资源为空得话
if (store.state.common.menus.length == 0) {
// 请求资源,生成动态路由
let url = `${baseAPIs.serverOne}/B2CMainPlat_13_SLHZ?CCB_IBSVersion=V6&PT_STYLE=1&PT_LANGUAGE=CN&TXCODE=VAUM99`
let params = {
txnFcnNo: '1',
token: localStorage.getItem('USERINFO')?JSON.parse(localStorage.getItem('USERINFO')).token:''
}
axios.get(url,{params: params}).then(response => {
if (response.data.CODE == 3){
Message({
message: response.data.MSG,
type: 'error',
duration: 3 * 1000,
})
setTimeout(()=>{
router.push({ path: '/login' })
location.reload()
},1000)
} else {
store.state.common.menus = response.data.MENUS //也就是说,不同权限的用户登陆,所返回展示的路由菜单是不一样的
getRouters(response.data.MENUS) //设置路由
addComps(rts) //在路由中懒加载对应要渲染的组件
let homeRts = [{
path: '/home',
component: (resolve) => require(['@/views/home/index.vue'], resolve),
children: [...rts, //从后台获取到的动态路由,展开后作为home页面的二级路由
{ path: '', redirect: rts[0].path },
{ path: 'consumeRule/couponDetail', name: '优惠券管理', component: resolve => require(['@/views/consumeRule/pages/couponDetail.vue'], resolve) },
{ path: 'error', name: '错误', component: resolve => require(['@/views/error/index.vue'], resolve) },
] //这三个是基本的都会有的路由和菜单
}]
router.addRoutes(homeRts) //将后台返回并配置好的路由,添加到路由配置中去,也就是home路由是和login并列的
}
}).catch(error => {
console.log(error)
})
}
next()
}
}
})
let rts = []
const getRouters = (menus) => {
for (let i = 0; i < menus.length; i++) {
if (menus[i].type == 1) {
let rt = {
name: menus[i].name,
path: menus[i].url
}
store.state.common.paths.push('/home/'+menus[i].url) //这里同样报错动态路由的url
rts.push(rt) //这里就将每个菜单的路由push进去了
}
if (menus[i].permission) { //查看本菜单,该登陆用用户的权限,
store.state.common.userInfo.permissions.push(menus[i].permission)
//有权限,就把所有该用户的权限保存起来,到时候该用户发请求,就利用这个判断该用户是否有权限完成该操作
}
if (menus[i].children && menus[i].children.length > 0) { //当存在子菜单时
getRouters(menus[i].children) //递归实现子菜单的路由设置
}
}
}
//路由懒加载对应的渲染组件
const addComps = (routres) => {
return routres.forEach(rt => {
rt.component = (resolve) => require([`@/views/${rt.path}`], resolve)
})
}
export default router
二,原理解析
对应的vuex中存储的: