# Menu组件的封装

# 递归组件

在Vue中,可以使用在某一组件中调用自身实现递归组件。

需要注意的是,递归组件需要导出一个name,若使用了setup语法糖,我们还需要再添加一个script标签用于导出name。

<script lang="ts">
// 所有script标签的lang需相同
export default {
  name: 'Menu', // 递归组件必须有name
}
</script>

# 递归的关键点

在拿到数据后需要遍历,通过if判断是否有子数据,若有需要调用自身组件进行递归

# 代码

<!-- Menu.vue -->
<template>
  <!-- 不能用div包裹否则会产生无用div -->
  <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="handleGoRoute"
      >
        <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="handleGoRoute"
      >
        <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>

<script setup lang="ts">
import type { RouteRecordRaw } from 'vue-router'
import { useRouter } from 'vue-router'
import { MenuItemRegistered } from 'element-plus'
const router = useRouter()
defineProps<{
  menuList: RouteRecordRaw[]
}>()

const handleGoRoute = (vc: MenuItemRegistered) => {
  router.push(vc.index)
}
</script>

<script lang="ts">
export default {
  name: 'Menu', // 递归组件必须有name
}
</script>

<style lang="sccss" scoped></style>

传入Menu组件的数据结构

import { RouteRecordRaw } from 'vue-router'

export const constantRoutes: RouteRecordRaw[] = [
  {
    path: '/login',
    name: 'Login',
    component: () => import('@/views/Login/index.vue'),
    meta: {
      title: '登录',
      hidden: true, // 路由标题是否隐藏
      icon: 'Promotion', // 菜单左侧图标
    },
  },
  {
    path: '/',
    name: 'Layout',
    component: () => import('@/layout/index.vue'),
    meta: {
      hidden: false,
      title: '',
      icon: '',
    },
    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: 'Acl',
        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/permission',
        component: () => import('@/views/Acl/Permission/index.vue'),
        name: 'Permission',
        meta: {
          title: '菜单管理',
          hidden: false,
          icon: 'Grid',
        },
      },
    ],
  },
  {
    path: '/product',
    component: () => import('@/layout/index.vue'),
    name: 'Product',
    meta: {
      title: '商品管理',
      icon: 'Goods',
      hidden: false,
    },
    redirect: '/product/trademark',
    children: [
      {
        path: '/product/trademark',
        component: () => import('@/views/Product/Trademark/index.vue'),
        name: 'Trademark',
        meta: {
          title: '品牌管理',
          icon: 'ShoppingCart',
          hidden: false,
        },
      },
      {
        path: '/product/attr',
        component: () => import('@/views/Product/Attr/index.vue'),
        name: 'Attr',
        meta: {
          title: '属性管理',
          icon: 'ChromeFilled',
          hidden: false,
        },
      },
      {
        path: '/product/spu',
        component: () => import('@/views/Product/Spu/index.vue'),
        name: 'Spu',
        meta: {
          title: 'Spu管理',
          icon: 'Management',
          hidden: false,
        },
      },
      {
        path: '/product/sku',
        component: () => import('@/views/Product/Sku/index.vue'),
        name: 'Sku',
        meta: {
          title: 'Sku管理',
          icon: 'Orange',
          hidden: false,
        },
      },
    ],
  },
  {
    path: '/404',
    name: '404',
    component: () => import('@/views/404/index.vue'),
    meta: {
      title: '404',
      hidden: true,
      icon: 'CloseBold',
    },
  },
  {
    // 任意路由
    path: '/:pathMatch(.*)*',
    redirect: '/404',
    name: 'Any',
    meta: {
      title: '任意路由',
      hidden: true,
      icon: 'Warning',
    },
  },
]