vue-router+vuex實(shí)現(xiàn)加載動(dòng)態(tài)路由和菜單

前言

動(dòng)態(tài)路由加載和動(dòng)態(tài)菜單渲染的應(yīng)用在后端權(quán)限控制中十分常見,后端只要加載權(quán)限路由進(jìn)行渲染返回到瀏覽器就可以。在前后端分離中,權(quán)限控制動(dòng)態(tài)路由和動(dòng)態(tài)菜單也是一個(gè)非常常見的問題。其實(shí)我們最最理想的效果是什么呢?
我們?cè)L問一個(gè)應(yīng)用,在登錄之前有哪些路由是一定要加載的呢?你看我總結(jié)如下,你看下是不是這些:

創(chuàng)新互聯(lián)專注為客戶提供全方位的互聯(lián)網(wǎng)綜合服務(wù),包含不限于網(wǎng)站設(shè)計(jì)制作、成都網(wǎng)站設(shè)計(jì)、鳳慶網(wǎng)絡(luò)推廣、重慶小程序開發(fā)、鳳慶網(wǎng)絡(luò)營銷、鳳慶企業(yè)策劃、鳳慶品牌公關(guān)、搜索引擎seo、人物專訪、企業(yè)宣傳片、企業(yè)代運(yùn)營等,從售前售中售后,我們都將竭誠為您服務(wù),您的肯定,是我們最大的嘉獎(jiǎng);創(chuàng)新互聯(lián)為所有大學(xué)生創(chuàng)業(yè)者提供鳳慶建站搭建服務(wù),24小時(shí)服務(wù)熱線:18980820575,官方網(wǎng)址:bm7419.com

1.登錄路由 (登錄功能路由)
2.系統(tǒng)路由(系統(tǒng)消息路由,比如歡迎界面,404,error等的路由)

但是在vue中,一旦實(shí)例化,就必須初始化路由,但這個(gè)時(shí)候你還沒有登錄,沒有獲取你的權(quán)限路由呀,如果加載全部路由,那么在瀏覽器上輸入路由你就可以訪問(這個(gè)問題可以使用router.beforeEach鉤子進(jìn)行權(quán)限鑒定解決),那么在前后端分離的開發(fā)項(xiàng)目中,vue是如何實(shí)現(xiàn)動(dòng)態(tài)路由加載實(shí)現(xiàn)權(quán)限控制的呢?這就是我們這篇文章要寫的內(nèi)容。

我們寫過后臺(tái)渲染都知道怎么去實(shí)現(xiàn),那么放到vue中如何去實(shí)現(xiàn)呢?我們先羅列幾個(gè)問題進(jìn)行思考,如下

1.vue中路由是如何初始化,放入到vue實(shí)例中的?
2.vue中提供了什么實(shí)現(xiàn)動(dòng)態(tài)路由加載呢?

我們先順著這兩個(gè)問題進(jìn)行思考,并且順著這兩個(gè)問題,我們進(jìn)行對(duì)應(yīng)方案解決,這個(gè)過程中會(huì)會(huì)出現(xiàn)很多新的問題,我們也針對(duì)新問題出對(duì)應(yīng)方案,并且進(jìn)行優(yōu)化。

路由初始化

路由初始化發(fā)生在什么時(shí)候呢?我們可以看主入口文件main.js,下面是我貼出的我的一個(gè)項(xiàng)目案例:

import Vue from 'vue'

import 'normalize.css/normalize.css' // A modern alternative to CSS resets

import Element from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'

import '@/styles/index.scss' // global css

import App from './App'
import router from './router'
import store from './store'

import i18n from './lang' // Internationalization
import './icons' // icon
import './errorLog' // error log
import './permission' // permission control
import './mock' // simulation data

import * as filters from './filters' // global filters

Vue.use(Element, {
  size: 'medium', // set element-ui default size
  i18n: (key, value) => i18n.t(key, value)
})

// register global utility filters.
Object.keys(filters).forEach(key => {
  Vue.filter(key, filters[key])
})

Vue.config.productionTip = false

// vue實(shí)例化就已經(jīng)把router初始化了
new Vue({
  el: '#app',
  router,
  store,
  i18n,
  render: h => h(App)
})

通過上面的主入口文件,我們就知道,這個(gè)路由初始胡就發(fā)生在vue實(shí)例化時(shí)。這個(gè)也很好理解如果你沒有初始化路由,那么你就默認(rèn)只能進(jìn)入到主窗口,那么接下來主窗口中你沒有路由你怎么跳轉(zhuǎn)?程序也不知道你有哪些地方可以跳轉(zhuǎn)呀,路由都是需要先注冊(cè)到實(shí)例中,實(shí)例才能定位到相應(yīng)的視圖。從中我們知道,路由初始化發(fā)生在vue實(shí)例化時(shí)

