哈喽你们好,在这个欢庆的日子里,老张祝你们工做都能蒸蒸日上!今天正好也是社团成立的第一天,我也是但愿今天能是个记念日,沾沾这个大喜庆!html
更新:前端
这篇文章获得张善友,张队的阅读,并提供了另外一个方案,你们能够看看,也是不错的。vue
放假这两天,却是学到了不少东西,我这个也是认可的,昨天的事务提交,今天的按钮级别的权限,都是群里小伙伴提供的方案和思路,我就是坐卧不安的写到文章里了,我总怕会说我是知识的偷盗者,固然我这个彻底是为了社区,我毕竟一分钱没有获得,不管访问量有多少,可能充其量就是数字好看。github
言归正传,还记得半年前(2019.02.27)的时候,个人 vue 项目之二:Blog.Admin 正式开源(https://github.com/anjoy8/Blog.Admin),当时打算作一个简单的权限后台系统,我本身想了经常使用的一些功能,固然有人说丑,有人说乱,可是也有人在本身项目和公司中使用,不过也是我付出心血的,并且也是完美的配合了 Blog.Core 项目,当时几大设想功能中,迟迟有一个功能没有实现,搁置了好久 —— 就是按钮级别的权限配置:数据库
当时我为啥没有作这个呢,有两点考虑,一、是由于超级管理员我没让你们访问,就怕误操做数据,对别人观看权限有影响;二、另外一个考虑,就是想把按钮暴漏出来,看看是否是真的 test 测试帐号能不能删除数据。后来我就开始思考,是时候把这个权限加进来了,就是没有删除的权限,删除按钮就不显示,可是考虑了好久,被一个小知识点给卡住了,就是没有想到如何动态事件绑定,这个不懂不要紧,我下文章会说到,前天由群管理 @大黄瓜和@Kawhi 提供了解决思路和方案,眼前一亮,终于实现了这个功能。后端
投稿做者:@大黄瓜 and @Kawhi;api
效果预览:我为了防止大改,目前只在 “角色管理” 页,增长了这个功能,后期所有替换;数组
在线地址:http://vueadmin.neters.club闭包
Github分支:主分支;
Tips : 目前我依然没有开放 Admin 权限,因此若是想看所有效果,能够下载本地,自行配置查看。
下边就开始正式讲解,分红了两部分,步骤+重点知识说明,因此看步骤的时候,直接动手操做就好了,不用管为何,下边的第三部分——重点知识的说明,会简单说说。
不知道还有没有小伙伴记得,我如今后台的权限系统中,左侧的导航条已经自动化了,所谓的自动化,就是已经彻底交给了数据库,不管增长多少权限,不用前端或者后端进行操做,只须要配置便可达到目的,当时呢,我把左侧的菜单和按钮揉到了一张表里,当时感受很不合理,可是如今又改起来简单,得益于这个设计思路,因此此次咱们几乎不用改什么,只须要把按钮信息给放出来便可,这里有两个小点:
一、Permission.cs 菜单表中,新建字段 Func ,用来存放当前按钮所对应的方法事件;
二、/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);三、在 RecursionHelper.cs 中,增长 IsButton 属性,将数据库数据,拼车 Tree 返回到前端;
这样咱们就把按钮数据配合着菜单数据一块儿返回前端了,你能够来查看下:
到这里,咱们第一部分——后端数据就完成了,固然,若是你想更炫酷,能够多增长字段,好比按钮的样式,或者其余属性等等等,这里你确定明白,我就不细说了。
从下边开始,咱们就开始说 Blog.Admin 项目了,请打开 VSCode ,来修改咱们的 Vue 项目:
这个步骤很简单,就是把上边咱们创建的那个 Func 字段,给在页面里增删改查一下就行了,具体的代码自行修改便可。
刚刚咱们上边说到了,把按钮数据配合着菜单一块儿开放了出来,那这个时候咱们要须要检查一下,不能和菜单的展现起冲突,这里我就直接说修改的地方了:
一、修改 Sidebar.vue 组件,让按钮的数据不要进行展现,具体的看看代码就明白了,很简单;
二、修改 src\router\index.js 中的动态路由注入方法,过滤掉按钮数据;
到了这里,咱们的第二部分——准备工做就作完了,接下来,就是本文的重中之重的重头戏,设计这个工具栏了,那具体怎么操做,这个时候我但愿你能够先暂停一下,先不要往下看,先本身脑中考虑一下,按照个人思路,就是按钮数据也已经有了,如何设计这个公共组件呢?考虑五分钟吧......
五分钟后,假设你已经考虑过了,那我就开始正式说明。
既然要作成自动化的组件,就必定要抽象出来,那第一步就是创建一个组件,不能每一个页面都写类似的一堆代码;
其实呢,咱们也要能够配置,不能仅仅把按钮给提出来,还应该有其余的好比<input />搜索框等等,都应该放到工具栏里;
必定要加载或者不加载,不能show or hide,这样别人也会在查看元素的时候,看到;
综上所述 ,个人设计是把表格里的按钮,所有提到了顶部,先给你们一个展现的效果图,这个删除颜色是我手动加的,你也能够本身加个字段配置:
首先咱们建立组件,src\components\Toolbar.vue ,具体的代码以下:
<template> <el-col v-if="buttonList.length>0" :span="24" 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 中的结构,毕竟我把按钮和菜单共有一套嘛。
那如今咱们设计好了子组件——工具栏,接下来就要设计父组件了,传递数据和接受子组件广播了。
刚刚咱们说到了 ,在 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 ,就是固然父组件的内容,就是咱们执行能够在子组件来调用父组件的方法了
这里再说下
上边咱们也说到了,咱们把 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,数据准备完毕。
到了这里就是最后一步了,咱们把以前的 tabel 右侧 “操做栏” 删掉,统一放到顶部,而后绑定数据,就能够加载出来了,
如今咱们把操做栏给取消了,可是咱们如何获取 scope.row 呢?是否是很麻烦,要修改不少呢,其实不是的。
这个功能特别简单,思路就是经过单击某一行,来获取这个 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:
若是不选中某项,会弹出警告:
搞定啦!是否是很简单,几乎没有修改什么,感受以前设计的方案还能够吧,至少扩展仍是很不错的!
到了这里,咱们的动态按钮权限功能,就已经彻底作完了,一个八个步骤,你们动手起来,搞一搞吧。
这块内容呢,其实咱们都已经讲过不少遍了,父传子很简单,只须要定一个自定属性便可,而后子组件接受,好比上文中的:
<toolbar :buttonList="buttonList" @callFunction="callFunction"></toolbar> name: "Toolbar", data() { return { searchVal: "" //双向绑定搜索内容 }; }, props: ["buttonList"], //接受父组件传值
比较复杂的就是 子传父 了,重点仍是要了解一些 $emit 这个api,二十║Vue基础终篇:传值+组件+项目说明 我这篇文章写的还算是详细,若是仍是不懂,我们再一对一讨论吧。
这个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(5,6)); //30 console.log(mul(6,6)); //12
说句简单的,我认为就是在其余地方,去调用某一个方法,很重要的一个点,就是 this 这个到底指向什么,本身能够好好调调。
这个在上边的步骤里我没有说到,是由于咱们把 按钮 给放出来之后,在动态菜单路由的时候,会出现重复的问题,因此咱们就须要坐下过滤,注意这个不是错误,是警告,意思就是咱们把一些重复的东西添加到路由里了,路由会忽略掉,只不过给你们一个 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 。可是这个目前还有一些小问题,我会后期继续优化。