详解Vue3如何加载动态菜单

目录
  • 1. 整体思路
  • 2. 实现细节
    • 2.1 加载细节
    • 2.2 getInfo
    • 2.3 generateRoutes

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,如下图:

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

sidebarRouters:

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

topbarRouters:

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

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

defaultRoutes:

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

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

好了,这就是这四个 routes 变量的作用,老实说,脚手架中这块的代码设计有点混乱,没必要搞这么多变量,等松哥抽空给大家优化下。

generateRoutes 方法最终会返回 rewriteRoutes 变量到前面说的那个前置导航守卫中,最终前置导航守卫将数据添加到 router 中。

菜单的渲染都是在 src/layout/components/Sidebar/index.vue 中完成的,看了下都是常规操作,没啥好说的。

到此这篇关于详解Vue3如何加载动态菜单的文章就介绍到这了,更多相关Vue3加载动态菜单内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Vue 动态添加路由及生成菜单的方法示例

    写后台管理系统,估计有不少人遇过这样的需求:根据后台数据动态添加路由和菜单. 为什么这么做呢?因为不同的用户有不同的权限,能访问的页面是不一样的. 在网上找了好多资料,终于想到了解决办法. 动态生成路由 利用 vue-router 的 addRoutes 方法可以动态添加路由. 先看一下官方介绍: router.addRoutes router.addRoutes(routes: Array<RouteConfig>) 动态添加更多的路由规则.参数必须是一个符合 routes 选项要求的数组.

  • vue+quasar使用递归实现动态多级菜单

    本文实例为大家分享了vue+quasar使用递归实现动态多级菜单的具体代码,供大家参考,具体内容如下 效果图: 菜单初始化入口 menu.vue,初始化侧边栏菜单组建,<my-q-menu/>才是递归开始 <template>   <q-drawer     v-model="is_hide_"     show-if-above     bordered     content-class="bg-grey-2"     :width

  • vue+element使用动态加载路由方式实现三级菜单页面显示的操作

    需要用到中间件的方式,这样就可以实现了我们想要的方式 publish-center.vue <template> <router-view></router-view> </template> <script> export default { } </script> <el-menu :default-active="$route.path" class="el-menu-vertical-dem

  • vue-router+vuex addRoutes实现路由动态加载及菜单动态加载

    此案例主要实现了一个功能是,在vue实例首次运行时,在加载了login和404两个路由规则,登录成功后,根据登录用户角色权限获取该角色相应菜单权限,生成新的路由规则添加进去. 做过后台管理系统都一定做过这个功能,在对菜单权限进行粗粒度权限控制的时候,通过角色获取菜单后,异步生成菜单,所以一开始拿到需求的时候,我也以为这和平常的没什么不同,不过做起来就发现了很多问题, 1.vue-router的实例,在new vue实例的时候,就加载了,且必须加载,这个时候,登录路由一定要加载,可是这个时候没有登

  • vue动态菜单、动态路由加载以及刷新踩坑实战

    目录 需求: 思路: 教训: 分享正文: 总结 需求: 从接口动态获取子菜单数据 动态加载 要求只有展开才加载子菜单数据 支持刷新,页面显示正常 思路: 一开始比较乱,思路很多.想了很多 首先路由和菜单共用一个全局route, 数据的传递也是通过store的route, 然后要考虑的俩个点就是一个就是渲染菜单和加载路由,可以在导航首位里处理路由,处理刷新. 还有一个地方就是菜单组件里展开事件里面 重新生成菜单数据,路由.大体思路差不多,做完就忘了..... 刷新的问题需要用本地缓存处理,之前一直

  • vue实现三级联动动态菜单

    本文实例为大家分享了vue实现三级联动动态菜单的具体代码,供大家参考,具体内容如下 三级联动动态菜单展示:一级菜单选中,生成二级菜单数据,二级菜单选中,生成三级菜单数据(根据上一级菜单的id,作为请求下一级菜单数据接口的参数) 1.代码 <template>   <div>     <!-- inline:代表的是行内表单,代表一行可以放置多个表单元素 -->     <el-form :inline="true" class="de

  • 详解Vue3如何加载动态菜单

    目录 1. 整体思路 2. 实现细节 2.1 加载细节 2.2 getInfo 2.3 generateRoutes 1. 整体思路 首先我们来梳理下整体上的实现思路,首先一点:整体思路和 vhr 一模一样. 考虑到有的小伙伴可能已经忘记 vhr 中前端动态菜单的实现思路了,因此本文再和大家分析一下. 为了确保在所有的 .vue 文件中都能访问到到菜单数据,所以选择将菜单数据存入 vuex 中,vuex 是 vue 中一个存储数据的公共地方,所有的 .vue 文件都可以从 vuex 中读取到数据

  • 详解Qt如何加载libxl库

    使用工具 1.Qt 5.12.3集成开发环境 2.libxl-3.9.4.3(官方下载地址:https://www.libxl.com/download.html) 提示:以下是本篇文章正文内容,下面案例可供参考 一.如何导入libxl库 由于官方给出的教程是MinGW32导入动态库我这边也照着导入libxl的32位动态库,使用MinGW64开发环境同理,如果qt使用的是mvsc环境的朋友可以不用参考此教程 1.pro文件导入静态链接库 1.把lib32.dll文件路径放入到pro文件中: LI

  • 详解Android Webview加载网页时发送HTTP头信息

    详解Android Webview加载网页时发送HTTP头信息 当你点击一个超链接进行跳转时,WebView会自动将当前地址作为Referer(引荐)发给服务器,因此很多服务器端程序通过是否包含referer来控制盗链,所以有些时候,直接输入一个网络地址,可能有问题,那么怎么解决盗链控制问题呢,其实在webview加载时加入一个referer就可以了,如何添加呢? 从Android 2.2 (也就是API 8)开始,WebView新增加了一个接口方法,就是为了便于我们加载网页时又想发送其他的HT

  • 详解JS异步加载的三种方式

    一:同步加载 我们平时使用的最多的一种方式. <script src="http://yourdomain.com/script.js"></script> <script src="http://yourdomain.com/script.js"></script> 同步模式,又称阻塞模式,会阻止浏览器的后续处理,停止后续的解析,只有当当前加载完成,才能进行下一步操作.所以默认同步执行才是安全的.但这样如果js中有输

  • web前端从Oracle数据库加载动态菜单所用到的数据表

    当我们在使用一款软件或者使用某某管理系统的时候,都会使用到分级菜单来给我们清晰的展示出其功能模块.而这些分级菜单呢,是需要通过对数据库的调用才能展示给我们的.今天,我就主要介绍一下制作分级菜单我们所要用到的数据表. 1.菜单表 create table menu( menu_id number(10) primary key, menu_name varchar(30), menu_url varchar(200), parent_menu_id number(10) ); /*如果菜单是一级菜

  • 详解Spring ApplicationContext加载过程

    1.找准入口,使用ClassPathXmlApplicationContext的构造方法加载配置文件,用于加载classPath下的配置文件 //第一行,执行完成之后就完成了spring配置文件的加载,刷新spring上下文 ClassPathXmlApplicationContext context=new ClassPathXmlApplicationContext( "classpath:spring-mvc.xml"); //获取实例Bean Person person=con

  • 详解Android布局加载流程源码

    一.首先看布局层次 看这么几张图 我们会发现DecorView里面包裹的内容可能会随着不同的情况而变化,但是在Decor之前的层次关系都是固定的.即Activity包裹PhoneWindow,PhoneWindow包裹DecorView.接下来我们首先看一下三者分别是如何创建的. 二.Activity是如何创建的 首先看到入口类ActivityThread的performLaunchActivity方法: private Activity performLaunchActivity(Activi

  • Spring详解四种加载配置项的方法

    目录 1.spring加载yml文件 2.spring 加载 properties 文件 3.spring加载系统磁盘(properties)文件 4.spring加载xml文件 5.Java基于InputStream读取properties配置文件 本文默认 spring 版本是 spring5 1 spring 加载 yml 文件 2 spring 加载 properties 文件 3 spring 加载 系统磁盘 文件 4 spring 加载 xml 文件 5 Java 基于 InputS

  • 详解react-router4 异步加载路由两种方法

    方法一:我们要借助bundle-loader来实现按需加载. 首先,新建一个bundle.js文件: import React, { Component } from 'react' export default class Bundle extends React.Component { state = { // short for "module" but that's a keyword in js, so "mod" mod: null } componen

  • 详解webpack异步加载业务模块

    虽然把我们用到的JS文件全部打包一个可以节省请求数,但如果打包后的JS文件过大,那么也容易出现白屏现象,许多操作失灵.而且一些区域是点到才出现,那么相关的JS其实可以剥离出这个大JS文件外.这就涉及到异步加载了.异步加载是SPA的重要构建方式之一. 我们沿着上一篇的目录,这次学习webpack的require.ensure API.此文件也叫做ensure.html,涉及到avalon, jquery,还有两个业务代码ensure.js与ensure_a.js. 先看我们的页面: <!DOCTY

随机推荐