目錄
1. 整體思路
2. 實作細節
2.1 載入細節
2.3 generateRoutes
首頁 web前端 Vue.js Vue3怎麼載入動態選單

Vue3怎麼載入動態選單

May 10, 2023 pm 01:58 PM
vue3

1. 整體思路

首先我們來梳理下整體上的實現思路,首先一點:整體思路和 vhr 一模一樣。

考慮到有的小夥伴可能已經忘記 vhr 中前端動態選單的實作想法了,因此本文再和大家分析一下。

為了確保在所有的 .vue 檔案中都能存取到選單數據,所以選擇將選單資料存入vuex 中,vuex 是vue 中一個儲存資料的公共地方,所有的 .vue 檔案都可以從 vuex 讀取到資料。儲存在 vuex 中的資料基本上是存在記憶體中,所以它有一個特點,就是瀏覽器按 F5 刷新之後,資料就沒了。所以在發生頁面的跳轉的時候,我們應該去區分一下,是用戶點擊了頁面上的選單按鈕之後發生了頁面跳轉還是用戶點擊了瀏覽器刷新按鈕(或者按了 F5)發生了跳轉。

為了實現這一點,我們需要用到vue 中的路由導航守衛功能,對於我們Java 工程師而言,這些可能聽起來有點陌生,但是你把它當作Java 中的Filter 來看待就好理解了,實際上我們影片中和小夥伴們講解的時候就是這麼類比的,將一個新事物跟我們腦海中一個已有的熟悉的事物進行類比,就很容易理解了。

vue 中的導航守衛就類似一個監控,它可以監控到所有的頁面跳轉,在頁面跳轉中,我們可以去判斷一下vuex 中的選單資料是否還在,如果還在,就說明使用者是點擊了頁面上的選單按鈕完成了跳轉的,如果不在,就說明使用者是點擊了瀏覽器的刷新按鈕或是按了F5 進行頁面刷新的,此時我們就要趕緊去服務端重新載入一下選單資料。

---xxxxxxxxxxxxxxxxxx---

整體上的實作想法就是這樣,接下來我們來看看一些具體的實作細節。

2. 實作細節

2.1 載入細節

首先我們來看看載入的細節。

小夥伴們知道,單一頁面專案的入口是 main.js,路由載入的內容在src/permission.js 檔案中,該檔案在main.js 中被引入, src/permission.js 中的前置導航守衛內容如下:

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
        // 判断当前用户是否已拉取完user_info信息
        useUserStore().getInfo().then(() => {
          isRelogin.show = false
          usePermissionStore().generateRoutes().then(accessRoutes => {
            // 根据roles权限生成可访问的路由表
            accessRoutes.forEach(route => {
              if (!isHttp(route.path)) {
                router.addRoute(route) // 动态添加可访问路由表
              }
            })
            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) {
      // 在免登录白名单,直接进入
      next()
    } else {
      next(`/login?redirect=${to.fullPath}`) // 否则全部重定向到登录页
      NProgress.done()
    }
  }
})
登入後複製

我跟大家捋一下這個前置導航守衛中的想法:

  • ##首先呼叫getToken方法,這個方法其實是去Cookie 拿認證Token,也就是登入成功後端回傳給前端的那個JWT 字串。

  • 如果getToken 方法有回傳值,表示使用者已經登入了,那麼就進入到if 分支中,如果getToken 沒拿到值,表示使用者未登錄,未登入的話,又分為兩種情況:i:訪問的目標地址處於免登入白名單中,那麼此時直接訪問即可;ii:訪問的目標地址不在白名單中,那麼此時就跳轉到登錄頁面去,跳轉的時候同時攜帶一個redirect 參數,這樣方便登入成功後,再跳回造訪的目標頁面。這個免登入存取的白名單,是一個在src/permission.js 檔案中定義的變量,預設有四個路徑,分別是 

    ['/login', '/auth-redirect', '/bind' , '/register']

  • 如果getToken 拿到了值,表示使用者已經登入了,此時又分情況:如果使用者造訪的路徑是登入頁面,那麼就給他重定向到專案首頁(也就是在已經登入的情況下,不允許使用者再次造訪登入頁面);如果使用者造訪的路徑不是登入頁面,那麼首先判斷vuex 中的roles 是否還有值?如果有值,表示當前就是使用者點擊了一個選單按鈕進行跳轉的,那麼直接跳轉就行了;如果沒有值,表示使用者是按了瀏覽器的刷新按鈕或是F5 按鈕刷新進行的頁面跳轉,那麼此時首先調用getInfo 方法(位於src/store/modules/user.js 檔案中)去服務端重新載入目前使用者的基本資訊、角色資訊以及權限信息,然後再呼叫generateRoutes 方法(位於src/store/modules /permission.js 檔案中)去服務端載入路由訊息,並將載入到的路由資訊放入到router 物件中(前提是這個路由物件不是一個http 鏈接,就是普通的路由位址)。

