关于一次RuoYi框架菜单的二次优化

周一同组的同事分配了一个小任务给正在摸鱼的我,我闲着没啥事也就接受了

事情起源于去年的菜单需求变更,在RuoYi原本的菜单基础上增加了门户菜单和顶部菜单

又因为当时开发时间短,写出的代码有些许凌乱,且没有输出文档

所以导致每次新增顶部菜单或这门户菜单时需要重新阅读代码,然后才添加这次优化主要是优化代码结构吧

菜单需求变更后的菜单

原代码

我们先看一下具体的逻辑的处理代码

顶部菜单

<!--用于输出顶部菜单-->
<div v-if="(i.meta &&(i.meta.title == '顶部菜单1') || (i.meta.title == '顶部菜单2')) ">
    <span 
     :class="isActive(i)?'active':''" 
     @click="handlelkk(i)">
      {{i.meta && i.meta.title}}
  </span>
 </div>

在上方代码中可以看到有何handlelkk方法,找一下具体实现代码

//看上去是根据点击顶部菜单返回的route中的path判断是哪个顶部菜单然后跳转至第一个路由地址
handlelkk (val) {
  // 成果登记
   if ((val.path == 'ResultsUploading/5') || (val.path == '/resultManagement/ResultsUploading/5')) {
        this.$router.push({ path: '/resultManagement/ResultsUploading/5' })
    }
  // 成果获奖
   if ((val.path == 'award/11') || (val.path == '/resultManagement/award/11')) {
        this.$router.push({ path: '/resultManagement/award/11' })
    }
}

门户菜单

if (isTop) {
  //这个应该跟上面那个一样 点击门户菜单跳转到指定路由
     if (routes[0].path == "/researchproject/satisticsQuery") {
         let that = this
         that.$router.push({ path: "/researchproject/satisticsQuery/projectDetails" })
      }
      else if (routes[0].path == "/assessmentReward/resultIncentive") {
         let that = this
         that.$router.push({ path: "/assessmentReward/resultIncentive/incentiveReward" })
      }
}

左侧路由

let arr = []
//这里看上去是判断当前的路由push当前路由所属菜单下面的所有路由
if (this.$route.path == '/researchproject/xiangmutaizhang' || this.$route.path == '/researchproject/satisticsQuery') {
      arr = []
      arr.push(routes[0])
 } else if (
      this.$route.path == '/researchproject/LongitudinalProject/xiangmushenbao/longreport' ||
      this.$route.path == '/researchproject/LongitudinalProject/xiangmushenbao/checkReportlong'
      || this.$route.path == '/researchproject/LongitudinalProject/xiangmuzaiyan/researchProject'
      || this.$route.path == '/researchproject/LongitudinalProject/xiangmuzaiyan/longresearch'
      || this.$route.path == '/researchproject/LongitudinalProject/xiangmuzaiyan/longresearch1'
      || this.$route.path == '/researchproject/LongitudinalProject/xiangmujieti/conclusionManage'
      || this.$route.path == '/researchproject/LongitudinalProject/xiangmujieti/conclusionCheckLong'
 ) {
       arr = []
       arr.push(routes[1])
 } else if (this.$route.path == '/researchproject/schoolProject/xiaoneishenbao/report' ||
      this.$route.path == '/researchproject/schoolProject/xiaoneishenbao/checkReport'
      || this.$route.path == '/researchproject/schoolProject/xiaoneizaiyan/researchProject'
      || this.$route.path == '/researchproject/schoolProject/xiaoneizaiyan/longresearch1'
      || this.$route.path == '/researchproject/schoolProject/xiaoneizaiyan/research'
      || this.$route.path == '/researchproject/schoolProject/xiaoneijieti/conclusionManage'
      || this.$route.path == '/researchproject/schoolProject/xiaoneijieti/conclusionCheck') {
        arr = []
        arr.push(routes[2])
 }else {
   //如果没有匹配就直接输出所有路由
    arr = arr.concat(routes)
 }

优化思考

