Vue + Element UI 實現權限管理系統 前端篇(十):動態加載菜單

動態加載菜單

以前咱們的導航樹都是寫死在頁面裏的,而實際應用中是須要從後臺服務器獲取菜單數據以後動態生成的。前端

咱們在這裏就用上一篇準備好的數據格式Mock出模擬數據,而後動態生成咱們的導航菜單。vue

接口模塊化

咱們向來說究模塊化,以前接口都集中在,interface.js,咱們如今把它更名爲 api.js,並把裏邊原來登陸、用戶、菜單的相關接口都轉移到咱們新建的接口模塊文件中。ios

模塊化以後的文件結構以下圖所示git

模塊化以後,模塊接口寫在相應的模塊接口文件中,以下面是登陸模塊json

login.jsaxios

import axios from '../axios'

/* 
 * 系統登陸模塊
 */

// 登陸
export const login = data => {
    return axios({
        url: '/login',
        method: 'post',
        data
    })
}

// 登出
export const logout = () => {
    return axios({
        url: '/logout',
        method: 'get'
    })
}

模塊化以後,父模塊能夠像這樣引入後端

api.jsapi

/* 
 * 接口統一集成模塊
 */
import * as login from './moudules/login'
import * as user from './moudules/user'
import * as menu from './moudules/menu'


// 默認所有導出
export default {
    login,
    user,
    menu
}

由於咱們這裏是導出的是父模塊,因此在具體接口調用的時候,也須要在原來的基礎上加上模塊了,像這樣。瀏覽器

如上面 api.js 中,咱們導出了 login 的整個文件,而 login 文件下有 login,logout 等多個方法。服務器

導航菜單樹接口

咱們在 menu.js 下建立一個查詢導航菜單樹的接口。

import axios from '../axios'

/* 
 * 菜單管理模塊
 */

export const findMenuTree = () => {
    return axios({
        url: '/menu/findTree',
        method: 'get'
    })
}

 api.js 中若是沒引入要記得引入。

頁面接口調用

接口已經有了,咱們在導航菜單組件 MenuBar.vue 中,加載菜單並存入 store 。

頁面菜單渲染

仍是在  MenuBar.vue 中,頁面經過封裝的菜單樹組件讀取store數據,遞歸生成菜單。

新建菜單樹組件,遞歸生成菜單,並在點擊響應函數裏面根據菜單URL跳轉到指定路由。

components/MenuTree/index.js

<template>
  <el-submenu v-if="menu.children && menu.children.length >= 1" :index="menu.menuId + ''">
    <template slot="title">
      <i :class="menu.icon"></i>
      <span slot="title">{{menu.name}}</span>
    </template>
    <MenuTree v-for="item in menu.children" :key="item.menuId" :menu="item"></MenuTree>
  </el-submenu>
  <el-menu-item v-else :index="menu.menuId + ''" @click="handleRoute(menu)">
    <i :class="menu.icon"></i>
    <span slot="title">{{menu.name}}</span>
  </el-menu-item>
</template>

<script>
  export default {
    name: 'MenuTree',
    props: {
      menu: {
        type: Object,
        required: true
      }
    },
    methods: {
      handleRoute (menu) {
        // 經過菜單URL跳轉至指定路由
        this.$router.push(menu.url)
      }
    }
  }
</script>

提供Mock數據

接口有了,頁面調用和渲染也寫好了,該提供Mock數據了。

mock/modules/menu.js 中 mock findTree接口,data 對應數據太多,這裏不貼了。

export function findTree() {
  return {
    url: 'http://localhost:8080/menu/findTree',
    type: 'get',
    data: menuTreeData // json 對象數據
  }
}

測試效果

啓動完成,進入主頁,咱們看到導航菜單已經成功加載進來了,oh yeah!

然而,咱們愉悅的點了點菜單,發現是這樣的狀況,oh no !

毛都沒有,不過顯然,聰明的你已經看穿了一切,咱們以前只提供了一個叫 /user 的路由,並無提供 /sys/user 的路由。

好吧,咱們稍微修改一下,打開路由配置,把 /user 改爲 /sys/user 試試。

果不其然,修改完以後即可以正常跳轉到用戶界面了。

但不對呀,這裏路由配置是寫死的,導航菜單是菜單數據動態生成的,這個路由配置也應該是根據菜單數據動態添加的啊,嗯,因此接下來咱們就來討論動態路由配置的問題。

