溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Vue3如何加載動(dòng)態(tài)菜單

發(fā)布時(shí)間:2023-05-10 14:13:29 來源:億速云 閱讀:146 作者:zzz 欄目:編程語言

今天小編給大家分享一下Vue3如何加載動(dòng)態(tài)菜單的相關(guān)知識(shí)點(diǎn),內(nèi)容詳細(xì),邏輯清晰,相信大部分人都還太了解這方面的知識(shí),所以分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后有所收獲,下面我們一起來了解一下吧。

1. 整體思路

為了確保在所有的 .vue 文件中都能訪問到到菜單數(shù)據(jù),所以選擇將菜單數(shù)據(jù)存入 vuex 中,vuex 是 vue 中一個(gè)存儲(chǔ)數(shù)據(jù)的公共地方,所有的 .vue 文件都可以從 vuex 中讀取到數(shù)據(jù)。存儲(chǔ)在 vuex 中的數(shù)據(jù)本質(zhì)上是存在內(nèi)存中,所以它有一個(gè)特點(diǎn),就是瀏覽器按 F5 刷新之后,數(shù)據(jù)就沒了。所以在發(fā)生頁面的跳轉(zhuǎn)的時(shí)候,我們應(yīng)該去區(qū)分一下,是用戶點(diǎn)擊了頁面上的菜單按鈕之后發(fā)生了頁面跳轉(zhuǎn)還是用戶點(diǎn)擊了瀏覽器刷新按鈕(或者按了 F5)發(fā)生了跳轉(zhuǎn)。

為了實(shí)現(xiàn)這一點(diǎn),我們需要用到 vue 中的路由導(dǎo)航守衛(wèi)功能,對(duì)于我們 Java 工程師而言,這些可能聽起來有點(diǎn)陌生,但是你把它當(dāng)作 Java 中的 Filter 來看待就好理解了,實(shí)際上我們視頻中和小伙伴們講解的時(shí)候就是這么類比的,將一個(gè)新事物跟我們腦海中一個(gè)已有的熟悉的事物進(jìn)行類比,就很容易理解了。

vue 中的導(dǎo)航守衛(wèi)就類似一個(gè)監(jiān)控,它可以監(jiān)控到所有的頁面跳轉(zhuǎn),在頁面跳轉(zhuǎn)中,我們可以去判斷一下 vuex 中的菜單數(shù)據(jù)是否還在,如果還在,就說明用戶是點(diǎn)擊了頁面上的菜單按鈕完成了跳轉(zhuǎn)的,如果不在,就說明用戶是點(diǎn)擊了瀏覽器的刷新按鈕或者是按了 F5 進(jìn)行頁面刷新的,此時(shí)我們就要趕緊去服務(wù)端重新加載一下菜單數(shù)據(jù)。

---xxxxxxxxxxxxxxxxxx---

整體上的實(shí)現(xiàn)思路就是這樣,接下來我們來看看一些具體的實(shí)現(xiàn)細(xì)節(jié)。

2. 實(shí)現(xiàn)細(xì)節(jié)

2.1 加載細(xì)節(jié)

首先我們來看看加載的細(xì)節(jié)。

小伙伴們知道,單頁面項(xiàng)目的入口是 main.js,路由加載的內(nèi)容在 src/permission.js 文件中,該文件在 main.js 中被引入,src/permission.js 中的前置導(dǎo)航守衛(wèi)內(nèi)容如下:

router.beforeEach((to, from, next) => {
  NProgress.start()
  if (getToken()) {
    to.meta.title && useSettingsStore().setTitle(to.meta.title)
    /* has token*/
    if (to.path === '/login') {
      next({ path: '/' })
      NProgress.done()
    } else {
      if (useUserStore().roles.length === 0) {
        isRelogin.show = true
        // 判斷當(dāng)前用戶是否已拉取完user_info信息
        useUserStore().getInfo().then(() => {
          isRelogin.show = false
          usePermissionStore().generateRoutes().then(accessRoutes => {
            // 根據(jù)roles權(quán)限生成可訪問的路由表
            accessRoutes.forEach(route => {
              if (!isHttp(route.path)) {
                router.addRoute(route) // 動(dòng)態(tài)添加可訪問路由表
              }
            })
            next({ ...to, replace: true }) // hack方法 確保addRoutes已完成
          })
        }).catch(err => {
          useUserStore().logOut().then(() => {
            ElMessage.error(err)
            next({ path: '/' })
          })
        })
      } else {
        next()
      }
    }
  } else {
    // 沒有token
    if (whiteList.indexOf(to.path) !== -1) {
      // 在免登錄白名單,直接進(jìn)入
      next()
    } else {
      next(`/login?redirect=${to.fullPath}`) // 否則全部重定向到登錄頁
      NProgress.done()
    }
  }
})

