前言

vue-admin-template是一个最基础的后台管理模板,只包含了一个后台需要最基础的东西,如果clone的是它的master分支,是没有权限管理的,只有完整版vue-element-admin有这个功能,但是为了小小的一个权限管理而用比较复杂的有点得不偿失。

我在网上找了一堆教程和资料,发现要么说的很乱,要么说的不全,最后连个完整代码都不让我白嫖(bushi)。自己复制粘贴过去都实现不出来,仔细查看发现人家写的教程漏了一写步骤/代码,而且还有bug(服了这些老六)。

在自己摸索了和看了花裤衩大佬的文章后,解决了一些bug自己实现出来了,代码中也有详细注释。完整代码放文末给大家了,大家记得给我star再走(不然小拳拳锤你胸口)。

权限管理?动态路由?

现在开发后台管理系统项目经常有权限管理的需求,权限管理其实就是根据不同的角色权限显示不同的路由,而其中的关键就是动态路由router.addRoutes
实现权限验证的基本思路就是:

  • 用户登录,通过token获取用户对应的 role
  • 动态根据用户的 role 算出其对应有权限的路由
  • 通过 router.addRoutes 动态挂载这些路由

以上步骤实现的核心是routervuex,下面就详细介绍如何实现(按代码执行逻辑倒推

具体实现

  1. 创建vue实例的时候将vue-router挂载,但这个时候vue-router挂载一些登录或者不用权限的公用的页面。
  2. 当用户登录后,获取用role,将role和路由表每个页面的需要的权限作比较,生成最终用户可访问的路由表。
  3. 调用router.addRoutes(store.getters.addRouters)添加用户可访问的路由。
  4. 使用vuex管理路由表,根据vuex中可访问的路由渲染侧边栏组件

本段转载自vue-element-admin的作者:花裤衩

1.router.js路由表

首先我们实现router.js路由表,
一共有两个路由表,

  • 一个是constantRoutes,这个用来放没有权限要求的页面,每个角色都可以访问,比如首页,登录页;
  • 一个是asyncRoutes 动态需要根据权限加载的路由表 。meta里面的roles就存放了页面需要的权限,这些页面只有roles数组里面的角色才能看到。

注意:404一定要放最后面,不然都会重定向到404
src/router/index.js代码如下:

import Vue from 'vue'
import Router from 'vue-router'Vue.use(Router)/* Layout */
import Layout from '@/layout'
/*** constantRoutes* 没有权限要求的基本页面*所有角色都可以访问如首页和登录页和一些不用权限的公用页面*/
export const constantRoutes = [{path: '/login',component: () => import('@/views/login/index'),hidden: true},{path: '/404',component: () => import('@/views/404'),hidden: true},{path: '/',component: Layout,redirect: '/dashboard',children: [{path: 'dashboard',name: 'Dashboard',component: () => import('@/views/dashboard/index'),meta: {title: 'Dashboard',icon: 'dashboard',}}]},
]
//异步挂载的路由
//动态需要根据权限加载的路由表
export const asyncRoutes = [{path: '/example',component: Layout,redirect: '/example/table',name: 'Example',alwaysShow: true,meta: {title: 'Example',icon: 'el-icon-s-help',},children: [{path: 'table',name: 'Table',component: () => import('@/views/table/index'),meta: {title: 'Table',icon: 'table',roles: ['editor']}},{path: 'tree',name: 'Tree',component: () => import('@/views/tree/index'),meta: {title: 'Tree',icon: 'tree',roles: ['admin', 'editor']}}]},{path: '/form',component: Layout,children: [{path: 'index',name: 'Form',component: () => import('@/views/form/index'),meta: {title: 'Form',icon: 'form',roles: ['editor']}}]},{path: '/nested',component: Layout,redirect: '/nested/menu1',alwaysShow: true,name: 'Nested',meta: {title: 'Nested',icon: 'nested',},children: [{path: 'menu1',component: () => import('@/views/nested/menu1/index'), // Parent router-viewname: 'Menu1',meta: {title: 'Menu1',roles: ['admin']},children: [{path: 'menu1-1',component: () => import('@/views/nested/menu1/menu1-1'),name: 'Menu1-1',meta: {title: 'Menu1-1'}},{path: 'menu1-2',component: () => import('@/views/nested/menu1/menu1-2'),name: 'Menu1-2',meta: {title: 'Menu1-2'},children: [{path: 'menu1-2-1',component: () => import('@/views/nested/menu1/menu1-2/menu1-2-1'),name: 'Menu1-2-1',meta: {title: 'Menu1-2-1'}},{path: 'menu1-2-2',component: () => import('@/views/nested/menu1/menu1-2/menu1-2-2'),name: 'Menu1-2-2',meta: {title: 'Menu1-2-2'}}]},{path: 'menu1-3',component: () => import('@/views/nested/menu1/menu1-3'),name: 'Menu1-3',meta: {title: 'Menu1-3'}}]},{path: 'menu2',component: () => import('@/views/nested/menu2/index'),name: 'Menu2',meta: {title: 'menu2',roles: ['editor']}}]},// 如果需要配置重定向404页面的话,需要配置在asyncRoutes的最后{path: '*',redirect: '/404',hidden: true}
]
// 实例化vue的时候只挂载constantRouter
const createRouter = () => new Router({// mode: 'history', // require service supportscrollBehavior: () => ({y: 0}),routes: constantRoutes
})const router = createRouter()// Detail see: https://github.com/vuejs/vue-router/issues/1234#issuecomment-357941465
export function resetRouter() {const newRouter = createRouter()router.matcher = newRouter.matcher // reset router
}
export default router

2. src/permission.js动态添加路由

我们在登录成功后,router会重定向一个新页面,在跳转之前src/permission.js里面路由守卫router.beforeEach会先做一些拦截验证,根据判断会做不同页面跳转和操作,比如没登录就先跳到登录页,根据获取到的用户信息roles筛选路由后动态添加路由。

src/permisson.js具体实现代码与注释:

import router from './router'
import store from './store'
import {Message
} from 'element-ui'
// 页面进度条组件
import NProgress from 'nprogress' // progress bar
import 'nprogress/nprogress.css' // progress bar style
import {getToken
} from '@/utils/auth' // get token from cookie
import getPageTitle from '@/utils/get-page-title'NProgress.configure({showSpinner: false
}) // NProgress 配置const whiteList = ['/login', '/404'] // 不重定向的白名单router.beforeEach(async (to, from, next) => {// start progress barNProgress.start()// 设置页面标题document.title = getPageTitle(to.meta.title)// 确定用户是否已登录 const hasToken = getToken()// 判断是否存在token,没有就重新登陆if (hasToken) {if (to.path === '/login') {// 如果已登录,则重定向到主页next({path: '/'})NProgress.done()} else {// 确定用户是否通过getInfo获得了权限角色const hasRoles = store.getters.roles && store.getters.roles.length > 0 //这里指的是src/store/getters.js的roles// console.log(hasRoles)//判断是否已经有roles了if (hasRoles) {next(); //当有用户权限的时候,说明所有可访问路由已生成 如访问没权限的全面会自动进入404页面} else {try {// get user info// 注意: roles 角色必须是对象数组! 例如: ['admin'] 或 ,['developer','editor']// 1. 获取rolesconst {roles} = await store.dispatch('user/getInfo') //第一步// 2. 根据角色生成可访问路由图// 获取通过权限验证的路由const accessRoutes = await store.dispatch('permission/generateRoutes', roles) //第二步// 3. 更新加载路由router.options.routes = store.getters.permission_routes //第三步// 动态添加可访问路由router.addRoutes(accessRoutes)// console.log(store)// console.log(accessRoutes);// hack方法 确保addRoutes已完成,以确保地址是完整的// 设置replace: true,这样导航就不会留下历史记录next({...to,replace: true})} catch (error) {// 删除token并转到登录页面重新登录await store.dispatch('user/resetToken')Message.error('出现错误~请重新登录')next(`/login?redirect=${to.path}`)NProgress.done()}}}} else {/* 没有token */if (whiteList.indexOf(to.path) !== -1) { // 在免登录白名单,直接进入next()} else {next(`/login?redirect=${to.path}`) // 否则全部重定向到登录页NProgress.done()}}
})

3. store/modules/user.js

而获取+存储roles通过store/modules/user.js实现,筛选+存储路由又是通过通过store/modules/permission.js实现的

store/modules/user.js主要是获取和存储roles



store/modules/user.js完整代码:

import {login,logout,getInfo
} from '@/api/user'
import {getToken,setToken,removeToken
} from '@/utils/auth'
import {resetRouter
} from '@/router'const getDefaultState = () => {return {token: getToken(),name: '',avatar: '',roles: [],}
}const state = getDefaultState()const mutations = {RESET_STATE: (state) => {Object.assign(state, getDefaultState())},SET_TOKEN: (state, token) => {state.token = token},SET_NAME: (state, name) => {state.name = name},SET_ROLES: (state, roles) => {state.roles = roles},SET_AVATAR: (state, avatar) => {state.avatar = avatar}
}const actions = {// user loginlogin({commit}, userInfo) {const {username,password} = userInforeturn new Promise((resolve, reject) => {login({username: username.trim(),password: password}).then(response => {const {data} = responsecommit('SET_TOKEN', data.token)setToken(data.token)resolve()}).catch(error => {reject(error)})})},// get user infogetInfo({commit,state}) {return new Promise((resolve, reject) => {// state.token之前没有传 出现了重复登陆问题getInfo(state.token).then(response => {const {data} = responseif (!data) {return reject('验证失败,请重新登录')}const {name,roles,avatar} = dataif (!roles || roles.length <= 0) {reject('getInfo:roles must be a non-null array!')}commit('SET_NAME', name)commit('SET_ROLES', roles)commit('SET_AVATAR', avatar)resolve(data)}).catch(error => {reject(error)})})},// user logoutlogout({commit,state}) {return new Promise((resolve, reject) => {logout(state.token).then(() => {removeToken() // must remove  token  firstresetRouter()commit('RESET_STATE')commit('SET_ROLES', [])resolve()}).catch(error => {reject(error)})})},// remove tokenresetToken({commit}) {return new Promise(resolve => {removeToken() // must remove  token  firstcommit('RESET_STATE')resolve()})}
}export default {// 加上这个会有报错,不加的话user/login这种方式用不了namespaced: true,state,mutations,actions
}

4. store/modules/permission.js筛选路由

store/modules/permission.js用于匹配权限,筛选角色对应的路由并存储起来

import {asyncRoutes,constantRoutes
} from '@/router'/*** 使用 meta.role 以确定当前用户是否具有权限* @param roles* @param route*/
// 匹配权限
function hasPermission(roles, route) {if (route.meta && route.meta.roles) {return roles.some(role => route.meta.roles.includes(role))} else {return true}
}/*** 通过递归过滤异步路由表* @param routes asyncRoutes* @param roles*/
export function filterAsyncRoutes(routes, roles) {const res = []routes.forEach(route => {const tmp = {...route}if (hasPermission(roles, tmp)) {if (tmp.children) {tmp.children = filterAsyncRoutes(tmp.children, roles)}res.push(tmp)}})return res
}const state = {routes: [],addRoutes: []
}const mutations = {SET_ROUTES: (state, routes) => {state.addRoutes = routesstate.routes = constantRoutes.concat(routes) // 将过滤后的路由和constantRoutes存起来}
}// 筛选
const actions = {generateRoutes({commit}, roles) {return new Promise(resolve => {let accessedRoutes// 管理员admin显示全部路由,// 我这里admin是想让它不显示全部的 想要admin能看见全部的话把注释去掉// if (roles.includes('admin')) {//   accessedRoutes = asyncRoutes || []// } else {//过滤路由accessedRoutes = filterAsyncRoutes(asyncRoutes, roles)// accessedRoutes这个就是当前角色可见的动态路由// }commit('SET_ROUTES', accessedRoutes)resolve(accessedRoutes)})}
}export default {namespaced: true,state,mutations,actions
}

这里的代码说白了就是干了一件事,通过用户的权限和之前在router.js里面asyncRoutes的每一个页面所需要的权限做匹配,最后返回一个该用户能够访问路由有哪些。

5. store/getter.js和store/index.js

当然了,新加的模块要记得引入进去
store/getter.js添加以下代码:

store/index.js

6. sidebar使用筛选后的路由

src\layout\components\Sidebar\index.vue

遍历之前算出来的permission_routers,通过vuex拿到之后动态v-for渲染

<sidebar-item v-for="route in permission_routes" :key="route.path" :item="route" :base-path="route.path" />

效果

如何看效果?如果是用的vue-admin-template的mock做登录和获取用户数据,登录用户名为admin则role为admin,用户名为editor则role为editor。
角色为admin看到的菜单栏

角色为editor看到的菜单栏

完整源码

记得给我一个star
https://gitee.com/yyy1203/vue-admin-template-permission.git

有兴趣可以看看花裤衩大佬文章:https://juejin.cn/post/6844903478880370701#heading-5

Vue实现角色权限动态路由详细教程,在vue-admin-template基础上修改,附免费完整项目代码相关推荐

  1. 使用css制作动态时钟详细教程

    使用css制作动态时钟详细教程 完成效果: 步骤一: 创建一个宽高为300px圆形时钟的外表盘,边框加上阴影更好看一点,页面背景色为黑色 代码: <!DOCTYPE html> <h ...

  2. vue中 根据权限 动态的设置路由

    本篇文章主要讲的是使用vue的框架 vue-element-admin, 来解决根据不同的角色权限, 显示不同的路由. ⽅式⼀,后台返回⾓⾊树形路由表,前端添加动态路由 ⽅式⼆,后台返回⽤户⾓⾊,前端 ...

  3. vue实现角色权限控制

    一.前言 在广告机项目中,角色的权限管理是卡了挺久的一个难点.首先我们确定的权限控制分为两大部分,其中根据粒的大小分的更细: 接口访问的权限控制 页面的权限控制 菜单中的页面是否能被访问 页面中的按钮 ...

  4. Vue系列vue-router的动态路由使用(二)

    前言 首先我们来看看官方文档上是怎么解释动态路由的?走你 你可以在一个路由中设置多段"路径参数",对应的值都会设置到 $route.params 中.例如: 一.什么场景下使用动态 ...

  5. vue使用addrouter添加动态路由

    路由拦截beforeEach和addRouter 注意: 1.一定要理解beforeEach(全局的路由守卫,每一次路由执行都会触发)和addRoutes的运行机制,这里容易陷入死循环,所以一定要做好 ...

  6. win10安装和搭建vue环境(超详细教程)Vue新手教程(1):

    PS:笔者为计科专业研一在读,最近打算学习Vue,踩了许多坑,在此将学习过程分享出来,后续还会持续更新. 一.安装Vue环境 首先,在安装Vue环境之前,我们首先需要安装npm这个玩意. 那么npm这 ...

  7. 用burpsuite抓包,谷歌 / 火狐浏览器 该怎么设置BurpSuite代理?——超详细教程——CTF Web小白入门基础篇

    以下是我自己操作过的,理一理思路. 使用BurpSuite的套路是:先浏览器设置BurpSuite代理-->BurpSuite 调至on状态-->访问web程序-->进行抓包分析 最 ...

  8. PixiJS超级详细教程【从入门到入土-上】

    PixiJS 来自GitHub教程 GitHub - Zainking/LearningPixi: ⚡️Pixi教程中文版 PixiJS超级详细教程[从入门到入土-下]地址[https://blog. ...

  9. 人脸检测和识别(中文标记)完整项目源代码(基于深度学习+python3.6+dlib+PIL+CNN+(tensorflow、keras)10分钟实现 区分欢乐颂中人物详细图文教程和完整项目代码)

    转载请注明:https://blog.csdn.net/wyx100/article/details/80428424 效果展示 未完待续... 环境配置 win7sp1 python         ...

最新文章

  1. workerman源码分析之启动过程
  2. 用leangoo怎么做迭代管理?(Sprint Backlog、任务看板、燃尽图)
  3. boost::container实现双端队列选项的测试程序
  4. 洛谷 - P3356 火星探险问题(最大费用最大流+拆点+路径打印)
  5. 蛮力法在求解最优解问题中的应用(JAVA)--旅行家问题、背包问题、分配问题
  6. python kmeans聚类_python机器学习之k-means聚类算法(1)
  7. 关于Keil中,变量函数跨文件引用报错问题
  8. mysql mvcc 读写阻塞_mysql面试题MVCC原理事务隔离级别_aiailingfei的博客-CSDN博客
  9. php redis 清除队列,PHP Redis Queue
  10. HTML网页头部小图标
  11. 【机器学习】PAC 学习理论
  12. 飞腾D20008核桌面处理器
  13. android 4.4 设置谷歌拼音输入法为默认输入法,android4.4修改出厂默认输入法
  14. 教你自定义Windows10微软输入法
  15. 物联卡中心:物联卡实名制安不安全?
  16. 多项式插值与牛顿差商
  17. 基于socket的聊天工具
  18. 模型可解释性-SHAPE
  19. Spring学习之旅(二) AOP(面向切面编程)的使用
  20. 简报 | 印度政府最终确定加密货币监管框架

热门文章

  1. 如何使用latex表示微分结果
  2. Deep Learning and the Information Bottleneck Principle 深度学习与信息瓶颈原理
  3. 系统主数据管理之物料(Item)五 Item 的制造商部件号(MPN)
  4. elementui table 表格固定列最后一行显示不全
  5. ArcGIS Pro制图-河流曲线图例
  6. 怎样才能像经济学家那样思考
  7. 错误: 找不到或无法加载主类 com.xxxx.xxx.Application
  8. 把分钟转化为小时分钟
  9. 联想r720安装固态_联想拯救者R720-15IKBN笔记本加装SSD不识别的案例
  10. mysql中top命令详解_top命令详解