当前位置: 首页 > 科技观察

如何在Vue3中加载动态菜单?_0

时间:2023-03-21 23:17:32 科技观察

之前写了两篇文章分享给大家TienChin项目中的菜单数据问题。没看过的请点击这里:如何在Vue中设计多级菜单看起来很专业?TienChin项目动态菜单界面解析这两篇文章主要给大家讲解一下后台是如何根据当前登录的用户动态生成一个菜单JSON的。那么现在的问题是,前端收到后端返回的菜单JSON后,如何渲染呢?这是我们目前面临的问题。TienChin项目是基于若一脚手架完成的,所以本文的分析也可以看作是对若一-Vue3项目的分析。一、总体思路首先梳理一下总体实现思路。首先,整体思路和vhr是一模一样的。考虑到有的朋友可能忘记了在vhr中实现前端动态菜单的思路,所以本文再和大家一起分析一下。为了保证菜单数据在所有.vue文件中都可以访问,我选择将菜单数据存储在vuex中。Vuex是vue中存储数据的公共场所。所有.vue文件都可以从vuex中读取数据。vuex中存储的数据本质上是存储在内存中的,所以它有一个特点,浏览器按F5刷新后,数据就没有了。所以当发生页面跳转时,我们要区分页面跳转是用户点击了页面上的菜单按钮后发生的,还是用户点击了浏览器刷新按钮(或按了F5)发生了页面跳转。为了实现这一点,我们需要使用vue中的路由导航守卫功能。对于我们Java工程师来说,这些可能听起来有些陌生,但是你可以理解为Java中的Filter。其实,这就是我们在视频中给小伙伴们解释的时候的类比。通过将新事物与我们脑海中已有的熟悉事物进行比较,很容易理解。vue中的导航守卫类似于一个监视器。它可以监视所有页面跳转。在页面跳转的过程中,我们可以判断vuex中的菜单数据是否还在。如果还在,说明用户点击了页面上的菜单按钮,完成了跳转。如果不存在,说明用户点击了浏览器的刷新按钮或者按了F5刷新了页面。这时候我们就得去服务器端重新加载菜单数据了。整体的实现思路是这样的,我们来看一些具体的实现细节。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)/*有令牌*/if(to.path==='/login'){next({path:'/'})NProgress.done()}else{if(useUserStore().roles.length===0){isRelogin.show=true//判断当前用户是否拉取了user_info信息useUserStore().getInfo().then(()=>{isRelogin.show=falseusePermissionStore().generateRoutes().then(accessRoutes=>{//根据角色权限生成可访问路由表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{//没有标记if(whiteList.indexOf(to.path)!==-1){//在免登录白名单中,直接进入next()}else{next(`/login?redirect=${to.fullPath}`)//否则,重启全部指向登录页面NProgress.done()}}})先说一下这个前置导航守卫中的思路:(1)首先调用getToken方法,其实就是从cookie,也就是登录成功后,后端返回JWT字符串给前端(2)如果getToken方法有返回值,说明用户已经登录,然后进入if分支,如果getToken没有notgetavalue,表示用户没有登录,如果没有登录,可以分为两种情况:i:访问的目标地址在免登录白名单中,那么可以直接访问此时;ii:访问的目标地址不在白名单中,此时跳转到登录页面,重定向的时候,同时携带一个redirect参数,方便跳回访问的目标页面登录成功后。这个免登录访问的白名单是在src/permission.js文件中定义的一个变量。默认有四个路径,分别是['/login','/auth-redirect','/bind','/register']。(3)如果getToken获取到值,说明用户已经登录了,这时候又分为不同的情况:如果用户访问的路径是登录页面,那么会被重定向到项目首页(即如果他已经登录,则不允许用户再次访问登录页面);如果用户访问的路径不是登录页面,首先判断vuex中的角色是否还有价值?如果有值,则表示用户点击了某个菜单按钮进行跳转,则直接跳转即可;如果没有值,说明用户按下了浏览器的刷新按钮或者F5按钮刷新了页面跳转,然后先调用getInfo方法(位于src/store/modules/user.js文件中)到server重新加载当前用户的基本信息、角色信息和权限信息,然后调用generateRoutes方法(位于/permission.js文件中)在server上加载路由信息,并将加载的路由信息??放入router对象中(前提是路由对象不是http链接,而是普通的路由地址)。这就是动态路由加载的整体思路。第三步涉及到两个方法,一个是getInfo,一个是generateRoutes。这两个方法也很关键,我们再多看一点。2.getInfo首先加载用户信息的方法位于src/store/modules/user.js文件中。也就是说,这些用户的基本信息加载完成后,存储在vuex中。如果刷新浏览器,数据将丢失:getInfo(){returnnewPromise((resolve,reject)=>{getInfo().then(res=>{constuser=res.userconstavatar=(user.avatar==""||user.avatar==null)?defAva:import.meta.env.VITE_APP_BASE_API+user.avatar;if(res.roles&&res.roles.length>0){//验证返回的角色是一个非空数组this.roles=res.rolesthis.permissions=res.permissions}else{this.roles=['ROLE_DEFAULT']}this.name=user.userNamethis.avatar=avatar;resolve(res)}).catch(error=>{reject(error)})})},方法逻辑其实没啥好说的,结合服务端返回的JSON格式,应该很容易理解(部分JSON):{"permissions":["*:*:*"],"roles":["admin"],"user":"userName":"admin","nickName":"TienChinFitness","avatar":"",}}另外需要强调的是,之前在vhr中,我们将请求封装成一个api.js文件,里面包含了常用的get、post、p你t和deleterequests等,然后直接调用这些方法在需要用到的地方发送请求,但是在TienChin中,脚手架的封装是把所有的请求都提前封装起来,需要的时候直接调用封装的方法来做连请求地址都不用传(封装的时候写死),所以小伙伴看到上面的getInfo方法只有方法调用,没有传路径参数。3.generateRoutesgenerateRoutes方法位于src/store/modules/permission.js文件中。这里还有更多值得说的地方:generateRoutes(roles){returnnewPromise(resolve=>{//向后端请求路由数据getRouters().then(res=>{constsdata=JSON.parse(JSON.stringify(res.data))constrdata=JSON.parse(JSON.stringify(res.data))constdefaultData=JSON.parse(JSON.stringify(res.data))constsidebarRoutes=filterAsyncRouter(sdata)constrewriteRoutes=filterAsyncRouter(rdata,false,true)constdefaultRoutes=filterAsyncRouter(defaultData)constasyncRoutes=filterDynamicRoutes(dynamicRoutes.(route)})this.setRoutes(rewriteRoutes)this.setSidebarRouters(constantRoutes.concat(sidebarRoutes))this.setDefaultRoutes(sidebarRoutes)这个。setTopbarRoutes(defaultRoutes)resolve(rewriteRoutes)})})}首先可以看到,服务端对返回的动态菜单数据进行了3次解析,分别得到了3个对象。这三个对象都是以后要用到的,只是使用的场景不同而已。以下结合页面详细给大家介绍一下(1)首先,调用filterAsyncRouter方法。该方法的核心功能是将服务器返回的component组件动态加载到一个组件对象中。但是在调用这个方法的过程中,背后有两个参数。二是lastRouter在这个方法中没有实质性的作用;第三个参数主要表示是否改写children的路径。朋友知道服务器返回的动态菜单的path属性只有一层。比如一级菜单系统管理的路径是system,二级菜单用户管理的路径是user,所以用户管理最终访问的路径是system/path。如果第三个参数为true,路径将被重写,路径最终设置正确。(2)所以这里的sidebarRoutes和defaultRoutes只能用于菜单渲染(因为这两个里面的菜单路径是错误的),最终的页面跳转只能通过rewriteRoutes来实现。(3)除了服务端返回的动态菜单外,前端本身也定义了一些基础菜单。前端的基础菜单分为两类,分别是constantRoutes和dynamicRoutes,其中constantRoutes是固定菜单,也就是一些与用户权限无关的菜单,比如404页面,首页等.;dynamicRoutes是一个动态菜单,也就是同样根据用户权限来确定的菜单,比如分配用户、字典数据、调度日志等等。(44)filterDynamicRoutes方法是过滤前端预先定义的dynamicRoutes菜单,找出那些满足当前用户权限的菜单,添加到路由中(这些菜单不需要在菜单栏中渲染).(5)接下来涉及到保存路由数据的四个不同的变量,分别是routes、addRoutes(根据宋哥的分析,这个变量没有实际作用,可以删除)、defaultRoutes、topbarRouters和sidebarRouters,四个路由变量的函数是difference:routes:routes保存了constantRoutes和服务器返回的动态路由数据,而这个动态路由数据中的路径被改写了,所以这个routes主要用在两个地方:(1)首页搜索:搜索在首页也可以根据路径搜索,所以需要用到这个路由,如下图:(2)在TagsView中使用,这个地方也需要根据页面渲染不同的菜单,同时使用路由:sidebarRouters:这是众所周知的侧边栏菜单。具体展示是constantRoutes+server返回的菜单。但是这些constantRoutes基本都有隐藏属性false,渲染的时候不会渲染。topbarRouters:这是在TopNav组件中使用的。这是将系统的一级菜单显示在头部,如下图:一级菜单显示在最上方,左侧显示二级和三级菜单。然后顶部菜单Rendering就用到了这个topbarRouters。defaultRoutes:打开顶部菜单,需要在src/layout/components/Settings/index.vue组件中设置,如下图:打开顶部菜单后,点击顶部菜单,左侧菜单酒吧将相应地切换。这时候就是从defaultRoutes中遍历出来相关的菜单设置到sidebarRouters中。嗯,这就是这四个路由变量的作用。老实说,脚手架中这个block的代码设计有点混乱。没必要搞那么多变数。松哥有空我再给大家优化一下。generateRoutes方法最终会将rewriteRoutes变量返回给前面提到的前导航守卫,最后由前导航守卫将数据添加到router中。菜单的渲染是在src/layout/components/Sidebar/index.vue中完成的,看完也是常规操作,没什么好说的。