我跟大家捋一下這個(gè)前置導(dǎo)航守衛(wèi)中的思路:

  • 首先調(diào)用 getToken 方法,這個(gè)方法實(shí)際上是去 Cookie 中拿認(rèn)證 Token,也就是登錄成功后后端返回給前端的那個(gè) JWT 字符串。

  • 如果 getToken 方法有返回值,說明用戶已經(jīng)登錄了,那么進(jìn)入到 if 分支中,如果 getToken 沒拿到值,說明用戶未登錄,未登錄的話,又分為兩種情況:i:訪問的目標(biāo)地址處于免登錄白名單中,那么此時(shí)直接訪問即可;ii:訪問的目標(biāo)地址不在白名單中,那么此時(shí)就跳轉(zhuǎn)到登錄頁面去,跳轉(zhuǎn)的時(shí)候同時(shí)攜帶一個(gè) redirect 參數(shù),這樣方便在登錄成功之后,再跳轉(zhuǎn)回訪問的目標(biāo)頁面。這個(gè)免登錄訪問的白名單,是一個(gè)在 src/permission.js 文件中定義的變量,默認(rèn)有四個(gè)路徑,分別是 ['/login', '/auth-redirect', '/bind', '/register']

  • 如果 getToken 拿到了值,說明用戶已經(jīng)登錄了,此時(shí)又分情況:如果用戶訪問的路徑是登錄頁面,那么就給他重定向到項(xiàng)目首頁(也就是在已經(jīng)登錄的情況下,不允許用戶再次訪問登錄頁面);如果用戶訪問的路徑不是登錄頁面,那么首先判斷 vuex 中的 roles 是否還有值?如果有值,說明當(dāng)前就是用戶點(diǎn)擊了一個(gè)菜單按鈕進(jìn)行跳轉(zhuǎn)的,那么直接跳轉(zhuǎn)就行了;如果沒有值,說明用戶是按了瀏覽器的刷新按鈕或者是 F5 按鈕刷新進(jìn)行的頁面跳轉(zhuǎn),那么此時(shí)首先調(diào)用 getInfo 方法(位于 src/store/modules/user.js 文件中)去服務(wù)端重新加載當(dāng)前用戶的基本信息、角色信息以及權(quán)限信息,然后再調(diào)用 generateRoutes 方法(位于 src/store/modules/permission.js 文件中)去服務(wù)端加載路由信息,并將加載到的路由信息放入到 router 對(duì)象中(前提是這個(gè)路由對(duì)象不是一個(gè) http 鏈接,就是普通的路由地址)。

這就是動(dòng)態(tài)路由的加載整體思路。

在第三步驟中,涉及到兩個(gè)方法,一個(gè)是 getInfo 還有一個(gè) generateRoutes,這兩個(gè)方法也都比較關(guān)鍵,我們?cè)賮砩晕⒖聪隆?/p>

2.2 getInfo

首先這個(gè)加載用戶信息的方法位于 src/store/modules/user.js 文件中,換言之,這些用戶的基本信息加載到之后,是存儲(chǔ)在 vuex 中的,如果刷新瀏覽器這些數(shù)據(jù)就會(huì)丟失:

getInfo() {
  return new Promise((resolve, reject) => {
    getInfo().then(res => {
      const user = res.user
      const avatar = (user.avatar == "" || user.avatar == null) ? defAva : import.meta.env.VITE_APP_BASE_API + user.avatar;
      if (res.roles && res.roles.length > 0) { // 驗(yàn)證返回的roles是否是一個(gè)非空數(shù)組
        this.roles = res.roles
        this.permissions = res.permissions
      } else {
        this.roles = ['ROLE_DEFAULT']
      }
      this.name = user.userName
      this.avatar = avatar;
      resolve(res)
    }).catch(error => {
      reject(error)
    })
  })
},