看到这我也看明白了,其实这没有大改RuoYi本身的路由,只是对路由进行了重新处理

我一开始是想直接读取RuoYi的路由,然后进行之前处理逻辑,直到我看到了这段代码,才意识到或许几年前的菜单管理并不规范

关键词匹配路由

由上图代码可以知道,他是根据路由关键词去匹配的,我直接去到菜单管理去看,好家伙,好几个不同的顶部菜单的子路由全在一个顶部菜单下面.......

好吧,也就是,我现在有两个选择:

  1. 更改菜单管理,使其规范,然后直接读取系统菜单数据
  2. 不更改菜单管理,优化代码结构或者处理逻辑,使其便于更改

第一种方案其实是最合适的,但是这不是我的个人项目,要承担风险的,于是我选择了第二种方案。

优化代码

原代码我只截取了一部分进行分析,实际上非常非常非常非常非常多,导致看的眼花缭乱且没有注释,当时写那段代码的人都得花时间去看是什么意思

我个人的思路就是,将菜单做成可配置项,而不是直接写在业务代码里面,这样既明确了菜单结构,也易于菜单添加修改等操作

配置项的文件结构

顶部菜单左侧菜单

左侧菜单和顶部菜单是强关联,点击顶部菜单就需求push顶部菜单的子菜单进去(也就是左侧菜单的地址)

先将顶部菜单的数据给抽离出来

// 顶部菜单名称
//topMenu.js
export const topNavName = ['顶部菜单', '顶部菜单1']

页面则判断当前的菜单名称是否存在顶部菜单数组即可

<div  v-if="(i.meta &&(topNavName.includes(i.meta.title)) ) ">
   <span
       :class="isActive(i)?'active':''"
       @click="handlelkk(i)"
    >{{i.meta && i.meta.title}}</span>
</div>
    import {mhName , topNavName , mhPathArrary , topPathArrary} from "./config/index"
    
    export default {
        computed: {
      mhName() {
        return mhName
      },
      topNavName(){
        return topNavName
      },
      mhPathArrary() {
        return mhPathArrary
      },
      topPathArrary() {
        return topPathArrary
      },
      }
    }

点击事件就更简单了,将原代码的判断条件及处理跳转的地址 分配一个字段,然后再跟原来一样处理即可

//顶部菜单路由配置
/**
 * Name 顶部菜单路由配置
 * param  Arrary[Object]
 * @title {String} 名称 
 * @pathMap {Arrary} 点击顶部菜单可能产生的path (关联方法: handlelkk)
 * @redirectPath {String} 点击门户菜单跳转的路由 (关联方法: handlelkk)
 * @childRoute {Object} 点击左侧菜单相关参数 (关联方法: activeRoutes)
 * @name {Arrary} 点击左侧路由时应该需要匹配该路由所属顶部菜单下面的所有路由  
 * @index {Number} 点击的左侧路由在父级路由的排序 当isKey为true时 该参数无效
 * @isKey {Boolean} 是否根据keyString匹配子级路由 开启该选项 index参数将失效 不建议根据keyString匹配该顶部菜单的子路由 该参数只为解决前期路由设置不规范 或由于需求问题多个顶部菜单的子路由处于同一个顶部菜单下面的问题
 * @keyString {String} 匹配顶部菜单子路由的关键词 isKey需为true
 */
export const topPathArrary = [
    {
        title: '我的团队',
        pathMap: ['/teamManagement/my/myTeam','my/myTeam'],
        redirectPath: '/teamManagement/my/myTeam',
        childRoute: {
            name: ['/teamManagement/my/myTeam'],
            index: 0,
            isKey: false,
            keyString: ''
        }
    },
    {
        title: '团队管理',
        pathMap: ['/teamManagement/team/teamPlan','team/teamPlan'],
        redirectPath: '/teamManagement/team/teamPlan',
        childRoute: {
            name: ['/teamManagement/team/teamPlan','/teamManagement/team/teamIndex','/teamManagement/team/teamCheck','/teamManagement/team/teamApply'],
            index: 1,
            isKey: false,
            keyString: ''
        }
    },
    {
        title: '团队台账',
        pathMap: ['/teamManagement/ledger/teamLedger','ledger/teamLedger'],
        redirectPath: '/teamManagement/ledger/teamLedger',
        childRoute: {
            name: ['/teamManagement/ledger/teamLedger'],
            index: 2,
            isKey: false,
            keyString: ''
        }
    },
]

那么顶部菜单的方法可以优化为:

handlelkk (val) {
          //    判断点击的路由是否存在配置文件中
      let pathIndex = topPathArrary.findIndex(item => item.pathMap.includes(val.path))
      if (pathIndex !== -1) {
        //如果存在则跳转至指定路由
        let targetPath = topPathArrary[pathIndex].redirectPath
        this.$router.push({ path: targetPath });
      }
    },

activeRoutes方法做了两件事,第一件事激活路由(顶部、门户、左侧激活路由都是这个方法),第二件事是组合左侧菜单路由

所以左侧路由的重组方法可以优化为

let arr = []
let leftPathObject = topPathArrary.find(item => item.childRoute.name.includes(this.$route.path));
if(leftPathObject){
  let  leftPathIndex = topPathArrary.findIndex(item => item === leftPathObject);
  let index = topPathArrary[leftPathIndex].childRoute.index
  let isKey = topPathArrary[leftPathIndex].childRoute.isKey
  let keyString = topPathArrary[leftPathIndex].childRoute.keyString
  if(isKey){
    let indexs = undefined
    routes.map((item, index) => {
        let flag = item.children.findIndex(items => items.path == keyString)
         if (flag != -1) {
              indexs = index
          }
        })
     arr = []
     arr = routes[indexs].children.map(i => {
          return { ...i, path: routes[indexs].path + '/' + i.path }
      })
  }else{
      arr = []
      arr = routes[index].children.map(i => {
         return { ...i, path: routes[index].path + '/' + i.path }
      })
  }   
}else {
  arr = arr.concat(routes) 
}

优化门户菜单

门户菜单其实和顶部菜单是一样的,只不过不用重新处理其他菜单的路由了

如下,将原门户菜单的代码输出成配置文件即可

//门户菜单路由
//mhMenu.js
/**
 *  Name 门户菜单路由
 *  param  Arrary[Object]
 * @title {String} 名称
 * @parentPath {String} 父级路由地址 暂无实际意义
 * @path {String} 点击门户菜单时路由的路径
 * @redirectPath {String} 点击门户菜单应跳转页面的地址 
 */
export const mhPathArrary = [
    {
        title: '门户菜单1',
        parentPath: '/menhu1',
        path: '/menhu1/my',
        redirectPath: '/menhu1/my/mymenhu'
    },
    {
        title: '门户菜单2',
        parentPath: '/menhu2',
        path: '/menhu2/my',
        redirectPath: '/menhu2/my/mymenhu'
    },
]

门户的点击事件也是他的激活事件

 //查找门户菜单路由是否存在用户点击菜单的path
 let index = mhPathArrary.findIndex(item => item.path === routes[0].path)
 //如果有则跳转至门户菜单路由中的redirectPath,并激活路由
 if(index !== -1){
   this.$store.commit("SET_SIDEBAR_ROUTERS", routes);
   let that = this;
   let targetPath = mhPathArrary[index].redirectPath
   that.$router.push({ path: targetPath });
 }else{
   //如果没有则跳转至routes的第一条路由
   let that = this;
   that.$router.push({ path: routes[0].path });
 }

总结

其实也没做很多优化,只是将菜单的处理方式由业务代码转换成了配置文件的方式,以后就不用在业务代码里面更改代码来实现菜单操作了,只需要修改配置文件即可,不然一个菜单存在一两千行代码也挺恐怖的吧,眼睛都能找花了去......

这样对菜单管理的规范也就没有那么严格的要求了,至少不会因为菜单设置错误就导致前端菜单结构异常的问题。

最后修改:2023 年 05 月 31 日
千圣皆过影,良知乃吾师