管理后台的权限控制

222 阅读3分钟

第一种方法(推荐):通过路由设置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)
    })
})