方法的邏輯其實(shí)倒沒啥好說的,結(jié)合服務(wù)端返回的 JSON 格式,應(yīng)該就很好理解了(部分 JSON):

{
    "permissions":[
        "*:*:*"
    ],
    "roles":[
        "admin"
    ],
    "user":
        "userName":"admin",
        "nickName":"TienChin健身",
        "avatar":"",
    }
}

另外再?gòu)?qiáng)調(diào)下,之前在 vhr 中,我們是將請(qǐng)求封裝成了一個(gè) api.js 文件,里邊有常用的 get、post、put 以及 delete 請(qǐng)求等,然后在需要使用的地方,直接去調(diào)用這些方法發(fā)送請(qǐng)求即可,但是在 TienChin 中,腳手架的封裝是將所有的請(qǐng)求都提前統(tǒng)一封裝好,在需要的時(shí)候直接調(diào)用封裝好的方法,連請(qǐng)求地址都不用傳遞了(封裝的時(shí)候就已經(jīng)寫死了),所以小伙伴們看上面的 getInfo 方法只有方法調(diào)用,沒有傳遞路徑參數(shù)等。

2.3 generateRoutes

generateRoutes 方法則位于 src/store/modules/permission.js 文件中,這里值得說道的地方就比較多了:

generateRoutes(roles) {
  return new Promise(resolve => {
    // 向后端請(qǐng)求路由數(shù)據(jù)
    getRouters().then(res => {
      const sdata = JSON.parse(JSON.stringify(res.data))
      const rdata = JSON.parse(JSON.stringify(res.data))
      const defaultData = JSON.parse(JSON.stringify(res.data))
      const sidebarRoutes = filterAsyncRouter(sdata)
      const rewriteRoutes = filterAsyncRouter(rdata, false, true)
      const defaultRoutes = filterAsyncRouter(defaultData)
      const asyncRoutes = filterDynamicRoutes(dynamicRoutes)
      asyncRoutes.forEach(route => { router.addRoute(route) })
      this.setRoutes(rewriteRoutes)
      this.setSidebarRouters(constantRoutes.concat(sidebarRoutes))
      this.setDefaultRoutes(sidebarRoutes)
      this.setTopbarRoutes(defaultRoutes)
      resolve(rewriteRoutes)
    })
  })
}

首先大家看到,服務(wù)端返回的動(dòng)態(tài)菜單數(shù)據(jù)解析了三次,分別拿到了三個(gè)對(duì)象,這三個(gè)對(duì)象都是將來要用的,只不過使用的場(chǎng)景不同,下面結(jié)合頁面的顯示跟大家細(xì)說。

  • 首先是調(diào)用 filterAsyncRouter 方法,這個(gè)方法的核心作用就是將服務(wù)端返回的 component 組件動(dòng)態(tài)加載為一個(gè) component 對(duì)象。不過這個(gè)方法在調(diào)用的過程中,后面還有兩個(gè)參數(shù),第二個(gè)是 lastRouter 在該方法中并無實(shí)質(zhì)性作用;第三個(gè)參數(shù)則主要是說是否需要對(duì) children 的 path 進(jìn)行重寫。小伙伴們知道,服務(wù)端返回的動(dòng)態(tài)菜單的 path 屬性都是只有一層的,例如一級(jí)菜單系統(tǒng)管理的 path 是 system,二級(jí)菜單用戶管理的 path 則是 user,那么用戶管理最終訪問的 path 就是 system/path,如果第三個(gè)參數(shù)為 true,則會(huì)進(jìn)行 path 的重寫,將 path 最終設(shè)置正確。

  • 所以這里的 sidebarRoutes 和 defaultRoutes 只是能用于菜單渲染(因?yàn)檫@兩個(gè)里邊的菜單 path 不對(duì)),而最終的頁面跳轉(zhuǎn)要通過 rewriteRoutes 才可以實(shí)現(xiàn)。

  • 除了服務(wù)端返回的動(dòng)態(tài)菜單,前端本身也定義了一些基礎(chǔ)菜單,前端的基礎(chǔ)菜單分為兩大類,分別是 constantRoutes 和 dynamicRoutes,其中 constantRoutes 是固定菜單,也就是一些跟用戶權(quán)限無關(guān)的菜單,例如 404 頁面、首頁等;dynamicRoutes 是動(dòng)態(tài)菜單,也就是也根據(jù)用戶權(quán)限來決定是否展示的菜單,例如分配用戶、字典數(shù)據(jù)、調(diào)度日志等等。

  • filterDynamicRoutes 方法則是將前端提前定義好的 dynamicRoutes 菜單進(jìn)行過濾,找出那些符合當(dāng)前用戶權(quán)限的菜單將之添加到路由中(這些菜單都不需要在菜單欄渲染出來)。

  • 接下來涉及到四個(gè)不同的保存路由數(shù)據(jù)的變量,分別是 routes、addRoutes(經(jīng)松哥分析,這個(gè)變量并無實(shí)際作用,可以刪除之)、defaultRoutes、topbarRouters 以及 sidebarRouters,四個(gè)路由變量的作用各有不同:

