Vue3 实现模块级权限控制的最佳实践
一、文章简介
在中大型前端项目中,权限控制是一项不可或缺的功能。无论是按钮级别、页面级别,还是模块级别的权限,合理的设计和高内聚的实现方式,直接影响系统的安全性和可维护性。
本文将详细讲解如何在 Vue3 项目中实现模块级权限控制,涵盖:
- 权限模型设计
- 动态路由控制
- 指令封装实现按钮级别权限
- 实战代码示例与业务抽象
二、权限模型设计
一个健壮的权限系统通常由以下三部分组成:
- 用户信息:当前用户的角色、权限码、所属组织等信息
- 权限码列表:由后端返回的权限标识集合(例如
["user:add", "order:view"]
) - 资源权限映射:前端根据权限码决定哪些资源可见、可操作
// 示例:用户权限码
const userPermissions = [
"dashboard:view",
"user:edit",
"order:create",
"setting:view"
];
三、模块级权限控制核心逻辑
1. 动态路由过滤
动态根据用户权限加载对应路由模块:
// src/router/permission.ts
function hasPermission(route, permissions) {
if (!route.meta || !route.meta.permission) return true;
return permissions.includes(route.meta.permission);
}
function filterRoutes(routes, permissions) {
return routes.filter((route) => {
if (hasPermission(route, permissions)) {
if (route.children) {
route.children = filterRoutes(route.children, permissions);
}
return true;
}
return false;
});
}
动态生成路由:
import {
userStore } from '@/stores/user';
import {
asyncRoutes } from './routes/async';
const permissions = userStore.getPermissions();
const allowedRoutes = filterRoutes(asyncRoutes, permissions);
router.addRoute(allowedRoutes);
每个路由配置中加上 meta.permission
字段:
{
path: '/user',
component: UserPage,
meta: {
permission: 'user:view'
}
}
四、按钮级权限:自定义指令 v-permission
// directives/permission.ts
import {
Directive } from 'vue';
import {
userStore } from '@/stores/user';
export const vPermission: Directive = {
mounted(el, binding) {
const value = binding.value;
const permissions = userStore.getPermissions();
if (!permissions.includes(value)) {
el.parentNode?.removeChild(el);
}
}
};
注册全局指令:
app.directive('permission', vPermission);
使用方式:
<el-button v-permission="'user:add'">新增用户</el-button>
五、模块级封装方案(推荐)
每个模块内部自带权限配置 + 路由文件:
// modules/user/router.ts
export default [
{
path: '/user',
component: () => import('./views/UserList.vue'),
meta: {
permission: 'user:view' }
}
];
统一在 modules/index.ts
中加载所有模块并过滤:
// modules/index.ts
import userRoutes from './user/router';
import orderRoutes from './order/router';
const allRoutes = [...userRoutes, ...orderRoutes];
export function getAuthorizedRoutes(userPermissions: string[]) {
return filterRoutes(allRoutes, userPermissions);
}
六、前后端配合规范
建议后端提供统一的权限码接口,例如:
GET /api/user/info
{
"username": "admin",
"permissions": [
"user:view",
"user:add",
"dashboard:view"
]
}
权限码建议使用统一命名规范:模块名:操作名
,如:
user:add
user:view
order:export
dashboard:view
七、拓展内容:权限异常处理与提示
- 当用户尝试访问未授权页面时,自动重定向至 403 页面
- 通过路由守卫全局拦截非法访问
router.beforeEach((to, _, next) => {
const permissions = userStore.getPermissions();
if (to.meta.permission && !permissions.includes(to.meta.permission)) {
return next('/403');
}
next();
});
八、动态菜单生成与权限联动渲染
菜单是权限系统的前端呈现核心,需根据权限码动态展示。
1. 从权限中生成菜单树
通常后端也会返回结构化菜单,带有 permission
字段,前端可以根据权限码过滤菜单:
function filterMenuTree(menuList, permissions) {
return menuList
.filter((item) => {
if (!item.permission) return true;
return permissions.includes(item.permission);
})
.map((item) => ({
...item,
children: item.children
? filterMenuTree(item.children, permissions)
: []
}));
}
2. Vue 模板动态渲染菜单
<template>
<el-menu>
<template v-for="item in filteredMenu">
<el-sub-menu v-if="item.children?.length" :index="item.path">
<template #title>{
{
item.title }}</template>
<el-menu-item
v-for="child in item.children"
:key="child.path"
:index="child.path"
>
{
{
child.title }}
</el-menu-item>
</el-sub-menu>
<el-menu-item v-else :index="item.path">{
{
item.title }}</el-menu-item>
</template>
</el-menu>
</template>
九、权限与页面缓存联动(keep-alive 配置)
如果页面权限变更后用户仍停留在旧页面,可能引发异常,需要通过 meta
配置控制缓存行为。
{
path: '/setting',
component: () => import('@/views/Setting.vue'),
meta: {
permission: 'setting:view',
keepAlive: true
}
}
统一封装 <KeepAlive>
包装器:
<template>
<keep-alive :include="cacheablePages">
<router-view v-slot="{ Component }">
<component :is="Component" />
</router-view>
</keep-alive>
</template>
<script setup>
import {
computed } from 'vue';
import {
useRoute } from 'vue-router';
const cacheablePages = computed(() => {
// 可结合权限码动态过滤
return ['Setting', 'Dashboard'];
});
</script>
十、权限开发中常见问题与排查思路
问题 | 原因 | 排查建议 |
---|---|---|
菜单不显示 | 权限码未匹配 | 打印后端权限码是否完整、格式是否一致 |
页面白屏 | 动态路由未注入成功 | 检查权限加载是否在路由加载之前完成 |
权限判断失效 | 自定义指令未生效 | 检查指令是否正确注册,全局权限数组是否响应式 |
页面缓存未更新权限 | keep-alive 缓存未清除 | 手动通过 key 刷新组件或移除缓存 |
十一、前后端权限联动建议
- 接口级权限校验始终在后端进行,前端权限仅作辅助控制。
- 后端返回权限码应:
- 统一命名风格
- 提供接口查询权限(如
/api/user/permissions
) - 提供角色-权限配置页面以便运维人员使用
- 前端应保持权限逻辑与 UI 解耦,通过:
- 路由
meta.permission
v-permission
指令- 动态菜单
permission
字段
- 路由
十二、结语
权限系统是前端系统的核心保障模块之一。通过动态路由、按钮指令、菜单渲染、缓存控制等多个维度的设计与实践,Vue3 项目可实现灵活、安全、可维护的权限控制体系。