哈嘍你們好,在這個歡慶的日子裏,老張祝你們工做都能蒸蒸日上!今天正好也是社團成立的第一天,我也是但願今天能是個記念日,沾沾這個大喜慶!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 。可是這個目前還有一些小問題,我會後期繼續優化。