SpringBoot + Vue + ElementUI 實現後臺管理系統模板 -- 前端篇(六):使用 vue-router 進行動態載入菜單

来源:https://www.cnblogs.com/l-y-h/archive/2020/06/05/13052196.html
-Advertisement-
Play Games

前提: (1) 相關博文地址: SpringBoot + Vue + ElementUI 實現後臺管理系統模板 -- 前端篇(一):搭建基本環境:https://www.cnblogs.com/l-y-h/p/12930895.html SpringBoot + Vue + ElementUI 實現 ...


前提:

(1) 相關博文地址:

SpringBoot + Vue + ElementUI 實現後臺管理系統模板 -- 前端篇(一):搭建基本環境:https://www.cnblogs.com/l-y-h/p/12930895.html
SpringBoot + Vue + ElementUI 實現後臺管理系統模板 -- 前端篇(二):引入 element-ui 定義基本頁面顯示:https://www.cnblogs.com/l-y-h/p/12935300.html
SpringBoot + Vue + ElementUI 實現後臺管理系統模板 -- 前端篇(三):引入 js-cookie、axios、mock 封裝請求處理以及返回結果:https://www.cnblogs.com/l-y-h/p/12955001.html
SpringBoot + Vue + ElementUI 實現後臺管理系統模板 -- 前端篇(四):引入 vuex 進行狀態管理、引入 vue-i18n 進行國際化管理:https://www.cnblogs.com/l-y-h/p/12963576.html
SpringBoot + Vue + ElementUI 實現後臺管理系統模板 -- 前端篇(五):引入 vue-router 進行路由管理、模塊化封裝 axios 請求、使用 iframe 標簽嵌套頁面:https://www.cnblogs.com/l-y-h/p/12973364.html

(2)代碼地址:

https://github.com/lyh-man/admin-vue-template.git

 

一、獲取動態菜單數據

1、介紹

(1)為什麼使用動態載入菜單?
  在之前的 router 中,菜單的顯示都是寫死的。
  而實際業務需求中,可能需要動態的從後臺獲取菜單數據並顯示,這就需要動態載入菜單了。
比如:
  需要根據用戶許可權,去展現不同的菜單選項。

(2)項目中如何使用?
  項目中使用時,各個 vue 組件事先定義好,並存放在固定位置。
  用戶登錄成功後,根據後臺返回的菜單數據,動態拼接路由信息,並顯示相應的菜單項(不同許可權用戶顯示不同菜單)。
  點擊菜單項,會連接到相應的 vue 組件,跳轉路由並顯示。

2、使用 mock 模擬返回 動態菜單數據

(1)Step1:
  封裝一個 菜單請求模塊,並引入。
  之前博客中已介紹使用了 模塊化封裝 axios 請求,這裡新建一個 menu.js 用來處理菜單相關的請求。

import http from '@/http/httpRequest.js'

export function getMenus() {
    return http({
        url: '/menu/getMenus',
        method: 'get'
    })
}

 

 

 引入 封裝好的模塊。

 

 

 

(2)Step2:
  使用 mock 模擬菜單數據。
  封裝一個 menu.js 並引入。

import Mock from 'mockjs'

// 登錄
export function getMenus() {
    return {
        // isOpen: false,
        url: 'api/menu/getMenus',
        type: 'get',
        data: {
            'code': 200,
            'msg': 'success',
            'data': menuList
        }
    }
}

/*
    menuId       表示當前菜單項 ID
    parentId     表示父菜單項 ID
    name_zh      表示菜單名(中文)
    name_en      表示菜單名(英語)
    url          表示路由跳轉路徑(自身模塊 或者 外部 url)
    type         表示當前菜單項的級別(0: 目錄,1: 菜單項,2: 按鈕)
    icon         表示當前菜單項的圖標
    orderNum     表示當前菜單項的順序
    subMenuList  表示當前菜單項的子菜單
*/
var menuList = [{
        'menuId': 1,
        'parentId': 0,
        'name_zh': '系統管理',
        'name_en': 'System Control',
        'url': '',
        'type': 0,
        'icon': 'el-icon-setting',
        'orderNum': 0,
        'subMenuList': [{
                'menuId': 2,
                'parentId': 1,
                'name_zh': '管理員列表',
                'name_en': 'Administrator List',
                'url': 'sys/UserList',
                'type': 1,
                'icon': 'el-icon-user',
                'orderNum': 1,
                'subMenuList': []
            },
            {
                'menuId': 3,
                'parentId': 1,
                'name_zh': '角色管理',
                'name_en': 'Role Control',
                'url': 'sys/RoleControl',
                'type': 1,
                'icon': 'el-icon-price-tag',
                'orderNum': 2,
                'subMenuList': []
            },
            {
                'menuId': 4,
                'parentId': 1,
                'name_zh': '菜單管理',
                'name_en': 'Menu Control',
                'url': 'sys/MenuControl',
                'type': 1,
                'icon': 'el-icon-menu',
                'orderNum': 3,
                'subMenuList': []
            }
        ]
    },
    {
        'menuId': 5,
        'parentId': 0,
        'name_zh': '幫助',
        'name_en': 'Help',
        'url': '',
        'type': 0,
        'icon': 'el-icon-info',
        'orderNum': 1,
        'subMenuList': [{
            'menuId': 6,
            'parentId': 5,
            'name_zh': '百度',
            'name_en': 'Baidu',
            'url': 'https://www.baidu.com/',
            'type': 1,
            'icon': 'el-icon-menu',
            'orderNum': 1,
            'subMenuList': []
        }, {
            'menuId': 7,
            'parentId': 5,
            'name_zh': '博客',
            'name_en': 'Blog',
            'url': 'https://www.cnblogs.com/l-y-h/',
            'type': 1,
            'icon': 'el-icon-menu',
            'orderNum': 1,
            'subMenuList': []
        }]
    }
]

 

 在 index.js 中聲明,並開啟 mock 攔截。

