核心概念
权限系统大概可以分为两类,一个是路由级的权限,也就是控制菜单的显示和隐藏,达到能不能看到菜单,进入页面的权限控制。另一个是按钮级别的权限,实现更精细的页面内的按钮或者组件的显示和隐藏的控制。
关键原理
路由级权限控制
路由级的权限,说白了就是用来控制页面的跳转,不同权限下的用户跳转统一路由因为权限达到成功和失败的结果。想要实现这样的结果,大概可以有两种思路:
- 跳转时判断是否有权限
- 动态注册已有权限的路由
第一种方式更简单一些,直接在路由的拦截器中去做相应的权限判断,路由的权限信息可以配置到元信息中
js
const routes = [
{
path: '/admin',
component: AdminPanel,
meta: { requiresAuth: true, roles: ['admin'] } // 需登录且角色为管理员
},
{
path: '/user',
component: UserPanel,
meta: { requiresAuth: true, roles: ['user', 'admin'] } // 需登录且角色为用户或管理员
}
];
然后在路由全局守卫中校验当前用户的权限,是否满足路由的配置
js
router.beforeEach((to, from, next) => {
const isAuthenticated = !!localStorage.getItem('token');
const userRole = store.state.user.role; // 从Vuex/Pinia获取用户角色
// 检查路由是否需要权限
if (to.matched.some(record => record.meta.requiresAuth)) {
if (!isAuthenticated) {
next('/login'); // 未登录跳转登录页
} else if (to.meta.roles && !to.meta.roles.includes(userRole)) {
next('/403'); // 无权限跳转错误页
} else {
next(); // 放行
}
} else {
next(); // 公开路由直接访问
}
});
[1,6](@ref)
这样的方式看似简单,但有一个致命的缺点,权限都在前端路由配置中写死,无所做到灵活配置 ,当然这样写的方式也太死板了,我们可以把权限不写到meta
中,而是通过接口登录后获取,在跳转后匹配前端路由和后端权限返回,共同决定路由是否可以跳转。 还有一个是菜单的显示,上面的方式是把所有的路由都注册了,但是显然我们不能显示所有的菜单,不能跳转的页面先出来也太傻了,这里也需要和后端来功能决定哪些菜单是需要展示的,哪些是隐藏的。 但是,都做到这一步了,我们完全可以用第二种动态路由来实现路由级权限来。。。
动态路由实现
在前端路由的代码里,我们只配置基础路由信息,也就是所有用户都会有的路由权限,而其他的根据后端返回,来进行动态注册
js
// 登录后获取用户权限路由数据
import router from './router'; // 引入路由实例
async function login() {
// ... 登录逻辑
const userPermissions = await fetchUserPermissions(); // 从后端获取用户权限
// 根据权限数据生成动态路由
const dynamicRoutes = generateRoutes(userPermissions);
// 动态添加到路由实例
dynamicRoutes.forEach(route => {
router.addRoute(route); // 动态添加路由
});
// 跳转到首页或目标页
router.push('/dashboard');
}
// 一个简单的生成路由的函数示例
function generateRoutes(permissions) {
return permissions.map(permission => ({
path: permission.path,
component: () => import(`./views/${permission.componentName}.vue`), // 动态导入组件
meta: {
title: permission.title,
icon: permission.icon
}
}));
}
这样的方式更彻底,菜单直接根据路由表生成,无需把所有的路由都注册,安全性更高。 但是动态路由注册也带来了一些问题
- 路由的重复注册 添加新的动态路由之前应该清除旧的路由,多次调用addRoute会导致路由重复,会抛出异常
- 页面刷新路由丢失、 动态路由保存在JavaScript内存中,页面刷新后,vue重新初始化,动态路由都会丢失,所以我们需要将动态路由存储起来,可以存储到localStorage或者sessionStorage中,在应用初始化或者路由守卫中读取存储的数据并重新添加路由。 当然也可以每次都去请求登录用户的动态路由接口,重新请求重新添加
按钮级别权限
按钮级别的权限会简单一点,登录后,获取到所有的按钮级别权限的数组,然后在每一个需要控制到按钮或组件的地方,单独判断该按钮的权限字符是否在当前用户的权限字符数组中,可以直接用v-if,或者更好的方式一个专门用来判断按钮权限的指令。
js
Vue.directive('permission', {
inserted(el, binding) {
const requiredRole = binding.value;
const userRole = store.state.user.role;
if (userRole !== requiredRole) {
el.parentNode?.removeChild(el); // 无权限则移除DOM元素
}
}
});
// 使用示例
<button v-permission="'admin'">删除数据</button>
一点需要注意的管理员权限可以用' * '来替代,我们的指令中可以判断 * 直接返回