這就是動態路由的載入整體思路。

在第三步驟中,牽涉到兩個方法,一個是 getInfo 還有一個 generateRoutes,這兩個方法也都比較關鍵,我們再來稍微看下去。

2.2 getInfo

首先這個載入使用者資訊的方法位於 

src/store/modules/user.js 檔案中,換言之,這些使用者的基本資料載入之後,是儲存在vuex 中的,如果刷新瀏覽器這些資料就會遺失:

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) { // 验证返回的roles是否是一个非空数组
        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)
    })
  })
},
登入後複製

方法的邏輯其實倒沒啥好說的,結合服務端回傳的JSON 格式,應該就很好理解了(部分JSON):

{
    "permissions":[
        "*:*:*"
    ],
    "roles":[
        "admin"
    ],
    "user":
        "userName":"admin",
        "nickName":"TienChin健身",
        "avatar":"",
    }
}
登入後複製

另外再强调下,之前在 vhr 中,我们是将请求封装成了一个 api.js 文件,里边有常用的 get、post、put 以及 delete 请求等,然后在需要使用的地方,直接去调用这些方法发送请求即可,但是在 TienChin 中,脚手架的封装是将所有的请求都提前统一封装好,在需要的时候直接调用封装好的方法,连请求地址都不用传递了(封装的时候就已经写死了),所以小伙伴们看上面的 getInfo 方法只有方法调用,没有传递路径参数等。

2.3 generateRoutes

generateRoutes 方法则位于 src/store/modules/permission.js 文件中,这里值得说道的地方就比较多了:

generateRoutes(roles) {
  return new Promise(resolve => {
    // 向后端请求路由数据
    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)
    })
  })
}
登入後複製

首先大家看到,服务端返回的动态菜单数据解析了三次,分别拿到了三个对象,这三个对象都是将来要用的,只不过使用的场景不同,下面结合页面的显示跟大家细说。

  • 首先是调用 filterAsyncRouter 方法,这个方法的核心作用就是将服务端返回的 component 组件动态加载为一个 component 对象。不过这个方法在调用的过程中,后面还有两个参数,第二个是 lastRouter 在该方法中并无实质性作用;第三个参数则主要是说是否需要对 children 的 path 进行重写。小伙伴们知道,服务端返回的动态菜单的 path 属性都是只有一层的,例如一级菜单系统管理的 path 是 system,二级菜单用户管理的 path 则是 user,那么用户管理最终访问的 path 就是 system/path,如果第三个参数为 true,则会进行 path 的重写,将 path 最终设置正确。

  • 所以这里的 sidebarRoutes 和 defaultRoutes 只是能用于菜单渲染(因为这两个里边的菜单 path 不对),而最终的页面跳转要通过 rewriteRoutes 才可以实现。

  • 除了服务端返回的动态菜单,前端本身也定义了一些基础菜单,前端的基础菜单分为两大类,分别是 constantRoutes 和 dynamicRoutes,其中 constantRoutes 是固定菜单,也就是一些跟用户权限无关的菜单,例如 404 页面、首页等;dynamicRoutes 是动态菜单,也就是也根据用户权限来决定是否展示的菜单,例如分配用户、字典数据、调度日志等等。

  • filterDynamicRoutes 方法则是将前端提前定义好的 dynamicRoutes 菜单进行过滤,找出那些符合当前用户权限的菜单将之添加到路由中(这些菜单都不需要在菜单栏渲染出来)。

  • 接下来涉及到四个不同的保存路由数据的变量,分别是 routes、addRoutes(经松哥分析,这个变量并无实际作用,可以删除之)、defaultRoutes、topbarRouters 以及 sidebarRouters,四个路由变量的作用各有不同:

