出处:掘金

原作者:金泽宸


权限系统不是“能隐藏按钮”那么简单,而是系统级的“可访问能力管理框架”

写在前面

权限系统是大型前端系统的标配—— “谁可以看到什么”、“谁可以操作什么”、“谁不能访问什么”,这背后都需要一个通用、可配置、可扩展的权限系统支撑

常见场景包括:

  • 隐藏无权限菜单
  • 禁用无权限按钮
  • 请求接口前判断用户是否具备权限
  • 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 />