最近在小伙伴们的强烈要求下,我们研究了下 RuoYi-Vue,感觉真的还挺好玩的,可以算是一个非常成熟的脚手架了,我们可以基于此快速开发一个商用项目。
有小伙伴想让松哥帮忙捋一捋这个项目,怎么说呢,如果你看过 vhr 的视频的话,我觉得这个项目应该是很容易懂的,基本上技术点都是一致的。
不过最近也刚好有空,博客也不知道写啥了,那么我试试能不能手把手带领小伙伴们以 RuoYi-Vue 为脚手架,开发一个健身房的会员管理系统。如果小伙伴们对此感兴趣的话,可以多多点赞转发,这样这个系列就不会烂尾~另外这个系列我假设大家已经做过 vhr 项目了,所以一些非常基础的知识点我就不重复啰嗦了。
1. 现有动态菜单分析
1.1 两种方案
动态菜单就是用户登录之后看到的菜单,不用角色的用户登录成功之后,会看到不用的菜单项,这个动态菜单要怎么实现呢?整体来说,有两种不同的方案,松哥曾经做过的项目中,两种方案也都有用过,这里分别来和大家分享一下。
1.1.1 后端动态返回
后端动态返回,这是我在微人事中采用的方案。微人事中,权限管理相关的表一共有五张表,如下:
其中 hr 表就是用户表,用户登录成功之后,可以查询到用户的角色,再根据用户角色去查询出来用户可以操作的菜单(资源),然后把这些可以操作的资源,组织成一个 JSON 数据,返回给前端,前端再根据这个 JSON 渲染出相应的菜单。以微人事为例,我们返回的 JSON 数据格式如下:
[
{
"id":2,
"path":"/home",
"component":"Home",
"name":"员工资料",
"iconCls":"fa fa-user-circle-o",
"children":[
{
"id":null,
"path":"/emp/basic",
"component":"EmpBasic",
"name":"基本资料",
"iconCls":null,
"children":[
],
"meta":{
"keepAlive":false,
"requireAuth":true
}
}
],
"meta":{
"keepAlive":false,
"requireAuth":true
}
}
]
这样的 JSON 在前端中再进行二次处理之后,就可以使用了,前端的二次处理主要是把 component 属性的字符串值转为对象。这一块具体操作大家可以参考微人事项目(具体在:https://github.com/lenve/vhr/blob/master/vuehr/src/utils/utils.js),我就不再赘述了。
这种方式的一个好处是前端的判断逻辑少一些,后端也不算复杂,就是一个 SQL 操作,前端拿到后端的返回的菜单数据,稍微处理一下就可以直接使用了。另外这种方式还有一个优势就是可以动态配置资源-角色以及用户-角色之间的关系,进而调整用户可以操作的资源(菜单)。
1.1.2 前端动态渲染
另一种方式就是前端动态渲染,这种方式后端的工作要轻松一些,前端处理起来麻烦一些,松哥去年年末帮一个律所做的一个管理系统,因为权限上比较容易,我就采用了这种方案。
这种方式就是我直接在前端把所有页面都在路由表里边定义好,然后在 meta 属性中定义每一个页面需要哪些角色才能访问,例如下面这样:
[
{
"id":2,
"path":"/home",
"component":Home,
"name":"员工资料",
"iconCls":"fa fa-user-circle-o",
"children":[
{
"id":null,
"path":"/emp/basic",
"component":EmpBasic,
"name":"基本资料",
"iconCls":null,
"children":[
],
"meta":{
"keepAlive":false,
"requireAuth":true,
"roles":['admin','user']
}
}
],
"meta":{
"keepAlive":false,
"requireAuth":true
}
}
]
这样定义表示当前登录用户需要具备 admin 或者 user 角色,才可以访问 EmpBasic 组件,当然这里不是说我这样定义了就行,这个定义只是一个标记,在项目首页中,我会遍历这个数组做菜单动态渲染,然后根据当前登录用户的角色,再结合当前组件需要的角色,来决定是否把当前组件所对应的菜单项渲染出来。
这样的话,后端只需要在登录成功后返回当前用户的角色就可以了,剩下的事情则交给前端来做。不过这种方式有一个弊端就是菜单和角色的关系在前端代码中写死了,以后如果想要动态调整会有一些不方便,可能需要改代码。特别是大项目,权限比较复杂的时候,调整就更麻烦了,所以这种方式我一般建议在一些简单的项目中使用。
1.2 菜单分析
在 RuoYi-Vue 中,采用的是方案一,即和 vhr 的方案是一样的:服务端动态返回菜单信息,前端再去渲染就行了。
所以如果我们想要定制自己的项目菜单,那就非常容易了,只需要搞明白这个项目中的菜单表,然后直接修改菜单表就可以了。
系统的菜单表是 sys_menu,各个字段含义如下:
这个我就不多说了,各个字段的含义作者都写的很清楚了。对于一些新手小伙伴,我着重解释一个跟前端显示相关的字段:
- order_num:这个菜单项在前端页面展示的顺序,例如一级菜单系统管理中有用户管理和菜单管理,那么用户管理和菜单管理这两个子项之间就存在一个展示顺序的问题,这个字段就是用来解决这个问题的。
- path:这个是前端的路由地址,可以简单理解为前端页面的跳转地址,假设系统管理菜单项的 path 为 system,系统管理下有一个子菜单日志管理,日志管理的 path 为 log,日志管理下有一个子菜单是操作日志,操作日志的 path 为 operlog,那么最终,前端访问操作日志时候的页面路由地址为 /system/log/operlog。
- component:这是前端的组件地址,因为前端的 vue 文件是动态加载的,这个参数表示组件的名称。
这几个参数可能对于新手小伙伴不好理解,其他的参数大家看注释就明白啥意思了,我也就不啰嗦了。
看明白了表,那么就可以直接上手了,直接在表上改了。
不过作者非常贴心的提供了管理页面,所以你要是懒得分析表,也可以直接在 系统管理->菜单管理中修改菜单,这个网页上的操作就比较简单了,我就不演示了。
1.3 代码分析
我们再来看看服务端菜单相关的代码。
菜单主要是有一个层级的问题,但是菜单的层级不会特别深,太深了前端不仅不好使用,也不方便展示。在 vhr 中,我假设菜单是三个层级,然后用了一个左连接就将所有的菜单信息查出来了。
但是在这个项目中,菜单没有固定的层级,可以有 N 层,所以查询也跟 vhr 不太一样,我们一起来看下。
返回菜单数据的接口是 org.javaboy.web.controller.system.SysLoginController#getRouters,我们来看下:
@GetMapping("getRouters")
public AjaxResult getRouters() {
Long userId = SecurityUtils.getUserId();
List<SysMenu> menus = menuService.selectMenuTreeByUserId(userId);
return AjaxResult.success(menuService.buildMenus(menus));
}
具体的实现代码我就不说了,这里就和大家说一下他这里的查询逻辑,这里的核心操作实际上就两步:
- menuService.selectMenuTreeByUserId 方法查询出来当前所有的菜单项,这里的查询思路是根据当前用户的 id,找到用户对应的菜单,查询的时候只查询类行为 M 和 C 的菜单项,M 表示目录(即里边有子菜单),C 表示菜单,全部查询出来之后,再遍历,归类,将 C 作为某一个 M 的 children。在松哥的 vhr 里边,我是直接用了一对多的思路去查询的,查询出来后不用再二次处理,这里则是查询出来后递归处理的,这一块的实现思路不同,做过 vhr 项目的小伙伴注意区分(小伙伴们也可以按照 vhr 的思路来改改这里的逻辑)。
- 由于刚查询出来的菜单并不满足前端渲染的要求,所以在 menuService.buildMenus 方法中,再对刚刚查询出来的 List 集合进行二次处理,这里主要是把 component、path 等属性的值捋清楚。
大致就是这样。
2. 自定义菜单数据
那我们自己这个健身会员的菜单会有所不同,我想要自己重新定义一下,根据前面第一小节的分析,这里我来创建八个和健身会员管理系统相关的菜单,如下:
系统原本的功能被我都收到系统管理这个菜单。
这个 SQL 脚本是比较简单的,大家在文末可以下载。我简单截个图大家看下:
根据第一小节的分析直接修改表即可(也可以在菜单管理页面手动进行添加)。
3. 自定义页面
后端加了数据,前端当然也要加页面。component 字段其实已经暗示了前端的页面地址,所以,我们根据后端的 component 字段,来创建前端页面即可:
每一个 .vue 文件都还没写内容,就一句话,类似下面这样:
后期再补充。
好啦,这样,前端 vue 登录成功之后,就可以看到相应的页面了,页面也都可以点击。
好啦,这样,我们初步实现了根据自己的需求在这个项目上自定义自己的菜单。
4. 项目地址
最后,文末给出一个项目地址,大家可以去看看。每篇文章的代码我都会提交上去,一步步完善,大家可以据此看到一个项目的成长过程,现在 star 就是老粉啦。
https://github.com/lenve/tienchin