Vue3中基于路由的动态递归菜单组件实现

文章目录


前言

在现代Web应用中,动态菜单是提升用户体验的关键功能,尤其在后端管理系统或权限控制场景中。Vue 3结合Vue Router,可以轻松实现根据路由信息动态生成菜单,并通过递归组件封装成可复用的模块。本文将一步步指导你创建这样一个组件:组件外部传入路由信息,组件内部递归调用自身处理嵌套路由。整个过程结构清晰,代码可靠,适用于实际项目。


一、动态菜单的核心原理

动态菜单的核心是递归组件:菜单组件接收一个路由数组数据,使用v-for遍历每个路由项,路由path作为唯一标识,如果路由不包含子路由,直接生成菜单项;如果路由只包含一个子路由,将子路由作为菜单项;如果路由包括至少两个子路由,则先将路由meta信息中的标题作为菜单标题,再递归调用菜单组件,这样子菜单的生成就遵循前两种菜单生成规则来生成,直到生成全部菜单项。

二、实现步骤详解

1.定义路由配置

在项目中,路由信息通常在router/routes.js中定义。示例路由配置如下:

javascript 复制代码
export const constantRoutes = [
  {
    path: '/login',
    component: () => import('@/views/login/index.vue'),
    name: 'login',
    meta: {
      title: '登录',
      hidden: true,
    },
  },
  {
    path: '/',
    component: () => import('@/layout/index.vue'),
    name: 'layout',
    meta: {
      title: '',
      hidden: true,
    },
    redirect: '/home',
    children: [
      {
        path: '/home',
        component: () => import('@/views/home/index.vue'),
        name: 'home',
        meta: {
          title: '首页',
          hidden: false,
          icon: 'HomeFilled',
        },
      },
    ],
  },
  {
    path: '/screen',
    component: () => import('@/views/screen/index.vue'),
    name: 'screen',
    meta: {
      title: '数据大屏',
      hidden: false,
      icon: 'Platform',
    },
  },
  {
    path: '/acl',
    component: () => import('@/layout/index.vue'),
    name: 'acl',
    meta: {
      title: '权限管理',
      hidden: false,
      icon: 'Lock',
    },
    redirect: '/acl/user',
    children: [
      {
        path: '/acl/user',
        component: () => import('@/views/acl/user/index.vue'),
        name: 'user',
        meta: {
          title: '用户管理',
          hidden: false,
          icon: 'User',
        },
      },
      {
        path: '/acl/role',
        component: () => import('@/views/acl/role/index.vue'),
        name: 'role',
        meta: {
          title: '角色管理',
          hidden: false,
          icon: 'UserFilled',
        },
      },
      {
        path: '/acl/menu',
        component: () => import('@/views/acl/menu/index.vue'),
        name: 'menu',
        meta: {
          title: '菜单管理',
          hidden: false,
          icon: 'List',
        },
      },
    ],
  },
  {
    path: '/product',
    component: () => import('@/layout/index.vue'),
    name: 'product',
    meta: {
      title: '商品管理',
      hidden: false,
      icon: 'Goods',
    },
    redirect: '/product/brand',
    children: [
      {
        path: '/product/brand',
        component: () => import('@/views/product/brand/index.vue'),
        name: 'brand',
        meta: {
          title: '品牌管理',
          hidden: false,
          icon: 'ShoppingCartFull',
        },
      },
      {
        path: '/product/attribute',
        component: () => import('@/views/product/attribute/index.vue'),
        name: 'attribute',
        meta: {
          title: '属性管理',
          hidden: false,
          icon: 'SetUp',
        },
      },
      {
        path: '/product/spu',
        component: () => import('@/views/product/spu/index.vue'),
        name: 'spu',
        meta: {
          title: 'SPU管理',
          hidden: false,
          icon: 'Suitcase',
        },
      },
      {
        path: '/product/sku',
        component: () => import('@/views/product/sku/index.vue'),
        name: 'sku',
        meta: {
          title: 'SKU管理',
          hidden: false,
          icon: 'CreditCard',
        },
      },

    ],
  },
  {
    path: '/404',
    component: () => import('@/views/404/index.vue'),
    name: '404',
    meta: {
      title: '404',
      hidden: true,
    },
  },
  {
    path: '/:pathMatch(.*)*',
    redirect: '/404',
    name: 'notfound',
    meta: {
      title: 'notfound',
      hidden: true,
    },
  },
]

