出处:掘金
原作者:金泽宸
权限系统不是“能隐藏按钮”那么简单,而是系统级的“可访问能力管理框架”
权限系统是大型前端系统的标配—— “谁可以看到什么”、“谁可以操作什么”、“谁不能访问什么”,这背后都需要一个通用、可配置、可扩展的权限系统支撑
常见场景包括:
这一篇我们将手把手设计并实现一套:页面级 + 控件级 + 接口级的统一权限系统,支持后端下发权限配置、前端本地判断、指令封装、权限高亮提示等功能
| 模块 | 职责 |
|---|---|
| 权限数据来源 | 后端下发角色权限数组 |
| 权限判断函数 | 判断当前用户是否拥有指定权限 |
| 权限指令(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))
}
// 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-permissionhasPermission(code)、hasAnyPermission(...)<NoPermission />