news 2026/10/13 15:03:26

按钮级权限用指令写:Vue3 v-permission 从封装到落地

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
按钮级权限用指令写:Vue3 v-permission 从封装到落地

按钮级权限用指令写:Vue3 v-permission 从封装到落地

导读:管理后台的权限,路由级好做,v-if 一判就行。真正烦的是按钮级——“这个岗位只能主管导出,专员只能看不能导”,如果每个按钮都写v-if="hasPermission('job:export')",代码里全是判断,换权限规则还得一个个找。qkl-boot-admin 的做法是封装一个 v-permission 指令,权限判断收敛到一处。

Vue3 里做按钮权限,常见的土办法是全局$hasPerm方法 + v-if。能用,但有两个毛病:模板里判断逻辑散落、指令无法复用 DOM 层面逻辑(比如没权限直接移除按钮,而不是渲染一个 disabled 的假按钮)。自定义指令v-permission可以解决得更干净——没权限时直接把元素从 DOM 里移除,连点击事件都不用防。

第一步:登录后把权限码存下来

权限数据哪来?登录接口返回用户角色和权限码列表,存到 store,页面刷新时再从接口拉一次(不能只存内存,刷新就没了):

// store/modules/user.jsimport{defineStore}from'pinia';import{getPerms}from'@/api/user';exportconstuseUserStore=defineStore('user',{state:()=>({token:'',permCodes:[]// 权限码数组,如 ['job:view', 'job:export']}),actions:{asyncfetchPerms(){constres=awaitgetPerms();// GET /api/user/permsthis.permCodes=res.data.permCodes||[];returnthis.permCodes;},hasPerm(code){// 支持通配:'job:*' 匹配所有 job 权限if(this.permCodes.includes('*'))returntrue;if(this.permCodes.includes(code))returntrue;// 父级通配:有 'job:*' 时 'job:export' 也算有权限constprefix=code.split(':')[0];returnthis.permCodes.includes(`${prefix}:*`);}}});

权限码的命名规则要提前定好:模块:操作,比如job:view(查看岗位)、job:export(导出岗位)、settle:audit(审核结算)。规则不统一,后面做通配就乱了——比如有人写viewJob、有人写job_view,前缀解析直接失效。

第二步:封装 v-permission 指令

在directives/permission.js里定义指令,登录后动态注册(没登录不能用,避免白屏):

// directives/permission.jsimport{useUserStore}from'@/store/modules/user';exportfunctionsetupPermissionDirective(app){app.directive('permission',{mounted(el,binding){conststore=useUserStore();constcode=binding.value;// 用法:v-permission="'job:export'"if(!code)return;// 数组形式:满足任一即可(v-permission="['job:export','job:view']")constcodes=Array.isArray(code)?code:[code];constallowed=codes.some(c=>store.hasPerm(c));if(!allowed){// 没权限:整个元素移除,连 disabled 都不用el.parentNode&&el.parentNode.removeChild(el);}}});}

main.js 里注册:

// main.jsimport{createApp}from'vue';importAppfrom'./App.vue';import{setupPermissionDirective}from'@/directives/permission';constapp=createApp(App);setupPermissionDirective(app);app.mount('#app');

模板里用法:

移除元素和 disabled 的区别:移除是"眼不见心不烦",彻底没有入口;disabled 是"看得见点不动",体验上更友好但保留信息泄露风险(元素还在 DOM 里)。敏感操作我倾向移除,普通操作可以用 disabled。qkl-boot-admin 的做法是:删除、导出、审核这类有后果的操作一律移除,只读类按钮用 disabled。

第三步:后端必须二次校验

前端指令只是"用户体验层"——按钮藏了,但接口还在。权限校验的真正防线在后端。前端藏按钮、后端不校验,等于门锁装在门框上没锁芯。qkl-boot 后端用注解做接口级权限:

// 导出接口:必须有 job:export 权限@PreAuthorize("hasAuthority('job:export')")@PostMapping("/job/export")publicRexportJob(@RequestBodyExportQueryquery){// 导出逻辑...returnR.ok(fileUrl);}// 删除接口:必须有 job:delete@PreAuthorize("hasAuthority('job:delete')")@DeleteMapping("/job/{id}")publicRdeleteJob(@PathVariableLongid){jobService.delete(id);returnR.ok();}

前端指令 + 后端注解双保险,按钮级权限才算闭环。前端判断用 store 里的 permCodes,后端判断用 SecurityContext 里的权限——两边数据源可能不一致,但权限码必须一致,所以权限码命名规范是前后端共享的契约,最好写进接口文档。

踩坑实录:换了个角色,按钮还在,权限没生效

现象:测试账号从"专员"切到"主管",登录后岗位列表的"导出"按钮还在,点一下直接 403(后端挡住了),但按钮没藏住,很尴尬。

排查过程:F12 看 Network,发现切换角色后前端只调了登录接口,getPerms根本没发请求——store 里的 permCodes 还是旧角色的。再看代码,问题在登录流程:登录成功后直接跳首页,权限拉取放在了App.vue的onMounted里,只在应用首次挂载时跑一次,切换账号是同一个 SPA 会话,不重新挂载,权限码自然是旧的。

定位思路:这是"权限数据刷新时机"问题。权限码必须跟着用户身份走,登录/切换账号时强制刷新。不是指令写错了,是数据没更新,指令拿到的还是旧权限。

最终解决:把权限拉取挪到登录成功的 action 里,和 token 一起写入,切换账号时 store 里的 permCodes 先清空再重新拉:

asynclogin(credentials){constres=awaitloginApi(credentials);this.token=res.data.token;// 关键:登录成功立刻拉权限,覆盖旧账号的权限this.permCodes=[];awaitthis.fetchPerms();// 路由跳转returnres;},asyncswitchAccount(newToken){this.token=newToken;this.permCodes=[];// 先清空,防止旧权限残留awaitthis.fetchPerms();}

同时给指令加一层兜底:如果 store 里 permCodes 还没加载(空数组),先不急着移除,等权限拉取完成再判断——否则首屏会闪一下按钮再消失:

// 首屏兜底:权限未加载时显示 loading,加载完成后指令再判断if(!store.permLoaded){awaitstore.fetchPerms();// 指令挂载时如果还没权限数据,主动拉一次}

改完测试:专员登录看不到导出,主管登录秒出导出按钮,切换账号后权限即时刷新,无残留。

可以直接抄的结论

  1. 权限码命名统一模块:操作,前后端共享契约,写进接口文档;
  2. 指令里没权限直接removeChild移除元素,敏感操作不显示、只读操作用 disabled;
  3. 权限码必须随登录/切换账号刷新,登录 action 里清空再拉,别放在 App onMounted;
  4. 指令挂载时检查权限数据是否已加载,没加载先 fetch 再判断,防首屏闪烁;
  5. 前端指令只是体验层,后端@PreAuthorize才是真正防线,双保险缺一不可。

按钮级权限这事,封装指令只是第一步,权限数据刷新时机才是大多数人翻车的地方。指令 + store 刷新 + 后端注解三件套配齐,权限管理才算真正落地。

项目源码:https://gitee.com/gzqkl/qkl-boot

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/13 15:03:10

C++自增运算符深析:++i与i++的语义、重载、性能与陷阱

很多人在刷题和写业务代码时都会碰到i和i这两个兄弟,面试的时候也几乎必问它们的区别。但说实话,绝大多数人只知道“前自增先加后用,后自增先用后加”,再深一层——为什么后自增在重载时要带一个哑元参数、为什么标准库里的某些迭…

作者头像 李华
网站建设 2026/10/13 15:03:05

电脑内存优化工具一键释放内存超简单

软件介绍 今天第一款是ReduceMemory,又是一款内存清理工具。这软件压缩后只有992KB,不到1MB,非常小巧。对于内存经常不够用、电脑动不动就卡顿的朋友来说,这种小工具备一个在电脑上准没错。 内存信息一目了然 打开软件后&#…

作者头像 李华
网站建设 2026/10/13 15:02:49

静态链表:用数组下标模拟指针的思维训练

1. 为什么静态链表是初学者绕不开的“思维体操”静态链表这个概念,一听到名字就容易让人犯怵——“静态”和“链表”两个词放在一起,像把冰块塞进滚烫的油锅,物理上就互相排斥。但如果你翻过沈俊老师那本被无数高校用作数据结构入门教材的《数…

作者头像 李华
网站建设 2026/10/13 15:01:06

NPOI 1.25 DLL 详解:版本辨析、Excel读写与升级迁移指南

简介:在.NET开发中,Excel文件的程序化读写是一项高频需求。许多老系统至今仍依赖NPOI 1.2.5(常被网盘简称为“1.25 DLL”)这类纯C#类库,无需安装Office即可生成xlsx或读取旧xls文件。理解版本号的真实含义、正确引用DL…

作者头像 李华
网站建设 2026/10/13 15:00:56

前端网络请求全指南:从fetch到axios,搞定跨域与拦截器

前端开发做到第三篇,终于轮到“网络请求”这个绕不开的核心主题了。很多同学写静态页面很溜,一到对接真实接口就发怵:数据从哪来?怎么发出去?报错了怎么看?这些问题我在带人时天天遇到。不管你是写原生Java…

作者头像 李华
网站建设 2026/10/13 14:59:14

二手车价格预测EDA实战:从数据探索到特征工程全流程

1. 赛题背景与EDA的核心价值定位二手车交易价格预测,本质是一个典型的回归类结构化数据挖掘任务。给定一辆车的品牌、行驶里程、注册时间、变速箱类型、燃油类型、车身类型、功率、地区编码等字段,要求预测其交易价格。这类赛题在数据挖掘学习路径中非常…

作者头像 李华