從壹開始 [vueAdmin後臺] 之三 || 動態路由配置 & 項目快速開發

回顧

今天VS 2019正式發佈,實驗一波,你安裝了麼?Blog.Core 預計今天會升級到 Core 3.0 版本。html

 

哈嘍你們週三好!原本今天呢要寫 Id4 了,可是寫到了一半,忽然有人問到了關於 Blog.Admin 管理後臺的一些問題,想着這個先後端系列是第一個項目,並且是之後學習的基礎,不能草草了事,因此就把重心往 Blog.Core + Blog.Admin 兩個項目上靠攏了下,明天再更新 IdentityServer4 吧,由於從上週末到今天,這幾天修改了一些東西,這裏就不一一的寫文章了,若是你是跟着系列看的小夥伴,應該知道我寫的是什麼意思,若是是路人,額不敢保證你能看懂我在說什麼,總結來講有如下五點:vue

 

一、Blog.Core 項目增長了單元測試項目 Blog.Core.Tests ,不過就幾行代碼,作了個小測試而已;git

 

二、Blog.Core 項目配置了 AOP Sql 的功能,就是你們在操做倉儲的時候,會生成指定的Sql語句,保存到日誌中,方便查看,這是SqlSugar的核心功能;github

 

 

三、Blog.Core 項目增長多線程日誌功能,防止出現死鎖,能夠查看 BlogLogAOP.cs ,固然你能夠封裝起來;
vue-router

 具體查看方法:static void OutSql2Log(string dataIntercept)數據庫

 

四、DDD開發之 Christ3D 項目完成了末尾開發,實現了 Identity 登陸(注意不是 Id4 ),並完成部署json

地址:http://123.206.33.109:4773後端

 

五、Blog.Admin 項目實現數據庫配置動態路由,這個是今天的重點內容,其餘的都很簡單,你們看看就行。api

而後也簡單說說,如何在 Blog.Admin 項目中,快速添加頁面,雖然這個真的很簡單。數組

 

好啦,立刻開始今天的 Share Time。

由於這個系列我尚未寫過一些文章,因此今天就把兩種寫法都寫上,既補充了以前的方案,又設計新的方案。

 

1、傳統的權限路由是如何設計的?

首先先來個動圖(注意這個方案已經棄用了,看看個過程便可,下邊第二節有個動圖,纔是之後開發的模板):

 

 

 

一、設計頁面

 這個步驟很簡單,也很普通,咱們開發項目,確定須要設計頁面了,這個不屬於權限路由的一部分,

不過要說的就是,這個頁面路徑的設計,要考慮清楚,有的小夥伴,習慣一股腦所有並列放頁面,也有詳情頁用 detail.vue 或者 id.vue ,還有的是 _id.vue 的,具體的這些規則,須要好好想一想,設計清楚,不細說。

 

二、路由實例配置

 ( routerManuaConfig.js 文件 )。相信這個路由你們都已經很明白了,只要是寫過 vue 的,有一點點基礎的,項目初期,咱們每次開發頁面,若是須要在項目中使用,就必須將路由實例添加到 Vue 實例裏,其實說白了,就是一個對象,這樣 Vue 實例才能調用,也就是能經過 url 訪問到咱們的頁面。這個實際上是很合理的,也是很正確的,官方也是這麼處理的,好比個人 Blog.Admin 項目中,就是這麼配置的:

 

但是,嗯,就怕但是,如今個人頁面大概有十多個,就已經一大串了,有時候修改一個路由,須要找半天,這個設計貌似不是隨着項目的擴大,愈來愈不合理,我見過一個項目,頁面有50+,那配置了一大大大頁的路由 json,嗯,感受會有解決方案的,沒錯,vue 官方也說提到了,具體的,下文會詳細說明,我們先安裝這個方案進行下去。

那既然是權限路由,路由有了,就該權限了。

 

三、配置權限菜單(路由)

Login.vue 、App.vue 文件)雖然我已經覆蓋了 GitHub 上的代碼,可是這個方案的寫法還保留着,你們若是真的就喜歡這種配置,也能夠看看。

 上邊咱們是把全部的路由都注入到了 Router 實例裏,那如何渲染權限菜單呢,沒錯,思路很簡單:

