第一种方法(推荐):通过路由设置roles来控制权限,此时后端就只需要返回role就行。
假设使用vue2+vue-router+axios+vuex3+elementui来写,左侧菜单先递归展示在页面上:
<!-- navMenu -->
<el-aside class="left-side">
<!-- 左侧导航 default-active默认展开当前的路由$route.path 精确匹配样式exact 路由方式router -->
<el-menu :default-active="$route.path" exact router>
<!-- 递归动态展示里面的菜单 getRoutes[0].children根据child存的不同而取值不同 -->
<sideItem v-for="v in getRoutes[0].children" :key="v.path" :item="v" :path="v.path"/>
</el-menu>
<!-- sideItem -->
<!-- 处理菜单展示 没有子级 -->
<el-menu-item :index="path" v-if="!item.children">
<i class="el-icon-setting"></i>
<span slot="title">{{item.meta.title}}</span>
</el-menu-item>
<!-- 处理菜单展示 有子级 -->
<el-submenu :index="path" v-else>
<template slot="title">{{item.meta.title}}</template>
<!-- 有子级的时候递归调用sideItem,这时候就需要给组件设置name了 -->
<sideItem v-for="child in item.children" :key="child.path" :item="child" :path="getPath(child.path)"/>
</el-submenu>
// navMenu
<script>
import {mapGetters} from 'vuex'
import sideItem from './sideItem'
export default {
computed:{
...mapGetters(['getRoutes'])
},
components:{
sideItem
}
}
</script>
// sideItem
<script>
import _path from 'path'
export default {
name:'sideItem',
props:["item","path"],
methods:{
//_path.resolve('a/b','./c') 转换成 '/a/b/c'
getPath(url){
return _path.resolve(this.path,url);
}
}
}
</script>
全局路由守卫,如果token过期或者没有角色都返回login
import router from './router'
import store from './store'
router.beforeEach(async(to,from,next)=>{
if(to.path=='/login'){
next();
}else {
//如果有角色 可选连需要装插件@babel/plugin-proposal-optional-chaining
let getRoles = store?.getters?.roles?.length>0;
if(getRoles) {
next();
} else {// 没有角色
let {roles} = await store.dispatch('getInfo');// 获取详情信息,发送请求
let rolesName= roles.map(v=>v.name); // 取出角色
router.addRoutes(filterRoutes);// 动态添加
if(roles) {
next({path:to.path})// 有角色才会跳转到具体页面
}else {
next({path:'/login'})// 没有角色就直接回登录
}
}
}
})
store去操作数据和获取路由数据
// store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
// 引入user 模块
import permission from "./modules/permission"
const store = new Vuex.Store({
modules: {
user,
permission
}
})
export default store
// store/modules/permission.js
import {constantRoutes,asyncRoutes} from '@/router'
function filterAsyncRouter(routes,name){ //过滤角色返回当前用户的路由菜单
var data = routes.filter(route=>{
return route.meta && route.meta.roles && name.some(v=>route.meta.roles.includes(v))
})
return data;
}
const state={
routes:[] //动态路由
};
const getters={
getRoutes:state=>state.routes,//把路由返回出去给页面布局的地方展示菜单
};
const actions={
// 第二种处理 优化了下下,不过获取的地方可能需要改一改
generateRoutes({ commit }, roles) {
// 返回Promise 通过角色来过滤路由
return new Promise(resolve => {
let accessedRoutes
if (roles.includes('admin')) {
accessedRoutes = asyncRoutes || []
} else {
accessedRoutes = filterAsyncRouter(asyncRoutes, roles)
}
commit('SET_ROUTES', accessedRoutes)
resolve(accessedRoutes)
})
},
// 第一种处理
GENERATEROUTES({commit,state},rolesName){ //通过角色来过滤路由
return new Promise((resolve, reject) => {
let _routes; // 返回过滤后的路由
let home = constantRoutes.filter(v=>v.path=='/home')[0]; // 拿到固定路由下的home下的第一个
home.children = []; //每次都清除children
if(rolesName.includes('administrator')){ //管理员
home.children = asyncRoutes;// 直接赋值所有的异步路由
}else {// 不是管理员
let filterRouter = filterAsyncRouter(asyncRoutes,rolesName);// 过滤角色后再赋值给child
home.children = filterRouter;
};
_routes = [home] || [];// 返回出去的路由赋值给过滤后的路由菜单数据
commit('SET_ROUTES', _routes);// 提交给mutations去存在vuex里面
resolve(_routes)// Promise成功后返回处理好的路由
})
},
};
const mutations={
SET_ROUTES:(state,routes)=>{
state.routes = routes;// 把action处理好的路由数据覆盖原来的数据存在vuex里面
}
};
export default{
state,
getters,
mutations,
actions
}
router路由文件如下:
// router.js
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)
// 解决ElementUI导航栏中的vue-router在3.0版本以上重复点菜单报错问题
const originalPush = VueRouter.prototype.push
VueRouter.prototype.push = function push(location) {
return originalPush.call(this, location).catch(err => err)
}
export const constantRoutes = [ //常规配置 一个登陆页,一个404页,一个布局页,权限菜单处理的页面都会追加到布局页child里面
{
path: '/',
redirect:'/home'
},
{
path: '/not-found', //404页面
name: 'lonot-foundgin',
component: () => import('../views/notfound/notfound')
},
{
path: '/login', //登录
name: 'login',
component: () => import('../views/login/index')
},
{
path: '/home', //布局页
name: 'home',
redirect:'/index',
meta:{title:'首页',keepAlive:true , //需要缓存
roles: ['input','approve'] },
component: () => import('../layout/index'),
// children:[
// ]
}
]
// 异步路由 追加到布局页首页children里面的内容 根据后端返回的roles不同展示不同,这里就是需要确定roles的种类
export const asyncRoutes = [
{
path: '/index', //首页
name: 'index',
meta:{title:'首页',roles: ['input','approve']},
component: () => import('../views/home/index')// 懒加载,还有个异步加载require
},
{
path: '/loan-input', //入学申请
name: 'loan-input',
meta:{title:'入学申请',roles: ['input']},
component: () => import('../views/loan-input/index')
},
...
{
path: '/loan-approve', //入学审批
name: 'loan-approve',
meta:{title:'入学审批',roles: ['approve']},
component: () => import('../views/loan-approve/index'),
children:[
....
]
}
...
]
const router = new VueRouter({
routes:constantRoutes
})
export default router
首页通常可以缓存一下: keep-alive :include="想要缓存的组件"
<!-- 主体内容 keep-alive缓存-->
<keep-alive>
<router-view v-if="$route.meta.keepAlive"></router-view>
</keep-alive>
<router-view v-if="!$route.meta.keepAlive"></router-view>
第二种方法是,后端返回所有的路由信息(name,path,title,component,icon等),前端根据数据来展示,这样每次添加页面都需要往数据库加路由菜单信息,第一种方法角色种类通常也不会有啥大改变
// 数据同样存在vuex中,该处理的前端还是需要处理,还麻烦
menu.forEach(item=>{
routes.push({
name:item.title,
path:item.title,
meta:{ title:item.title},
component:()=>import('../前端路径/'+item.component)
})
})