做到了系统的权限管理部分,以前没有接触过,下面来记录一下。
权限管理主要有三步:
- 根据权限的不同,渲染不同的菜单,显示不同的页面
- 在页面验证用户权限,防止越权
- 最后,有些权限是页面内部的,需要使用自定义指令来限制
1. 根据权限渲染菜单
在点击登陆之后,会返回一系列的数据,里面包括该用户所有权限内的菜单,然后根据这个菜单来进行相应的渲染。
(1)在获取到菜单之后,为方便之后使用,将这些数据存储在Vuex以及本地缓存中
state: {
navBar: {
active: '0',
list: []
}
},
mutations: {
// 创建菜单
createNavBar(state, menus) {
// 获取菜单以及其子菜单
let list = menus.map(item => {
let submenu = item.child.map(v => {
return {
icon: v.icon,
name: v.name,
pathname: v.desc
}
})
return {
name: item.name,
subAcive: '0',
submenu: submenu,
}
})
// 将菜单列表存储在state数据中
state.navBar.list = list
// 将数据存储在本地sessionStorage中
window.sessionStorage.setItem('navBar', JSON.stringify(state.navBar))
},
},
(2)在登录页面初始化菜单
// 生成后台菜单
this.$store.commit('createNavBar', data.tree)
(3)在主布局页面获取数据
...mapState({
navBar : state => state.menu.navBar
}),
这样菜单的初始化就基本完成了,但是我们刷新网页来看。所有的菜单都消失了,这是因为,我们初始化菜单是在登录之后进入页面时完成的,再次刷新页面的时候,不会再执行该方法,所以无法在初始化菜单。
那么,就需要在入口文件处定义一个初始化菜单的方法,只要刷新页面,就调用该方法,初始化菜单。
mutations: {
//初始化菜单
initNavBar(state) {
// 从本地缓存获取菜单
let navBar = window.sessionStorage.getItem('navBar')
navBar = navBar ? JSON.parse(navBar) : {
active: '0',
list: []
}
// 将结果存储在Vuex中,便于后面使用
state.navBar = navBar
}
},
在APP.vue
中,调用该方法:
created(){
// 初始化菜单
this.$store.commit('initNavBar')
}
这样,即使每次刷新页面,也会重新初始化菜单了。
2. 验证页面权限
上面解决了根据权限渲染菜单的问题,但是,即使我们没有权限,在浏览器输入权限外的URL地址,还是可以访问到相应的页面,那么我们就需要对用户进行显示限制。
这里主要使用 路由全局前置守卫 来对用户的权限进行管理。在登录之后,返回的数据中,有一个rules
字段,里面包含着用户的权限。
(1)在登录的时候,由于后续没有用到的地方,所以获取到的权限数据只需要存到本地缓存即可,不需要存储在Vuex中:
let data = res.data.data
// 存储权限规则
if(data.role && data.role.rules){
window.sessionStorage.setItem('rules', JSON.stringify(data.role.rules))
}
(2)在router.js
中对进行路由进行页面权限的设置
// 全局前置守卫
router.beforeEach((to, from, next) => {
let token = window.sessionStorage.getItem('token')
if (token) {
// 防止重复登录
if (to.path === '/login') {
Vue.prototype.$message.error('请不要重复登录')
return next({ name: from.name ? from.name : 'index' })
}
// 页面权限设置
if (to.name !== 'error_404') {
// 获取本地存储的权限
let rules = window.sessionStorage.getItem('rules')
rules = rules ? JSON.parse(rules) : []
// 对要跳转到的页面在规则中查找
let index = rules.findIndex(item => {
return item.rule_id > 0 && item.desc === to.name
})
// 如果没有找到,就告诉用户没有权限,并跳回上一级页面
if (index === -1) {
Vue.prototype.$message.error('没有权限')
return next({ name: from.name ? from.name : 'error_404' })
}
}
next()
} else {
// 防止用户未登录时输入URL跳转
if (to.path === '/login') {
return next()
}
Vue.prototype.$message.error('请先登录系统')
next({ path: '/login' })
}
})
这样,页面的权限设置就基本完成了。
需要注意的是,如果一直返回到上一页,还没有,就会一直跳转,就会进入一个死循环,页面无法加载。
这里就是设置了一个404 的页面,如果上一级找不到相应的权限规则,就跳转到404页面,这样就避免了死循环的问题。如果已经是在404页面,就不再进行验证了。
3. 页面内部权限设置
解决完路由跳转的问题,还有一个问题就是,我们只是根据菜单渲染出了页面,但是有些页面内部一些功能,有些用户是没有权限的。这时我们就需要隐藏相应的DOM元素,让用户看不到这功能。
这就用到了Vue的自定义指令,在登录的时候,返回的数据中有一个字段是用户的权限列表,我们根据这个权限列表来控制DOM的显示和隐藏,如果有这个权限,就显示,否则就不显示。
(1)在main.js
中自定义指定
// 添加全局自定义指令,其中auth是自定义指令的名称
Vue.directive('auth', {
// 总共有四个参数,这里只用到了两个
inserted(el, binding) {
// 获取用户的所有信息
let user = window.sessionStorage.getItem('user')
user = user ? JSON.parse(user) : {}
// 如果不是超级管理员就进行验证
if (!user.super) {
// 获取所有的权限列表
let rules = user.ruleNames ? user.ruleNames : []
// 在权限中查找所有的权限的名称
let v = rules.find(item => item === binding.value)
// 如果不存在权限,就移除该DOM节点
if (!v) {
el.remove()
}
}
}
})
其中, inserted
:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。
指令钩子函数会被传入以下参数:
- el:指令所绑定的元素,可以用来直接操作 DOM。
- binding:一个对象,包含以下 property:
- name:指令名,不包括 v- 前缀。
- value:指令的绑定值,例如:v-my-directive=“1 + 1” 中,绑定值为 2。
- oldValue:指令绑定的前一个值,仅在 update 和 componentUpdated 钩子中可用。无论值是否改变都可用。
- expression:字符串形式的指令表达式。例如 v-my-directive=“1 + 1” 中,表达式为 “1 + 1”。
- arg:传给指令的参数,可选。例如 v-my-directive:foo 中,参数为 “foo”。
- modifiers:一个包含修饰符的对象。例如:v-my-directive.foo.bar 中,修饰符对象为 { foo: true, bar: true }。
- vnode:Vue 编译生成的虚拟节点。移步 VNode API 来了解更多详情。
- oldVnode:上一个虚拟节点,仅在 update 和 componentUpdated 钩子中可用。
跟多关于Vue自定义指令,可以查看官网的介绍:官网链接
(2)使用自定义指令
下面就该使用刚刚自定义的指令了,我们拿一个来说明:
<el-button size="mini" type="success" @click="append(data)" v-auth="'添加规则'">增加</el-button>
这里是一个添加管理员规则的按钮,但是这个权限不是所有用户都有的,这里将制定的内容定义为一个字符串,只需要和权限中规则进行匹配,如果有这个权限,就显示,否则就移除该节点。
这样就完成了权限的管理。
对于权限管理,应该还有很多其他的方式,值得去探索。主要还是根据项目的需要来使用合适的方法…
最后补充一下关于Vuex和sessionStorage的区别:
- vuex存储在内存中,sessionStorage是会话存储,在关闭浏览器之后,就会清除
- vuex可以存储任何类型的数据,sessionStorage只能存储字符串,对于复杂类型,需要使用stringify和parse来处理
- vuex主要用于组件之间的传值,sessionStorage主要用于不同页面之间的传值
- 在刷新页面时,vuex数据会丢失,sessionStorage数据不会丢失
- vuex的数据是响应式的,sessionStorage的数据不是响应式的