前提:
(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 =https://www.cnblogs.com/l-y-h/p/> { // 如果 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 =https://www.cnblogs.com/l-y-h/p/> { // 如果 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 =https://www.cnblogs.com/l-y-h/p/> { 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 =https://www.cnblogs.com/l-y-h/p/> { 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) { commit("updateMainTabs", tabs) }, updateMainTabsActiveName({commit, state}, name) { commit("updateMainTabsActiveName", name) }, updateDynamicRoutes({commit, state}, routes) { commit("updateDynamicRoutes", routes) }, resetState({commit, state}) { commit("resetState") } } }

如何在 router 中使用 vuex 呢?
通過 router.app.$options.store 可以獲取到 vuex 內容,
【獲取 state 資料:】 router.app.$options.store.state 【調用 vuex 方法:】 router.app.$options.store.dispatch() 【使用:】 let results = fnAddDynamicMenuRoutes(response.data.data) // 如果動態選單資料存在,對其進行處理 if (results && results.length > 0) { // 遍歷第一層資料 results.map(value =https://www.cnblogs.com/l-y-h/p/> { // 如果 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') } }) } // 使用 vuex 管理動態路由資料 router.app.$options.store.dispatch('common/updateDynamicRoutes', results) console.log(JSON.stringify(router.app.$options.store.state.common.dynamicRoutes)) // 使用 router 實體的 addRoutes 方法,給當前 router 實體添加一個動態路由 router.addRoutes(results) // 查看當前路由資訊 console.log(router.options) // 測驗一下路由是否能正常跳轉,能跳轉,則顯示路由添加成功 router.push({name : "sys-UserList"})

(5)使用 addRoute 添加路由 -- 最終版
此專案中,使用 vuex 管理動態選單資料,而不在 options 中顯示(當然兩種方式結合一起亦可),
完整 router 的 index.js 邏輯如下,
import Vue from 'vue' import VueRouter from 'vue-router' import Home from '../views/Home.vue' import { getToken } from '@/http/auth.js' import http from '@/http/http.js' import { isURL, isDynamicRoutes } from '@/utils/validate.js' Vue.use(VueRouter) // 定義路由跳轉規則 // component 采用 路由懶加載形式 // 此專案中,均采用 name 方式指定路由進行跳轉 /* meta 用于定義路由元資訊, 其中 isRouter 用于指示是否開啟路由守衛(true 表示開啟), isTab 用于表示是否顯示為標簽頁(true 表示顯示) iframeUrl 用于表示 url,使用 http 或者 https 開頭的 url 使用 iframe 標簽展示 */ const routes = [{ path: '/', redirect: { name: "Login" } }, { path: '/404', name: '404', component: () => import('@/components/common/404.vue') }, { path: '/Login', name: 'Login', component: () => import('@/components/common/Login.vue') }, { path: '/Home', name: 'Home', component: () => import('@/views/Home.vue'), redirect: { name: 'HomePage' }, children: [{ path: '/Home/Page', name: 'HomePage', component: () => import('@/views/menu/HomePage.vue'), meta: { isRouter: true } }, { path: '/Home/Demo/Echarts', name: 'Echarts', component: () => import('@/views/menu/Echarts.vue'), meta: { isRouter: true, isTab: true } }, { path: '/Home/Demo/Ueditor', name: 'Ueditor', component: () => import('@/views/menu/Ueditor.vue'), meta: { isRouter: true, isTab: true } }, { path: '/Home/Demo/Baidu', name: 'Baidu', meta: { isRouter: true, isTab: true, iframeUrl: '@/test.html' } }, // 路由匹配失敗時,跳轉到 404 頁面 { path: "*", redirect: { name: '404' } } ] } ] // 創建一個 router 實體 const router = new VueRouter({ // routes 用于定義 路由跳轉 規則 routes, // mode 用于去除地址中的 # mode: 'history', // scrollBehavior 用于定義路由切換時,頁面滾動, scrollBehavior: () => ({ y: 0 }), // isAddDynamicMenuRoutes 表示是否已經添加過動態選單(防止頻繁請求動態選單) isAddDynamicMenuRoutes: false }) // 添加全域路由導航守衛 router.beforeEach((to, from, next) => { // 當開啟導航守衛時,驗證 token 是否存在, // to.meta.isRouter 表示是否開啟動態路由 // isDynamicRoutes 判斷該路由是否為動態路由(頁面重繪時,動態路由沒有 isRouter 值,此時 to.meta.isRouter 條件不成立,即動態路由拼接邏輯不能執行) if (to.meta.isRouter || isDynamicRoutes(to.path)) { // 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 => { // 資料回傳成功時 if (response && response.data.code === 200) { // 設定動態選單為 true,表示不用再次獲取 router.options.isAddDynamicMenuRoutes = true // 獲取動態選單資料 let results = fnAddDynamicMenuRoutes(response.data.data) // 如果動態選單資料存在,對其進行處理 if (results && results.length > 0) { // 遍歷第一層資料 results.map(value =https://www.cnblogs.com/l-y-h/p/> { // 如果 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') } }) } // 使用 vuex 管理動態路由資料 router.app.$options.store.dispatch('common/updateDynamicRoutes', results) // 使用 router 實體的 addRoutes 方法,給當前 router 實體添加一個動態路由 router.addRoutes(results) } })) } } next() }) // 用于處理動態選單資料,將其轉為 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 } // 解決相同路徑跳轉報錯 const routerPush = VueRouter.prototype.push; VueRouter.prototype.push = function push(location, onResolve, onReject) { if (onResolve || onReject) return routerPush.call(this, location, onResolve, onReject) return routerPush.call(this, location).catch(error => error) } export default router
二、動態選單的展示
前面已經獲取到了動態選單資料,并使用 vuex 對其進行了管理,
現在只需要將資料進行展示即可,
1、定義一個 DynamicMenu.vue 組件
定義一個 DynamicMenu.vue 組件,用于展示動態選單,
與之前寫 Aside.vue 代碼類似,將其 copy 過來直接修改即可,
其中:
menu 表示選單項資料,是由父組件通過 props 傳遞進來的,
index 顯示選單選中項,通過 menu.name 系結,
icon 顯示選單項圖示,通過 menu.meta.icon 系結,
選單資料國際化通過 menu.meta.name_zh、menu.meta.name_en 系結,
subMenu 表示當前選單的子選單選項,需要系結路由跳轉事件,
而路由跳轉事件的相關處理,在之前的一篇博客中已經做了處理,此處直接使用即可,
路由跳轉、iframe 嵌套參考:https://www.cnblogs.com/l-y-h/p/12973364.html
<template>
<el-submenu :index="menu.name">
<template slot="title">
<i :></i>
<span>{{$i18n.locale === 'zh' ? menu.meta.name_zh : menu.meta.name_en}}</span>
</template>
<el-menu-item v-for="subMenu in menu.children" :key="subMenu.meta.menuId" :index="subMenu.name" @click="$router.push({ name: subMenu.name })">
<i :></i>
<span slot="title">{{$i18n.locale === 'zh' ? subMenu.meta.name_zh : subMenu.meta.name_en}}</span>
</el-menu-item>
</el-submenu>
</template>
<script>
export default {
name: 'DynamicMenu',
props: {
menu: {
type: Object,
required: true
}
}
}
</script>
<style>
</style>

2、在 Aside.vue 中引入 DynamicMenu.vue
組件定義好后,就需要引入了,
(1)引入動態選單組件:
Step1:import 引入相關組件,
Step2:components 宣告相關組件,
Step3:使用組件,
<template>
<DynamicMenu v-for="menu in dynamicRoutes" :key="menu.meta.menuId" :menu="menu"></DynamicMenu>
</template>
<script>
import DynamicMenu from '@/views/dynamic/DynamicMenu.vue'
export default {
name: 'Aside',
components: {
DynamicMenu
}
}
</script>

(2)引入 vuex 管理的動態選單資料
由于之前已經引入過 vuex,此處直接引入 動態選單資料 dynamicRoutes 即可,
computed: { ...mapState('common', ['menuActiveName', 'mainTabs', 'dynamicRoutes']) }

3、完整效果

轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/73770.html
標籤:JavaScript
上一篇:斐波那契列數JS的三種實作