那么這個(gè)時(shí)候我們接著我們想要的權(quán)限控制目標(biāo)走:程序一開始,只注冊(cè)登錄路由、系統(tǒng)信息路由(歡迎頁面,404路由,error路由),我們稱這些為靜態(tài)路由,登錄后我們通過接口獲取權(quán)限拿到了菜單,這個(gè)時(shí)候需要進(jìn)行添加動(dòng)態(tài)路由,把這些菜單信息注冊(cè)為路由,我們稱這些為動(dòng)態(tài)路由。那么vue實(shí)例化時(shí),vue-router就已經(jīng)被初始化,那么我們是不是能夠通過類似于往router實(shí)例里面添加路由項(xiàng)的方式進(jìn)行注冊(cè)路由呢?我們可以查閱文檔,也可以查看vue-router源碼,有一個(gè)叫做addRoutes的方法進(jìn)行動(dòng)態(tài)注冊(cè)路由信息,路由對(duì)象其實(shí)就是一個(gè)路由數(shù)組,我們通過addRoutes就可以進(jìn)行動(dòng)態(tài)注冊(cè)路由,這個(gè)跟那個(gè)數(shù)組中extend功能類似的。

所以說道這里我們知道可以通過addRoutes進(jìn)行動(dòng)態(tài)路由注冊(cè)。好,那么我們就順著這個(gè)思路走下去。

在登錄模塊中,登錄成功后,我們通過api獲取后臺(tái)權(quán)限菜單,然后注冊(cè)路由。代碼如下:

// 登錄頁登錄方法
handleLogin() {
      this.$refs.loginForm.validate(valid => {
        if (valid && this.isSuccess) {
          this.loading = true
          this.$store.dispatch('LoginByUsername', this.loginForm).then(() => {
           // 在這個(gè)時(shí)候進(jìn)行獲取后臺(tái)權(quán)限及菜單
            this.$store.dispatch('getMenus', this.loginForm.name).then((res) => {
             // 把這個(gè)菜單信息注冊(cè)為路由信息
              this.$router.addRoutes(menuitems)
            })
            this.loading = false
           // 除了登錄路由、和系統(tǒng)消息路由,這個(gè)跟路由是一個(gè)歡迎路由,是靜態(tài)路由
            this.$router.push({ path: '/' })
          }).catch(() => {
            this.$message.error('登陸失敗,請(qǐng)檢查用戶名或密碼是否正確')
            this.loading = false
          })
        } else {
          if (!this.isSuccess) {
            this.$message.error('請(qǐng)拉滑動(dòng)條')
          }
          console.log('error submit!!')
          return false
        }
      })
  }

// 登錄方法計(jì)算屬性
computed: { 
   ...mapGetters([ 
    'menuitems', 
   ]) 
  },

總結(jié)一下:
登錄成功以后(持久化token),調(diào)用獲取權(quán)限菜單(保存在store里面),這個(gè)時(shí)候就完成了登錄后動(dòng)態(tài)初始化權(quán)限菜單的功能。那么這里面所有的路由就是當(dāng)前用戶可訪問的菜單,就實(shí)現(xiàn)了我們的目標(biāo)效果。但是呢,store存儲(chǔ)權(quán)限菜單會(huì)有個(gè)問題,一旦刷新里面的值就刷掉了,那么這個(gè)時(shí)候就重新實(shí)例化的時(shí)候就會(huì)跳到404路由中,菜單信息也沒有了,那如何解決這個(gè)刷新時(shí)的問題呢?

我們先分析一下思路:

1.初始化vue實(shí)例時(shí),初始化router,包括所有的靜態(tài)路由。
2.全局鉤子檢查token是否有效?
        a.如果有效,則通過token獲取用戶信息保存到store中,根據(jù)用戶信息獲取權(quán)限菜單保存到store中,
        動(dòng)態(tài)注冊(cè)權(quán)限菜單的路由信息;
        b.如果token無效,重新定位到靜態(tài)登錄路由進(jìn)行登錄.
3.登錄模塊中,登錄成功后獲取用戶信息保存到store中,將token保存到store中并持久化到本地,
獲取權(quán)限菜單保存到store中,動(dòng)態(tài)注冊(cè)權(quán)限菜單的路由信息
4.動(dòng)態(tài)加載完路由后,直接跳到歡迎界面的靜態(tài)路由
5.一旦頁面刷新,那么token就會(huì)從store中清除,token失效,那么就會(huì)去獲得持久化在本地的token
,重新去獲取用戶信息,權(quán)限菜單,重新動(dòng)態(tài)注冊(cè)路由。
6.token持久化在本地也是有時(shí)間限制的,假設(shè)token有效期為一周,一旦過了有效期,那么會(huì)走2的b情況。

那么上面的思路就是動(dòng)態(tài)加載權(quán)限菜單路由信息的簡述,整個(gè)的環(huán)路就通了,刷新問題就解決了。