一、當前用戶登陸;

二、獲取到用戶標識,好比 Id ;

三、根據用戶標識獲取權限菜單的 Json 數據;

四、根據 Json  數據,渲染到左側菜單上。//查看 App.vue 文件中的 routes 這個數組。

 其實前三步都是在 Login.vue 頁面內進行的,很簡單的兩個方法 GetNavigationBar() ,本身看看便可。

 

四、菜單表中,添加新建的頁面菜單

上邊的步驟中,僅僅是將以前的頁面渲染出來了,若是咱們新建了一個頁面,新的頁面尚未顯示出來,

因此就須要在後臺管理 -> 菜單權限管理 -> 菜單管理中,新建一條數據:

 

五、角色與菜單的分配

上邊菜單數據配置好後 ,就須要對當前用戶所對應的角色,進行權限的再次分配了,這個很好理解,這個是核心,前邊因此的操做都是給這個作鋪墊的:

 

六、存在的小問題

其實關於權限路由,上邊五步走已經實現了,是否是感受很簡單(這裏只說菜單,不說API權限問題,這個是上一篇的《二 || 完美實現 JWT 滑動受權刷新》已經說到了),可是如今面臨着兩個問題:

一、就是上邊提到的,須要將不少的路由一一的配置好,注入到路由實例;

二、由於這樣是一次性把所有的路由都注入,若是當前用戶沒有這個頁面的權限,雖然左側的菜單看不到這個頁面,可是若是他強行訪問這個url的話,仍是會出現的;

固然咱們設計了 api 權限,他看不到內容,可是再強行訪問這個URL的時候,仍是會在當前頁面停留,並看到頁面骨架(就是沒有數據,可是有按鈕啥的),

固然咱們可讓用戶直接跳轉到403頁面,嗯,也是一個辦法。

 那有沒有什麼辦法,能夠動態的生成路由實例呢?欸,要是有這個想法,就是人才,請往下看。

 

2、動態生成路由實例


 上邊咱們研究了通常實現權限路由菜單的方法,很簡單,很直觀,可是有兩個小問題,其實對我來講,主要的仍是手動配置路由的問題,頁面如今的太多,每次開發一個頁面,不只須要添加路由,還須要在菜單表裏,增長該菜單,而後對權限勾選該菜單,從這個邏輯來看,好像在vue項目中,配置路由就成了冗餘的一步了,那若是不配置了,咱們該怎麼辦呢?這就是今天要說的重頭戲 —— 動態路由實例。

 

首先再來個動圖,之後就用這個方法進行快速開發了:

 

 

 上邊的這個已經對當前路由頁面作了權限匹配,咱們匹配好了頁面,剩下的就是開發 API 接口了,開發成功後,要注意兩點:

一、增長接口的受權特性;

 

二、後臺對路由進行編輯,增長api接口;

 

 

一、在路由實例中注入項目基礎路由

 這裏要說的重點是 基礎路由 ,那什麼是基礎路由呢?就是咱們在項目啓動的時候首次運行的頁面(好比登陸頁),或者不須要參與數據庫權限配置的某些路由(好比歡迎頁,404頁),好比我將基本的路由實例從新整理以下:

在項目的 src 文件夾下,新建一個 router 文件夾,而後新建主程序文件 —— index.js ,這個文件之後就是咱們的新的路由方案,用來替換以前的 router.js(如今改名爲 routerManuaConfig.js

 

 你們能夠看到,如今除了這幾個基礎路由,其餘的都被刪除了,統一經過動態注入的方式添加。

 

二、根據環境配置導入組件文件

 這裏實際上是一個小坑,之前沒有研究過,後來搜索了下,才知道的,原來 vue 動態導入 vue 文件,在不一樣的環境還不同,因此這裏特別用了一節來講明下,雖然內容很簡單:

仍是在 src/router 文件夾下,創建兩個文件,而後定義導入方法:

 

_import_development.js
//開發環境導入組件
module.exports = file => require('@/views' + file + '.vue').default


_import_production.js
//生產環境導入組件
module.exports = file => () => import('@/views' + file + '.vue')

 

那如何進行導入呢,就是下邊的重頭戲了。

 

三、動態生成權限路由(核心) 

這個具體的code 在 src 的根目錄下的 promissionRouter.js 文件裏,

A、組件導入 —— _import

這個其實很簡單,只須要根據當前的環境變量,獲取指定的導入方案,傳入路徑地址做爲參數,就能夠很好的實現固然地址的注入。

//獲取組件的方法
const _import = require('./router/_import_' + process.env.NODE_ENV)

// .......

//導入路徑下的組件
route.component = _import(route.path)

 

B、獲取數據與保存鉤子 —— router.beforeEach

 這個是一個核心,就是咱們每次在路由切換的時候,都須要動態處理路由實例,這裏還有點兒瑕疵,我會在之後慢慢完善,可是思路就是這樣的,這裏的路由數據來自兩個方面,一個是api接口獲取,一個是將獲取到的數據存放在本地:

var storeTemp = store;
router.beforeEach((to, from, next) => {
  

    //動態添加路由
    {
        //不加這個判斷,路由會陷入死循環
        if (!getRouter) {
            if (!getObjArr('router')) {
                var user = window.localStorage.user ? JSON.parse(window.localStorage.user) : null;
                if (user && user.uID > 0) {
                    var loginParams = {uid: user.uID};
                    getNavigationBar(loginParams).then(data => {
                        console.log('router before each get navigation bar from api succeed!')
                        if (data.success) {
                            getRouter = data.response.children//後臺拿到路由
                            saveObjArr('router', getRouter) //存儲路由到localStorage
                            routerGo(to, next)//執行路由跳轉方法
                        }
                    });
                }
            } else {
                //從localStorage拿到了路由
                getRouter = getObjArr('router')//拿到路由
                routerGo(to, next)
            }
        } else {
            console.log(to)
           if(to.name&&to.name != 'login'){
               getRouter = getObjArr('router')//拿到路由
               global.antRouter = getRouter
               routerGo(to, next)//執行路由跳轉方法
           }
            next()
        }
    }
});

具體的寫法都很簡單,就是判斷和保存到路由,不細說了,你們pull 下代碼,看看便可。

 

 

C、路由過濾 ——  filterAsyncRouter

 這裏要說下,這個是關鍵,由於咱們獲取到的,僅僅是一個json格式的權限列表,咱們的路徑仍是一個字符串 url ,可是咱們的動態路由,須要一個 component ,也就是剛剛咱們獲取到的對應組件,因此,咱們須要來一個過濾器,將獲取到的json數據中,每個菜單,都添加進去一個 component 組件信息。

//遍歷後臺傳來的路由字符串,轉換爲組件對象
function filterAsyncRouter(asyncRouterMap) {
    //注意這裏的 asyncRouterMap 是一個數組
    const accessedRouters = asyncRouterMap.filter(route => {
        if (route.path) {
            if (route.path === '/') {//Layout組件特殊處理
                route.component = Layout
            } else {
                route.component = _import(route.path)
            }
        }
        if (route.children && route.children.length) {
            route.children = filterAsyncRouter(route.children)
        }
        return true
    })

    return accessedRouters
}

 

 

D、生成路由實例與運行 —— routerGo

 上邊全部的工程中,咱們把路由已經生成好了,就剩下最後一步注入到實例裏,沒錯,就是紅色的部分,是否是很簡單:

function routerGo(to, next) {
    getRouter = filterAsyncRouter(getRouter) //過濾路由
    router.addRoutes(getRouter) //動態添加路由
    global.antRouter = getRouter //將路由數據傳遞給全局變量,作側邊欄菜單渲染工做
    next({...to, replace: true})
}

 

四、遺留問題——重登新用戶路由不一樣步

這個是個很搞笑的問題,怎麼說搞笑呢,好像上邊的 addRoutes 這個方法設計之初,沒有設計過動態刪除?額好吧,我就是開個玩笑,對尤大大的框架,仍是很給力的。

如今有一個問題,就是我如今是test帳號,而後沒有 測試管理 這一組,這個期間我刷新了頁面,而後當我切換超級管理員 blogadmin 的時候,雖然有這個左側菜單,可是點擊測試頁面1的時候,提示404,證實啥,證實這個路由雖然渲染出來了,可是沒有導入到路由實例裏,路由對象裏,仍是以前的,這個時候,必須刷新一下才能正常訪問,請看:

 

 

這個時候我研究出來兩個方案,雖然兩個在本地均可以,可是第二種在發佈模式下不行,也就是dist 上傳到服務器就不行了,這裏作下記錄,暫時使用第一種方案,可能你遇不到。

一、在系統登出,也就是退出登陸的時候,刷新頁面,使用 window.location.reload(),在 App.vue 的logout 方法裏;

二、使用 router.matcher 來替換,在 router/index.js 下的 resetRouter方法,和調用處:promissionRouter.js 下的 routerGo 方法裏調用;

目前一切正常,就暫時使用刷新頁面的第一種方案,第二種之後慢慢研究。

您可參考這個issues

 

3、支持多級菜單——遞歸

這個其實很簡單,之前個人版本只是支持兩級菜單,那若是想實現無限級別的多級菜單的話,就確定須要用到遞歸的概念了,簡單來講,就是——遞歸組件。

具體的原理相信有一點基礎的小夥伴都能理解,就是寫一個遞歸組件,將路由菜單 JSON 給遞歸渲染出來便可:

一、設計菜單遞歸組件

在 src -> components -> 下,新建 Sidebar.vue 組件,實現自身嵌套遞歸:

<template>
    <div>
        <template v-if="item.children">
            <el-submenu :index="item.id+'index'" v-if="!item.leaf">
                <template slot="title">
                    <i class="fa" :class="item.iconCls"></i>
                    <span class="title-name" slot="title">{{item.name}}</span>
                </template>
                <template v-for="child in item.children">
                    <!-- 這裏實現本身遞歸嵌套 注意這個名稱 -->
                    <sidebar
                            v-if="child.children&&child.children.length>0"
                            :item="child"
                            :key="child.path+'5'"/>
                    <el-menu-item v-else :key="child.path+'5'" :index="child.path+'5'">
                        <i v-if="child.children&&child.children.length>0" :class="item.iconCls"></i>{{child.name}}
                    </el-menu-item>
                </template>
            </el-submenu>
        </template>
        <!-- 沒有子節點,直接輸出 -->
        <template v-else>
            <el-menu-item :index="item.path">
                <i class="fa" :class="item.iconCls"></i>
                <template slot="title">
                    <span class="title-name" slot="title">{{item.name}}</span>
                </template>
            </el-menu-item>
        </template>
    </div>
</template>

<script>
    export default {
        name: 'Sidebar',
        props: {
            item: {
                type: Object,
                required: true
            }
        }
    }
</script>

 

二、在App.vue 中進行調用

固然,你也能夠放到模板組件,也就是 Layout.vue 中調用,把左側菜單,頁頭,頁腳放到 Lauout 佈局頁,不過我都放到了 App.vue 了:

 <el-menu  :default-active="$route.path"
                                 class="el-menu-vertical-demo" @open="handleopen" @close="handleclose" @select="handleselect"
                                 unique-opened router :collapse="isCollapse"
                                 background-color="#2f3e52"
                                 text-color="#fff"
                                 active-text-color="#ffd04b">
                            <sidebar v-for="(menu,index) in routes" :key="index" :item="menu" />
                        </el-menu>

 

記得導入組件:

 

來看看最終的效果吧:

 

 

 

4、結語

 好啦,今天的文章,就先寫到這裏吧,最近比較累心,感受努力付出和收穫不是正比,雖然還在學習,仍是想要歇一歇了。

今天的全部內容都已經提交到 Github,而後在線 demo 也作了改變,你們能夠自行查看。

 

5、Github && Gitee

 

.NET CORE 源碼:

Github:  https://github.com/anjoy8/Blog.Core

Gitee :   https://gitee.com/laozhangIsPhi/Blog.Core

 

VUE 項目開源代碼:

https://github.com/anjoy8/Blog.Vue

 

--- ♥♥♥ ---

相關文章
相關標籤/搜索