vue-element-admin 如何使用動態菜單


最近在使用 vue-element-admin 將相關心得進行總結:

vue-element-admin 是 vue 生態中一個后界面解決方案,文檔地址:https://panjiachen.gitee.io/vue-element-admin-site/zh/guide/

 

在使用過程中有這樣一個問題,vue-element-admin 的菜單列表是通過遍歷路由進行渲染的,由前端定義,可以在 router.js 中看到相關代碼,即是路由也是菜單;

好處是我們不用重復定義菜單列表信息和路由之間的綁定了;但是我們的菜單信息想通過服務端進行動態輸出來達到權限控制的效果就不是那么容易了;

網上搜索了一圈,基本上的方案是由服務端輸出完整的 vue-element-admin 路由信息並進行綁定,這樣雖然能達到動態菜單的效果,但是給服務端也造成了不必要的煩惱;

作為服務端開發:不關心 菜單對應的是哪個 vue 里面的 component ,也不希望將菜單的格式限定得那個嚴格,甚至不關心菜單的圖標是什么,只需要嚴格按照服務端的要求顯示或隱藏菜單即可;

為了解決這個問題,我的優化方案如下,服務端只需輸出菜單顯示或隱藏,路由信息定義都在前端寫死,這樣達到完美的前后端分離要求。

 

1.定義路由

在 src/router/index.js 中將 constantRoutes 常量中定義的側邊欄顯示的菜單信息刪除掉;然后重新定義一個 dynamicRoutes 常量寫入菜單信息,dynamicRoutes  中每個節點都添加 srvName 屬性,通過它來和服務端返回的菜單信息進行關聯。

export const constantRoutes = [
  {
    path: '/login',
    component: () => import('@/views/login/index'),
    hidden: true
  },

  {
    path: '/404',
    component: () => import('@/views/404'),
    hidden: true
  },

  {
    path: '/',
    component: Layout,
    redirect: '/dashboard',
    children: [{
      path: 'dashboard',
      name: 'Dashboard',
      component: () => import('@/views/dashboard/index'),
      meta: { title: 'Dashboard', icon: 'dashboard' }
    }]
  },

  // 404 page must be placed at the end !!!
  { path: '*', redirect: '/404', hidden: true }
]

export const dynamicRoutes = [
  {
    path: '/example',
    component: Layout,
    redirect: '/example/table',
    srvName: '/example',
    name: 'Example',
    meta: { title: 'Example', icon: 'example' },
    children: [
      {
        path: 'table',
        name: 'Table',
        srvName: '/example/table',
        component: () => import('@/views/table/index'),
        meta: { title: 'Table', icon: 'table' }
      },
      {
        path: 'tree',
        name: 'Tree',
        srvName: '/example/tree',
        component: () => import('@/views/tree/index'),
        meta: { title: 'Tree', icon: 'tree' }
      }
    ]
  }

]



const createRouter = () => new Router({
// mode: 'history', // require service support
scrollBehavior: () => ({ y: 0 }),
routes: constantRoutes.concat(dynamicRoutes) // 初始化時將所有路由都加載上,否則會出現刷新頁面404的情況
})
 

 

2. 服務端接口

服務端接口返回數據格式如下,節點中 srvName 和前端的路由進行匹配,通過 show 屬性來確定顯示或隱藏

服務端也無需將菜單的子/父級關系輸出,只需要將所有的菜單信息輸出一個數組即可。

[
  { srvName: '/example', id: 1, show: true },
  { id: 2, srvName: '/example/table', show: true },
  { id: 3, srvName: '/example/tree', show: true },
  { id: 4, srvName: '/nested', show: true },
  { id: 5, srvName: '/nested/menu1', show: true }

]

 

3.定義 api 請求模塊

在 src/api/ 目錄下創建 menus.js 

import request from '@/utils/request'

export function getMenus(token) {
  return request({
    url: '/menus',
    method: 'get',
    params: { token }
  })
}

 

4.配置 store 調用

新增文件 src/store/modules/menus.js

import { getMenus } from '@/api/menus'
import { getToken } from '@/utils/auth'
import { dynamicRoutes } from '@/router/index'

const getDefaultState = () => {
  return {
    token: getToken(),
    menuList: []
  }
}

const state = getDefaultState()

const mutations = {
  SET_MENUS: (state, menus) => {
    state.menuList = menus
  }
}

