资讯动态

RBAC按钮级权限控制:前端权限体系的完整实战落地

发布时间:2026/10/17 14:34:39 来源:尧图企业网站定制
做了几年后台管理系统权限这块从最初的“路由里有菜单就完事”慢慢做到路由级、按钮级、甚至接口级联动的控制中间踩过的坑确实不少。说句实话按钮级权限在大多数管理端项目里已经不是“可选增强项”了而是默认的硬性需求——你辛辛苦苦把菜单根据角色藏起来结果用户直接访问URL进入页面后页面上还残留着一堆“删除”“导出”“审批通过”的按钮这场景谁遇到谁知道尴尬。这篇文章我打算把RBAC在前端落地的全过程拆开聊一聊重点放在按钮级别权限控制上。内容包括权限点的设计规范、登录后权限数据的获取与存储、自定义指令和v-if两种实现方案的取舍、路由权限与按钮权限的联动以及后端接口鉴权如何做到“前端展示、后端兜底”。我会把自己实操过、验证过的代码结构直接放出来也会把踩过的坑一并记录希望能帮你一次把权限体系搭稳。1. 项目背景与整体设计思路1.1 为什么路由权限远远不够我见过不少早期后台系统权限控制的粒度只到“路由级别”不同角色登录后菜单不一样可访问的路由也不一样。这种实现平常看着挺正常但只要稍微深入一点就会发现问题——前端路由控制本质上只是“隐藏入口”并不是真正的安全控制。举个例子某系统里有“订单管理”这个路由普通操作员没有菜单入口但如果他直接手动在浏览器里输入该路由对应的URL页面照样可以被加载出来。如果这个页面里的“导出Excel”按钮也直接渲染出来了那问题就更严重了用户的交互入口根本没有被拦住。所以路由级权限只能解决“看不到”解决不了“点不了”的问题。按钮级权限要解决的就是这一层在“能进入页面”的前提下再对页面内部的具体操作做二次校验比如“新增”“编辑”“删除”“导出”“审核”“分配角色”这些细粒度动作按当前用户所属角色来决定是否渲染、是否可用。这样做的好处很直观菜单和页面不必再为不同角色单独定制一个组件搞定权限变化后前端只需刷新权限数据页面按钮就能跟随角色配置动态收敛后端接口鉴权仍然保留前端按钮隐藏只是体验层面的第一道“软防线”真正安全靠接口。1.2 RBAC模型在前端的正确姿势RBAC基于角色的访问控制对应的核心模型是“用户-角色-权限点”三层结构用户不直接拥有权限而是通过拥有某个角色角色再绑定一组权限点。前端在权限设计时需要把这套模型“翻译”成自己能消费的数据结构。我推荐的前端权限数据结构大概是这样{ userId: u_1001, roles: [admin, operator], permissionPoints: [ order:view, order:create, order:edit, order:delete, order:export ] }这里最关键的不是roles数组而是permissionPoints权限点列表。每个权限点对应一个具体的操作按钮权限点编码要有固定的命名规范比如“模块:动作”的格式举几个规范例子order:view表示查看订单order:create表示新增订单order:delete表示删除订单system:user:assignRole表示分配角色data:report:export表示导出报表。权限点编码太长不好维护太短容易歧义我这边总结出一个通用格式资源域:资源对象:动作。如果只有两级也可以写成资源对象:动作用英文冒号分隔。每个权限点在数据库中要有唯一性约束前端拿到的全部权限点是一个扁平字符串数组判定时做集合成员判断简单高效。很多项目在设计时还会遇到“角色和权限谁的粒度更细”的问题。我的建议很明确前端只认权限点不认角色名。角色是用来给管理员批量赋权的但页面按钮的显隐判定只跟权限点有关。这样哪怕是临时给某个用户直接指定权限点而不走角色也不会出现权责不清的问题。1.3 方案选型前端控制还是后端控制按钮级权限的方案选型我把它划分为两条路线纯前端控制登录后后端返回当前用户的权限点列表前端用指令或v-if判断按钮是否渲染。缺点是如果某个按钮的数据被恶意手动构造请求提交到后端而接口没做校验那就存在安全问题。前后端联合控制前端控制“展示不展示”后端控制“允许不允许”。后端接口在每次请求时校验当前用户的权限点没有权限则返回403。在做系统设计时我不会把安全寄托在前端隐藏上。道理很简单前端代码对用户来说是“透明”的浏览器控制台改一下DOM就能让隐藏的按钮重新出现前端隐藏的目的从来都只是提升操作效率、减少无权限用户接触入口而真正的安全任务始终由后端接口完成。方案选型上现代中后台项目我建议直接用前后端分离模式后端通过网关或中间件校验JWT中携带的用户权限信息前端则基于接口返回的权限点做展示控制。至于有些老项目采用“后端渲染页面前端辅助”的方式今天不太常见就不展开聊了。2. 权限数据的获取与存储2.1 登录链路中权限数据如何传递权限数据的拉取遵循“登录后立刻拉取”的原则。我常见的一种做法是在用户登录接口返回中包含权限点列表另一种做法是登录接口成功后先拿token然后前端再调用/api/user/permissions接口。两种做法都有人用但我更推荐后者。原因在于登录接口返回token和基本用户信息权限点单独走一个接口后续权限点更新时比如管理员改了角色权限前端只需刷新权限接口即可不需要重新登录而且token本身就带有过期时间权限接口可以随token一起做校验安全性更高。一个典型的拉取时序用户输入账号密码前端调用POST /api/auth/login后端校验成功后返回{ token, userInfo }前端存储token到本地localStorage或sessionStorage前端带着token调用GET /api/user/permissions拿到permissionPoints数组把permissionPoints存入全局状态管理同时作为后续所有权限判定的数据源。这里有个容易踩的坑不要在路由守卫里每次进入页面都重新请求权限接口。路由守卫是同步逻辑接口是异步请求处理起来非常绕。我建议把权限拉取放到登录后的初始化流程中等权限数据到位后再渲染应用主框架路由守卫只做判断即可。2.2 状态管理与本地缓存的取舍前端状态管理我用的是Vue的PiniaVue 3项目或VuexVue 2老项目权限数据放在单独模块里。存一份到内存同时把原始权限点数组持久化到本地缓存刷新页面时应用主框架在挂载前先从缓存里恢复权限数据然后再走路由判断。一个注意点是本地缓存不能作为唯一数据源。用户权限是有时效性的管理员修改了角色权限后老用户刷新页面还能看到旧权限点这不合理。所以项目里要做“缓存版本号”或者“用户状态变更后的主动清理”登录成功后写入权限点的同时把后端返回的permissionsVersion也存下来每次进入应用时后台静默校验一次token有效期如果token过期清空权限缓存并跳回登录页管理员在后台修改角色权限时可以让后端把受影响用户的token版本号加一前端请求接口时发现token版本号不匹配则强制重新拉取权限。权限数据在store里的基本形态大概是这样// store/permission.js (以 Pinia 为例) import { defineStore } from pinia export const usePermissionStore defineStore(permission, { state: () ({ permissionPoints: [], menuRoutes: [], roleNames: [] }), getters: { hasPermission: (state) (point) { return state.permissionPoints.includes(point) } }, actions: { async fetchPermissions() { const res await api.getUserPermissions() this.permissionPoints res.permissionPoints || [] this.menuRoutes res.menuRoutes || [] this.roleNames res.roles || [] // 同时写入本地缓存 localStorage.setItem(permission_points, JSON.stringify(this.permissionPoints)) localStorage.setItem(permission_version, res.version || 1) }, clearPermissions() { this.permissionPoints [] this.menuRoutes [] this.roleNames [] localStorage.removeItem(permission_points) localStorage.removeItem(permission_version) } } })2.3 权限点缺失时的前端兜底有时候后端返回的权限点列表为空但当前用户是管理员业务上管理员应该有所有权限。这种场景我见过两种常见处理方式后端在返回时给管理员生成全部权限点列表前端不区分前端约定角色名为admin时跳过权限判断。我偏向于第一种让后端直接返回具体权限点前端不做“硬编码特殊角色”的例外判断。如果后端权限体系确实做不到为admin生成全量权限点再退而求其次用第二种。但第二种方案有一个很现实的隐患角色名一旦写死将来换了角色命名前端代码得跟着改更麻烦的是如果某天一个普通角色也取名叫admin整个权限防线就形同虚设了。3. 按钮级权限的核心实现3.1 自定义指令v-permission的完整实现按钮级权限最常见的实现方式就是自定义指令。以Vue 3为例我封装了一个v-permission指令支持单个权限点和数组权限点两种传入方式。指令代码如下// directive/permission.js import { usePermissionStore } from /store/permission function checkPermission(el, binding) { const { value } binding const store usePermissionStore() // 支持 v-permissionorder:create 或 v-permission[order:create, order:edit] const requiredPermissions Array.isArray(value) ? value : [value] const hasPermission requiredPermissions.some((point) store.hasPermission(point)) if (!hasPermission) { // 如果已有父节点说明之前已经移除过避免重复移除导致页面闪烁 if (el.parentNode) { el.parentNode.removeChild(el) } } else { // 有权限时确保元素存在如果之前被移除过需要重新挂载但这种场景较少一般配合v-if使用 } } export default { mounted(el, binding) { checkPermission(el, binding) }, updated(el, binding) { // 权限点可能在运行时变化例如刷新权限后updated阶段重新校验 checkPermission(el, binding) } }这版指令实现里我用了some而不是every因为实际业务里经常出现“拥有其中一个权限点就能看到按钮”的场景。比如“订单管理”按钮同时对应order:create和order:edit权限运营人员只要拥有其中一个就应该看到这个按钮。如果要改为“必须同时拥有所有权限点才能显示”就把some替换为every。指令注册可以在main.js中全局注册import { createApp } from vue import App from ./App.vue import permission from ./directive/permission const app createApp(App) app.directive(permission, permission) app.mount(#app)使用方式el-button v-permissionorder:create typeprimary clickhandleCreate 新增订单 /el-button el-button v-permission[order:edit, order:delete] clickhandleBatchOp 批量操作 /el-button如果当前角色没有对应权限点这段按钮DOM会在指令的mounted阶段直接被移除用户根本看不到按钮入口。有一个小细节要注意移除元素使用removeChild而不是el.style.display none。隐藏元素只是看不见但DOM节点还留在页面上通过开发者工具改样式就能让按钮“露出来”而直接移除DOM节点攻击者至少需要额外构造元素才能实现操作哪怕这不能替代接口鉴权但能让前端体验层面的控制更加彻底。3.2 v-if与函数式判断的精细化场景自定义指令适合绝大多数按钮级权限场景但遇到一种情况就有点难受没有权限时除了隐藏按钮还想同时隐藏一段占位文本或提示。比如表格“操作列”里没有权限时我想渲染一个“无权限”的禁用态而不是直接移除按钮。这种场景用指令做不到精准控制因为指令是在DOM挂载后操作的无法影响模板渲染分支。我的做法是封装一个可全局注入的权限判断函数配合v-if使用// utils/permission.js import { usePermissionStore } from /store/permission export function usePermission() { const store usePermissionStore() function hasPerm(point) { return store.permissionPoints.includes(point) } function hasAnyPerm(points) { return points.some((point) store.permissionPoints.includes(point)) } function hasEveryPerm(points) { return points.every((point) store.permissionPoints.includes(point)) } return { hasPerm, hasAnyPerm, hasEveryPerm } }模板中的用法template v-ifhasPerm(order:delete) el-popconfirm title确定删除该订单 confirmhandleDelete(row) el-button typedanger sizesmall删除/el-button /el-popconfirm /template template v-else el-tooltip content当前账号无删除权限 el-button typeinfo sizesmall disabled删除/el-button /el-tooltip /template在这个例子里没有权限的用户仍然能看到一个“删除”按钮但它是灰色禁用态鼠标放上去还会提示为什么不能用。这种方案在需要“向用户解释为什么不能操作”的场景下体验更好。两种方案的选择原则我总结为希望用户完全不知道这个操作存在用指令希望用户知道操作但被限制用v-if禁用态。3.3 指令与函数方式的边界问题指令方式和函数方式不是互斥的我实际项目里是两套并行简单按钮隐藏用指令复杂交互或需要提示语时用函数。但在使用中要注意几个边界问题动态渲染的按钮如果按钮在v-for循环中渲染指令在mounted阶段会正常执行没问题。但如果数据是异步加载的而按钮依赖异步数据才渲染出来需要确认权限点数据先于按钮渲染到位否则容易在按钮渲染时权限点还是空数组误删按钮。解决方案是确保路由守卫里先拉取权限再进入页面。权限点不存在时的默认行为如果后端返回了某个权限点但前端代码里对应按钮的v-permission里写了一个拼错的权限点按钮会被误删。调试时建议在指令的实现里加一条console.warn当传入的权限点不在已知权限点字典里时给出告警。store未初始化如果指令需要在store初始化之前运行比如应用外部的某个模块直接调用会导致undefined报错。可以在checkPermission里对store做一次防御性判断。4. 路由权限与按钮权限的联动4.1 动态路由注册与菜单渲染按钮权限要发挥作用前提是用户能进入对应的页面。所以路由权限和按钮权限的联动是非常关键的一步。常见实现方案有两类前端静态配置全部路由只在菜单渲染时根据权限过滤。页面URL可被直接访问路由层面不做拦截。前端只保留公共路由登录页、404等其余路由在拿到权限点后由前端根据权限点动态生成并通过router.addRoute注册。我推荐第二种因为它的入口控制更严。动态路由设计如下前端维护一份“路由与权限点的映射表”每个路由项使用meta.permission声明其所需权限点// router/routes.js export const asyncRoutes [ { path: /order, name: Order, component: () import(/views/order/index.vue), meta: { title: 订单管理, icon: list, permission: order:view }, children: [ { path: detail, name: OrderDetail, component: () import(/views/order/detail.vue), meta: { title: 订单详情, permission: order:detail } } ] }, { path: /system, name: System, component: () import(/views/system/index.vue), meta: { title: 系统设置, permission: system:view }, children: [ { path: user, name: UserManage, component: () import(/views/system/user.vue), meta: { title: 用户管理, permission: system:user:view } } ] } ]在路由守卫中根据已经获取的权限点列表过滤这份异步路由表过滤后通过router.addRoute动态注册// router/index.js import { asyncRoutes } from ./routes import { usePermissionStore } from /store/permission router.beforeEach(async (to, from, next) { const store usePermissionStore() if (!store.permissionPoints.length) { // 第一次进入先拉取权限 try { await store.fetchPermissions() const accessibleRoutes filterRoutes(asyncRoutes, store.permissionPoints) accessibleRoutes.forEach(route { router.addRoute(route) }) // 重新进入目标路由确保动态路由已注册完成 next({ ...to, replace: true }) } catch (error) { next(/login) } } else { next() } })filterRoutes的核心逻辑是根据路由的meta.permission判断是否拥有权限没有权限的路由直接过滤掉并且会递归处理子路由。4.2 菜单与按钮的父子级权限联动有些系统菜单和按钮是分开配置的这就容易出现一种很尴尬的状态用户有按钮权限但没有菜单权限或者反过来有菜单权限但页面上的按钮一个都没有。设计时需要明确权限点的层级关系。我习惯把权限点按模块分组维护形成一棵权限树订单管理 (order:view) ├── 查看列表 (order:list) ├── 新增订单 (order:create) ├── 编辑订单 (order:edit) ├── 删除订单 (order:delete) └── 导出订单 (order:export)在这种结构下如果用户只有order:delete但没有order:view意味着他无法进入订单管理页面那order:delete这个权限点是无效的。所以前后端在给角色分配权限时需要保证父级权限存在时子级权限才有意义。前端可以在分配权限的界面上做联动勾选勾选子级时自动带上父级权限后端在保存角色权限时也做一次校验避免数据错误。另一种常见场景是按钮权限直接放在路由的meta.permission上页面组件内通过当前路由的meta.permission来获取“当前页面需要哪个权限点”再进一步判断子按钮权限。这种依赖路由meta的做法比较内聚但维护时需要特别注意路由配置与权限点的同步更新。4.3 刷新页面后的权限恢复SPA应用刷新会重新初始化整个JavaScript上下文store里的权限数据也会被清空。如果用户在某个需要权限的页面直接刷新路由守卫中权限数据还未恢复时会直接跳转去拉取权限接口。在拉取权限期间页面可能会白屏或闪烁。我的处理方式是在应用入口初始化的main.js或App.vue的setup阶段先从本地缓存恢复权限点再注册路由守卫。这样刷新时不会出现权限重新加载的空白期路由守卫最多做一次本地数据的校验确定权限点存在后再放行。如果后端token过期了才会走到重新登录的逻辑。5. 接口鉴权与前端兜底5.1 按钮权限不等于安全边界我一直强调一个观点前端按钮隐藏只是交互层的“软控制”接口层面的鉴权才是“硬控制”。很多新手开发者会在前端隐藏按钮后认为权限已经控制好了结果被有经验的用户抓到漏洞——直接调用接口就能操作无权限的数据这种问题在安全审计时非常突出。后端的权限控制方式通常是这样的用户登录拿到JWTtoken中包含用户ID和角色信息后端在接口入口处通过拦截器或注解如自定义的RequiresPermission(order:delete)校验当前用户是否拥有该权限点没有权限则返回统一错误码403前端捕获到之后给出提示或跳转无权限页面。前端请求库封装时需要对403这种特定状态码做统一处理// utils/request.js import axios from axios import { ElMessage } from element-plus import { usePermissionStore } from /store/permission const service axios.create({ baseURL: import.meta.env.VITE_API_BASE_URL, timeout: 15000 }) // 响应拦截器 service.interceptors.response.use( (response) { return response.data }, (error) { if (error.response) { const { status, data } error.response if (status 401) { // token过期清理权限缓存跳转登录页 const store usePermissionStore() store.clearPermissions() window.location.href /login } else if (status 403) { // 无权限 ElMessage.error(data.message || 抱歉您没有权限执行该操作) } } return Promise.reject(error) } )前端隐藏按钮避免用户“看到”后端校验避免用户“做到”两者配合才能形成完整闭环。5.2 按钮权限的期望行为与接口403的差异这里共享一个很容易忽略的体验细节如果按钮权限和接口权限不一致就会出现“按钮显示可点击点击后却提示无权限”的情况。比如管理员给用户分配了order:create权限点但后端在接口层面只校验了用户是否有角色order_admin这样用户前端看得到新增按钮点击后却收到403体验非常差。解决方案是前端权限点和后端接口权限点要保持完全一致最好由同一套权限数据源生成。如果因为历史原因存在两套权限体系那就必须提供一份映射关系并且做定期的权限一致性巡检。实际项目里我遇到过很典型的案例前端权限点已经改成了order:export后端接口校验的还是老编码export:order结果导出功能在测试环境完全无法用排查了一天发现是前后端权限编码不对齐导致的这类问题在权限体系重构时尤其常见。5.3 防连点与请求层的二次拦截按钮级权限之外还有一个与权限无关但经常被归到“按钮控制”里的问题用户拿到权限后疯狂点击按钮重复提交数据。比如新增订单按钮用户快速点击三次就会同时发起三个新增请求这种情况在权限控制完成后越发明显因为暴露出来的操作按钮都是用户有权限的反而更容易出现误操作。前端可以在请求层做防重拦截以method url 关键参数作为key在请求发起后的短时间窗口内拦截重复请求// utils/request.js 中的请求防重 const pendingRequest new Map() function generateKey(config) { const { method, url, params, data } config return [method, url, JSON.stringify(params || {}), JSON.stringify(data || {})].join() } service.interceptors.request.use((config) { const key generateKey(config) if (config.repeatable ! true) { if (pendingRequest.has(key)) { return Promise.reject(new Error(重复提交请稍后再试)) } pendingRequest.set(key, true) } return config }) service.interceptors.response.use( (response) { const key generateKey(response.config) pendingRequest.delete(key) return response }, (error) { if (error.config) { const key generateKey(error.config) pendingRequest.delete(key) } return Promise.reject(error) } )这个防重逻辑还支持单个按钮级别的repeatable配置有些接口本身就允许重复请求比如查询操作就额外排除一下即可。6. 常见问题与排查技巧实录6.1 权限指令明明写了按钮却不生效这种情况我排查过多次最后定位的原因一般是这几个权限点编码不一致前端写的是order:create后端返回的是order:CREATE大小写不同导致判断失败。store里还没有权限数据路由守卫里拉权限的异步逻辑还没完成页面已经渲染出来了指令挂在mounted阶段执行时权限点还是空数组。v-permission放在组件标签上而非原生DOM上比如el-button v-permissionorder:create本质上还是在原生button上生效的但如果作用在某个自定义封装的子组件上指令的mounted触发时机可能先于子组件内部按钮的渲染导致的后果是移除失败或移除后又渲染回来了。我自己解决这类问题的标准流程是打开开发者工具在控制台打印一下store里的permissionPoints再打印一下当前按钮绑定的权限点对比一下数据是否匹配。几乎所有按钮不生效的问题最后都能归结为数据没有对齐。6.2 角色权限改了用户仍然看到旧按钮这是权限缓存导致的。用户登录后权限点被持久化在本地缓存即使管理员在后台修改了角色权限用户可能不会实时刷新。如果项目对实时性要求不高可以在用户下次发起任何请求时让后端校验权限版本号不一致则返回特殊状态码前端收到后静默重新拉取权限。具体实现上后端在JWT中可以附带一个permVersion字段每次角色权限变更时permVersion加一。前端在路由守卫中比较本地缓存的版本号与后端返回的版本号不一致就强制刷新权限。这个方案虽然增加了一点后端逻辑但能彻底解决“权限变更不生效”的抱怨。6.3 动态注册路由后刷新404这是我早期做动态路由时经典的坑动态路由是通过router.addRoute注册的但刷新后store清空路由注册也需要重新执行。如果守卫逻辑写得不严谨刷新时用户带着URL直接访问某个动态路由此时路由表里还没有该路由Vue Router会先匹配404再执行守卫导致直接进入404页面。解决方案就是我在4.1里写的那套流程守卫里发现权限数据为空时先调用fetchPermissions拉权限再注册路由然后next({ ...to, replace: true })重新进入目标路由。注意重新进入的动作一定要带replace: true否则路由跳转记录里会产生多余的历史记录用户在浏览器点返回时可能会陷入死循环。6.4 父子组件权限作用域混乱按钮权限最常见的遗漏场景是在弹窗组件、抽屉组件或表格操作列里这些组件往往由父组件动态渲染权限指令写在弹窗内部但弹窗组件本身是懒加载或异步渲染的。比如表格的“操作列”里的按钮权限如果按钮是在子组件内部通过v-permission控制的父组件在异步拿到列表数据后才渲染子组件子组件挂载时store权限点已经存在指令执行没有问题。但有一种例外子组件内部通过v-show控制按钮显隐再叠加权限指令两个逻辑叠加后按钮虽然被指令移除但父组件里的v-show还可能继续引用这个已经被移除的DOM造成控制台报错。这类问题更稳妥的规避方式是把权限判断统一收敛到一个方法里按钮是否渲染由同一个函数决定不要在多个位置分散判断逻辑。我在项目里会把这些判断统一封装成hasPerm函数按钮的渲染只由模板里的v-ifhasPerm(order:delete)决定不再上面套一层v-show彻底避免权限指令和显示指令的冲突。6.5 权限点太多导致接口返回慢当系统模块多、权限点数量上千时每次登录都拉取全量权限点接口的响应时间会明显上升。我遇到过权限点列表500多个、接口返回300ms的业务系统虽然不至于不可用但体验确实不好。优化方向有两个后端把权限点按模块分组返回树形结构前端使用flat展开为数组减少传输量按需拉取进入某个模块页面时再拉取该模块的权限点但这会增加前端路由守卫的逻辑复杂度适合模块边界清晰、权限点特别多的超大型系统。普通后台项目没必要做这么细全量拉取并缓存就够了。7. 一些经验总结与避坑提示走到这里RBAC按钮级权限控制的核心链路已经全部走通了。回看整个设计和实现过程有几个原则我个人认为值得反复强调。前端权限只解决“展示”问题。无论指令还是函数本质都是根据权限点数据决定渲染什么。再巧妙的前端实现也不能替代后端的接口校验权限设计时必须同时考虑前后端两层的职责边界。权限点是唯一的事实来源。不要在代码里直接判断用户角色名比如if (roles.includes(admin)角色会变权限点不会角色名写死在代码里将来权限模型调整时前端是灾难现场。权限点编码规范了前后端校验逻辑可以完全对齐排查问题也轻松。权限数据的加载时机要放在路由守卫之前的初始化流程中。应用启动、路由注册、权限拉取这三件事的先后顺序必须清晰否则会出现各种“页面闪烁、按钮忽隐忽现、路由跳404”的疑难杂症。权限变更尽量做成“后端主动通知”。如果后端不能主动推送至少要在请求链路里埋一个权限版本号校验点让老权限在最短时间内失效避免用户长时间带着过期的权限点使用系统。权限设计越早做越好。如果项目已经上线再回头补按钮级权限会非常痛苦。页面里散落着大量裸按钮没有权限点规划代码里还有各种v-ifuserType 1的判断稍一改动就容易漏掉某个角落。新项目从一开始就把权限点字典建出来哪怕初期只有几个模块也远比临时收集要省力得多。最后再分享一个实际操作中的小技巧给权限点命名时动作动词不要用中文也尽量不要缩写。像order:create、order:export这种英文格式在代码里很直观但在权限配置页面里给管理员展示时需要一份“权限点编码-业务含义”的映射表。把这份映射表做成接口的一个返回字段前端权限分配页面就能直接拉取并展示成树形结构权限点再多也不会弄混。按钮级权限这事能不能做好其实不在于具体用了指令还是v-if而在于数据模型是否清晰、前后端是否对齐、边界条件是否考虑周全。按照上面的链路一步步搭建下来你的系统权限体系至少能支撑未来很长一段时间的业务扩展。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