vue addRoutes實(shí)現(xiàn)動(dòng)態(tài)權(quán)限路由菜單的示例
需求
最近接手一個(gè)后臺(tái)管理系統(tǒng),需要實(shí)現(xiàn)導(dǎo)航菜單從后臺(tái)拉取的效果;根據(jù)登錄用戶的權(quán)限不同分別拉出來的導(dǎo)航菜單也不一樣,另外可操作的界面也存在區(qū)別。
問題
因?yàn)楹笈_(tái)管理系統(tǒng)是準(zhǔn)備使用vue+vue-router+element-ui+vuex的搭配來做的,可是單頁應(yīng)用在進(jìn)入頁面之前就已經(jīng)將vue-router實(shí)例化并且注入vue實(shí)例中了,所以在進(jìn)入登錄頁面的時(shí)候舊沒辦法在重新定制路由了。接下來各種百之谷之,發(fā)現(xiàn)vue-router在2.0版本中提供了addRoutes方法添加路由,希望的曙光出現(xiàn)。
經(jīng)過一番折騰終于實(shí)現(xiàn)了功能,記錄下來便于回顧,也希望能幫助到同樣有需求的同志。
思路
1、首先在本地配置好固定不變的路由地址,例如登錄,404這些頁面,如下:
import Vue from 'vue' import Router from 'vue-router' import store from '@/vuex/store' Vue.use(Router) let router = new Router({ routes: [ { path: '/login', name: 'login', meta: {requireAuth: false}, // 模塊使用異步加載 component: (resolve) => require(['../components/login/login.vue'], resolve) }] }) // 攔截登錄,token驗(yàn)證 router.beforeEach((to, from, next) => { if (to.meta.requireAuth === undefined) { if (store.state.token) { next() } else { next({ path: '/login' }) } } else { next() } }) export default router
配置好這些固定的路由后我們才能夠到登錄頁面,不然是無法繼續(xù)下去的。
2、然后重要的一步,我們需要跟后端老鐵約定好需要返回的權(quán)限菜單列表信息;首先這里我們先分析一下自己需要的路由結(jié)構(gòu),這里以我自己的路由作為例子。如果是我自己直接定義路由的話,會(huì)是以下結(jié)構(gòu):
let router = new Router({ routes: [ { path: '/login', name: 'login', meta: {requireAuth: false}, component: (resolve) => require(['../components/login/login.vue'], resolve) }, { path: '/', redirect: '/layout' }, { path: '/layout', component: (resolve) => require(['../layout.vue'], resolve), children: [ { path: 'index', meta: { type: '1', //控制是否顯示隱藏 1顯示,2隱藏 code: 00010001, // 后面需要控制路由高亮 title: '首頁', // 菜單名稱 permissonList: [] // 權(quán)限列表 } component: (resolve) => require(['@/components/index/index.vue'], resolve) }, { ... } ] }] })
根據(jù)以上結(jié)構(gòu)分析,其實(shí)真正需要?jiǎng)討B(tài)配置的路由其實(shí)是/layout下面的children部分,所以需要后端返回給我們包含所有路由的一個(gè)數(shù)組就可以了
返回的數(shù)據(jù)中rootList中是一級(jí)導(dǎo)航的列表,一級(jí)導(dǎo)航實(shí)際是沒有路由功能,只是作為切換二級(jí)菜單的觸發(fā)器,subList才是我們真正需要的路由信息。
3、拿到權(quán)限路由信息后,需要我們?cè)诒镜貙?duì)數(shù)據(jù)進(jìn)行處理組裝成我們需要的數(shù)據(jù):
// 登錄 login () { let params = { account: this.loginForm.username, password: encrypt(this.loginForm.password) } this.loading = true this.$http.post(this.$bumng + '/login', this.$HP(params)) .then((res) => { this.loging = false console.info('菜單列表:', res) if (res.resultCode === this.$state_ok) { // 合并一級(jí)菜單和二級(jí)菜單,便于顯示 let menus = handleMenu.mergeSubInRoot(res.rootList, res.subList) // 本地化處理好的菜單列表 this.saveRes({label: 'menuList', value: menus}) // 根據(jù)subList處理路由 let routes = handleMenu.mergeRoutes(res.subList) // 本地化subList,便于在刷新頁面的時(shí)候重新配置路由 this.saveRes({label: 'subList', value: res.subList}) // 防止重復(fù)配置相同路由 if (this.$router.options.routes.length <= 1) { this.$router.addRoutes(routes) // this.$router不是響應(yīng)式的,所以手動(dòng)將路由元注入路由對(duì)象 this.$router.options.routes.push(routes) } this.$router.replace('/layout/index') } }) .catch((err) => { this.loging = false console.error('錯(cuò)誤:', err) }) },
處理菜單列表和subList的方法:mergeSubInRoot 和 mergeRoutes
const routes = [ { path: '/', redirect: '/layout' }, { path: '/layout', component: (resolve) => require(['../layout.vue'], resolve), children: [] } ] export default { /** * 合并主菜單和子菜單 * @param: rootList [Array] 主菜單列表 * @param: subList [Array] 子菜單 * */ mergeSubInRoot (roots, subs) { if (roots && subs) { for (let i = 0; i < roots.length; i++) { let rootCode = roots[i].code roots[i].children = [] for (let j = 0; j < subs.length; j++) { if (rootCode === subs[j].code.substring(0, 4)) { roots[i].children.push(subs[j]) } } } } return roots }, /** * 合并遠(yuǎn)程路由到本地路由 * @param: subList [Array] 遠(yuǎn)程路由列表 * @param: routes [Array] 本地路由列表 * */ mergeRoutes (subs) { if (subs) { for (let i = 0; i < subs.length; i++) { let temp = { path: subs[i].actUrl, name: subs[i].actUrl, component: (resolve) => require([`@/components/${subs[i].component}.vue`], resolve), meta: { type: subs[i].type, code: subs[i].code, title: subs[i].name, permissionList: subs[i].permissionList } } routes[1].children.push(temp) } } return routes } }
至此我們已經(jīng)將權(quán)限路由成功配置進(jìn)本地路由了,我的系統(tǒng)登錄進(jìn)入如下
后續(xù)優(yōu)化
1、菜單列表的顯示以及二級(jí)導(dǎo)航切換:
<template> <div class="mainMenu"> <el-menu class="menubar" mode="horizontal" :default-active="activeCode" background-color="#545c64" text-color="#fff" active-text-color="#ffd04b"> <el-menu-item :index="item.code | splitCode" v-for="item in menuList" :key="item.code" @click="switchSubMenu(item)" v-if="item.code !== '0008'"> <i :class="`iconfont icon-${item.imgUrl}`"></i> <span slot="title">{{item.name}}</span> </el-menu-item> </el-menu> </div> </template> <script type="text/ecmascript-6"> import {mapState, mapMutations} from 'vuex' export default { name: 'menu', data () { return { msg: 'Welcome to Your Vue.js App' } }, computed: { ...mapState(['menuList']), activeCode () { // 通過code保證在切換字路由的情況下一級(jí)路由也是高亮顯示 return this.$route.meta.code.substring(0, 4) } }, methods: { ...mapMutations(['saveRes']), // 切換二級(jí)路由 switchSubMenu (route) { console.info('路由:', route) if (route.actUrl !== 'index') { // 用currentSubMenu控制二級(jí)路由數(shù)據(jù) this.saveRes({label: 'currentSubMenu', value: route.children}) this.$router.push(`/layout/${route.children[0].actUrl}`) } else { // 不存在二級(jí)路由隱藏二級(jí) this.saveRes({label: 'currentSubMenu', value: ''}) this.$router.push(`/layout/${route.actUrl}`) } } }, filters: { splitCode (code) { return code.substring(0, 4) } } } </script>
2、防止刷新路由丟失;由于在刷新的時(shí)候單頁應(yīng)用會(huì)重新初始化,這時(shí)候所有配置的路由都會(huì)丟失,一朝回到解放前,只有本地配置的路由能夠跳轉(zhuǎn)。這時(shí)候我們可以在app.vue(ps:不論在哪里進(jìn)行刷新,app.vue都會(huì)執(zhí)行)中執(zhí)行如下代碼:
<script> import {decrypt} from '@/libs/AES' import handleMenu from '@/router/handleMenu' export default { name: 'app', created () { // 當(dāng)this.$router.options.routes的長度為1,且本地緩存存在菜單列表的時(shí)候才重新配置路由 if (this.$router.options.routes.length <= 1 && sessionStorage.getItem('subList')) { let subList = JSON.parse(decrypt(sessionStorage.getItem('subList'))) let routes = handleMenu.mergeRoutes(subList) this.$router.addRoutes(routes) // this.$router不是響應(yīng)式的,所以手動(dòng)將路由元注入路由對(duì)象 this.$router.options.routes.push(routes) } } } </script>
這樣即使刷新,也會(huì)重新配置路由了。
3、關(guān)于頁面按鈕級(jí)別控制,可以自定義一個(gè)指令,去做這件事情。因?yàn)槲覀円呀?jīng)權(quán)限列表放入了相應(yīng)路由的meta對(duì)象中,所以我們可以很方便的在每個(gè)頁面回去到當(dāng)前用戶在當(dāng)前頁面所擁有的權(quán)限
參考官方文檔自定義指令
結(jié)語
打完收工,得虧vue-router2中添加了addRoutes的方法,不然
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
解決vue路由組件vue-router實(shí)例被復(fù)用問題
這篇文章介紹了解決vue路由組件vue-router實(shí)例被復(fù)用的問題,對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-06-06從零開始實(shí)現(xiàn)Vue簡(jiǎn)單的Toast插件
這篇文章主要給大家介紹了如何從零開始實(shí)現(xiàn)Vue簡(jiǎn)單的Toast插件的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2018-12-12使用vue-draggable-plus實(shí)現(xiàn)拖拽排序
最近遇到一個(gè)需求,在 Vue3 的一個(gè) H5 頁面當(dāng)中點(diǎn)擊拖拽圖標(biāo)上下拖動(dòng) tab 子項(xiàng),然后點(diǎn)擊保存可以保存最新的 tab 項(xiàng)順序,同事說可以用 vue-draggable-plus 這個(gè)庫來實(shí)現(xiàn)拖拽,所以本文給大家介紹了如何使用vue-draggable-plus實(shí)現(xiàn)拖拽排序,需要的朋友可以參考下2024-01-01實(shí)現(xiàn)一個(gè)Vue自定義指令懶加載的方法示例
這篇文章主要介紹了實(shí)現(xiàn)一個(gè)Vue自定義指令懶加載的方法示例,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-06-06詳解如何實(shí)現(xiàn)一個(gè)簡(jiǎn)單的 vuex
本篇文章主要介紹了如何實(shí)現(xiàn)一個(gè)簡(jiǎn)單的 vuex,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-02-02vue自定義底部導(dǎo)航欄Tabbar的實(shí)現(xiàn)代碼
這篇文章主要介紹了vue自定義底部導(dǎo)航欄Tabbar的實(shí)現(xiàn)代碼,非常不錯(cuò),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2018-09-09