2.创建递归菜单组件

javascript 复制代码
<script setup>
  import { useRouter } from 'vue-router'
  const router = useRouter()
  defineProps(['menuList'])
  const goRoute = (menuItem) => {
    router.push(menuItem.index)
  }
</script>
<script>
  export default {
    name: 'Menu',
  }
</script>
<template>
  <!-- 根据路由动态递归生成菜单 -->
  <template
    v-for="item in menuList"
    :key="item.path"
  >
    <!-- 没有子菜单的菜单项 -->
    <template v-if="!item.children">
      <el-menu-item
        v-if="!item.meta.hidden"
        :index="item.path"
        @click="goRoute"
      >
        <el-icon>
          <component :is="item.meta.icon"></component>
        </el-icon>
        <template #title>

          <span>{{ item.meta.title }}</span>
        </template>
      </el-menu-item>
    </template>
    <!-- 只有一个子菜单的菜单项 -->
    <template v-if="item.children && item.children.length === 1">
      <el-menu-item
        v-if="!item.children[0].meta.hidden"
        :index="item.children[0].path"
        @click="goRoute"
      >
        <el-icon>
          <component :is="item.children[0].meta.icon"></component>
        </el-icon>
        <template #title>

          <span>{{ item.children[0].meta.title }}</span>
        </template>
      </el-menu-item>
    </template>
    <!-- 多个子菜单的菜单项 -->
    <el-sub-menu
      v-if="item.children && item.children.length > 1"
      :index="item.path"
    >
      <template #title>
        <el-icon>
          <component :is="item.meta.icon"></component>
        </el-icon>
        <span>{{ item.meta.title }}</span>
      </template>
      <!-- 递归生成子菜单 -->
      <Menu :menuList="item.children"></Menu>
    </el-sub-menu>
  </template>
</template>
<style scoped></style>

3.在父组件中使用动态菜单

javascript 复制代码
<script setup>
  import Menu from './menu/index.vue'
  import { constantRoutes } from '@/router/routes.js'
	...
</script>
<template>
...
	<Menu :menuList="constantRoutes"></Menu>
...
</template>

总结

通过封装递归组件,Vue 3能高效实现动态菜单:外部传入路由信息,内部递归处理嵌套结构。这不仅减少了代码冗余,还提高了可维护性。在实际项目中,你可以根据需求扩展功能,如添加图标、权限校验等。

相关推荐
女生寝室0387 分钟前
DiskSGenius Pro [6.0.1.1645][单文件汉化版] 下载
前端
前端老宋Running17 分钟前
别让你那 5MB 的 JS 文件把用户吓跑:React 代码分割(Code Splitting)实战指南
前端·javascript·react.js
雨落在了我的手上25 分钟前
知识扩展:进制的详细介绍
c语言·学习
im_AMBER33 分钟前
Leetcode 67 长度为 K 子数组中的最大和 | 可获得的最大点数
数据结构·笔记·学习·算法·leetcode
小飞侠在吗41 分钟前
vue ref
前端·javascript·vue.js
悟能不能悟42 分钟前
在 Vue Router 4 中,如何设置base参数
前端·javascript·vue.js
Slaughter信仰43 分钟前
图解大模型_生成式AI原理与实战学习笔记(第四章)
人工智能·笔记·学习
martian6651 小时前
详解高阶数学领域-信息论与深度学习:互信息在对比学习中的应用
人工智能·深度学习·学习
Lovely_Ruby1 小时前
前端er Go-Frame 的学习笔记:实现 to-do 功能(三),用 docker 封装成镜像,并且同时启动前后端数据库服务
前端·后端
Wiktok1 小时前
TailwindCSS学习路径方法总结
学习·css3·tailwindcss