出处:掘金
原作者:金泽宸
权限系统不是“能隐藏按钮”那么简单,而是系统级的“可访问能力管理框架”
写在前面
权限系统是大型前端系统的标配—— “谁可以看到什么”、“谁可以操作什么”、“谁不能访问什么”,这背后都需要一个通用、可配置、可扩展的权限系统支撑
常见场景包括:
- 隐藏无权限菜单
- 禁用无权限按钮
- 请求接口前判断用户是否具备权限
- A 角色可以看字段 X,B 角色不能看
这一篇我们将手把手设计并实现一套:页面级 + 控件级 + 接口级的统一权限系统,支持后端下发权限配置、前端本地判断、指令封装、权限高亮提示等功能
前端权限系统核心组成
模块 | 职责 |
---|---|
权限数据来源 | 后端下发角色权限数组 |
权限判断函数 | 判断当前用户是否拥有指定权限 |
权限指令(Vue) | 控制 DOM 显示、禁用、隐藏等 |
菜单/路由权限 | 控制哪些页面可访问 |
接口权限 | 控制哪些请求是否允许发起 |
权限模型设计
常见三种模型:
模型 | 描述 | 示例 |
---|---|---|
RBAC(角色权限)✅ | 基于角色的权限控制 | admin 拥有用户增删改查权限 |
ABAC(属性权限) | 基于属性控制 | 用户部门 = 财务部才可查看财务报表 |
PBAC(策略权限) | 组合条件策略 | “用户是领导”且“项目已归档”才可审批 |
前端通常采用:简化版 RBAC(角色权限)+ 前端表达式辅助判断
权限数据下发方式
后端返回用户信息中附带权限字段:
{
"userId": "u123",
"username": "张三",
"roles": ["admin"],
"permissions": [
"user.create",
"user.update",
"user.delete",
"report.finance.view"
]
}
前端缓存到全局 store:
// store/user.ts
export const useUserStore = defineStore('user', {
state: () => ({
permissions: [] as string[],
}),
})
前端判断函数封装
// shared/utils/permission.ts
import { useUserStore } from '@/store/user'
export function hasPermission(code: string): boolean {
const userStore = useUserStore()
return userStore.permissions.includes(code)
}
扩展多条件判断:
export function hasAnyPermission(...codes: string[]) {
const userStore = useUserStore()
return codes.some((code) => userStore.permissions.includes(code))
}
Vue 权限指令封装(按钮级)
// shared/directives/permission.ts
import { hasPermission } from '@/shared/utils/permission'
export default {
mounted(el: HTMLElement, binding: any) {
const code = binding.value
if (!hasPermission(code)) {
el.style.display = 'none'
}
},
}
注册:
app.directive('permission', permissionDirective)
使用方式:
<pf-button v-permission="'user.create'">创建用户</pf-button>
好处:
- 可复用
- 不影响原组件逻辑
- 业务清晰明了
菜单和路由权限控制
路由配置加上权限字段:
{
path: '/user',
component: UserList,
meta: { permission: 'user.view' }
}
全局守卫判断权限:
router.beforeEach((to, from, next) => {
const required = to.meta.permission
if (required && !hasPermission(required)) {
return next('/403')
}
next()
})
接口权限控制(灰度功能)
const showFinanceReport = hasPermission('report.finance.view')
if (showFinanceReport) {
fetchFinanceData()
} else {
toast.warning('您没有查看权限')
}
或者统一封装接口:
function secureCall(permission: string, fn: () => void) {
if (hasPermission(permission)) {
fn()
} else {
toast.warning('无权限')
}
}
权限不足时的高亮提示
可统一封装组件或弹窗指令:
app.directive('disabled-permission', {
mounted(el, binding) {
const hasAuth = hasPermission(binding.value)
if (!hasAuth) {
el.setAttribute('disabled', 'true')
el.title = '当前角色无操作权限'
}
},
})
权限系统模块化设计建议
shared/
├── directives/
│ └── permission.ts
├── utils/
│ └── permission.ts
├── components/
│ └── PermissionButton.vue
│ └── NoPermissionFallback.vue
├── hooks/
│ └── usePermission.ts
store/
└── user.ts
增强建议(平台化)
能力 | 建议方案 |
---|---|
权限平台 | 后台系统配置权限码 → 分配到角色 |
权限码文档化 | 统一维护 permissionList ,便于产品 + 开发协同 |
预设权限集 | 内置 admin / editor / viewer 模板 |
自动生成权限码 | 页面或按钮设计时支持自动生成 page:action 权限标识 |
实战建议(组件)
- 通用按钮组件:
<PermissionButton />
- 通用权限指令:
v-permission
- 全局权限检测 API:
hasPermission(code)
、hasAnyPermission(...)
- 菜单/路由权限统一控制模块
- “无权限提示” fallback 组件:
<NoPermission />