Vue3中如何加载动态菜单?
松哥之前写了两篇文章和大家分享了TienChin项目中的菜单数据问题,还没看过的小伙伴请戳这里:
Vue里,多级菜单要如何设计才显得专业?
TienChin项目动态菜单接口分析
这两篇文章主要是和大家说明了后端如何根据当前登录用户,动态生成一个菜单JSON。
那么现在的问题就是,当前端收到后端返回来的菜单JSON之后,该如何将之渲染出来?这就是我们目前所面临的问题了。
TienChin项目基于RuoYi脚手架来完成,所以本文的分析你也可以看作是对RuoYi-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)/*hastoken*/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=>{//根据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{//没有tokenif(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.2getInfo首先这个加载用户信息的方法位于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){//验证返回的roles是否是一个非空数组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":"TienChin健身","avatar":"",}}另外再强调下,之前在vhr中,我们是将请求封装成了一个api.js文件,里边有常用的get、post、put以及delete请求等,然后在需要使用的地方,直接去调用这些方法发送请求即可,但是在TienChin中,脚手架的封装是将所有的请求都提前统一封装好,在需要的时候直接调用封装好的方法,连请求地址都不用传递了(封装的时候就已经写死了),所以小伙伴们看上面的getInfo方法只有方法调用,没有传递路径参数等。
2.3generateRoutesgenerateRoutes方法则位于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)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中完成的,看了下都是常规操作,没啥好说的。
3.小结好啦,这就是RuoYi-Vue3中的动态菜单渲染逻辑,不知道小伙伴们看明白没有?视频即将奉上,对视频感兴趣的小伙伴请戳这里:TienChin项目配套视频来啦。
我正在参与掘金技术社区创作者签约计划招募活动,点击链接报名投稿。
原文:https://juejin.cn/post/7122294942302470174虚拟机如何安装苹果系统
最后重启,在进入OSX菜单时,迅速按F8,就可以看到菜单了,错过了的话会返回上一级 BTW: 关于虚拟机安装教程中是有一些可能需要注意的地方请参照以下修正操作方式操作以确保安装成功 1。Mac分区设为逻辑分区,我发现按照教程里面设为主分区启动不了。2。要用虚拟光驱软件载入iso,不能用VM载入。3。根据CP...
怎么才能“重装”?
1> 右键单击屏幕,选择“属性”。点击“外观”标签,在“外观与按钮”的下拉菜单中选择“Windows经典样式”,然后在“设置”标签里,按个人喜好调整屏幕分辨率大写,一般为1024*768。如果你用的是普通CRT显示器,需要再点“监视器”标签,将“屏幕刷新频率”调整至“85赫兹”,点确定!屏幕看起来是不是舒服多了? 2> 右键...