routes:

routes 中保存的是 constantRoutes 以及服务端返回的动态路由数据,并且这个动态路由数据中的 path 已经完成了重写,所以这个 routes 主要用在两个地方:

首页的搜索上:首页的搜索也可以按照路径去搜索,所以需要用到这个 routes,如下图:

Vue3怎麼載入動態選單

用在 TagsView,这个地方也需要根据页面渲染不同的菜单,也是用的 routes:

Vue3怎麼載入動態選單

sidebarRouters:

这个就是大家所熟知的侧边栏菜单了,具体展示是 constantRoutes+服务端返回的菜单,不过这些 constantRoutes 基本上 hidden 属性都是 false,渲染的时候是不会被渲染出来的。

Vue3怎麼載入動態選單

topbarRouters:

这个是用在 TopNav 组件中,这个是将系统的一级菜单在头部显示出来的,如下图:

Vue3怎麼載入動態選單

一级菜单在顶部显示,左边显示的都是二级三级菜单,那么顶部菜单的渲染,用的就是这个 topbarRouters。

defaultRoutes:

想要开启顶部菜单,需要在 src/layout/components/Settings/index.vue 组件中设置,如下图:

Vue3怎麼載入動態選單

开启顶部菜单之后,点击顶部菜单,左边菜单栏会跟着切换,此时就是从 defaultRoutes 中遍历出相关的菜单设置给 sidebarRouters。

好了,這就是這四個 routes 變數的作用,老實說,腳手架中這塊的程式碼設計有點混亂,沒必要搞這麼多變量,等松哥抽空給大家優化下。

generateRoutes 方法最終會返回 rewriteRoutes 變數到前面說的那個前置導航守衛中,最終前置導航守衛將資料加入 router 中。

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

以上是Vue3怎麼載入動態選單的詳細內容。更多資訊請關注PHP中文網其他相關文章!

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門文章

<🎜>:泡泡膠模擬器無窮大 - 如何獲取和使用皇家鑰匙
3 週前 By 尊渡假赌尊渡假赌尊渡假赌
北端:融合系統,解釋
3 週前 By 尊渡假赌尊渡假赌尊渡假赌
Mandragora:巫婆樹的耳語 - 如何解鎖抓鉤
3 週前 By 尊渡假赌尊渡假赌尊渡假赌

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

熱門話題

Java教學
1664
14
CakePHP 教程
1423
52
Laravel 教程
1321
25
PHP教程
1269
29
C# 教程
1249
24
Vue3如何實作刷新頁面局部內容 Vue3如何實作刷新頁面局部內容 May 26, 2023 pm 05:31 PM

想要實現頁面的局部刷新,我們只需要實現局部元件(dom)的重新渲染。在Vue中,想要實現這效果最簡單的方式方法就是使用v-if指令。在Vue2中我們除了使用v-if指令讓局部dom的重新渲染,也可以新建一個空白元件,需要刷新局部頁面時跳轉至這個空白元件頁面,然後在空白元件內的beforeRouteEnter守衛中又跳轉回原來的頁面。如下圖所示,如何在Vue3.X中實現點擊刷新按鈕實現紅框範圍內的dom重新加載,並展示對應的加載狀態。由於Vue3.X中scriptsetup語法中組件內守衛只有o

vue3專案中怎麼使用tinymce vue3專案中怎麼使用tinymce May 19, 2023 pm 08:40 PM

