今天聊聊一个经典的面试题:Vue项目中,怎么做权限管理?尤其是如果要控制到按钮级别的权限,又该怎么做呢?看起来像个基本操作,但细细一想,真是个“内有乾坤”的技术活儿。今天,我就用程序员的思维带大家拆解一下 Vue 权限管理的“全家桶”。
咱们先从权限这个概念聊起吧。权限管理嘛,本质就是控制用户能访问什么、不能触碰什么,确保大家各守其位。对于前端来说,权限控制可以简单分为页面级和按钮级,即用户能不能打开某个页面,能不能点击某个按钮。说白了,前端权限管理就是控制请求的发起权,毕竟绝大多数权限请求都来源于页面加载和用户点击。
掌控页面访问:路由权限控制
要控制页面访问,咱们得先盯住路由。实现路由级别的权限控制,可以分成两种方式:
1. 全局加载+路由守卫控制
这一方式算是“满汉全席”了,即把所有路由全挂上,然后在全局路由守卫里“严防死守”。比如,可以给每个路由添加meta
字段来标识访问权限。每次跳转前,我们根据用户的角色与路由的meta.roles
进行比对,看是否符合权限要求:
const routerMap = [
{
path: '/admin',
component: AdminPage,
meta: { roles: ['admin'] }
},
{
path: '/user',
component: UserPage,
meta: { roles: ['user', 'admin'] }
}
];
router.beforeEach((to, from, next) => {
const userRoles = store.getters.roles;
if (hasPermission(userRoles, to.meta.roles)) {
next();
} else {
next('/403');
}
});
function hasPermission(userRoles, routeRoles) {
return routeRoles ? routeRoles.some(role => userRoles.includes(role)) : true;
}
上面的代码就很直观了:用户角色只要匹配到路由的roles
字段,就能访问。要是“无权越级”,系统就把他丢到403提示页去。这个方法看似简单,但因为每次跳转都要检查权限,路由挂载多了会有点卡。
2. 按需加载+动态挂载
想让系统更灵活些?那就试试按需加载吧!用户登录后,咱们可以通过接口获取他的角色信息,再根据角色筛选有权访问的路由,动态挂载需要的部分。用 Vue Router 的addRoutes
来动态挂载路由:
import router from './router';
import store from './store';
router.beforeEach((to, from, next) => {
if (!store.getters.isLoggedIn) {
next('/login');
} else {
if (store.getters.userRoles.length === 0) {
store.dispatch('GetUserRoles').then(() => {
router.addRoutes(store.getters.filteredRoutes);
next({ ...to, replace: true });
});
} else {
next();
}
}
});
这种按需挂载的方式大大减少了初始加载时的路由数量,而且新路由只在用户有权限时才添加,既减小了页面开销,还确保了用户看不到自己无权访问的页面。
想按需显示按钮?没问题!
按钮权限控制其实就是更细化的权限管理。最简单粗暴的方法是在按钮组件上直接用v-if
进行判断,不过呢,页面一多就要手动写很多v-if
。所以更聪明的做法是自定义一个指令,这样我们只需给按钮加个指令,权限鉴定就能由它自动处理。
自定义指令方式:
下面是个简单的自定义指令代码,通过检测用户权限控制按钮显示与否:
import Vue from 'vue';
Vue.directive('has', {
inserted: (el, binding) => {
const userPermissions = store.getters.permissions;
if (!userPermissions.includes(binding.value)) {
el.parentNode && el.parentNode.removeChild(el);
}
}
});
// 使用指令
<el-button @click="edit" v-has="'editPermission'">编辑</el-button>
在这个指令中,我们用binding.value
传入按钮权限,比如“编辑”的权限可能是editPermission
。指令会检查用户的权限数组里是否包含这个权限,没有的话直接移除按钮。
菜单权限:前后端合作一波
权限管理里另一个常见问题是菜单显示。大家可以选择让菜单和路由一起在前端定义,也可以让后端把菜单数据传过来。这里就介绍下常用的前后端分离菜单权限方案。
菜单-路由分离方案
菜单和路由分离后,菜单数据由后端返回。前端只需根据后端传回来的菜单项和权限信息动态生成可见菜单,并挂载相应路由。
// 前端仅保留路由组件,不定义权限
const routeComponents = {
Home: () => import('@/pages/Home.vue'),
Admin: () => import('@/pages/Admin.vue'),
};
axios.get('/api/menu').then(response => {
const menuRoutes = response.data.menu.map(menuItem => ({
path: menuItem.path,
component: routeComponents[menuItem.component],
name: menuItem.name,
}));
router.addRoutes(menuRoutes);
});
前端拿到菜单数据后,直接根据字段生成动态路由表,然后添加到Vue Router
实例。这样不仅简化了前端代码,还避免了前端每次改动菜单都要重编译发布。
接口权限:终极兜底
虽然有了路由和按钮的权限限制,但万一漏网之鱼偷偷发起一个越权请求怎么办?所以最后一道防线——接口权限拦截,少不了。在前端可以通过axios
请求拦截器来加个“检查岗”,凡是没有权限的请求一律打回重做。
axios.interceptors.request.use(config => {
config.headers['Authorization'] = 'Bearer ' + store.getters.token;
return config;
});
axios.interceptors.response.use(
response => response,
error => {
if (error.response.status === 401) {
router.push('/login');
}
return Promise.reject(error);
}
);
上面代码做了两件事:一是给每个请求加上Authorization
头部,二是遇到401状态直接踢回登录页,确保越权请求无所遁形。
面试官问 Vue 的权限管理怎么做,咱们直接从路由权限、按钮权限、菜单权限和接口权限四个方面来分层次解释:
路由权限控制:通过在路由里配置角色信息,然后用
beforeEach
路由守卫在跳转前进行权限验证,确保用户只能访问自己有权访问的页面。按钮权限控制:建议用自定义指令来实现按钮级别的权限判断,更灵活方便。指令会自动控制按钮显示,有效减少繁琐的
v-if
判断。菜单权限:前端通过与后端合作,动态生成用户权限范围内的菜单,实现了菜单与路由的解耦。
接口权限:最后用
axios
的请求拦截器设置权限控制,确保无论是页面加载还是按钮点击,所有请求都受控。
目前,对编程、职场感兴趣的同学,大家可以联系我微信:golang404,拉你进入“程序员交流群”。
虎哥私藏精品 热门推荐 虎哥作为一名老码农,整理了全网最全《前端资料合集》。