代碼如下:

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' // getToken from cookie

NProgress.configure({ showSpinner: false })// NProgress Configuration

// 權(quán)限判斷
function hasPermission(roles, permissionRoles) {
  if (roles.indexOf('admin') >= 0) return true // admin permission passed directly
  if (!permissionRoles) return true
  return roles.some(role => permissionRoles.indexOf(role) >= 0)
}

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

// 全局鉤子
router.beforeEach((to, from, next) => {
  NProgress.start() // start progress bar
  // 如果有token
  if (getToken()) { // determine if there has token
    // 登錄后進(jìn)入登錄頁
    if (to.path === '/login') {
      next({ path: '/' })
      NProgress.done() // if current page is dashboard will not trigger afterEach hook, so manually handle it
    } else {
      // 當(dāng)進(jìn)入非登錄頁時(shí),需要進(jìn)行權(quán)限校驗(yàn)
      if (store.getters.roles.length === 0) { // 判斷當(dāng)前用戶是否已拉取完user_info信息
        store.dispatch('GetUserInfo').then(res => { // 拉取user_info
           const roles = res.data.data.roles // note: roles must be a array! such as: ['editor','develop']
           store.dispatch('GenerateRoutes', { roles }).then(() => { // 根據(jù)roles權(quán)限生成可訪問的路由表
             router.addRoutes(store.getters.addRouters) // 動(dòng)態(tài)添加可訪問路由表
             next({ ...to, replace: true }) // hack方法 確保addRoutes已完成 ,set the replace: 
           })
        }).catch((err) => {
          store.dispatch('FedLogOut').then(() => {
            Message.error(err || 'Verification failed, please login again')
            next({ path: '/' })
          })
        })
      } else {
        // 沒有動(dòng)態(tài)改變權(quán)限的需求可直接next() 刪除下方權(quán)限判斷 ↓
        if (hasPermission(store.getters.roles, to.meta.roles)) {
          next()
        } else {
          next({ path: '/401', replace: true, query: { noGoBack: true }})
        }
        // 可刪 ↑
      }
    }
  } else {
    /* has no token*/
    if (whiteList.indexOf(to.path) !== -1) { // 在免登錄白名單,直接進(jìn)入
      next()
    } else {
      next('/login') // 否則全部重定向到登錄頁
      NProgress.done() // if current page is login will not trigger afterEach hook, so manually handle it
    }
  }
})

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

備注:根據(jù)模塊獨(dú)立性,我把登錄中獲取權(quán)限列表去掉,都放置在全局鉤子中,把上面的代碼直接引入到主入口文件main.js中。

另外這里采用vuex進(jìn)行狀態(tài)管理,所以從新捋一下思路:

1.vue實(shí)例化,初始化靜態(tài)路由
2.全局鉤子進(jìn)行檢查:
    a.token有效
          -如果當(dāng)前跳轉(zhuǎn)路由是登錄路由,直接進(jìn)入根路由/
            -如果跳轉(zhuǎn)路由非登錄路由,則需要進(jìn)行權(quán)限校驗(yàn),如果用戶信息和權(quán)限菜單沒拉取,
            則進(jìn)行拉取后將權(quán)限菜單動(dòng)態(tài)注冊(cè)到router中,進(jìn)行權(quán)限判斷,如果有用戶信息和權(quán)限菜單信息,
            則直接進(jìn)行權(quán)限判斷。
    b.token無效
          -如果在白名單中,則直接進(jìn)入
            -進(jìn)入到登錄頁

3.全局狀態(tài)管理采用vuex

到這里我們就已經(jīng)完成了vue-router+vuex動(dòng)態(tài)注冊(cè)路由控制權(quán)限的方式就說完了,這里我留個(gè)思考題給大家:現(xiàn)在根據(jù)上面的方式我再引入一個(gè)產(chǎn)品實(shí)體,(用戶 - 產(chǎn)品 - 菜單 ), 用戶可以有多個(gè)產(chǎn)品權(quán)限,每個(gè)產(chǎn)品有公用的菜單,也有各產(chǎn)品定制化的菜單,那么這個(gè)時(shí)候我在前端如果做好權(quán)限校驗(yàn)?zāi)??要求:?dāng)前用戶當(dāng)前產(chǎn)品的權(quán)限菜單才可被訪問。

網(wǎng)頁標(biāo)題:vue-router+vuex實(shí)現(xiàn)加載動(dòng)態(tài)路由和菜單
文章鏈接:http://bm7419.com/article36/gocepg.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供營銷型網(wǎng)站建設(shè)企業(yè)建站、面包屑導(dǎo)航、移動(dòng)網(wǎng)站建設(shè)、網(wǎng)站排名

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場,如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)

成都網(wǎng)頁設(shè)計(jì)公司