vue 实现基础权限管理
持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第11天,点击查看活动详情。
权限管理一般分为两部分:页面权限和按钮权限。不同的用户能访问的页面和按钮不一样。本篇博客使用 Vue 实现基础的权限管理,即根据用户不同的角色给他赋予不同的权限。
页面权限
实现的方式也分为纯前端实现和后端实现:
- 纯前端实现:在前端保存一个路由表,登陆后拿到用户权限信息,然后从路由表中过滤出路由信息,动态添加到路由中。
- 后端实现:路由信息保存在数据库中,登陆后拿到权限信息,然后从数据库中拿到用户能访问到的路由,将路由信息返回给前端,前端动态添加到路由中。
纯前端实现
路由白名单
首先我们需要给项目设置一个白名单路由表即不需要权限的路由(例如登录、注册页面路由等)。
export const routes = [
{
path: "/login",
name: "login",
component: () => import("../views/Login.vue"),
},
{
path: "/",
redirect: "/loginorregister",
},
];
并把它们暴露出去,这样在实例化 Vue-router 时就可以把路由数据作为参数传进去,添加路由。
import VueRouter from "vue-router";
import { routes } from "./router";
Vue.use(VueRouter);
const router = new VueRouter({
mode: "history",
base: process.env.BASE_URL,
routes,
});
这样就能进入登录页面了
通过
router.getRoutes()获取当前所有路由信息如图:此时只有登录路由和 重定向路由。
鉴权路由
准备鉴权路由信息
export default [{
path: "/manage",
name: "manage",
component: () => import("@/views/Manage.vue"),
meta: {roles:["user","admin"]},
children: [
{
path: "home",
name: "home",
component: () => import("../views/Home.vue"),
meta: {roles:["user","admin"]},
},
{
path: "mine",
name: "mine",
component: () => import("../views/Mine.vue"),
meta: {roles:["admin"]},
},
{
path: "/manage",
redirect: "home",
meta: {roles:["user","admin"]},
},
],
}]
可以看到每个路由需要的权限都保存在meta中,只有当用户的权限在路由所需权限数组中时才将该路由添加到结果数组中。
过滤路由方法
因为有可能有子路由,所以需要在遍历的时候判断一下,若有子路由就递归处理。
export const deepRouters = (role,routers = []) => {
const resRouters = [];
routers.forEach(router => {
if (router.meta.roles.includes(role)) {
if (router.children?.length > 0) {
const tempRouter = {
path:router.path,
name:router.name,
component:router.component,
meta:router.meta
}
tempRouter.children = deepRouters(role,router.children);
resRouters.push(tempRouter);
} else {
resRouters.push(router);
}
}
})
return resRouters;
}
在全局路由拦截中过滤添加路由
这里主要是依赖 vue-router 提供的添加路由的方法 addroute和全局前置守卫。
import Vue from "vue";
import VueRouter from "vue-router";
import { routes } from "./router";// 白名单路由
import { getToken, getUserInfo, deepRouters } from "@/util";
import asyncRoutes from '../router/asyncRouters';
Vue.use(VueRouter);
const router = new VueRouter({
mode: "history",
base: process.env.BASE_URL,
routes,
});
let flag = true;
router.beforeEach(async (to, from, next) => {
// 如果已登录
if (getToken()) {
try {
if (to.path === "/login") {
next("/manage");
} else {
if (flag) {// 判断路由信息是否已经添加过了
const userInfo = getUserInfo();//获取用户信息
const resRouters = deepRouters(userInfo.role,asyncRoutes)
resRouters.forEach(item => {
router.addRoute(item);
})
flag = false;
next({ ...to, replace: true });
} else {
next();
}
}
} catch (err) {
console.log(err);
}
} else {//未登录
if (to.fullPath == "/login") {
next();
} else {
next("/login");
}
}
});
export default router;
用户角色是 user 时,所有路由情况
用户角色是 admin 时,所有路由情况
纯前端实现权限管理的缺点很明显,当你需要更改路由信息时,必须在鉴权路由信息中更改,然后重新打包部署,显然很麻烦。
后端实现
后端实现就比较简单了,首先登录成功后,后端会经过过滤操作把能访问的路由信息返回回来,然后就可以在全局路由拦截中将返回的路由信息添加到路由中,就可以正常使用了。
同时也没有纯前端实现的问题,如果想要配置路由信息,完全可以写几个接口实现路由信息管理功能。
这里后端返回的路由信息保存在 vuex state 里边。
import Vue from "vue";
import VueRouter from "vue-router";
import { routes } from "./router";//路由白名单
import { getToken, getUserInfo } from "@/util";
import store from "@/store";
Vue.use(VueRouter);
const router = new VueRouter({
mode: "history",
base: process.env.BASE_URL,
routes,
});
let flag = true;
router.beforeEach(async (to, from, next) => {
// 如果已登录
if (getToken()) {
try {
if (to.path === "/login") {
next("/manage");
} else {
if (flag) {
await store.dispatch("getRoutes", getUserInfo().role);// 用当前用户角色请求路由信息,路由信息放在vuex state中
store.getters.routes.forEach(item => {
router.addRoute({
path: item.path,
name: item.name,
component: () => import(`@/views/${item.component}`),//处理 component
meta: item.meta,
});
});
flag = false;
next({ ...to, replace: true });
} else {
next();
}
}
} catch (err) {
console.log(err);
}
} else {
//未登录
if (to.fullPath == "/login") {
next();
} else {
next("/login");
}
}
});
export default router;
注意:数据库里component保存的是组件文件名,所以在动态添加路由时需要做一下处理,将路由变成按需加载模式。
动态菜单
拿到了路由信息,如果有嵌套路由只需要将子路由与父路由路径拼接后把数组拍平,然后传给侧边栏组件。
function navRouteList(routes, parentPath = null) {
const routeList = [];
routes.forEach(item => {
if (item.name && item.name != "/") {
let path = item.path;
if (parentPath) {
path = parentPath + '/' + item.path;
}
routeList.push({
path,
name: item.name
});
if (item.children) {
const routes = navRouteList(item.children,item.path);
routeList.push(...routes);
}
}
});
return routeList;
}
测试结果
按钮权限
按钮权限可以使用v-if实现,但是如果页面过多,每个页面都要获取用户信息并且和路由信息中的按钮权限信息比较,会降低性能。
自定义指令
我们还可以通过自定义指令做按钮权限。
先配置路由
export default [{
path: "/manage",
name: "manage",
component: () => import("@/views/Manage.vue"),
meta: {btnPermissions:["user","admin"]},
children: [
{
path: "home",
name: "home",
component: () => import("../views/Home.vue"),
meta: {btnPermissions:["user","admin"]},
},
{
path: "mine",
name: "mine",
component: () => import("../views/Mine.vue"),
meta: {btnPermissions:["admin"]},
},
{
path: "/manage",
redirect: "home",
meta: {btnPermissions:["user","admin"]},
},
],
}]
自定义指令
import Vue from 'vue'
import { getUserInfo } from './index';
/**权限指令**/
export const has = Vue.directive('has', {
inserted: function (el, binding, vnode) {
// 获取页面按钮权限
let btnPermissionsArr = [];
if (binding.value) {
// 如果指令传值,获取指令参数,根据指令参数和当前登录人按钮权限做比较。
btnPermissionsArr = Array.of(binding.value);
} else {
// 否则获取路由中的参数,根据路由的btnPermissionsArr和当前登录人按钮权限做比较。
btnPermissionsArr = vnode.context.$route.meta.btnPermissions;
}
if (!Vue.prototype.$_has(btnPermissionsArr)) {
el.parentNode.removeChild(el);
}
}
});
// 权限检查方法
Vue.prototype.$_has = function (value) {
let isExist = false;
// 获取用户按钮权限
let btnPermissionsStr = getUserInfo().role;
if (btnPermissionsStr == undefined || btnPermissionsStr == null) {
return false;
}
if (value.indexOf(btnPermissionsStr) > -1) {
isExist = true;
}
return isExist;
};
然后再函数中只需要使用v-has就好了。
<button v-has>提交</button>
好了,基础权限管理的内容就这么多了,其实思路很简单,就是实现的时候,前后端的配合还是很重要和繁琐的,需要特别注意细节,我当时实现的时候就搞了好久。我是孤城浪人,一名正在前端路上摸爬滚打的菜鸟,欢迎你的关注。