vue + elementui + vuex实现权限动态路由
应用场景
后端管理系统有多个角色,每个角色的权限不同可访问的页面也不同;
这时前端就需要判断用户角色权限,动态加载左侧菜单栏;
实现思路
第一种: 通过vue提供的addRoutes,动态添加路由 (三级菜单时建议使用)
1 ) 创建vue实例的时候将vue-router挂载,但这个时候vue-router挂载一些登录或者不用权限的公用的页面
2 ) 当用户登录后,获取用role,将role和路由表每个页面的需要的权限作比较,生成最终用户可访问的路由表
3 ) 调用router.addRoutes(store.getters.addRouters)添加用户可访问的路由
4 ) 使用vuex管理路由表,根据vuex中可访问的路由渲染侧边栏组件
第二种: 通过vuex操作数据,判断路由表,通过v-if 判断是否展示 (本文采用此方式)
1 ) 创建vue实例的时候将vue-router挂载,但这个时候vue-router挂载登录页面
2 ) 用户登录,获取role,将role和路由表每个页面的需要的权限作比较,存在添加show=true,反之show=false,返回新路由表
3 ) 使用vuex管理路由表,左侧菜单栏,动态渲染返回的路由表,通过v-if指令判断是否展示
实现过程
首先确定项目中是否安装vuex,没有先 npm insatll vuex --save
在src下创建store文件夹,然后创建一个index.js ,配置如下:
import Vue from "vue";
import Vuex from "vuex";
import { apiUserAuthorize } from "../api/login";
import bus from "../components/common/bus";
import { dealGetDirs, checkPermission } from "../utils/Recursive"; // 核心文件,遍历路由表方法
Vue.use(Vuex);
export default new Vuex.Store({
// 根据环境 配置 生产环境不使用严格模式
strict: process.env.NODE_ENV !== "development",
state: {
token: null, // 标识
userInfo: null, // 用户信息
userDirs: null, // 用户权限
},
getters: {
// 返回新的路由表
userDirsItems: state => {
// 遍历账户权限 数组 => 对象
const arr = state.userDirs;
const result = dealGetDirs(arr);
// 本地路由配置格式
let items = [
{
icon: "el-icon-lx-goods",
index: "goods",
title: "商品管理",
subs: [
{
index: "supplierList",
title: "供应商"
},
{
index: "categoryList",
title: "商品名录"
},
]
}
];
// 账户权限与本地路由对比,路由 ? show = true :show = false
items = checkPermission(items, result);
return items;
},
},
mutations: {
// 添加token
setToken(state, token) {
state.token = token;
},
// 移除token
setTokenNull(state) {
state.token = null;
},
// 添加用户信息
setUserInfo(state, obj) {
state.userInfo = obj;
},
// 移除用户信息
setUserInfoNull(state) {
state.userInfo = null;
},
// 添加权限
setDirs(state, obj) {
state.userDirs = obj;
},
// 移除权限
setDirsNull(state) {
state.userDirs = null;
},
},
actions: {
// 设置token
actionSetToken({ commit }, { token }) {
return new Promise((ro, rj) => {
try { commit("setToken", token); ro(); }
catch (error) { rj(error); }
});
},
// 设置userinfo
actionSetUserinfo({ commit }, { users }) {
return new Promise((ro, rj) => {
try { commit("setUserInfo", users); ro(); }
catch (error) { rj(error); }
});
},
// 退出登录
actionLogout({ commit }) {
commit("setTokenNull");
commit("setUserInfoNull");
commit("setDirsNull")
sessionStorage.clear();
},
// 获取权限
actionGetDirs({ commit }) {
new Promise((ro, rj) => {
// 调用获取权限接口
apiUserAuthorize().then(res => {
commit("setDirs", res);
bus.$emit("collapse", false); // 菜单栏折叠
ro(res);
}).catch(err => {
rj(err);
});
});
},
}
});
配置好store => index.js之后,在main.js中,引用挂载stroe,
然后利用路由守卫,跳转拦截 传送门:VUE-导航守卫
//引入store
import store from './store'
// 挂载
new Vue({
router,
i18n,
store,
render: h => h(App)
})
/使用钩子函数对路由进行权限跳转
router.beforeEach((to, from, next) => {
let token = store.state.token;
let users = null;
const isLogin = !!store.state.userInfo && !!store.state.userDirs;
if (!token) {
token = sessionStorage.getItem("ttoken");
users = sessionStorage.getItem("userInfo");
// 异步操作store
store.dispatch('actionSetToken', { token: token });
store.dispatch('actionSetUserinfo', { users: JSON.stringify(users) })
}
if (!token && to.path !== '/login') {
next('/login');
} else if (!isLogin && to.path !== "/login") {
// 获取当前登录用户权限
store.dispatch("actionGetDirs").then(
() => { next(); }
).catch(
err => { next("/login"); }
);
} else {
next();
}
})
此时是无法登陆的,我们需要在登陆页面写点东西
apiSignin({
username: ...,
password: ...,
imgcodes: ...
}).then(res = >{
sessionStorage.setItem("ttoken", res.access_token);
sessionStorage.setItem("userInfo", JSON.stringify(res.userInfo));
this.$router.push("/");
}).
catch(err = >{
this.$message({
message: err.message,
type: "warning"
});
});
到此基本配置,逻辑工作就完成了,接下来就是对比路由表了, 接下来是 关键步骤 !!!
// 1.递归处理导航信息 数组 => 对象
const result = {};
export const dealGetDirs = arr => {
if (arr instanceof Array) {
arr.map(item => {
result[item.name] = item.name;
// itemMenu是获取到的二级权限
if (item.hasOwnProperty("itemMenu") && item.itemMenu) {
dealGetDirs(item.itemMenu);
}
});
}
return result;
};
// 2. 对比导航信息
export const checkPermission = (items, DirsResult) => {
items.map(item => {
item.show = DirsResult[item.title] ? true : false;
// subs是store中的items(slidebar的子菜单配置)
if (item.hasOwnProperty("subs") && item.subs) {
checkPermission(item.subs, DirsResult);
}
});
return items;
};
此时已经拿到了新的路由表,接下来就是去slidebar菜单栏,渲染出来就可以了
<template>
<div class="sidebar">
<el-menu class="sidebar-el-menu" :default-active="onRoutes" :collapse="collapse" unique-opened router>
<template v-for="item in items">
<template v-if="item.subs">
<el-submenu :index="item.index" :key="item.index" v-if="item.show">
<template slot="title">
<i :class="item.icon"></i>
<span slot="title">{{ item.title }}</span>
</template>
<template v-for="subItem in item.subs">
<el-menu-item v-if="subItem.show" :index="subItem.index" :key="subItem.index">
<i class="icon"></i>
{{ subItem.title }}
</el-menu-item>
</template>
</el-submenu>
</template>
<template v-else>
<el-menu-item :index="item.index" :key="item.index" v-if="item.show">
<i :class="item.icon"></i>
<span slot="title">{{ item.title }}</span>
</el-menu-item>
</template>
</template>
</el-menu>
</div>
</template>
<script>
import bus from "../common/bus";
export default {
data() {
return {
collapse: false,
roleArr: {} // 对于权限的处理后
};
},
computed: {
onRoutes() {
return this.$route.path.replace("/", "");
},
// 返回的新的路由表
items: function() {
return this.$store.getters.userDirsItems;
}
},
created() {
// 通过 Event Bus 进行组件间通信,来折叠侧边栏
bus.$on("collapse", msg => {
this.collapse = msg;
});
// 侧边栏更新
bus.$on("sidebarup", msg => {
this.getPermission();
});
},
methods: {
getPermission() {
const userDirs = this.$store.state.userDirs;
this.dealPermission(userDirs, this.roleArr);
this.checkPermission(this.items);
},
checkPermission(items) {
items.map(item => {
item.show = this.roleArr[item.title];
if (item.hasOwnProperty("itemMenu")) {
this.checkPermission(item.itemMenu);
}
});
},
dealPermission(arr, result) {
if (arr instanceof Array) {
arr.map(item => {
result[item.title] = item.show;
if (item.hasOwnProperty("subs")) {
this.dealPermission(item.subs, result);
}
});
}
},
// 侧边栏折叠
collapseChage() {
this.collapse = !this.collapse;
bus.$emit("collapse", this.collapse);
}
}
};
</script>
教程指南
本作品采用 知识共享署名-相同方式共享 4.0 国际许可协议 进行许可。