内容
权限管理的应用
vue 项目中的权限管理
前言
权限管理大家一定再熟悉不过,就是不同角色拥有不同的权限(就像我们登陆游戏只能进行自身的基本配置,而管理员就可以进行全服的统一配置)。那么对于 vue 项目如何更好的进行权限的分配,愿本文观点对您有所帮助。
1. 思路
- 什么是权限管理
- 根据不同用户渲染不同的菜单
- 严格控制用户的权限
- 权限管理的作用
- 可以让不同的用户有不同的操作
- 同时也能避免信息的泄漏
- 实现步骤
- 动态添加路由(
router.addRoutes
api 地址:router.addRoutes) - 对后台数据格式依赖
- 触发时机(登录验证成功时触发)
- 导航守卫(控制页面跳转)
实现
当用户登陆成功后,后台返回此用户所拥有的用户信息
以及用户权限
,通过 sessionStorage
、localStorage
、cookie
等把数据存放在本地。然后通过本地存储把数据存放在 vuex
中进行统一管理(防止刷新页面,vuex 数据丢失),此处用 vuex 对数据进行集中管理,只为后续使用方便。
具体项
- 关于后台返回数据
menus: [ //模拟超级管理员权限数据
{
path: '/', //路由地址
lable: '首页', //路由名称
icon: 'discount', //icon 图标
url: 'Home/Home', //路由对应组件路径(生成动态路由时拼接)
},
{
path: '/video',
lable: '视频管理',
icon: 'picture-outline',
url: 'Video/Video',
},
{
path: '/user',
lable: '用户管理',
icon: 'user',
url: 'UserMessage/UserMessage',
},
{
lable: '其他',
icon: 'coin',
children: [
{
path: '/page1',
lable: '页面1',
icon: 'user',
url: 'Other/Page1',
},
{
path: '/page2',
lable: '页面2',
icon: 'user',
url: 'Other/Page2',
},
],
},
],
token1:'Ivj6eZRx40+MTx2ZvnG8nA', //模拟超级管理员登录返回的 token
noneMenus:[ //模拟普通用户权限数据
{
path: '/',
lable: '首页',
icon: 'discount',
url: 'Home/Home',
},
{
path: '/video',
lable: '视频管理',
icon: 'picture-outline',
url: 'Video/Video',
}
],
token2:'Ivj6eZRx40+MTx2ZvnG8nB' //模拟普通用户登录返回的 token
注意:此数据既用于菜单栏的显示,也用于动态的渲染路由;其中的 token 字符串用于模拟登录身份验证,其将保存在本地(下文包含)
- vuex 中的数据
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
isCollapse: false,
menu: [],
token: '',
currentMenu: null,
todoList: [{
path: '/',
lable: '首页',
icon: 'discount',
}]
},
mutations: {
// 登陆成功把菜单列表存在sessionStrong中
setMenu(state, val) {
state.menu = val;
sessionStorage.setItem('menu', JSON.stringify(val))
},
// 清空sessionStrong
cleareMenu(state) {
state.menu = [];
sessionStorage.removeItem('menu');
},
// 根据后台返回数据(动态渲染路由)
addRouter(state, router) {
if (!sessionStorage.getItem('menu')) { //首先判断 sessionStorage 中是否存在 menu
return
}
let menu = JSON.parse(sessionStorage.getItem('menu')); //从本地存储中获取菜单
state.menu = menu; //避免舒心state 数据丢失
// 在菜单下追加动态路由
let currentMenu = [{ //基本路由(每个用户皆有)
path: '/',
component: () =>
import (`../views/Main/Main.vue`),
children: []
}]
menu.forEach(item => {
if (item.children) {
item.children = item.children.map(item => {
item.component = () =>
import ('../views/' + item.url)
return item
})
currentMenu[0].children.push(...item.children)
} else {
item.component = () =>
import ('../views/' + item.url)
currentMenu[0].children.push(item)
}
})
router.addRoutes(currentMenu)
},
// 保存后台返回的token
setToken(state, val) {
state.token = val;
sessionStorage.setItem('token', val);
},
// 清空token
clearToken(state) {
state.token = '';
sessionStorage.removeItem('token')
},
selectMenu(state, val) {
// val.path != '/' ? state.currentMenu = val : state.currentMenu = null
if (val.path != '/') {
state.currentMenu = val;
let result = state.todoList.some(item => item.path == val.path) //#判断是否已经添加过
result == true ? '' : state.todoList.push(val)
} else {
state.currentMenu = null
}
},
changeCollapse(state) {
state.isCollapse = !state.isCollapse
},
closeTab(state, val) { //#关闭菜单,删除对应的标签
let index = state.todoList.findIndex(item => item.path == val.path);
state.todoList.splice(index, 1)
}
},
actions: {},
modules: {}
})
注意:此处动态追加路由方法为服务器端返回了路由对应的路径。若服务器无返回路径,我们可以定义全部需要显示的路由,然后根据菜单 名称 或 路径 进行匹配追加
- 当用户登录时(点击登录按钮)
methods: {
submitForm(formName) {
this.$refs[formName].validate((valid) => { //表单验证(数据格式效验)
if (valid) {
if (this.ruleForm.name == 'cwen' && this.ruleForm.pass == '123456') { //超级管理员账号
this.$store.commit('cleareMenu'); //先清除菜单,防止用户二次登录(后期用路由守卫登录拦截解决)
this.$store.commit('setMenu', this.menus); // 通过 vuex 把获取的后台数据存在sessionStorage 中
this.$store.commit('setToken',this.token1); // 通过vuex 把后台返回的token 保存在sessionStorage 中
this.$store.commit('addRouter', this.$router); // 根据后台数据,动态生成路由
this.$router.push('/'); // 生成动态路由成功后跳转,主页面 (渲染根组件时刷新)
} else if (
//普通用户账号
this.ruleForm.name == 'none' &&
this.ruleForm.pass == '123456'
) {
this.$store.commit('cleareMenu'); //先清除菜单,防止用户二次登录(后期用路由守卫登录拦截解决)
this.$store.commit('setMenu', this.noneMenus); // 通过 vuex 把获取的后台数据存在sessionStorage 中
this.$store.commit('setToken',this.token2); // 通过vuex 把后台返回的token 保存在sessionStorage 中
this.$store.commit('addRouter', this.$router); // 根据后台数据,动态生成路由
this.$router.push('/'); // 生成动态路由成功后跳转,主页面 (渲染根组件时刷新)
} else {
return this.$message.error('用户名或密码有误');
}
} else {
return false;
}
});
},
//---------------------------华丽分割线------------------------------------------------------
//main.js
//实例渲染时用钩子获取路由数据(防止路由追加失败)
new Vue({
router,
store,
render: h => h(App),
created() {
store.commit('addRouter', router)
}
}).$mount('#app')
补充:此处的用户登录为简洁模拟,但基本业务逻辑皆可实现。关于菜单栏的数据渲染,可参照 NavMenu 导航菜单 进行自行配置
- 动态追加路由后的 router.js
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)
const routes = [{
path: '/login',
component: () =>
import ( /* webpackChunkNmae:"login"*/ '../views/Login/Login.vue'),
}
// 此处路由全部由后台返回数据,动态渲染
// {
// path: '/',
// component: () =>
// import ( /*webpackChunkName: "home" */ '../views//Main/Main.vue'),
// children: [{
// path: '/',
// component: () =>
// import ( /*webpackChunkName: "home" */ '../views/Home/Home.vue')
// }, {
// path: '/video',
// component: () =>
// import ( /*webpackChunkName: "home" */ '../views/Video/Video.vue')
// }, {
// path: '/user',
// component: () =>
// import ( /*webpackChunkName: "home" */ '../views/UserMessage/UserMessage.vue')
// }, {
// path: '/page1',
// component: () =>
// import ( /*webpackChunkName: "home" */ '../views/Other/Page1.vue')
// }, {
// path: '/page2',
// component: () =>
// import ( /*webpackChunkName: "home" */ '../views/Other/Page2.vue')
// }]
// }
]
const router = new VueRouter({
routes
})
router.beforeEach((to, from, next) => { //路由守卫 完成身份验证
// 防止处于首页的用户访问登录页
if (sessionStorage.getItem('token') && to.path == '/login') return next('/');
// 判断用户是否处于登录状态
if (!sessionStorage.getItem('token') && to.path !== '/login') {
next('/login')
} else {
next()
}
})
//# 重复点击报错问题(降低版本 或者 加以下代码(对 router.push() 方法进行更改);也可使用菜单的 router 属性,进行点击路由跳转)
const originalPush = VueRouter.prototype.push
VueRouter.prototype.push = function push(location) {
return originalPush.call(this, location).catch(err => err)
}
export default router
注意:注释部分为之前的静态路由,router.beforeEach()
路由守卫阻止用户无权限跳转
come on ヾ(●´∀`●)