
Vue Vben Admin 权限控制完全指南前端、后端与混合模式及按钮细粒度权限实战【免费下载链接】vue-vben-adminA modern vue admin panel built with Vue3, Shadcn UI, Vite, TypeScript, and Monorepo. Its fast!项目地址: https://gitcode.com/GitHub_Trending/vu/vue-vben-admin本文是 Vue Vben AdminVue 3 Vite TypeScript Monorepo 架构权限体系的实战指南系统讲解框架内置的三种路由访问控制模式前端frontend、后端backend、混合mixed的实现原理、配置步骤与菜单数据规范并深入介绍基于权限码与角色的按钮细粒度控制组件 / API / 指令三种用法。读完本文你将能在自己的业务应用中独立完成菜单路由权限与页面按钮权限的完整配置与二次开发。权限体系总览Vue Vben Admin 框架内置了三种权限控制方式均通过应用目录下的preferences.ts中的app.accessMode配置切换模式配置值适用场景前端访问控制frontend角色相对固定、菜单由前端写死的系统后端访问控制backend权限复杂、菜单由后端接口动态下发的系统混合访问控制mixed同时需要前端固定路由权限与后端动态菜单的复杂场景三种模式的核心差异在于路由表由谁生成前端模式路由权限写死在代码中只初始化通用路由需要权限的路由在登录拿到角色后通过角色遍历过滤后经router.addRoute动态加入后端模式登录后调用菜单接口后端按约定数据结构返回菜单前端解析为标准路由结构后动态注册混合模式并行处理前端固定路由权限与后端动态菜单最终按路由name合并两部分路由。无论哪种模式最终都会汇聚到同一个核心函数generateAccessible由它统一完成路由注册与菜单生成源码位于 packages/effects/access/src/accessible.ts。前端访问控制实现原理前端访问控制模式下路由的访问权限直接写在路由的meta.authority中。登录前只初始化通用公开路由需要权限的路由不会进入路由表登录获取用户角色后框架根据角色遍历完整路由表过滤出该角色可访问的路由再通过router.addRoute动态注册到路由实例实现权限过滤。底层过滤逻辑由 packages/utils/src/helpers/generate-routes-frontend.ts 中的generateRoutesByFrontend完成// 根据角色标识过滤路由表,判断当前用户是否拥有指定权限 const finalRoutes filterTree(routes, (route) { return hasAuthority(route, roles); });其权限判断函数hasAuthority的核心规则为function hasAuthority(route: RouteRecordRaw, access: string[]) { const authority route.meta?.authority; if (!authority) { return true; // 未配置 authority默认可见 } const canAccess access.some((value) authority.includes(value)); return canAccess || (!canAccess menuHasVisibleWithForbidden(route)); }缺点权限不够灵活如果后台改动角色前端也需要跟着改动适合角色较固定的系统。配置步骤第一步确保当前模式为前端访问控制模式调整对应应用目录下的preferences.ts确保accessModefrontendimport { defineOverridesPreferences } from vben/preferences; export const overridesPreferences defineOverridesPreferences({ // overrides app: { // 默认值可不填 accessMode: frontend, }, });第二步配置路由权限在路由的meta中声明authority权限数组如果不配置默认可见{ meta: { authority: [super], }, },第三步确保接口返回的角色和路由表的权限匹配可查看应用下的src/store/auth找到下面的代码// 设置登录用户信息需要确保 userInfo.roles 是一个数组且包含路由表中的权限 // 例如userInfo.roles[super, admin] userStore.setUserInfo(userInfo);到这里配置即完成。你需要确保登录后接口返回的角色和路由表的权限匹配否则无法访问。菜单可见但禁止访问有时我们希望菜单可见但禁止访问此时可设置menuVisibleWithForbidden: true菜单依然显示但点击访问会跳转 403 页面。{ meta: { menuVisibleWithForbidden: true, }, },从源码看这一特性在前端模式下由generateRoutesByFrontend实现过滤时对menuVisibleWithForbidden的路由保留hasAuthority中!canAccess menuHasVisibleWithForbidden(route)时返回true随后在mapTree阶段将该路由的组件替换为 403 组件// 如果有禁止访问的页面将禁止访问的页面替换为403页面 return mapTree(finalRoutes, (route) { if (menuHasVisibleWithForbidden(route)) { route.component forbiddenComponent; } return route; });后端访问控制实现原理后端访问控制模式通过接口动态生成路由表后端遵循约定好的数据结构返回菜单。前端将接口数据转换为框架可识别的结构后同样通过router.addRoute动态注册实现权限的动态生成。底层转换逻辑由 packages/utils/src/helpers/generate-routes-backend.ts 中的generateRoutesByBackend完成先调用fetchMenuListAsync获取后端菜单再通过convertRoutes将字符串形式的component映射为真实的组件引用。映射时依赖两个 MaplayoutMap布局组件映射例如BasicLayout、IFrameViewpageMap页面组件映射通常由import.meta.glob(../views/**/*.vue)自动收集。function convertRoutes(routes, layoutMap, pageMap): RouteRecordRaw[] { return mapTree(routes, (node) { // layout转换component 命中 layoutMap 则替换为布局组件 if (component layoutMap[component]) { route.component layoutMap[component]; } else if (component) { // 页面组件转换按 /views/... 规范化路径并补全 .vue 后缀后在 pageMap 中查找 // 查找失败则回退到 /_core/fallback/not-found.vue } return route; }); }缺点后端需要提供符合规范的数据结构前端需要处理数据结构适合权限较为复杂的系统。配置步骤第一步确保当前模式为后端访问控制模式调整对应应用目录下的preferences.ts确保accessModebackendimport { defineOverridesPreferences } from vben/preferences; export const overridesPreferences defineOverridesPreferences({ // overrides app: { accessMode: backend, }, });第二步确保接口返回的菜单数据结构正确可查看应用下的src/router/access.ts找到下面的代码以 playground 应用为例见 playground/src/router/access.tsasync function generateAccess(options: GenerateMenuAndRoutesOptions) { return await generateAccessible(preferences.app.accessMode, { fetchMenuListAsync: async () { // 这个接口为后端返回的菜单数据 return await getAllMenus(); }, }); }在 playground 应用中generateAccess还会注入pageMap、layoutMap与forbiddenComponent其中forbiddenComponent指向 403 页面组件views/_core/fallback/forbidden.vue用于处理menuVisibleWithForbidden的菜单const forbiddenComponent () import(#/views/_core/fallback/forbidden.vue); const pageMap: ComponentRecordType import.meta.glob(../views/**/*.vue); const layoutMap: ComponentRecordType { BasicLayout, IFrameView }; return await generateAccessible(preferences.app.accessMode, { ...options, fetchMenuListAsync: async () await getAllMenusApi(), forbiddenComponent, layoutMap, pageMap, });第三步后端按规范返回菜单数据接口返回的菜单数据示例如下注意其中的关键约定const dashboardMenus [ { meta: { order: -1, title: page.dashboard.title, }, name: Dashboard, path: /dashboard, redirect: /dashboard/analytics, children: [ { name: Analytics, path: analytics, // 这里为页面的路径需要去掉 views/ 和 .vue component: /dashboard/analytics/index, meta: { affixTab: true, title: page.dashboard.analytics, }, }, { name: Workspace, path: workspace, component: /dashboard/workspace/index, meta: { title: page.dashboard.workspace, }, }, ], }, { name: Test, path: /test, component: /test/index, meta: { title: page.test, // 部分特殊页面如果不需要基础布局页面顶部和侧边栏可将noBasicLayout设置为true noBasicLayout: true, }, }, ];数据结构要点component为页面路径的字符串形式需去掉views/前缀和.vue后缀框架会自动规范化路径并匹配到真实组件meta.title推荐使用国际化 key如page.dashboard.title便于多语言场景顶层redirect用于指定默认子页面不需要基础布局顶部导航与侧边栏的独立页面可设置meta.noBasicLayout: true。到这里配置即完成。你需要确保登录后接口返回的菜单格式正确否则无法访问。混合访问控制实现原理混合模式同时结合了前端访问控制与后端访问控制两种方式系统会并行处理前端固定路由权限generateRoutesByFrontend与后端动态菜单数据generateRoutesByBackend最终将两部分路由合并提供更灵活的权限控制方案。在 packages/effects/access/src/accessible.ts 的generateRoutes中可以看到混合模式的处理逻辑case mixed: { const [frontend_resultRoutes, backend_resultRoutes] await Promise.all([ generateRoutesByFrontend(routes, roles || [], forbiddenComponent), generateRoutesByBackend(options), ]); resultRoutes mergeRoutesByName(backend_resultRoutes, frontend_resultRoutes); break; }其中mergeRoutesByName以后端路由为基准keep backend as base按路由name与前端路由合并同名路由的children递归合并meta 冲突时后端优先。优点兼具前端控制的性能优势与后端控制的灵活性适合复杂业务场景下的权限管理。配置步骤第一步确保当前模式为混合访问控制模式调整对应应用目录下的preferences.ts确保accessModemixedimport { defineOverridesPreferences } from vben/preferences; export const overridesPreferences defineOverridesPreferences({ // overrides app: { accessMode: mixed, }, });第二步配置前端路由权限同前端访问控制模式的路由权限配置方式meta.authority。第三步配置后端菜单接口同后端访问控制模式的接口配置方式fetchMenuListAsync。第四步确保角色和权限匹配需要同时满足前端路由权限配置和后端菜单数据返回的要求确保用户角色与两种模式的权限配置都匹配。到这里配置即完成。混合模式会自动合并前端和后端的路由提供完整的权限控制功能。按钮细粒度控制在某些情况下我们需要对按钮进行细粒度的控制可以借助接口返回的权限码或者用户角色来控制按钮的显示。框架在 packages/effects/access/src/index.ts 中统一导出了AccessControl组件、useAccess组合式函数与v-access指令全部来自vben/access包。权限码权限码为接口返回的权限码通过权限码来判断按钮是否显示逻辑在应用下的src/store/authconst [fetchUserInfoResult, accessCodes] await Promise.all([ fetchUserInfo(), getAccessCodes(), ]); userInfo fetchUserInfoResult; userStore.setUserInfo(userInfo); accessStore.setAccessCodes(accessCodes);找到getAccessCodes对应的接口可根据业务逻辑进行调整。权限码返回的数据结构为字符串数组例如[AC_100100, AC_100110, AC_100120, AC_100010]。有了权限码就可以使用vben/access提供的AccessControl组件及 API 进行按钮的显示与隐藏。组件方式script langts setup import { AccessControl, useAccess } from vben/access; const { accessMode, hasAccessByCodes } useAccess(); /script template !-- 需要指明 typecode -- AccessControl :codes[AC_100100] typecode Button Super 账号可见 [AC_1000001] /Button /AccessControl AccessControl :codes[AC_100030] typecode Button Admin 账号可见 [AC_100010] /Button /AccessControl AccessControl :codes[AC_1000001] typecode Button User 账号可见 [AC_1000001] /Button /AccessControl AccessControl :codes[AC_100100, AC_100010] typecode Button Super Admin 账号可见 [AC_100100,AC_1000001] /Button /AccessControl /template组件默认typerole按角色判断需要按权限码判断时必须显式传入typecode。其内部实现见 packages/effects/access/src/access-control.vue当codes数组为空时不渲染插槽否则通过hasAccessByCodes/hasAccessByRoles计算hasAuth决定是否渲染插槽内容。API 方式script langts setup import { AccessControl, useAccess } from vben/access; const { hasAccessByCodes } useAccess(); /script template Button v-ifhasAccessByCodes([AC_100100]) Super 账号可见 [AC_1000001] /Button Button v-ifhasAccessByCodes([AC_100030]) Admin 账号可见 [AC_100010] /Button Button v-ifhasAccessByCodes([AC_1000001]) User 账号可见 [AC_1000001] /Button Button v-ifhasAccessByCodes([AC_100100, AC_1000001]) Super Admin 账号可见 [AC_100100,AC_1000001] /Button /template指令方式指令支持绑定单个或多个权限码。单个时可以直接传入字符串或数组中包含一个权限码多个权限码则传入数组。template Button classmr-4 v-access:codeAC_100100 Super 账号可见 AC_100100 /Button Button classmr-4 v-access:code[AC_100030] Admin 账号可见 [AC_100010] /Button Button classmr-4 v-access:code[AC_1000001] User 账号可见 [AC_1000001] /Button Button classmr-4 v-access:code[AC_100100, AC_1000001] Super Admin 账号可见 [AC_100100,AC_1000001] /Button /template指令由 packages/effects/access/src/directive.ts 中的registerAccessDirective(app)注册为全局v-access指令。需要注意它的一个行为细节指令在mounted阶段通过el?.remove()直接移除无权限的元素且判断逻辑与accessMode相关——仅当accessMode frontend且参数为role时按角色判断其余情况一律按权限码判断。角色角色判断方式不需要接口返回的权限码直接通过用户角色来判断按钮是否显示。组件方式script langts setup import { AccessControl } from vben/access; /script template AccessControl :codes[super] Button Super 角色可见 /Button /AccessControl AccessControl :codes[admin] Button Admin 角色可见 /Button /AccessControl AccessControl :codes[user] Button User 角色可见 /Button /AccessControl AccessControl :codes[super, admin] Button Super Admin 角色可见 /Button /AccessControl /templateAPI 方式script langts setup import { useAccess } from vben/access; const { hasAccessByRoles } useAccess(); /script template Button v-ifhasAccessByRoles([super]) Super 账号可见 /Button Button v-ifhasAccessByRoles([admin]) Admin 账号可见 /Button Button v-ifhasAccessByRoles([user]) User 账号可见 /Button Button v-ifhasAccessByRoles([super, admin]) Super Admin 账号可见 /Button /template指令方式指令支持绑定单个或多个角色。单个时可以直接传入字符串或数组中包含一个角色多个角色均可访问则传入数组。template Button classmr-4 v-access:rolesuper Super 角色可见 /Button Button classmr-4 v-access:role[super] Super 角色可见 /Button Button classmr-4 v-access:role[admin] Admin 角色可见 /Button Button classmr-4 v-access:role[user] User 角色可见 /Button Button classmr-4 v-access:role[super, admin] Super Admin 角色可见 /Button /template权限判断的底层实现useAccess是组件、指令与 API 方式共用的权限判断核心源码见 packages/effects/access/src/use-access.ts。它的两个核心判断函数都基于集合交集实现——只要传入的数组与用户已有的角色/权限码存在任意交集即视为有权限function hasAccessByRoles(roles: string[]) { const userRoleSet new Set(userStore.userRoles); const intersection roles.filter((item) userRoleSet.has(item)); return intersection.length 0; } function hasAccessByCodes(codes: string[]) { const userCodesSet new Set(accessStore.accessCodes); const intersection codes.filter((item) userCodesSet.has(item)); return intersection.length 0; }即数组内任一命中即可访问OR 语义。此外useAccess还暴露了accessMode当前访问模式响应式与toggleAccessMode()在frontend与backend之间切换适合调试场景。路由注册的完整链路无论选择哪种访问模式最终都会进入generateAccessiblepackages/effects/access/src/accessible.ts其完整流程为深拷贝路由表cloneDeep避免污染原始配置按模式生成可访问路由generateRoutes(mode, options)内部按frontend/backend/mixed分发到对应的生成函数统一规范化路由树mapTree遍历所有路由为未配置redirect的父级路由自动补全指向第一个子路由的重定向同时若启用了keep-alive会将懒加载组件包装为与路由name相同的组件名以支持条件缓存动态注册路由优先挂载到根路由/的childrennoBasicLayout为true的路由则直接router.addRoute已存在的同名路由会更新替换避免切换用户时一级目录未更新导致 404生成菜单通过generateMenus(accessibleRoutes, router)依据最终可访问路由生成侧边栏菜单数据。关于自动补全redirect的行为仓库测试用例 packages/effects/access/src/tests/accessible.test.ts 给出了详细边界约定首子路由为动态参数如:id时不生成redirect避免地址栏出现字面量:id导致 404首子路由为绝对路径或空路径时不生成redirect已存在的redirect保持不变深层嵌套时按层级逐级拼接累计绝对路径如/demos/nested/menu2/menu2-1。若你的后端菜单使用对象形式的redirect如{ name: xxx }框架会回退为基于parent.path拼接这些细节在对接后端菜单时值得特别关注。总结Vue Vben Admin 的权限体系以accessMode为总开关通过frontend、backend、mixed三种模式覆盖从简单到复杂的权限需求前端模式配置轻量、适合角色固定的系统后端模式灵活可扩展、适合权限复杂且由后端统一管理的系统混合模式则兼顾两者。路由层的权限过滤统一收敛在generateAccessible而页面按钮层的细粒度控制则由vben/access提供的AccessControl组件、useAccessAPI 与v-access指令三种方式共同支撑。理解这套路由过滤 动态注册 细粒度判断的分层设计即可在业务中按需组合使用。【免费下载链接】vue-vben-adminA modern vue admin panel built with Vue3, Shadcn UI, Vite, TypeScript, and Monorepo. Its fast!项目地址: https://gitcode.com/GitHub_Trending/vu/vue-vben-admin创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考