位置: IT常识 - 正文
推荐整理分享Vue实现角色权限动态路由详细教程,在vue-admin-template基础上修改,附免费完整项目代码(vue角色管理),希望有所帮助,仅作参考,欢迎阅读内容。
文章相关热门搜索词:vue权限怎么做,vue用户权限解决方案,vue角色权限系统,vue角色权限管理系统,vue角色权限页面,vue角色权限管理系统,vue角色权限系统,vue角色权限管理系统,内容如对您有帮助,希望把文章链接给更多的朋友!
vue-admin-template是一个最基础的后台管理模板,只包含了一个后台需要最基础的东西,如果clone的是它的master分支,是没有权限管理的,只有完整版vue-element-admin有这个功能,但是为了小小的一个权限管理而用比较复杂的有点得不偿失。
我在网上找了一堆教程和资料,发现要么说的很乱,要么说的不全,最后连个完整代码都不让我白嫖(bushi)。自己复制粘贴过去都实现不出来,仔细查看发现人家写的教程漏了一写步骤/代码,而且还有bug(服了这些老六)。
在自己摸索了和看了花裤衩大佬的文章后,解决了一些bug自己实现出来了,代码中也有详细注释。完整代码放文末给大家了,大家记得给我star再走(不然小拳拳锤你胸口)。
权限管理?动态路由?现在开发后台管理系统项目经常有权限管理的需求,权限管理其实就是根据不同的角色权限显示不同的路由,而其中的关键就是动态路由router.addRoutes 实现权限验证的基本思路就是:
用户登录,通过token获取用户对应的 role动态根据用户的 role 算出其对应有权限的路由通过 router.addRoutes 动态挂载这些路由以上步骤实现的核心是router和vuex,下面就详细介绍如何实现(按代码执行逻辑倒推)
具体实现创建vue实例的时候将vue-router挂载,但这个时候vue-router挂载一些登录或者不用权限的公用的页面。当用户登录后,获取用role,将role和路由表每个页面的需要的权限作比较,生成最终用户可访问的路由表。调用router.addRoutes(store.getters.addRouters)添加用户可访问的路由。使用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-view name: '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的时候只挂载constantRouterconst createRouter = () => new Router({ // mode: 'history', // require service support scrollBehavior: () => ({ y: 0 }), routes: constantRoutes})const router = createRouter()// Detail see: https://github.com/vuejs/vue-router/issues/1234#issuecomment-357941465export function resetRouter() { const newRouter = createRouter() router.matcher = newRouter.matcher // reset router}export default router2. 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 barimport 'nprogress/nprogress.css' // progress bar styleimport { getToken} from '@/utils/auth' // get token from cookieimport getPageTitle from '@/utils/get-page-title'NProgress.configure({ showSpinner: false}) // NProgress 配置const whiteList = ['/login', '/404'] // 不重定向的白名单router.beforeEach(async (to, from, next) => { // start progress bar NProgress.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. 获取roles const { 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 login login({ commit }, userInfo) { const { username, password } = userInfo return new Promise((resolve, reject) => { login({ username: username.trim(), password: password }).then(response => { const { data } = response commit('SET_TOKEN', data.token) setToken(data.token) resolve() }).catch(error => { reject(error) }) }) }, // get user info getInfo({ commit, state }) { return new Promise((resolve, reject) => { // state.token之前没有传 出现了重复登陆问题 getInfo(state.token).then(response => { const { data } = response if (!data) { return reject('验证失败,请重新登录') } const { name, roles, avatar } = data if (!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 logout logout({ commit, state }) { return new Promise((resolve, reject) => { logout(state.token).then(() => { removeToken() // must remove token first resetRouter() commit('RESET_STATE') commit('SET_ROLES', []) resolve() }).catch(error => { reject(error) }) }) }, // remove token resetToken({ commit }) { return new Promise(resolve => { removeToken() // must remove token first commit('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 = routes state.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
下一篇:2023英伟达显卡排名天梯图(已更新)(2023英伟达显卡天梯图完整版)
友情链接: 武汉网站建设