import * as menu from './modules/menu.js'
fnCreate(menu, true)

 

 

(3)使用
  使用時,通過如下代碼可以訪問到 mock。
  this.$http 是封裝的 axios 請求(詳情可見:https://www.cnblogs.com/l-y-h/p/12973364.html)

// 獲取動態菜單
this.$http.menu.getMenus().then(response => {
    console.log(response)
})

 

(4)簡單測試一下
  在 Login.vue 的登錄方法中,簡單測試一下。

dataFormSubmit() {
    this.$http.login.getToken().then(response => {
        // 獲取動態菜單
        this.$http.menu.getMenus().then(response => {
            console.log(response)
        })
        this.$message({
            message: this.$t("login.signInSuccess"),
            type: 'success'
        })
        // 保存 token
        setToken(response.data.token)
        this.updateName(this.dataForm.userName)
        console.log(response)
        this.$router.push({
            name: 'Home'
        })
    })
}

 

 

 

(5)測試結果

 

 

 

3、動態菜單數據國際化問題

(1)問題
  動態菜單數據國際化問題,數據都是保存在資料庫中,中文系統下返回英文數據、或者 英文系統下返回中文數據,感覺都有些彆扭,所以需要進行國際化處理,某語言系統返回該語言的數據。

(2)解決
思路一:
  從資料庫下手,給表維護欄位,比如:name-zh、name-en,使用時,根據語言選擇對應的欄位顯示即可。修改時修改語言對應的欄位即可,簡單明瞭。
  當然這個缺點也很明顯,增加了額外的欄位,如果還有其他語言,那麼會增加很多額外的欄位。

思路二:
  也從資料庫下手,額外增加一個語言數據表,裡面維護了各種語言下對應的數據。使用時,從這張表查對應的數據即可。修改時也只要修改這張表對應的欄位即可。
  缺點也有,畢竟多維護了一張數據表。

思路三:
  資料庫不做國際化處理,由前端或者後端去做,資料庫維護的是 國際化的變數名。即從資料庫取出數據後,根據該數據返回國際化數據。
  缺點嘛,國際化數據都寫死了,無法直接修改。需要修改國際化的配置文件。

(3)項目中使用
  在這個項目中,我採用第一種方式,此項目就是一個練手的項目,國際化也就 中英文切換,增加一個額外的欄位影響不大。所以 上面 mock 數據中,我使用了 name_zh 表示中文、name_en 表示英語。
  當然,有更好的方式,還望大家不吝賜教。

 

4、修改 router 獲取動態菜單數據

(1)思路:
Step1:首先確定獲取動態菜單數據的時機。
  一般在登錄成功跳轉到主頁面時,獲取動態菜單數據。
  由於涉及到路由的跳轉(登錄頁面 -> 主頁面),所以可以使用 beforeEach 進行路由守衛操作。
  但由於每次路由跳轉均會觸發 beforeEach ,所以為了防止頻繁獲取動態路由值,可以使用一個全局的布爾型變數去控制,比如 false 表示未獲取動態路由,獲取動態路由後,將其置為 true,每次路由跳轉時,判斷該值是否為 true,為 false 就重新獲取動態菜單數據並處理。

Step2:其次,處理動態菜單數據,將其變化成 route 的形式。
  route 格式一般如下:
其中:
  path:指路由路徑(可以根據路徑定位路由)。
  name:指路由名(可以根據路由名定位路由)。
  component:指路由所在的組件。
  children:指的是路由組件中嵌套的子路由。
  meta:用於定義路由的一些元信息。

route = {
    path: '',
    name: '',
    component: null,
    children: [],
    meta: {}
}

簡單舉例:
  如下菜單數據轉換成 route:
    url 可以轉換為 path、name,用於定位路由。
    url 也用於定位 component 組件。
    subMenuList 可以轉換為 children,用於設置子路由(當然子路由也進行同樣處理)。
    其餘的屬性,可以放在 meta 中。

【動態菜單數據:】
[{
        'menuId': 1,
        'parentId': 0,
        'name_zh': '系統管理',
        'name_en': 'System Control',
        'url': '',
        'type': 0,
        'icon': 'el-icon-setting',
        'orderNum': 0,
        'subMenuList': [{
                'menuId': 2,
                'parentId': 1,
                'name_zh': '管理員列表',
                'name_en': 'Administrator List',
                'url': 'sys/UserList',
                'type': 1,
                'icon': 'el-icon-user',
                'orderNum': 1,
                'subMenuList': []
            }
        ]
    }
]

【轉換後:】
[
    path: '',
    name: '',
    componment: null,
    children: [{
        path: 'sys-UserList',
        name: 'sys-UserList',
        componment: () => import(/sys/UserList.vue),
        meta: {
            'menuId': 2,
            'parentId': 1,
            'type': 1,
            'icon': 'el-icon-user',
            'orderNum': 1,
            'name_zh': '管理員列表',
            'name_en': 'Administrator List',
        }
    ]],
    meta: {
        'menuId': 1,
        'parentId': 0,
        'type': 0,
        'icon': 'el-icon-setting',
        'orderNum': 0,
        'name_zh': '系統管理',
        'name_en': 'System Control',
    }
]

 

Step3:使用 addRoute 方法,添加動態路由到主路由上。
  對於轉換好的路由數據(route),可以使用 addRoute 將其添加到主路由上。
註(有個小坑,簡單說明一下,後續會演示):
  此處若直接使用 addRoute 添加路由,通過 路由實例的 options 方法會無法看到新添加的路由信息,也即無法通過 路由實例去獲取動態添加的路由數據。
  若想看到新添加的路由信息,可以指定一個路由實例上的路由,併在其 children 中綁定動態路由,然後通過 addRoute 添加該路由,數據才會被顯示。

 

(2)代碼實現 -- 獲取動態菜單數據:
Step1:確定獲取動態菜單數據的時機。
  時機:一般在登錄成功併進入主頁面時,獲取動態菜單並顯示。
  由於涉及到路由跳轉(登錄頁面 -> 主頁面),所以可以使用 beforeEach 定義一個全局守衛,當從登錄頁面跳轉到主頁面時可以觸發獲取數據的操作。
  當然,有其他方式觸發獲取數據的操作亦可。

Step2:定義一個全局變數,用於控制是否獲取過動態菜單數據。
  由於在 beforeEach 內部定義路由,每次路由跳轉均會觸發此操作,為了防止頻繁獲取動態菜單,可以定義一個全局布爾變數來控制是否已經獲取過動態菜單。
  可以在 router 實例中 添加一個變數用於控制(isAddDynamicMenuRoutes)。
  通過 router.options.isAddDynamicMenuRoutes 可以獲取、修改該值。

// 創建一個 router 實例
const router = new VueRouter({
    // routes 用於定義 路由跳轉 規則
    routes,
    // mode 用於去除地址中的 #
    mode: 'history',
    // scrollBehavior 用於定義路由切換時,頁面滾動。
    scrollBehavior: () => ({
        y: 0
    }),
    // isAddDynamicMenuRoutes 表示是否已經添加過動態菜單(防止頻繁請求動態菜單)
    isAddDynamicMenuRoutes: false
})

 

Step3:獲取動態數據。
  由於之前對主頁面的路由跳轉,已經定義過一個 beforeEach,用於驗證 token 是否存在。
  從登錄頁面跳轉到主頁面會觸發該 beforeEach,且登錄後存在 token,所以可以直接復用。

import http from '@/http/http.js'

router.beforeEach((to, from, next) => {
    // 當開啟導航守衛時,驗證 token 是否存在。
    // to.meta.isRouter 表示是否開啟動態路由
    if (to.meta.isRouter) {
        // console.log(router)
        // 獲取 token 值
        let token = getToken()
        // token 不存在時,跳轉到 登錄頁面
        if (!token || !/\S/.test(token)) {
            next({
                name: 'Login'
            })
        }
        // token 存在時,判斷是否已經獲取過 動態菜單,未獲取,即 false 時,需要獲取
        if (!router.options.isAddDynamicMenuRoutes) {
                http.menu.getMenus().then(response => {
                    console.log(response)
                })
        }
    }
}

 

當然,需要對代碼的邏輯進行一些修改(填坑記錄 =_= )。

填坑:
  頁面刷新時,動態路由跳轉不正確。
  之前為了驗證 token,增加了一個 isRouter 屬性值,用於給指定路由增加判斷 token 的邏輯。
  對於動態路由,頁面刷新後,路由會重新創建,即此時動態路由並不存在,也即 isRouter 不存在,從而 if 中的邏輯並不會觸發,動態路由並不會被創建,從而頁面跳轉失敗。
  所以在 if 判斷中,增加了一個條件 isDynamicRoutes,用於判斷是否為動態路由,因為動態路由只存在於主頁面中,所以其與 isRouter 的作用(為主頁面增加 token 驗證邏輯)不矛盾。

  isDynamicRoutes 可以寫在公用的 validate.js 中。使用時需要將其引入。
  由於此項目動態路由路徑中均包含 DynamicRoutes,所以以此進行正則表達式判斷。

/**
 * 判斷是否為 動態路由
 * @param {*} s
 */
export function isDynamicRoutes(s) {
    return /DynamicRoutes/.test(s)
}

 

 

import { isDynamicRoutes } from '@/utils/validate.js'

// 添加全局路由導航守衛
router.beforeEach((to, from, next) => {
    // 當開啟導航守衛時,驗證 token 是否存在。
    // to.meta.isRouter 表示是否開啟動態路由
    // isDynamicRoutes 判斷該路由是否為動態路由(頁面刷新時,動態路由沒有 isRouter 值,此時 to.meta.isRouter 條件不成立,即動態路由拼接邏輯不能執行)
    if (to.meta.isRouter || isDynamicRoutes(to.path)) {
        // 獲取 token 值
        let token = getToken()
        // token 不存在時,跳轉到 登錄頁面
        if (!token || !/\S/.test(token)) {
            next({
                name: 'Login'
            })
        }
        // token 存在時,判斷是否已經獲取過 動態菜單,未獲取,即 false 時,需要獲取
        if (!router.options.isAddDynamicMenuRoutes) {
                http.menu.getMenus().then(response => {
                    console.log(response)
                })
        }
    }
}

 

(3)代碼實現 -- 處理動態菜單數據
Step1:
  設置全局布爾變數為 true,上面已經定義了一個 isAddDynamicMenuRoutes 變數,當獲取動態菜單成功後,將其值置為 true(表示獲取動態菜單成功),用於防止頻繁獲取菜單。
  然後,再去處理動態菜單數據,此處將處理邏輯抽成一個方法 fnAddDynamicMenuRoutes。

import { isDynamicRoutes } from '

// 添加全局路由導航守衛
router.beforeEach((to, from, next) => {
    // 當開啟導航守衛時,驗證 token 是否存在。
    // to.meta.isRouter 表示是否開啟動態路由
    // isDynamicRoutes 判斷該路由是否為動態路由(頁面刷新時,動態路由沒有 isRouter 值,此時 to.meta.isRouter 條件不成立,即動態路由拼接邏輯不能執行)
    if (to.meta.isRouter || isDynamicRoutes(to.path)) {
        // 獲取 token 值
        let token = getToken()
        // token 不存在時,跳轉到 登錄頁面
        if (!token || !/\S/.test(token)) {
            next({
                name: 'Login'
            })
        }
        // token 存在時,判斷是否已經獲取過 動態菜單,未獲取,即 false 時,需要獲取
        if (!router.options.isAddDynamicMenuRoutes) {
                http.menu.getMenus().then(response => {
                    // 數據返回成功時
                if (response && response.data.code === 200) {
                    // 設置動態菜單為 true,表示不用再次獲取
                    router.options.isAddDynamicMenuRoutes = true
                    // 獲取動態菜單數據
                    let results = fnAddDynamicMenuRoutes(response.data.data)
                    console.log(results)
                }
                })
        }
    }
}

 

Step2:
  fnAddDynamicMenuRoutes 用於遞歸菜單數據。
  getRoute 用於返回某個數據轉換的 路由格式。
下麵註釋寫的應該算是很詳細了,主要講一下思路:
  對數據進行遍歷,
  定義兩個數組(temp,route),temp 用於保存沒有子路由的路由,route 用於保存存在子路由的路由。
  如果某個數據存在 子路由,則對子路由進行遍歷,並將其返回結果作為當前數據的 children。並使用 route 保存路由。
  如果某個數據不存在子路由,則直接使用 temp 保存路由。
  最後,返回兩者拼接的結果,即為轉換後的數據。

// 用於處理動態菜單數據,將其轉為 route 形式
function fnAddDynamicMenuRoutes(menuList = [], routes = []) {
    // 用於保存普通路由數據
    let temp = []
    // 用於保存存在子路由的路由數據
    let route = []
    // 遍曆數據
    for (let i = 0; i < menuList.length; i++) {
        // 存在子路由,則遞歸遍歷,並返回數據作為 children 保存
        if (menuList[i].subMenuList && menuList[i].subMenuList.length > 0) {
            // 獲取路由的基本格式
            route = getRoute(menuList[i])
            // 遞歸處理子路由數據,並返回,將其作為路由的 children 保存
            route.children = fnAddDynamicMenuRoutes(menuList[i].subMenuList)
            // 保存存在子路由的路由
            routes.push(route)
        } else {
            // 保存普通路由
            temp.push(getRoute(menuList[i]))
        }
    }
    // 返迴路由結果
    return routes.concat(temp)
}

// 返迴路由的基本格式
function getRoute(item) {
    // 路由基本格式
    let route = {
        // 路由的路徑
        path: '',
        // 路由名
        name: '',
        // 路由所在組件
        component: null,
        meta: {
            // 開啟路由守衛標誌
            isRouter: true,
            // 開啟標簽頁顯示標誌
            isTab: true,
            // iframe 標簽指向的地址(數據以 http 或者 https 開頭時,使用 iframe 標簽顯示)
            iframeUrl: '',
            // 開啟動態路由標誌
            isDynamic: true,
            // 動態菜單名稱(nameZH 顯示中文, nameEN 顯示英文)
            name_zh: item.name_zh,
            name_en: item.name_en,
            // 動態菜單項的圖標
            icon: item.icon,
            // 菜單項的 ID
            menuId: item.menuId,
            // 菜單項的父菜單 ID
            parentId: item.parentId,
            // 菜單項排序依據
            orderNum: item.orderNum,
            // 菜單項類型(0: 目錄,1: 菜單項,2: 按鈕)
            type: item.type
        },
        // 路由的子路由
        children: []
    }
    // 如果存在 url,則根據 url 進行相關處理(判斷是 iframe 類型還是 普通類型)
    if (item.url && /\S/.test(item.url)) {
        // 若 url 有首碼 /,則將其去除,方便後續操作。
        item.url = item.url.replace(/^\//, '')
        // 定義基本路由規則,將 / 使用 - 代替
        route.path = item.url.replace('/', '-')
        route.name = item.url.replace('/', '-')
        // 如果是 外部 url,使用 iframe 標簽展示,不用指定 component,重新指定 path、name 以及 iframeUrl。
        if (isURL(item.url)) {
            route['path'] = `iframe-${item.menuId}`
            route['name'] = `iframe-${item.menuId}`
            route['meta']['iframeUrl'] = item.url
        } else {
            // 如果是項目模塊,使用 route-view 標簽展示,需要指定 component(載入指定目錄下的 vue 組件)
            route.component = () => import(`@/views/dynamic/${item.url}.vue`) || null
        }
    }
    // 返回 route
    return route
}

可以查看當前輸出結果:

 

 

 

[
    {
        "path": "",
        "name": "",
        "component": null,
        "meta": {
            "isRouter": true,
            "isTab": true,
            "iframeUrl": "",
            "isDynamic": true,
            "name_zh": "系統管理",
            "name_en": "System Control",
            "icon": "el-icon-setting",
            "menuId": 1,
            "parentId": 0,
            "orderNum": 0,
            "type": 0
        },
        "children": [
            {
                "path": "sys-UserList",
                "name": "sys-UserList",
                "meta": {
                    "isRouter": true,
                    "isTab": true,
                    "iframeUrl": "",
                    "isDynamic": true,
                    "name_zh": "管理員列表",
                    "name_en": "Administrator List",
                    "icon": "el-icon-user",
                    "menuId": 2,
                    "parentId": 1,
                    "orderNum": 1,
                    "type": 1
                },
                "children": []
            },
            {
                "path": "sys-RoleControl",
                "name": "sys-RoleControl",
                "meta": {
                    "isRouter": true,
                    "isTab": true,
                    "iframeUrl": "",
                    "isDynamic": true,
                    "name_zh": "角色管理",
                    "name_en": "Role Control",
                    "icon": "el-icon-price-tag",
                    "menuId": 3,
                    "parentId": 1,
                    "orderNum": 2,
                    "type": 1
                },
                "children": []
            },
            {
                "path": "sys-MenuControl",
                "name": "sys-MenuControl",
                "meta": {
                    "isRouter": true,
                    "isTab": true,
                    "iframeUrl": "",
                    "isDynamic": true,
                    "name_zh": "菜單管理",
                    "name_en": "Menu Control",
                    "icon": "el-icon-menu",
                    "menuId": 4,
                    "parentId": 1,
                    "orderNum": 3,
                    "type": 1
                },
                "children": []
            }
        ]
    },
    {
        "path": "",
        "name": "",
        "component": null,
        "meta": {
            "isRouter": true,
            "isTab": true,
            "iframeUrl": "",
            "isDynamic": true,
            "name_zh": "幫助",
            "name_en": "Help",
            "icon": "el-icon-info",
            "menuId": 5,
            "parentId": 0,
            "orderNum": 1,
            "type": 0
        },
        "children": [
            {
                "path": "iframe-6",
                "name": "iframe-6",
                "component": null,
                "meta": {
                    "isRouter": true,
                    "isTab": true,
                    "iframeUrl": "https://www.baidu.com/",
                    "isDynamic": true,
                    "name_zh": "百度",
                    "name_en": "Baidu",
                    "icon": "el-icon-menu",
                    "menuId": 6,
                    "parentId": 5,
                    "orderNum": 1,
                    "type": 1
                },
                "children": []
            },
            {
                "path": "iframe-7",
                "name": "iframe-7",
                "component": null,
                "meta": {
                    "isRouter": true,
                    "isTab": true,
                    "iframeUrl": "https://www.cnblogs.com/l-y-h/",
                    "isDynamic": true,
                    "name_zh": "博客",
                    "name_en": "Blog",
                    "icon": "el-icon-menu",
                    "menuId": 7,
                    "parentId": 5,
                    "orderNum": 1,
                    "type": 1
                },
                "children": []
            }
        ]
    }
]

 

5、使用 addRoute 添加路由:

  通過上面的步驟,已經獲取到動態菜單數據,並將其轉為路由的格式。
  現在只需要使用 addRoute 將其添加到路由上,即可使用該動態路由。
有個小坑,下麵有演示以及解決方法。

 

(1)添加三個基本組件頁面,用於測試。
  基本組件頁面的位置,要與上面 component 指定的位置相一致。

【MenuControl.vue】
<template>
    <div>
        <h1>Menu Control</h1>
    </div>
</template>

<script>
</script>

<style>
</style>

【RoleControl.vue】
<template>
    <div>
        <h1>Role Control</h1>
    </div>
</template>

<script>
</script>

<style>
</style>

【UserList.vue】
<template>
    <div>
        <h1>User List</h1>
    </div>
</template>

<script>
</script>

<style>
</style>

 

 

 

(2)使用 addRoute 添加路由 -- 基本版
  根據項目需求,自行處理相關顯示邏輯(此處只是最簡單的版本)。
  由於此處的菜單顯示,只是二級菜單。且菜單內容,均顯示在 Home.vue 組件中,所以最終路由的格式應該如下所示,所有的路由均顯示在 children 中。

{
    path: '/DynamicRoutes',
    name: 'DynamicHome',
    component: () => import('@/views/Home.vue'),
    children: [{
     }]
}

 

想要實現這個效果,得對轉換後的動態數據進行進一步的處理。對於第一層菜單數據,需要指定相應的組件,此處為 Home.vue。

// 獲取動態菜單數據
let results = fnAddDynamicMenuRoutes(response.data.data)
// 如果動態菜單數據存在,對其進行處理
if (results && results.length > 0) {
    // 遍歷第一層數據
    results.map(value => {
        // 如果 path 值不存在,則對其賦值,並指定 component 為 Home.vue
        if (!value.path) {
            value.path = `/DynamicRoutes-${value.meta.menuId}`
            value.name = `DynamicHome-${value.meta.menuId}`
            value.component = () => import('@/views/Home.vue')
        }
    })
}
console.log(results)

 

 

 

可以看到路由信息都已完善,此時,即可使用 addRoute 方法添加路由。

// 使用 router 實例的 addRoutes 方法,給當前 router 實例添加一個動態路由
router.addRoutes(results)
// 查看當前路由信息
console.log(router.options)
// 測試一下路由是否能正常跳轉,能跳轉,則顯示路由添加成功
setTimeout(()=> {
    router.push({name : "sys-UserList"})
}, 3000)

 

 

 

  如上圖所示,動態路由添加成功,且能夠成功跳轉。
  但是有一個問題,如何獲取到動態路由的值呢?

  一般通過 router 實例對象的 options 方法可以查看到當前路由的信息。(使用 router.options 或者 this.$router.options 可以查看)。

  但是從上圖可以看到,動態添加的數據並沒有在 options 中顯示出來,但路由確實添加成功了。沒有看源代碼,所以不太清除裡面的實現邏輯(有人知道的話,還望不吝賜教)。谷歌搜索了一下,沒有找到滿意的答案,基本都是說如何在 options 中顯示路由信息(=_=),所以此處省略原理,以後有機會再補充,此處直接上解決辦法。

 

獲取動態路由值一般有兩種方式。
  第一種:通過 router 實例對象的 options 方式。
    動態路由使用靜態路由的 children 展開,即事先定義好靜態路由,添加動態路由時,將該動態路由添加到靜態路由 children 上,即可通過 options 顯示。 

  第二種:通過 vuex 方式。
    通過 vuex 保存當前動態路由的信息。即單獨保存一份動態路由的信息,使用時從 vuex 中獲取即可。當然,使用 localStorage、sessionStorage 保存亦可。

 

(3)獲取動態路由值 -- 方式一
  通過靜態路由的 children 的形式,添加動態路由信息。
Step1:
  定義一個靜態路由,當然,router 實例對象中的 routes 也需要修改。

// 用於保存動態路由
const dynamicRoutes = [{
    path: '/DynamicRoutes',
    component: () => import('@/views/Home.vue'),
    children: []
}]

// 創建一個 router 實例
const router = new VueRouter({
    // routes 用於定義 路由跳轉 規則
    routes: dynamicRoutes.concat(routes),
    // routes,
    // mode 用於去除地址中的 #
    mode: 'history',
    // scrollBehavior 用於定義路由切換時,頁面滾動。
    scrollBehavior: () => ({
        y: 0
    }),
    // isAddDynamicMenuRoutes 表示是否已經添加過動態菜單(防止頻繁請求動態菜單)
    isAddDynamicMenuRoutes: false
})

 

Step2:
  修改 addRoutes 規則。
  首先需要將 動態路由添加到 靜態路由的 children 方法中。
  然後使用 addRoutes 添加靜態路由。

// 如果動態菜單數據存在,對其進行處理
if (results && results.length > 0) {
    // 遍歷第一層數據
    results.map(value => {
        // 如果 path 值不存在,則對其賦值,並指定 component 為 Home.vue
        if (!value.path) {
            value.path = `/DynamicRoutes-${value.meta.menuId}`
            value.name = `DynamicHome-${value.meta.menuId}`
            value.component = () => import('@/views/Home.vue')
        }
    })
}
// 將動態路由信息添加到靜態路由的 children 中
dynamicRoutes[0].children = results
dynamicRoutes[0].name = 'DynamicRoutes'
// 使用 router 實例的 addRoutes 方法,給當前 router 實例添加一個動態路由
router.addRoutes(dynamicRoutes)
// 查看當前路由信息
console.log(router.options)
// 測試一下路由是否能正常跳轉,能跳轉,則顯示路由添加成功
router.push({name : "sys-UserList"})

 

 

 

  通過上圖,可以看到,使用 addRoutes 添加數據到靜態路由的 children 後,可以使用 router 實例對象的 options 查看到相應的動態數據。

  但是還是出現了一些小問題,由於靜態路由指定了 Home.vue 組件,所以其 children 不應該再出現 Home.vue 組件,否則出現了會出現上圖那樣的情況(Home.vue 組件中套 Home.vue 組件)。

 

  感覺沒有什麼好的解決辦法,只能粗暴的捨棄一些數據。
  比如捨棄第一層的數據,將其餘數據拼接成一個數組,然後添加到靜態路由的 children 上。

// 保存動態菜單數據
let temp = []
// 如果動態菜單數據存在,對其進行處理
if (results && results.length > 0) {
    // 捨棄第一層數據
    results.map(value => {
        if (!value.path) {
            temp = temp.concat(value.children)
        }
    })
}
// 將動態路由信息添加到靜態路由的 children 中
dynamicRoutes[0].children = temp
dynamicRoutes[0].name = 'DynamicRoutes'
// 使用 router 實例的 addRoutes 方法,給當前 router 實例添加一個動態路由
router.addRoutes(dynamicRoutes)
// 查看當前路由信息
console.log(router.options)
// 測試一下路由是否能正常跳轉,能跳轉,則顯示路由添加成功
router.push({name : "sys-UserList"})

 

 

 

當然,不捨棄第一層數據,將其全部拼接成一個數組亦可。

// 保存動態菜單數據
let temp = []
// 如果動態菜單數據存在,對其進行處理
if (results && results.length > 0) {
    // 將全部數據拼接成一個數組
    results.map(value => {
        if (!value.path) {
            temp = temp.concat(value.children)
            value.children = []
            value.path = `/DynamicRoutes-${value.meta.menuId}`
            value.name = `DynamicHome-${value.meta.menuId}`
            temp = temp.concat(value)
        }
    })
}
// 將動態路由信息添加到靜態路由的 children 中
dynamicRoutes[0].children = temp
dynamicRoutes[0].name = 'DynamicRoutes'
// 使用 router 實例的 addRoutes 方法,給當前 router 實例添加一個動態路由
router.addRoutes(dynamicRoutes)
// 查看當前路由信息
console.log(router.options)
// 測試一下路由是否能正常跳轉,能跳轉,則顯示路由添加成功
router.push({name : "sys-UserList"})

 

 

 

(4)獲取動態路由值 -- 方式二
  使用 vuex 保存動態路由的值,使用時從 vuex 中獲取。
  如下,在 common.js 中定義相關路由處理操作。

【進行相關處理:】
export default {
    state: {
        dynamicRoutes: []
    },
    // 更改 state(同步)
    mutations: {
        updateDynamicRoutes(state, routes) {
            state.dynamicRoutes = routes
        },
        resetState(state) {
            let stateTemp = {
                dynamicRoutes: []
            }
            Object.assign(state, stateTemp)
        }
    },
    // 非同步觸發 mutations
    actions: {
        updateDynamicRoutes({commit, state}, routes) {
            commit("updateDynamicRoutes", routes)
        },
        resetState({commit, state}) {
            commit("resetState")
        }
    }
}


【完整 common.js】
export default {
    // 開啟命名空間(防止各模塊間命名衝突),訪問時需要使用 模塊名 + 方法名
    namespaced: true,
    // 管理數據(狀態)
    state: {
        // 用於保存語言設置(國際化),預設為中文
        language: 'zh',
        // 表示側邊欄選中的菜單項的名
        menuActiveName: '',
        // 表示標簽頁數據,數組
        mainTabs: [],
        // 表示標簽頁中選中的標簽名
        mainTabsActiveName: '',
        // 用於保存動態路由的數據
        dynamicRoutes: []
    },
    // 更改 state(同步)
    mutations: {
        updateLanguage(state, data) {
            state.language = data
        },
        updateMenuActiveName(state, name) {
            state.menuActiveName = name
        },
        updateMainTabs(state, tabs) {
            state.mainTabs = tabs
        },
        updateMainTabsActiveName(state, name) {
            state.mainTabsActiveName = name
        },
        updateDynamicRoutes(state, routes) {
            state.dynamicRoutes = routes
        },
        resetState(state) {
            let stateTemp = {
                language: 'zh',
                menuActiveName: '',
                mainTabs: [],
                mainTabsActiveName: '',
                dynamicRoutes: []
            }
            Object.assign(state, stateTemp)
        }
    },
    // 非同步觸發 mutations
    actions: {
        updateLanguage({commit, state}, data) {
            commit("updateLanguage", data)
        },
        updateMenuActiveName({commit, state}, name) {
            commit("updateMenuActiveName", name)
        },
        updateMainTabs({commit, state}, tabs)

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • React簡介和使用 博客說明 文章所涉及的資料來自互聯網整理和個人總結,意在於個人學習和經驗彙總,如有什麼地方侵權,請聯繫本人刪除,謝謝! 簡介 用於構建用戶界面的 JavaScript 庫 特點 聲明式 React 使創建互動式 UI 變得輕而易舉。為你應用的每一個狀態設計簡潔的視圖,當數據改變 ...
  • 一、路徑別名設置: [email protected]: 1 // build/webpack.base.conf.js中: 2 resolve: { 3 extensions: [".js", ".vue", ".json", ".css", ".scss"], 4 alias: { 5 vue$: "vue ...
  • 有很多同學留言說,越學越迷茫,不知道該從哪裡下手,今天梳理了一些學習web前端的經驗,以及學習的步驟,分享給大家,希望對你們學習能有幫助。 電腦行業很多領域都符合82定律,也就是20%的東西的使用頻率占到80%,所以很適合囫圇吞棗,因此我們的重點就是把這20%學起來,而首先要做的就是把這20%的東 ...
  • 送福利啦! 歷經兩個月發展,本人終於回歸前端崗位!裸辭的艱辛已然消逝在這裡特地講我自己這兩個月整理的相關面試題分享給大家,免費獲取哦~ 內容: 基礎題(293題) 進階題(30題) 高級題(91題) 電腦基礎題(14題) 高頻考點(37題) 綜合問題(125題) 大廠面試真題(阿裡、網易等) 個人 ...
  • 幾乎整個互聯網行業都缺前端工程師,不僅在剛起步的創業公司,對上市公司乃至巨頭這個問題也一樣存在。據統計,國外的前端開發人員和後端開發人員比例約1:1,但是在國內比例卻在1:3以下, Web前端開發職位人才缺口巨大。前端工程師的發展之路十分有“錢”景。每天,HR 群都有人在吐槽招不到前端工程師。實話說 ...
  • 在vue.config.js中,設置 module.exports = { publicPath: '/app', devServer: { proxy: { '/test': { target: 'http://localhost:88', ws: true, changeOrigin: true ...
  • 一、return語句 1.註意點:(1)如果函數沒有使用return語句,那麼函數預設的返回值:undefined;(2)如果函數使用return語句,那麼跟在return後面的值,就成了函數的返回值;(3)如果函數使用return語句,但是return後面沒有任何值,那麼函數的返回值也是undef ...
  • 今年一月份毅然裸辭,誰都想不到後面事情的發展變成了這樣!疫情的到來讓本不富裕的我雪上加霜 一直維繫到大概四月初,才開始正式找工作,過程並不簡單!雖然自認為,找一份合適的工作應該不難,可最後往往都拜倒在“合適”這兩個字上! 以下是我在四五月份總共面試的比較知名幾家企業經驗總結,“前赴”以倒,“後繼”加 ...
一周排行
    -Advertisement-
    Play Games
  • 示例項目結構 在 Visual Studio 中創建一個 WinForms 應用程式後,項目結構如下所示: MyWinFormsApp/ │ ├───Properties/ │ └───Settings.settings │ ├───bin/ │ ├───Debug/ │ └───Release/ ...
  • [STAThread] 特性用於需要與 COM 組件交互的應用程式,尤其是依賴單線程模型(如 Windows Forms 應用程式)的組件。在 STA 模式下,線程擁有自己的消息迴圈,這對於處理用戶界面和某些 COM 組件是必要的。 [STAThread] static void Main(stri ...
  • 在WinForm中使用全局異常捕獲處理 在WinForm應用程式中,全局異常捕獲是確保程式穩定性的關鍵。通過在Program類的Main方法中設置全局異常處理,可以有效地捕獲並處理未預見的異常,從而避免程式崩潰。 註冊全局異常事件 [STAThread] static void Main() { / ...
  • 前言 給大家推薦一款開源的 Winform 控制項庫,可以幫助我們開發更加美觀、漂亮的 WinForm 界面。 項目介紹 SunnyUI.NET 是一個基於 .NET Framework 4.0+、.NET 6、.NET 7 和 .NET 8 的 WinForm 開源控制項庫,同時也提供了工具類庫、擴展 ...
  • 說明 該文章是屬於OverallAuth2.0系列文章,每周更新一篇該系列文章(從0到1完成系統開發)。 該系統文章,我會儘量說的非常詳細,做到不管新手、老手都能看懂。 說明:OverallAuth2.0 是一個簡單、易懂、功能強大的許可權+可視化流程管理系統。 有興趣的朋友,請關註我吧(*^▽^*) ...
  • 一、下載安裝 1.下載git 必須先下載並安裝git,再TortoiseGit下載安裝 git安裝參考教程:https://blog.csdn.net/mukes/article/details/115693833 2.TortoiseGit下載與安裝 TortoiseGit,Git客戶端,32/6 ...
  • 前言 在項目開發過程中,理解數據結構和演算法如同掌握蓋房子的秘訣。演算法不僅能幫助我們編寫高效、優質的代碼,還能解決項目中遇到的各種難題。 給大家推薦一個支持C#的開源免費、新手友好的數據結構與演算法入門教程:Hello演算法。 項目介紹 《Hello Algo》是一本開源免費、新手友好的數據結構與演算法入門 ...
  • 1.生成單個Proto.bat內容 @rem Copyright 2016, Google Inc. @rem All rights reserved. @rem @rem Redistribution and use in source and binary forms, with or with ...
  • 一:背景 1. 講故事 前段時間有位朋友找到我,說他的窗體程式在客戶這邊出現了卡死,讓我幫忙看下怎麼回事?dump也生成了,既然有dump了那就上 windbg 分析吧。 二:WinDbg 分析 1. 為什麼會卡死 窗體程式的卡死,入口門檻很低,後續往下分析就不一定了,不管怎麼說先用 !clrsta ...
  • 前言 人工智慧時代,人臉識別技術已成為安全驗證、身份識別和用戶交互的關鍵工具。 給大家推薦一款.NET 開源提供了強大的人臉識別 API,工具不僅易於集成,還具備高效處理能力。 本文將介紹一款如何利用這些API,為我們的項目添加智能識別的亮點。 項目介紹 GitHub 上擁有 1.2k 星標的 C# ...