tinymce是一個功能齊全的富文本編輯器插件,但在vue中引入tinymce並不像別的Vue富文本插件一樣那麼順利,tinymce本身並不適配Vue,還需要引入@tinymce/tinymce-vue,並且它是國外的富文本插件,沒有透過中文版本,需要在其官網下載翻譯包(可能需要翻牆)。 1.安裝相關依賴npminstalltinymce-Snpminstall@tinymce/tinymce-vue-S2、下載中文包3.引入皮膚和漢化包在項目public資料夾下新建tinymce資料夾,將下載的

vue3+vite:src使用require動態匯入圖片報錯怎麼解決 vue3+vite:src使用require動態匯入圖片報錯怎麼解決 May 21, 2023 pm 03:16 PM

vue3+vite:src使用require動態導入圖片報錯和解決方法vue3+vite動態的導入多張圖片vue3如果使用的是typescript開發,就會出現require引入圖片報錯,requireisnotdefined不能像使用vue2這樣imgUrl:require(' …/assets/test.png')導入,是因為typescript不支援require所以用import導入,下面介紹如何解決:使用awaitimport

Vue3怎麼解析markdown並實現程式碼高亮顯示 Vue3怎麼解析markdown並實現程式碼高亮顯示 May 20, 2023 pm 04:16 PM

Vue實作部落格前端,需要實作markdown的解析,如果有程式碼則需要實作程式碼的高亮。 Vue的markdown解析函式庫很多,如markdown-it、vue-markdown-loader、marked、vue-markdown等。這些庫都大同小異。這裡選用的是marked,程式碼高亮的函式庫選用的是highlight.js。具體實現步驟如下:一、安裝依賴庫在vue專案下開啟命令窗口,並輸入以下命令npminstallmarked-save//marked用於將markdown轉換成htmlnpmins

Vue3中怎麼實現選取頭像並裁剪 Vue3中怎麼實現選取頭像並裁剪 May 29, 2023 am 10:22 AM

最終效果安裝VueCropper組件yarnaddvue-cropper@next上面的安裝值針對Vue3的,如果時Vue2或想使用其他的方式引用,請訪問它的npm官方地址:官方教程。在元件中引用使用時也很簡單,只需要引入對應的元件和它的樣式文件,我這裡沒有在全域引用,只在我的元件檔案中引入import{userInfoByRequest}from'../js/api' import{VueCropper}from'vue-cropper&

Vue3復用元件怎麼使用 Vue3復用元件怎麼使用 May 20, 2023 pm 07:25 PM

前言無論是vue還是react,當我們遇到多處重複程式碼的時候,我們都會想著如何重複使用這些程式碼,而不是一個檔案裡充斥著一堆冗餘程式碼。實際上,vue和react都可以透過抽組件的方式來達到復用,但如果遇到一些很小的程式碼片段,你又不想抽到另外一個檔案的情況下,相比而言,react可以在相同文件裡面宣告對應的小元件,或透過renderfunction來實現,如:constDemo:FC=({msg})=>{returndemomsgis{msg}}constApp:FC=()=>{return(

怎麼使用vue3+ts+axios+pinia實現無感刷新 怎麼使用vue3+ts+axios+pinia實現無感刷新 May 25, 2023 pm 03:37 PM

vue3+ts+axios+pinia實作無感刷新1.先在專案中下載aiXos和pinianpmipinia--savenpminstallaxios--save2.封裝axios請求-----下載js-cookienpmiJS-cookie-s//引入aixosimporttype{AxiosRequestConfigig ,AxiosResponse}from"axios";importaxiosfrom'axios';import{ElMess

vue3項目打包發佈到伺服器後訪問頁面顯示空白怎麼解決 vue3項目打包發佈到伺服器後訪問頁面顯示空白怎麼解決 May 17, 2023 am 08:19 AM

vue3專案打包發佈到伺服器後存取頁面顯示空白1、處理vue.config.js檔案中的publicPath處理如下:const{defineConfig}=require('@vue/cli-service')module.exports=defineConfig({publicPath :process.env.NODE_ENV==='production'?'./':'/&

See all articles