后台管理类系统大多都涉及权限管理,菜单权限,按钮权限。
菜单权限对应 - 路由。菜单权限 - 根据用户角色不同,路由文件动态配置。
vue-router
Vue Router
vue-router是vue项目在进行开发过程中必不可少缺少的插件,目前vue2依赖的是vue-router3,vue3依赖的vue-router4
在进行权限控制之前一定要了解哪些路由需要权限哪些不需要
知识点集结
动态添加更多的路由规则。参数必须是一个符合 routes
选项要求的数组。已废弃目前版本再使用该api会被提示已经废弃,但是暂时依旧可以使用
router.addRoutes(routes: Array)
添加一条新路由规则。如果该路由规则有 name
,并且已经存在一个与之相同的名字,则会覆盖它。
addRoute(route: RouteConfig)
获取所有活跃的路由记录列表。注意只有文档中记录下来的 property 才被视为公共 API,避免使用任何其它 property,例如 regex
,因为它在 Vue Router 4 中不存在。
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: ' '
})
正常注册完路由就可以开始进行权限设置了
(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;
//按钮的值 aa
const value = binding.value; //a
const hasPermissions = data.includes(value);
if(!hasPermissions){
//隐藏按钮
el.style.display = "none";
setTimeout(()=>{
el.parentNode.removeChild(el)
},0)
}
}
})