routes:

routes 中保存的是 constantRoutes 以及服務(wù)端返回的動(dòng)態(tài)路由數(shù)據(jù),并且這個(gè)動(dòng)態(tài)路由數(shù)據(jù)中的 path 已經(jīng)完成了重寫,所以這個(gè) routes 主要用在兩個(gè)地方:

首頁的搜索上:首頁的搜索也可以按照路徑去搜索,所以需要用到這個(gè) routes,如下圖:

Vue3如何加載動(dòng)態(tài)菜單

用在 TagsView,這個(gè)地方也需要根據(jù)頁面渲染不同的菜單,也是用的 routes:

Vue3如何加載動(dòng)態(tài)菜單

sidebarRouters:

這個(gè)就是大家所熟知的側(cè)邊欄菜單了,具體展示是 constantRoutes+服務(wù)端返回的菜單,不過這些 constantRoutes 基本上 hidden 屬性都是 false,渲染的時(shí)候是不會(huì)被渲染出來的。

Vue3如何加載動(dòng)態(tài)菜單

topbarRouters:

這個(gè)是用在 TopNav 組件中,這個(gè)是將系統(tǒng)的一級(jí)菜單在頭部顯示出來的,如下圖:

Vue3如何加載動(dòng)態(tài)菜單

一級(jí)菜單在頂部顯示,左邊顯示的都是二級(jí)三級(jí)菜單,那么頂部菜單的渲染,用的就是這個(gè) topbarRouters。

defaultRoutes:

想要開啟頂部菜單,需要在 src/layout/components/Settings/index.vue 組件中設(shè)置,如下圖:

Vue3如何加載動(dòng)態(tài)菜單

開啟頂部菜單之后,點(diǎn)擊頂部菜單,左邊菜單欄會(huì)跟著切換,此時(shí)就是從 defaultRoutes 中遍歷出相關(guān)的菜單設(shè)置給 sidebarRouters。

好了,這就是這四個(gè) routes 變量的作用,老實(shí)說,腳手架中這塊的代碼設(shè)計(jì)有點(diǎn)混亂,沒必要搞這么多變量,等松哥抽空給大家優(yōu)化下。

generateRoutes 方法最終會(huì)返回 rewriteRoutes 變量到前面說的那個(gè)前置導(dǎo)航守衛(wèi)中,最終前置導(dǎo)航守衛(wèi)將數(shù)據(jù)添加到 router 中。

菜單的渲染都是在 src/layout/components/Sidebar/index.vue 中完成的,看了下都是常規(guī)操作,沒啥好說的。

以上就是“Vue3如何加載動(dòng)態(tài)菜單”這篇文章的所有內(nèi)容,感謝各位的閱讀!相信大家閱讀完這篇文章都有很大的收獲,小編每天都會(huì)為大家更新不同的知識(shí),如果還想學(xué)習(xí)更多的知識(shí),請(qǐng)關(guān)注億速云行業(yè)資訊頻道。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI