从壹开始 [Admin] 之五 ║ 实现『按钮』级别权限配置
一、前情回顾
哈喽大家好,在这个欢庆的日子里,老张祝大家工作都能蒸蒸日上!今天正好也是社团成立的第一天,我也是希望今天能是个纪念日,沾沾这个大喜庆!
更新:
这篇文章得到张善友,张队的阅读,并提供了另一个方案,大家可以看看,也是不错的。
放假这两天,倒是学到了很多东西,我这个也是承认的,昨天的事务提交,今天的按钮级别的权限,都是群里小伙伴提供的方案和思路,我就是诚惶诚恐的写到文章里了,我总怕会说我是知识的偷盗者,当然我这个完全是为了社区,我毕竟一分钱没有得到,无论访问量有多少,可能充其量就是数字好看。
言归正传,还记得半年前(2019.02.27)的时候,我的 vue 项目之二:Blog.Admin 正式开源(https://github.com/anjoy8/Blog.Admin),当时打算做一个简单的权限后台系统,我自己想了常用的一些功能,当然有人说丑,有人说乱,但是也有人在自己项目和公司中使用,不过也是我付出心血的,而且也是完美的配合了 Blog.Core 项目,当时几大设想功能中,迟迟有一个功能没有实现,搁置了很久 —— 就是按钮级别的权限配置:
当时我为啥没有做这个呢,有两点考虑,1、是因为超级管理员我没让大家访问,就怕误操作数据,对别人观看权限有影响;2、另一个考虑,就是想把按钮暴漏出来,看看是不是真的 test 测试账号能不能删除数据。后来我就开始思考,是时候把这个权限加进来了,就是没有删除的权限,删除按钮就不显示,但是考虑了很久,被一个小知识点给卡住了,就是没有想到如何动态事件绑定,这个不懂没关系,我下文章会说到,前天由群管理 @大黄瓜和@Kawhi 提供了解决思路和方案,眼前一亮,终于实现了这个功能。
投稿作者:@大黄瓜 and @Kawhi;
效果预览:我为了防止大改,目前只在 “角色管理” 页,增加了这个功能,后期全部替换;
在线地址:http://vueadmin.neters.club
Github分支:主分支;
Tips : 目前我依然没有开放 Admin 权限,所以如果想看全部效果,可以下载本地,自行配置查看。
下边就开始正式讲解,分成了两部分,步骤+重点知识说明,所以看步骤的时候,直接动手操作就行了,不用管为什么,下边的第三部分——重点知识的说明,会简单说说。
二、详细设计步骤
1、后端微调,保存按钮相关信息
不知道还有没有小伙伴记得,我现在后台的权限系统中,左侧的导航条已经自动化了,所谓的自动化,就是已经完全交给了数据库,无论增加多少权限,不用前端或者后端进行操作,只需要配置即可达到目的,当时呢,我把左侧的菜单和按钮揉到了一张表里,当时感觉很不合理,但是现在又改起来简单,得益于这个设计思路,所以这次我们几乎不用改什么,只需要把按钮信息给放出来即可,这里有两个小点:
1、Permission.cs 菜单表中,新建字段 Func ,用来存放当前按钮所对应的方法事件;
2、/Permission/GetNavigationBar 接口中,把 IsButton==false 限制去掉,使之可以配合菜单进行递归;
- //var rolePermissionMoudles = (await _permissionServices.Query(d => pids.Contains(d.Id) && d.IsButton == false)).OrderBy(c => c.OrderSort);
- var rolePermissionMoudles = (await _permissionServices.Query(d => pids.Contains(d.Id))).OrderBy(c => c.OrderSort);
3、在 RecursionHelper.cs 中,增加 IsButton 属性,将数据库数据,拼车 Tree 返回到前端;
这样我们就把按钮数据配合着菜单数据一起返回前端了,你可以来查看下:
到这里,我们第一部分——后端数据就完成了,当然,如果你想更炫酷,可以多增加字段,比如按钮的样式,或者其他属性等等等,这里你肯定明白,我就不细说了。
从下边开始,我们就开始说 Blog.Admin 项目了,请打开 VSCode ,来修改我们的 Vue 项目:
2、修改后台权限管理,添加按钮事件
这个步骤很简单,就是把上边我们建立的那个 Func 字段,给在页面里增删改查一下就好了,具体的代码自行修改即可,填写的按钮事件就是【点击的方法名不带括号】。
注意这里必须要添加上事件,否则就算页面显示了按钮,点击也会没有效果。
(我的菜单管理页,使用了树形Table的延迟加载,更直观)
3、控制“按钮”不要和“菜单”展示冲突
刚刚我们上边说到了,把按钮数据配合着菜单一起开放了出来,那这个时候我们要需要检查一下,不能和菜单的展示起冲突,这里我就直接说修改的地方了:
1、修改 Sidebar.vue 组件,让按钮的数据不要进行展示,具体的看看代码就明白了,很简单;
2、修改 src\router\index.js 中的动态路由注入方法,过滤掉按钮数据;
到了这里,我们的第二部分——准备工作就做完了,接下来,就是本文的重中之重的重头戏,设计这个工具栏了,那具体怎么操作,这个时候我希望你可以先暂停一下,先不要往下看,先自己脑中考虑一下,按照我的思路,就是按钮数据也已经有了,如何设计这个公共组件呢?考虑五分钟吧......
五分钟后,假设你已经考虑过了,那我就开始正式说明。
4、设计工具栏组件——Toolbar.vue
既然要做成自动化的组件,就一定要抽象出来,那第一步就是建立一个组件,不能每个页面都写相似的一堆代码;
其实呢,我们也要可以配置,不能仅仅把按钮给提出来,还应该有其他的比如<input />搜索框等等,都应该放到工具栏里;
一定要加载或者不加载,不能show or hide,这样别人也会在查看元素的时候,看到;
综上所述 ,我的设计是把表格里的按钮,全部提到了顶部,先给大家一个展示的效果图,这个删除颜色是我手动加的,你也可以自己加个字段配置:
首先我们创建组件,src\components\Toolbar.vue ,具体的代码如下:
- <template>
- <el-col v-if="buttonList.length>0" :span="" class="toolbar" style="padding-bottom: 0px;">
- <el-form :inline="true" @submit.native.prevent>
- <el-form-item>
- <el-input v-model="searchVal" placeholder="请输入内容"></el-input>
- </el-form-item>
- <!-- 这个就是当前页面内,所有的btn列表 -->
- <el-form-item v-for="item in buttonList">
- <!-- 这里触发点击事件 -->
- <el-button type="primary" @click="callFunc(item)">{{item.name}}</el-button>
- </el-form-item>
- </el-form>
- </el-col>
- </template>
- <script>
- export default {
- name: "Toolbar",
- data() {
- return {
- searchVal: "" //双向绑定搜索内容
- };
- },
- props: ["buttonList"], //接受父组件传值
- methods: {
- callFunc(item) {
- item.search = this.searchVal;
- this.$emit("callFunction", item); //将值传给父组件
- }
- }
- };
- </script>
相信每个人都能看的懂,只是字面意思能看得懂,其中的核心知识点就是 List for渲染,父给子传值,子给父传值,我下文会重点讲到,其中 buttonList 数组的格式,很简单,你可以自己后端封装一下,我这里就偷懒了,直接使用的菜单的数据结果,就是上边我 localstorage.routes 中的结构,毕竟我把按钮和菜单共有一套嘛。
那现在我们设计好了子组件——工具栏,接下来就要设计父组件了,传递数据和接受子组件广播了。
5、将按钮事件绑定到组件上
刚刚我们说到了 ,在 Toolbar.vue 中,核心的内容,就是把动态的事件方法给推送到一个个父组件上,这里是以 Role.vue 页面举例的,所有用到了 $emit("callFunction", item) 方法,这个如果你开发vue的话,肯定都知道这个的,这个父子通讯实例中,使用很多,具体的我在之前的文中中,也讲到了,你可以看看,这里不细说,说白了一句话,就是子组件执行父组件方法。二十║Vue基础终篇:传值+组件+项目说明。其实到这个地方,我也想到了,但是问题来了:你可以先看看 emit 的用法,使用 emit 一般都是传递数据,但是如果传递 function 的话,肯定也是一个 name 的字符串,那父组件接受到这个 function name 的时候,很容易当成一个 data,如果强行执行,他们又不在一个对象里,因为有闭包,如何让页面执行这个 function 呢?我思考了很久(说明自己学的不到家)。
这个就是这两个月来困扰我的地方,前边的思路和后边的 Table 隔离我都想到了,只是这里我没有想到,看来还是需要一些高级前端的朋友哟,前天听到了一个 apply 方法后,我豁然开朗,原来可以这样,那下边我就详细的说一说,如何父组件执行事件:
在 src\views\User\Roles.vue 页面呢,修改我们的工具栏使用:
这种引用组件,在data中,定义 buttonList ,就不说,重点还是要理解 @callFunction 这个必须要和子组件的 $emit 中的方法名一致。然后我们定义 callFunction,用来动态执行一个个事件:
- callFunction(item) {//这个 item 就是我们的 permission.cs 数据
- this.filters = {
- name: item.search
- };//这里是把子组件中的 search 内容,也接受过来
- this[item.Func].apply(this, item);//核心就是要执行 apply 方法
- },
是不是很简单,难点就在于,.apply()这个方法,下文会说到。这个 this ,就是当然父组件的内容,就是我们执行可以在子组件来调用父组件的方法了
这里再说下
6、父组件获取 ButtonList 数据
上边我们也说到了,我们把 button 和 菜单揉在一起了,所以我们很简单操作一下之前的数据就行,做一下筛选:
- // 在 mounted 钩子中,调用 router
- let routers = window.localStorage.router
- ? JSON.parse(window.localStorage.router)
- : [];
- this.getButtonList(routers);
- // 定义方法,目的我为了递归
- getButtonList(routers) {
- let _this = this;
- routers.forEach(element => {
- let path = this.$route.path.toLowerCase();
- if (element.path && element.path.toLowerCase() == path) {
- _this.buttonList = element.children;
- return;
- } else if (element.children) {
- _this.getButtonList(element.children);
- }
- });
- }
OK,数据准备完毕。
7、修改 Table 组件,将工具栏与 Table 逻辑隔离
到了这里就是最后一步了,我们把之前的 tabel 右侧 “操作栏” 删掉,统一放到顶部,然后绑定数据,就可以加载出来了,
现在我们把操作栏给取消了,但是我们如何获取 scope.row 呢?是不是很麻烦,要修改很多呢,其实不是的。
8、Table 改为单选,通过点击,选择某行
这个功能特别简单,思路就是通过单击某一行,来获取这个 table 的 row,这个 element 官网写的很详细,我就简单的说一下吧:
- //触发事件,获取到这个row
- selectCurrentRow(val) {
- this.currentRow = val;
- },
- <!--列表-->
- <el-table
- :data="users"
- highlight-current-row
- v-loading="listLoading"
- @current-change="selectCurrentRow"
- style="width: 100%;"
- >
然后只需要简单的修改一下我们的 edit 和 delete 方法即可,因为我们已经拿到了这个 row:
如果不选中某项,会弹出警告:
搞定啦!是不是很简单,几乎没有修改什么,感觉之前设计的方案还可以吧,至少扩展还是很不错的!
到了这里,我们的动态按钮权限功能,就已经完全做完了,一个八个步骤,大家动手起来,搞一搞吧。
三、重点知识解析
1、组件 ——子传父 & 父传子
这块内容呢,其实我们都已经讲过很多遍了,父传子很简单,只需要定一个自定属性即可,然后子组件接受,比如上文中的:
- <toolbar :buttonList="buttonList" @callFunction="callFunction"></toolbar>
- name: "Toolbar",
- data() {
- return {
- searchVal: "" //双向绑定搜索内容
- };
- },
- props: ["buttonList"], //接受父组件传值
比较复杂的就是 子传父 了,重点还是要了解一些 $emit 这个api,二十║Vue基础终篇:传值+组件+项目说明 我这篇文章写的还算是详细,如果还是不懂,咱们再一对一讨论吧。
2、动态事件绑定—— apply
这个apply 有点儿想 call 回调函数,首先,每个函数都包含两个非继承而来的方法:.apply()和 .call()。这两个方法的用途都是在特定的作用域中调用函数,实际等于设置函数体内this对象的值。
这两个方法接收的参数可以分为两个部分,
第一部分是在其中运行函数的作用域,如果就在当前函数体中运行,就可以直接使用this值,如果在window作用域中使用,可以传入window值,这样,可以实现扩充作用域;
第二部分是参数组,在apply中可以传入Array实例,也可以是arguments对象;在call中,传递给函数的参数必须逐个列举;如果没有参数,这个部分可以省略。
首先我们来看看网上apply()方法的定义:
1. apply()方法能劫持另外一个对象的方法,继承另外一个对象的属性
2.Function.apply(obj,args)方法能接收两个参数
3.obj:这个对象将代替Function类里this对象
4.args:这个是数组,它将作为参数传给Function(args–>arguments)
举个例子,如下所示:
- function sum(num1,num2){
- return num1+num2;
- }
- //两个数相等就相加,不相等就相乘
- function mul(num1,num2){
- if(num1 != num2){
- return num1*num2;
- }else{
- return sum.apply(this,arguments);
- //可以为 sum.apply(this,[num1,num2])或sum.call(this,num1,num2);
- }
- }
- console.log(mul(,)); //
- console.log(mul(,)); //
说句简单的,我认为就是在其他地方,去调用某一个方法,很重要的一个点,就是 this 这个到底指向什么,自己可以好好调调。
3、动态路由过滤—— addRoutes
这个在上边的步骤里我没有说到,是因为我们把 按钮 给放出来以后,在动态菜单路由的时候,会出现重复的问题,所以我们就需要坐下过滤,注意这个不是错误,是警告,意思就是我们把一些重复的东西添加到路由里了,路由会忽略掉,只不过给大家一个 warm 而已。
所以呢,我做了一个过滤,封装了下 route.addRoutes——在 src\router\index.js 中,我们过滤下重复路由,还是递归:
- router.$addRoutes = (params) => {
- var f = item => {
- if (item['children']) {
- item['children'] = item['children'].filter(f);
- return true;
- } else if (item['IsButton']) {
- return item['IsButton']===false;
- } else {
- return true;
- }
- }
- var paramsFilt = params.filter(f);
- router.addRoutes(paramsFilt)
- }
然后咋其他的地方,将 router.addRoutes 统一都换成 router.$addRoutes 。但是这个目前还有一些小问题,我会后期继续优化。
四、Github && Gitee
Core: https://github.com/anjoy8/Blog.Core
Vue: https://github.com/anjoy8/Blog.Admin
从壹开始 [Admin] 之五 ║ 实现『按钮』级别权限配置的更多相关文章
- 『BASH』——文件权限批量恢复脚本——「Permission Revovery」
一.恢复指定程序包所有文件的权限: #!/bin/bash #Assume that you have mounted a correct orignal-system on /mnt read -p ...
- 『转载』hadoop2.x常用端口、定义方法及默认端口
『转载』hadoop2.x常用端口.定义方法及默认端口 1.问题导读 DataNode的http服务的端口.ipc服务的端口分别是哪个? NameNode的http服务的端口.ipc服务的端口分别是哪 ...
- 『实践』VirtualBox 5.1.18+Centos 6.8+hadoop 2.7.3搭建hadoop完全分布式集群及基于HDFS的网盘实现
『实践』VirtualBox 5.1.18+Centos 6.8+hadoop 2.7.3搭建hadoop完全分布式集群及基于HDFS的网盘实现 1.基本设定和软件版本 主机名 ip 对应角色 mas ...
- iOS 多线程:『RunLoop』详尽总结
1. RunLoop 简介 1.1 什么是 RunLoop? 可以理解为字面意思:Run 表示运行,Loop 表示循环.结合在一起就是运行的循环的意思.哈哈,我更愿意翻译为『跑圈』.直观理解就像是不停 ...
- Vue多页面 按钮级别权限控制 directive指令控制
利用driective 构建自己的指令,实现按钮级别权限 项目结构如下: 修改router.js { path: 'schools', name: '列表', component: () => ...
- 关于『HTML5』:第二弹
关于『HTML5』:第二弹 建议缩放90%食用 咕咕咕咕咕咕咕!!1 (蒟蒻大鸽子终于更新啦) 自开学以来,经过了「一脸蒙圈的 半期考试」.「二脸蒙圈的 体测」的双重洗礼,我终于有空肝 HTML5 辣 ...
- 『AngularJS』$location 服务
项目中关于 $location的用法 简介 $location服务解析在浏览器地址栏中的URL(基于window.location)并且让URL在你的应用中可用.改变在地址栏中的URL会作用到$loc ...
- [原创] 【2014.12.02更新网盘链接】基于EasySysprep4.1的 Windows 7 x86/x64 『视频』封装
[原创] [2014.12.02更新网盘链接]基于EasySysprep4.1的 Windows 7 x86/x64 『视频』封装 joinlidong 发表于 2014-11-29 14:25:50 ...
- JS 中通过对象关联实现『继承』
JS 中继承其实是种委托,而不是传统面向对象中的复制父类到子类,只是通过原型链将要做的事委托给父类. 下面介绍通过对象关联来实现『继承』的方法: Foo = { // 需要提供一个 init 方法来初 ...
随机推荐
- python 25 类的成员
目录 1. 类的私有成员 -- __ 1.1 类的私有静态属性 1.2 类的私有方法 1.3 对象的私有方法 2. 类的其他方法 2.1 类方法 @classmethod 2.2 静态方法 @stat ...
- 【凭据不工作】Win远程桌面提示您的凭据不工作
1.浏览器直接进入云服务器 2.打开运行 --输入gpedit.msc--计算机配置--管理模板--windows组件--远程桌面服务--远程桌面会话主机--安全--远程(RDP)链接要求使用制定的安 ...
- HashMap原理。图文并茂式解读。这些注意点你一定还不了解
目录 概述 属性详解 table entrySet size modCount threshold.loadFactor 源码知识点必备 getGenericInterfaces和getInterfa ...
- [企业微信通知系列]Jenkins发布后自动通知
一.前言 最近使用Jenkins进行自动化部署,但是部署后,并没有相应的通知,虽然有邮件发送通知,但是发现邮件会受限于接收方的接收设置,导致不能及时看到相关的发布内容.而由于公司使用的是企业微信,因此 ...
- 洛谷P1240-诸侯安置+递推非搜索
诸侯安置 这道题是一题递推题,一开始自己不知道,用了搜索,只过了三个样例: 两两相同的合并, 成 1,1,3,3,5,5........n*2-1; 然后我们会容易发现一种不同与搜索的动态规划做法. ...
- HDU-DuoXiao第二场hdu 6315 Naive Operations 线段树
hdu 6315 题意:对于一个数列a,初始为0,每个a[ i ]对应一个b[i],只有在这个数字上加了b[i]次后,a[i]才会+1. 有q次操作,一种是个区间加1,一种是查询a的区间和. 思路:线 ...
- hdu 5977 Garden of Eden(点分治+状压)
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=5977 题解:这题一看就知道是状压dp然后看了一下很像是点分治(有点明显)然后就是简单的点分治+状压dp ...
- hdu 1269 迷宫城堡(trajan判环)
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1269 题意:略 题解:trajan模版直接求强连通分量. #include <iostream& ...
- C++ STL vector的学习
vector就是一个不定长数组,vector是动态数组,随着元素的加入,它的内部机制会自行扩充空间以容纳新元素,使用vector之前,必须包含相应的头文件和命名空间. #include <vec ...
- CentOS 磁盘扩容
本人对Linux本来就不是很熟,但是由于各种原因开始捣鼓CentOS(大家都说是这个系统比较好学),开始预计20G的磁盘就足够了,没想到装Oracle就直接报磁盘空间不足,更别提在Tomcat下部署近 ...