從壹開始 [Admin] 之五 ║ 實現『按鈕』級別權限配置

  • 2019 年 10 月 3 日
  • 筆記

一、前情回顧

哈嘍大家好,在這個歡慶的日子裏,老張祝大家工作都能蒸蒸日上!今天正好也是社團成立的第一天,我也是希望今天能是個紀念日,沾沾這個大喜慶!

 

更新:

這篇文章得到張善友,張隊的閱讀,並提供了另一個方案,大家可以看看,也是不錯的。

地址: https://mp.weixin.qq.com/s/CCGANQ5i0Ainq3jcSt7k4A

 

放假這兩天,倒是學到了很多東西,我這個也是承認的,昨天的事務提交,今天的按鈕級別的權限,都是群里小夥伴提供的方案和思路,我就是誠惶誠恐的寫到文章里了,我總怕會說我是知識的偷盜者,當然我這個完全是為了社區,我畢竟一分錢沒有得到,無論訪問量有多少,可能充其量就是數字好看。

 

言歸正傳,還記得半年前(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 字段,給在頁面里增刪改查一下就好了,具體的代碼自行修改即可。

 

 

 

 

3、控制“按鈕”不要和“菜單”展示衝突

剛剛我們上邊說到了,把按鈕數據配合著菜單一起開放了出來,那這個時候我們要需要檢查一下,不能和菜單的展示起衝突,這裡我就直接說修改的地方了:

 

1、修改 Sidebar.vue 組件,讓按鈕的數據不要進行展示,具體的看看代碼就明白了,很簡單;

2、修改 srcrouterindex.js 中的動態路由注入方法,過濾掉按鈕數據;

 

 

到了這裡,我們的第二部分——準備工作就做完了,接下來,就是本文的重中之重的重頭戲,設計這個工具欄了,那具體怎麼操作,這個時候我希望你可以先暫停一下,先不要往下看,先自己腦中考慮一下,按照我的思路,就是按鈕數據也已經有了,如何設計這個公共組件呢?考慮五分鐘吧……

 

五分鐘後,假設你已經考慮過了,那我就開始正式說明。

 

4、設計工具欄組件——Toolbar.vue

 

既然要做成自動化的組件,就一定要抽象出來,那第一步就是建立一個組件,不能每個頁面都寫相似的一堆代碼;

其實呢,我們也要可以配置,不能僅僅把按鈕給提出來,還應該有其他的比如<input />搜索框等等,都應該放到工具欄里;

一定要加載或者不加載,不能show or hide,這樣別人也會在查看元素的時候,看到;

 

綜上所述 ,我的設計是把表格里的按鈕,全部提到了頂部,先給大家一個展示的效果圖,這個刪除顏色是我手動加的,你也可以自己加個字段配置:

 

 

 

首先我們創建組件,srccomponentsToolbar.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 中的結構,畢竟我把按鈕和菜單共有一套嘛。

那現在我們設計好了子組件——工具欄,接下來就要設計父組件了,傳遞數據和接受子組件廣播了。

 

 

5、將按鈕事件綁定到組件上

剛剛我們說到了 ,在 Toolbar.vue 中,核心的內容,就是把動態的事件方法給推送到一個個父組件上,這裡是以 Role.vue 頁面舉例的,所有用到了  $emit(“callFunction”, item)  方法,這個如果你開發vue的話,肯定都知道這個的,這個父子通訊實例中,使用很多,具體的我在之前的文中中,也講到了,你可以看看,這裡不細說,說白了一句話,就是子組件執行父組件方法。二十║Vue基礎終篇:傳值+組件+項目說明。其實到這個地方,我也想到了,但是問題來了:你可以先看看 emit 的用法,使用 emit 一般都是傳遞數據,但是如果傳遞 function 的話,肯定也是一個 name 的字符串,那父組件接受到這個 function name 的時候,很容易當成一個 data,如果強行執行,他們又不在一個對象里,因為有閉包,如何讓頁面執行這個 function 呢?我思考了很久(說明自己學的不到家)。

這個就是這兩個月來困擾我的地方,前邊的思路和後邊的 Table 隔離我都想到了,只是這裡我沒有想到,看來還是需要一些高級前端的朋友喲,前天聽到了一個 apply 方法後,我豁然開朗,原來可以這樣,那下邊我就詳細的說一說,如何父組件執行事件:

在 srcviewsUserRoles.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(5,6));   //30  console.log(mul(6,6));   //12

 

說句簡單的,我認為就是在其他地方,去調用某一個方法,很重要的一個點,就是 this 這個到底指向什麼,自己可以好好調調。

 

 

3、動態路由過濾—— addRoutes

 這個在上邊的步驟里我沒有說到,是因為我們把 按鈕 給放出來以後,在動態菜單路由的時候,會出現重複的問題,所以我們就需要坐下過濾,注意這個不是錯誤,是警告,意思就是我們把一些重複的東西添加到路由里了,路由會忽略掉,只不過給大家一個 warm 而已。

 

 

 

所以呢,我做了一個過濾,封裝了下 route.addRoutes——在 srcrouterindex.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