// 動態菜單還是定義在前端,后台只會返回有權限的菜單列表,通過遍歷服務端的菜單數據,沒有的將對於菜單進行隱藏
// 這樣的好處是服務端無需返回前端菜單相關結構,並且菜單顯示又可以通過服務端來控制,進行菜單的動態控制
// 前端新增頁面也無需先通過服務端進行菜單添加,遵循了前后端分離原則
export function generaMenu(routes, srvMenus) {
  for (let i = 0; i < routes.length; i++) {
    const routeItem = routes[i]
    var showItem = false
    for (let j = 0; j < srvMenus.length; j++) {
      const srvItem = srvMenus[j]

      // 前后端數據通過 srvName 屬性來匹配
      if (routeItem.srvName !== undefined && routeItem.srvName === srvItem.srvName && srvItem.show === true) {
        showItem = true
        routes[i]['hidden'] = false
        break
      }
    }
    if (showItem === false) {
      routes[i]['hidden'] = true
    }

    if (routeItem['children'] !== undefined && routeItem['children'].length > 0) {
      generaMenu(routes[i]['children'], srvMenus)
    }
  }
}

const actions = {
 
  getMenus({ commit }) {
    return new Promise((resolve, reject) => {
      getMenus(state.token).then(response => {
        const { data } = response
        console.log(response)
        if (!data) {
          reject('Verification failed, please Login again.')
        }

        const srvMenus = data.items
        var pushRouter = dynamicRoutes
        generaMenu(pushRouter, srvMenus)
        commit('SET_MENUS', pushRouter)
        resolve(data)
      }).catch(error => {
        reject(error)
      })
    })
  }
}

export default {
  namespaced: true,
  state,
  mutations,
  actions
}

 

 

以下標紅是修改部分

src/store/index.js

import Vue from 'vue'
import Vuex from 'vuex'
import getters from './getters'
import app from './modules/app'
import settings from './modules/settings'
import user from './modules/user'
import menus from './modules/menus'

Vue.use(Vuex)

const store = new Vuex.Store({
  modules: {
    app,
    settings,
    user,
    menus
  },
  getters
})

export default store

 

src/store/getters.js

const getters = {
  sidebar: state => state.app.sidebar,
  device: state => state.app.device,
  token: state => state.user.token,
  avatar: state => state.user.avatar,
  name: state => state.user.name,
  menuList: state => state.menus.menuList
}
export default getters

 

 

5.渲染動態菜單

這里是最關鍵一步,通過修改 src/permission.js 文件來渲染動態菜單,再該文件中可以看到登陸成功后的相關操作,我們在登陸成功后添加渲染菜單相關代碼即可

          await store.dispatch('menus/getMenus').then((res) => {
            console.log(store.getters.menuList)
            router.addRoutes(store.getters.menuList)
            router.options.routes = constantRoutes.concat(store.getters.menuList)
          })

 

 

完整的 src/permission.js 內容

import router from './router'
import store from './store'
import { Message } from 'element-ui'
import NProgress from 'nprogress' // progress bar
import 'nprogress/nprogress.css' // progress bar style
import { getToken } from '@/utils/auth' // get token from cookie
import getPageTitle from '@/utils/get-page-title'
import { constantRoutes } from './router/index'
NProgress.configure({ showSpinner: false }) // NProgress Configuration

const whiteList = ['/login'] // no redirect whitelist

router.beforeEach(async(to, from, next) => {
  // start progress bar
  NProgress.start()

  // set page title
  document.title = getPageTitle(to.meta.title)

  // determine whether the user has logged in
  const hasToken = getToken()

  if (hasToken) {
    if (to.path === '/login') {
      // if is logged in, redirect to the home page
      next({ path: '/' })
      NProgress.done()
    } else {
      const hasGetUserInfo = store.getters.name
      if (hasGetUserInfo) {
        next()
      } else {
        try {
          // get user info
          await store.dispatch('user/getInfo')

          await store.dispatch('menus/getMenus').then((res) => {
            console.log(store.getters.menuList)
            router.addRoutes(store.getters.menuList)
            router.options.routes = constantRoutes.concat(store.getters.menuList) })
          next()
        } catch (error) {
          // remove token and go to login page to re-login
          await store.dispatch('user/resetToken')
          Message.error(error || 'Has Error')
          next(`/login?redirect=${to.path}`)
          NProgress.done()
        }
      }
    }
  } else {
    /* has no token*/

    if (whiteList.indexOf(to.path) !== -1) {
      // in the free login whitelist, go directly
      next()
    } else {
      // other pages that do not have permission to access are redirected to the login page.
      next(`/login?redirect=${to.path}`)
      NProgress.done()
    }
  }
})

router.afterEach(() => {
  // finish progress bar
  NProgress.done()
})

 

這樣我們就可以通過 api中的 /menus 接口來獲取動態的菜單了,並且前端在開發時也不需要找服務端來新增路由信息了,路由定義依然在前端。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM