动态路由菜单实现

158 阅读3分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第5天,点击查看活动详情

什么是动态路由

根据路由表的配置,自动生成对应的 menu 菜单。当路由表发生变化时,menu 菜单自动发生变化

动态路由的实现

要实现的动态路由效果如图

luyou.png

要实现上述效果,需要获取到所有的路由信息,并且得到合理格式的路由表,再去渲染。

想要获取路由表数据,我们可以使用getRoutes方法

router.getRoutes()

<script setup>
import { useRouter } from 'vue-router'
const router = useRouter()
console.log(router.getRoutes())
</script>

得到的数据如下图所示

getRoutes.png

[
    {
        "path":"/user/info/:id",
        "name":"userInfo",
        "meta":{
            "title":"userInfo"
        },
        "children":[

        ]
    },
    {
        "path":"/article/editor/:id",
        "meta":{
            "title":"articleEditor"
        },
        "children":[

        ]
    },
    {
        "path":"/user/manage",
        "meta":{
            "title":"userManage",
            "icon":"personnel-manage"
        },
        "children":[

        ]
    },
    {
        "path":"/user/role",
        "meta":{
            "title":"roleList",
            "icon":"role"
        },
        "children":[

        ]
    },
    {
        "path":"/user/permission",
        "meta":{
            "title":"permissionList",
            "icon":"permission"
        },
        "children":[

        ]
    },
    {
        "path":"/user/import",
        "name":"import",
        "meta":{
            "title":"excelImport"
        },
        "children":[

        ]
    },
    {
        "path":"/article/ranking",
        "meta":{
            "title":"articleRanking",
            "icon":"article-ranking"
        },
        "children":[

        ]
    },
    {
        "path":"/article/create",
        "meta":{
            "title":"articleCreate",
            "icon":"article-create"
        },
        "children":[

        ]
    },
    {
        "path":"/article/:id",
        "meta":{
            "title":"articleDetail"
        },
        "children":[

        ]
    },
    {
        "path":"/login",
        "meta":{

        },
        "children":[

        ]
    },
    {
        "path":"/profile",
        "name":"profile",
        "meta":{
            "title":"profile",
            "icon":"el-icon-user"
        },
        "children":[

        ]
    },
    {
        "path":"/404",
        "name":"404",
        "meta":{

        },
        "children":[

        ]
    },
    {
        "path":"/401",
        "name":"401",
        "meta":{

        },
        "children":[

        ]
    },
    {
        "path":"/",
        "redirect":"/profile",
        "meta":{

        },
        "children":[
            {
                "path":"/profile",
                "name":"profile",
                "meta":{
                    "title":"profile",
                    "icon":"el-icon-user"
                }
            },
            {
                "path":"/404",
                "name":"404"
            },
            {
                "path":"/401",
                "name":"401"
            }
        ]
    },
    {
        "path":"/user",
        "redirect":"/user/manage",
        "meta":{
            "title":"user",
            "icon":"personnel"
        },
        "children":[
            {
                "path":"/user/manage",
                "meta":{
                    "title":"userManage",
                    "icon":"personnel-manage"
                }
            },
            {
                "path":"/user/role",
                "meta":{
                    "title":"roleList",
                    "icon":"role"
                }
            },
            {
                "path":"/user/permission",
                "meta":{
                    "title":"permissionList",
                    "icon":"permission"
                }
            },
            {
                "path":"/user/info/:id",
                "name":"userInfo",
                "meta":{
                    "title":"userInfo"
                }
            },
            {
                "path":"/user/import",
                "name":"import",
                "meta":{
                    "title":"excelImport"
                }
            }
        ]
    },
    {
        "path":"/article",
        "redirect":"/article/ranking",
        "meta":{
            "title":"article",
            "icon":"article"
        },
        "children":[
            {
                "path":"/article/ranking",
                "meta":{
                    "title":"articleRanking",
                    "icon":"article-ranking"
                }
            },
            {
                "path":"/article/:id",
                "meta":{
                    "title":"articleDetail"
                }
            },
            {
                "path":"/article/create",
                "meta":{
                    "title":"articleCreate",
                    "icon":"article-create"
                }
            },
            {
                "path":"/article/editor/:id",
                "meta":{
                    "title":"articleEditor"
                }
            }
        ]
    }
]

要渲染出菜单,我们需要菜单标签和菜单图片,它对应的值保存在meta中,在我们想要的路由表中,没有meta的数据不应该存在,而且不应有重复的路由数据

我们理想中的路由表如下

[
  { title: '个人中心', path: '' },
  {
    title: '用户',
    children: [
      { title: '员工管理', path: '' },
      { title: '角色列表', path: '' },
      { title: '权限列表', path: '' }
    ]
  },
  {
    title: '文章',
    children: [
      {
        title: '文章排名',
        path: ''
      },
      {
        title: '创建文章',
        path: ''
      }
    ]
  }
]

首先我们先去除重复的路由数据,可以看到重复的都是子路由数据,子路由数据在他们的父路由数据的那一层也存在,我们要把他们去除掉。

对此可以先获取所有的子路由的数组,再遍历整个路由表,如果数组中含有该路由,就把这个路由去除

在根目录下创建utils/route.js,定义去除重复路由的方法filterRouters

// 去除重复路由
export const filterRouters = routes => {
  const childrenRoutes = getChildrenRoutes(routes)
  return routes.filter(route => {
    // 如果子路由数组中含有route,就不返回
    return !childrenRoutes.find(childrenRoute => {
      return childrenRoute.path === route.path
    })
  })
}
// 获取所有子路由
const getChildrenRoutes = routes => {
  const result = []
  routes.forEach(route => {
    if (route.children && route.children.length > 0) {
      result.push(...route.children)
    }
  })
  return result
}

去除后的数据如图

1.png

其次应该把不含有meta的数据也去除掉,有以下三种情况

  • 不存在 children 并且 不存在 meta

直接return

  • 存在 children 不存在 meta

迭代children

  • 存在meta存在children 存在meta无children

需要加到一级路由,还要迭代children

export function generateMenus(routes, basePath = '') {
  const result = []
  // 遍历路由表
  routes.forEach((item) => {
    // 不存在 children && 不存在 meta 直接 return        meta存在说明是菜单项   children说明有子项
    if (isNull(item.meta) && isNull(item.children)) return
    // 存在 children 不存在 meta,进入迭代
    if (isNull(item.meta) && !isNull(item.children)) {
      result.push(...generateMenus(item.children))
      console.log(generateMenus(item.children))
      return
    }
    // 有meta有children       有meta无children

    // 合并 path 作为跳转路径
    // console.log('我的: ' + item.path)
    const routePath = path.resolve(basePath, item.path)
    // console.log('合并: ' + routePath)
    // 路由分离之后,存在同名父路由的情况,需要单独处理
    let route = result.find((item) => item.path === routePath) // /user
    if (!route) {
      route = {
        ...item,
        path: routePath,
        children: []
      }

      // icon 与 title 必须全部存在
      if (route.meta.icon && route.meta.title) {
        // meta 存在生成 route 对象,放入 arr
        result.push(route)
      }
    }

    // 存在 children 进入迭代到children
    if (item.children) {
      route.children.push(...generateMenus(item.children, route.path))
    }
  })
  return result
}

最后得到我们理想中的路由表

2.png

接下来渲染即可

渲染