手把手教学vue的路由权限问题
作者:哆来A梦没有口袋
后台管理类系统大多都涉及权限管理,菜单权限,按钮权限。
菜单权限
菜单权限对应 - 路由。菜单权限 - 根据用户角色不同,路由文件动态配置。
相关知识点了解
vue-router是vue项目在进行开发过程中必不可少缺少的插件,目前vue2依赖的是vue-router3,vue3依赖的vue-router4
在进行权限控制之前一定要了解哪些路由需要权限哪些不需要
知识点集结
router.addRoutes()
动态添加更多的路由规则。参数必须是一个符合 routes
选项要求的数组。
已废弃目前版本再使用该api会被提示已经废弃,但是暂时依旧可以使用
router.addRoutes(routes: Array<RouteConfig>)
router.addRoute()
添加一条新路由规则。如果该路由规则有 name
,并且已经存在一个与之相同的名字,则会覆盖它。
addRoute(route: RouteConfig)
router.getRoutes()
获取所有活跃的路由记录列表。注意只有文档中记录下来的 property 才被视为公共 API,避免使用任何其它 property,例如 regex
,因为它在 Vue Router 4 中不存在。
路由导航守卫 - beforeEach
router.beforeEach((to, from, next) => { /* 必须调用 `next` */ })
全局前置守卫 - 跳转一个路由之前都会执行.
3个参数:
to
: 即将进入的目标的路由对象from
:当前导航正在离开的路由next
: 是个函数,进入下一个钩子
next()
: 进行管道中的下一个钩子。如果全部钩子执行完了,则导航的状态就是 confirmed (确认的)。next(false)
: 中断当前的导航。如果浏览器的 URL 改变了 (可能是用户手动或者浏览器后退按钮),那么 URL 地址会重置到 from 路由对应的地址。next('/')
或者next({ path: '/' })
: 跳转到一个不同的地址。当前的导航被中断,然后进行一个新的导航。你可以向 next 传递任意位置对象,且允许设置诸如 replace: true、name: 'home' 之类的选项以及任何用在 router-link 的 to prop 或 router.push 中的选项。next(error)
: (2.4.0+) 如果传入 next 的参数是一个 Error 实例,则导航会被终止且该错误会被传递给 router.onError() 注册过的回调。
功能实现过程
路由权限第一种可以是后端全部返回,直接调用接口使用后端的可以使用的路由,但是这种情况一般较少。
第二种前端有一个份完整路由,根据后端接口进行筛选,这里讲解第二种情况。
(1)定义路由文件
router -> index.js
import Vue from 'vue' import Router from 'vue-router' import store from '../store' Vue.use(Router) //没有权限的路由 export const constantRoutes = [ { path: '/', name: 'index', redirect: '/login' }, { path: '/login', name: 'login', component: () => import('@/views/login') }, { path: '/register', name: 'register', component: () => import('@/views/register') }, { path: '/forget', name: 'forget', component: () => import('@/views/forget') }, { path: '/404', name: 'notfing', component: () => import('@/views/404')} ] //有权限的路由 export const myAsyncRoute = [{ path: '/portal', name: 'portal', component: LayoutPortal, redirect: '/portal/home', meta: {id: 'no'}, children: [ { path: '/portal/home', name: 'portal-home', component: () => import('@/views/portal/home/index.vue'), meta: {id: 100100, title: '首页', show: true} }, { path: '/portal/user', name: 'portal-user', component: () => import('@/views/layout/submenu'), meta: {id: 100200, title: '统一身份认证', show: true}, redirect: '/portal/user/userInfo', children: [ { path: '/portal/user/userInfo', name: 'portal-user-userInfo', component: () => import('@/views/portal/userInfo/index.vue'), meta: {id: 100201, title: '统一用户管理', show: true} }, { path: '/portal/user/userInfo/detial', name: 'portal-userInfo-detial', component: () => import('@/views/portal/userInfo/detial.vue'), meta: { id: 100201, activeMenu: '/portal/user/userInfo', title: '统一用户管理', show: false}, }, { path: '/portal/user/userAuth', name: 'portal-user-userAuth', component: () => import('@/views/portal/userAuth/index.vue'), meta: {id: 100202, title: '用户认证', show: true} }, ] }, { path: '/portal/journal', name: 'portal-journal', component: () => import('@/views/portal/journal/index.vue'), meta: {id: 100303, title: '统一日志管理', show: true}, }, { path: 'personal', name: 'portal-personal', component: () => import('@/views/portal/personal/index.vue'), meta: { id: 'no', activeMenu: '/portal/home', show: false }, }, ], }] export default new Router({ routes: constantRoutes, })
(2)注册路由
main.js
import router from './router' new Vue({ el: '#app', router, store, components: { App }, template: '<App/>' })
正常注册完路由就可以开始进行权限设置了
(3)获取有权限路由
不同的项目获取路由权限并不相同,大多数 - 登录接口返回/单独接口进行获取
登录获取权限保存
let res = await this.$api.user.login(data); if(res.token) { this.$store.commit('setToken', res.token); this.$store.commit('setUsername', res.nickName); this.$store.commit('setUserInfo', res); //存储权限 localStorage.setItem('menuIdList', JSON.stringify(res.menuIdList)) this.$message({ message: '登录成功', type: 'success' }); setTimeout(() => { this.loading = false; this.$router.push('/portal') }, 1000); }
在main.js中拦截
获取权限进行匹配 - beforeEach一定要有一个next()的出口,不然会陷入死循环
let flag = true; router.beforeEach((to, from, next) => { if (['/login', '/forget', '/register'].includes(to.path)) { next(); } else { let token = localStorage.getItem("token"); if (token) { if(flag) { try { //获取有权限的路由进行组装 let route = asyncRoute() || []; router.addRoutes(route) router.addRoute({ path: '*', redirect: '/404' }) flag = false next({...to, replace:true}) }catch(e) { next('/login') } }else { next(); } }else { next({ path: '/login' }) } } } );
注意: addRoute之后,打印router是看不见的,要获取所有的权限,必须使用router.getRoute()进行查看
(4)路由权限匹配
router.js-根据后端返回的权限,和自己的路由权限匹配,组织成新的路由,和自己想要的格式
export const asyncRoute = function() { let menuIdList = localStorage.getItem('menuIdList') ? JSON.parse(localStorage.getItem('menuIdList')) : []; let tempArr = filterRoute(myAsyncRoute, menuIdList); store.dispatch('getRoute', tempArr) let showRoute = []; let nowRoute =JSON.parse(JSON.stringify(tempArr)) if(nowRoute[0].children && nowRoute[0].children.length){ nowRoute[0].children.forEach(item => { let arr = []; if(item.children && item.children.length){ arr = item.children.filter(obj => obj.meta.show) } if(item.meta.show){ item['showRouteChildren'] = arr; showRoute.push(item) } }) } store.dispatch('getShowRoute', showRoute) return tempArr } function filterRoute(arr, menuIdList) { if(!arr.length) return []; return arr.filter(item => { if(item.children && item.children.length) { item.children = filterRoute(item.children, menuIdList); } return (item.meta && item.meta.id && menuIdList.includes(item.meta.id)) || (item.meta && item.meta.id == 'no') || (item.children && item.children.length > 0) }) }
在这个过程中,在store存储了路由
import Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex) export default new Vuex.Store({ state: { routes: JSON.parse(localStorage.getItem('routes')) || [], addRoutes: JSON.parse(localStorage.getItem('addRoutes')) ||[], showRoutes: [] }, mutations: { setRoutes(state, routes) { state.addRoutes = routes; state.routes = constantRoutes.concat(routes) localStorage.setItem('routes', JSON.stringify(state.routes)) localStorage.setItem('addRoutes', JSON.stringify(state.addRoutes)) }, setShowRoutes(state, routes) { state.showRoutes = routes; } }, actions: { getRoute({commit}, list) { return new Promise(resolve => { commit('setRoutes', list) resolve(list) }) }, getShowRoute({commit}, list) { return new Promise(resolve => { commit('setShowRoutes', list) resolve(list) }) } } })
总结: 最后在理一下整个过程 - 存储权限路由数据,在进行跳转的时候进行筛选组合路由。其实筛选路由不一定要写在router.js,只要是你认为合适的地方都可以,在权限控制过程中最重要的是路由拦截。
模拟一下路由拦截的过程
假设login之后进入的/index,路由拦截的过程
/index - > token -> flag(true) ->获取路由权限 -> next('/index') -> 重新进入beforeEach-> token->flag(false) -> next() ->结束
按钮权限 - 操作(自定义指令)
按钮权限主要涉及的知识点就是全局自定义指令
写在main.js。或者单独js文件,main.js进行引入
import Vue from "vue" import store from "../store" //自定义指令 v-has进行权限判断 Vue.directive("has",{ inserted : function (el,binding){ //按钮权限 const data = store.state.buttons; //按钮的值 <el-button v-has>aa</el-button> const value = binding.value; //a const hasPermissions = data.includes(value); if(!hasPermissions){ //隐藏按钮 el.style.display = "none"; setTimeout(()=>{ el.parentNode.removeChild(el) },0) } } })
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。