動態路由實現

在 vue 的 route 中提供了 addRoutes 來實現動態路由,打開 MenuBar.vue ,咱們在加載導航菜單的同時添加動態路由配置。

MenuBar.vue

其中 addDynamicMenuRoutes 是根據菜單返回動態路由配置的關鍵代碼。

addDynamicMenuRoutes 方法詳情:

    /**
     * 添加動態(菜單)路由
     * @param {*} menuList 菜單列表
     * @param {*} routes 遞歸建立的動態(菜單)路由
     */
    addDynamicMenuRoutes (menuList = [], routes = []) {
      var temp = []
      for (var i = 0; i < menuList.length; i++) {
        if (menuList[i].children && menuList[i].children.length >= 1) {
          temp = temp.concat(menuList[i].children)
        } else if (menuList[i].url && /\S/.test(menuList[i].url)) {
          menuList[i].url = menuList[i].url.replace(/^\//, '')
          // 建立路由配置
          var route = {
            path: menuList[i].url,
            component: null,
            name: menuList[i].name,
            meta: {
              menuId: menuList[i].menuId,
              title: menuList[i].name,
              isDynamic: true,
              isTab: true,
              iframeUrl: ''
            }
          }
          // url以http[s]://開頭, 經過iframe展現
          if (isURL(menuList[i].url)) {
            route['path'] = menuList[i].url
            route['name'] = menuList[i].name
            route['meta']['iframeUrl'] = menuList[i].url
          } else {
            try {
              // 根據菜單URL動態加載vue組件,這裏要求vue組件須按照url路徑存儲
              // 如url="sys/user",則組件路徑應是"@/views/sys/user.vue",不然組件加載不到
              let array = menuList[i].url.split('/')
              let url = array[0].substring(0,1).toUpperCase()+array[0].substring(1) + '/' + array[1].substring(0,1).toUpperCase()+array[1]  .substring(1)
              route['component'] = resolve => require([`@/views/${url}`], resolve)
            } catch (e) {}
          }
          routes.push(route)
        }
      }
      if (temp.length >= 1) {
        this.addDynamicMenuRoutes(temp, routes)
      } else {
        console.log(routes)
      }
      return routes
    }

動態菜單頁面的組件結構稍微調整下,須要跟菜單url匹配,才能根據菜單url肯定組件路徑來動態加載組件。

 

把路由文件清理一下,把動態菜單相關的路由配置處理掉,留下一些固定的全局路由就好。

動態路由測試

啓動完成,進入主頁,點擊用戶管理,路由到了用戶管理頁面。

 

 點擊機構管理,路由到了機構管理頁面。

 

好了,到這裏動態路由功能已經實現了,給本身鼓個掌吧。

頁面刷新出大坑

先前咱們是將導航菜單和路由的加載放在菜單欄頁面MenuBar.vue中,一切顯示和路由也都正常,看起來沒什麼問題。然而當咱們在非根據路徑刷新頁面時,問題出現了。

以下圖所示,咱們在用戶管理頁面的時候,點擊刷新瀏覽器,而後就白茫茫一片了,這是由於瀏覽器的刷新會致使整個vue從新加載,路由被從新初始化了,後面在Menu.bar添加的動態路由沒有了,因此跳轉的時候沒有找到匹配路由,跳轉的是一個不存在的頁面,故而白茫茫一片。

專業填坑指南

這顯然是動態菜單和路由的加載時機不對,怎麼解決這個問題呢,既然問題出在加載時機,那就找一個在頁面刷新的時候也能觸發從新加載的地方就行了。

這樣的地方也很多,像vue加載過程當中的鉤子函數,路由導航守衛函數等均可以,咱們這裏就選擇在路由導航守衛的 beforeEach 函數內加載,保證每次路由跳轉的時候都可以擁有動態菜單和路由。

把原先在MenuBar.vue中加載動態菜單和路由的代碼,轉移到路由配置 router/index 中來。

beforeEach:

router.beforeEach((to, from, next) => {
  // 登陸界面登陸成功以後,會把用戶信息保存在會話
  // 存在時間爲會話生命週期,頁面關閉即失效。
  let isLogin = sessionStorage.getItem('user') if (to.path === '/login') { // 若是是訪問登陸界面,若是用戶會話信息存在,表明已登陸過,跳轉到主頁 if(isLogin) { next({ path: '/' }) } else { next() } } else { // 若是訪問非登陸界面,且戶會話信息不存在,表明未登陸,則跳轉到登陸界面 if (!isLogin) { next({ path: '/login' }) } else { // 加載動態菜單和路由  addDynamicMenuAndRoutes() next() } } })
addDynamicMenuAndRoutes:
/**
* 加載動態菜單和路由
*/
function addDynamicMenuAndRoutes() {
  api.menu.findMenuTree()
  .then( (res) => { store.commit('setMenuTree', res.data) // 添加動態路由 let dynamicRoutes = addDynamicRoutes(res.data) router.options.routes[0].children = router.options.routes[0].children.concat(dynamicRoutes) router.addRoutes(router.options.routes); }) .catch(function(res) { alert(res); }); }
addDynamicRoutes:
/**
* 添加動態(菜單)路由
* @param {*} menuList 菜單列表
* @param {*} routes 遞歸建立的動態(菜單)路由
*/
function addDynamicRoutes (menuList = [], routes = []) {
 var temp = [] for (var i = 0; i < menuList.length; i++) { if (menuList[i].children && menuList[i].children.length >= 1) { temp = temp.concat(menuList[i].children) } else if (menuList[i].url && /\S/.test(menuList[i].url)) { menuList[i].url = menuList[i].url.replace(/^\//, '') // 建立路由配置 var route = { path: menuList[i].url, component: null, name: menuList[i].name, meta: { menuId: menuList[i].menuId, title: menuList[i].name, isDynamic: true, isTab: true, iframeUrl: '' } } // url以http[s]://開頭, 經過iframe展現 if (isURL(menuList[i].url)) { route['path'] = menuList[i].url route['name'] = menuList[i].name route['meta']['iframeUrl'] = menuList[i].url } else { try { // 根據菜單URL動態加載vue組件,這裏要求vue組件須按照url路徑存儲 // 如url="sys/user",則組件路徑應是"@/views/sys/user.vue",不然組件加載不到 let array = menuList[i].url.split('/') let url = array[0].substring(0,1).toUpperCase()+array[0].substring(1) + '/' + array[1].substring(0,1).toUpperCase()+array[1] .substring(1) route['component'] = resolve => require([`@/views/${url}`], resolve) } catch (e) {} } routes.push(route) } } if (temp.length >= 1) { addDynamicRoutes(temp, routes) } else { console.log(routes) } return routes }

固然,別忘了把要用到的幾個東西引入進來,把導航菜單欄的代碼清理一下。

測試效果

啓動完成,進入主頁,點擊用戶管理,點擊刷新按鈕。

刷新後,菜單收起來了,然而頁面仍是正確的停留在用戶管理頁面。媽媽不再用擔憂我會刷新了!

保存加載狀態

如今每次路由跳轉前都會從新獲取菜單數據生成菜單和路由,及時頁面沒有刷新也會重複獲取,這樣很影響性能。咱們改良一下,加載成功以後把狀態保存到store,每次加載以前先檢查store的加載狀態,這樣就能夠避免在非頁面刷新的情形下還頻發重複的加載了。

 在 store 中添加菜單路由加載狀態,避免頁面未刷新而重複加載。

修改路由配置,在加載以前判斷加載狀態,只有未加載的狀況下才加載,並在加載以後保存加載狀態。

求解一個問題

在路由跳轉的時候,路由好像是在原路徑基礎上疊加路由路徑跳轉的。

如路徑在 http://localhost:8090/#/sys/dept 的時候,點擊用戶管理。

代碼對應 this.$router.push(‘’sys/user),路由就賺到了 http://localhost:8090/#/sys/sys/user。

比正確路由多了一個 sys,目前還不到爲何。

目前我是在實際跳轉以前,先跳回主頁面而後在作真正的跳轉。

這樣問題能夠解決,但無故端多了一步跳轉總歸很差,求解中。。。

 

源碼下載

後端:https://gitee.com/liuge1988/kitty

前端:https://gitee.com/liuge1988/kitty-ui.git


做者:朝雨憶輕塵
出處:https://www.cnblogs.com/xifengxiaoma/ 版權全部,歡迎轉載,轉載請註明原文做者及出處。

相關文章
相關標籤/搜索