上一篇讲到用判断去实现菜单渲染,但这样比较生硬,且不利于维护。因此本篇使用了递归思想去解决。
留意第三个一级菜单的路由配置的showMenu为false,因为只展示的是一级菜单。
第一步:首先新建一个RecursiveMenu.vue的文件,用于递归菜单数据。
<template>
<div>
<template v-for="item in menus.menus"> // 这里使用的是menus.menus
<!-- 判断是否有子菜单且是否显示 -->
<a-sub-menu v-if="item.children && item.children.length > 0 && item.meta.showMenu" :key="item.name">
<template #title>
<span>{{ item.meta.title }}</span>
</template>
<!-- 递归调用 RecursiveMenu 组件来渲染子菜单 -->
<recursive-menu :menus="item.children" @navigate="handleNavigation" />
</a-sub-menu>
<!-- 没有子菜单,直接显示菜单项 -->
<a-menu-item v-else-if="!item.meta.showMenu" :key="item.name" @click="handleNavigation(item)" :title="item.meta.title">
{{ item.meta.title }}
</a-menu-item>
</template>
</div>
</template>
<script setup lang='ts'>
let menus = defineProps(['menus'])
const emit = defineEmits(["navigate"])
const handleNavigation = (item:any) => {
emit('navigate', item);
}
</script>
<style lang='less' scoped>
</style>
其次在layout文件中使用:
<template>
<a-layout class="components-layout-demo-custom-trigger">
<a-layout-sider :trigger="null" :collapsible="true" class="layoutSide">
<a-menu mode="inline"
>
<recursive-menu :menus="menus" @navigate="toPage" /> <=======
递归文件
===================>
</a-menu>
</a-layout-sider>
<a-layout class="layoutContent">
<a-layout-header>
</a-layout-header>
<a-layout-content
:style="{
margin: '10px',
padding: '20px',
background: '#fff',
minHeight: 'calc(100% - 160px)',
overFlow: 'hidden',
overflowY: 'auto',
borderRadius: '10px'
}"
>
<!-- 内容展示区 -->
<router-view></router-view>
</a-layout-content>
</a-layout>
</a-layout>
</template>
<script setup lang="ts">
import { reactive } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import route from '@/router/index.ts'; // 引入第三步的路由
import recursiveMenu from './RecursiveMenu.vue' // 引入第一步的递归文件
import menusList from '@/router/index.ts'
let menus = reactive([])
Object.assign(menus, menusList.options.routes)
console.log('menus', menus, menusList);
let toPage = (val:object) => {
router.push({
path: val.path
})
}
</script>
第三步:路由配置
import { createRouter, createWebHistory } from 'vue-router'
import Layout from '@/components/layout/index.vue'
import webApplication from '@/views/assetDataMaintenance/itEquipment/safetyEquipment/webApplication/index.vue'
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: '/',
name: '1',
component: Layout,
redirect: '/itEquipment/safetyEquipment/webApplication',
meta: {
title: '一级菜单',
showMenu: true // 菜单项被标记为是否可显示
},
children: [
{
path: '/itEquipment',
name: 'itEquipment',
// component: webApplication,
meta: {
title: '二级菜单',
showMenu: true
},
children: [{
path: '/itEquipment/safetyEquipment',
name: 'safetyEquipment',
// component: webApplication,
meta: {
title: '三级菜单',
showMenu: true
},
children: [{
path: '/itEquipment/safetyEquipment/webApplication',
name: 'webApplication',
component: webApplication,
meta: {
title: '四级菜单1',
showMenu: false
}
},
{
path: '/itEquipment/safetyEquipment/logCollectionAnalysisSystem',
name: 'logCollectionAnalysisSystem',
component: () => import('@/views/assetDataMaintenance/itEquipment/safetyEquipment/logCollectionAnalysisSystem/index.vue'),
meta: {
title: '四级菜单2',
showMenu: false
}
}]
}]
}
]
},
{
path: '/assetDataManagement',
name: 'assetDataManagement',
component: Layout,
meta: {
title: '一级菜单',
showMenu: true
},
children: [
{
path: '/assetDataManagement/changeManagement',
name: 'changeManagement',
component: () => import('@/views/assetDataManagement/changeManagement/index.vue'),
meta: {
title: '二级菜单',
showMenu: false
}
},
]
},
{
path: '/ldap',
name: 'ldap',
component: Layout,
meta: {
title: '一级菜单',
showMenu: false // 这里设置为false,是因为这个只显示一级菜单
},
children: [{
path: '/ldap',
name: 'ldap',
component: () => import('@/views/ldap/index.vue'),
meta: {
title: '二级菜单',
showMenu: false
},
}
]
},
{
path: '/system',
name: 'system',
component: Layout,
meta: {
title: '一级菜单',
showMenu: true
},
children: [{
path: '/paramsModel',
name: 'paramsModel',
component: () => import('@/views/system/paramsModel/index.vue'),
meta: {
title: '二级菜单',
showMenu: false
},
}
]
},
{
path: '/login',
name: 'login',
component: () => import('@/views/login/index.vue'),
meta: {
title: '登录',
showMenu: true
}
},
{
path: '/404',
name: 'errorPage',
component: () => import('@/views/errorPage/index.vue'),
meta: {
title: '404',
showMenu: true
}
}
]
})
export default router
对RecursiveMenu.vue的文件的理解:
1. 加载组件:当你在父组件中使用 <recursive-menu :menus="menus" /> 时,Vue.js 会创建 RecursiveMenu 组件的实例。
2. 传递 Props:menus 数据作为 prop 传递给 RecursiveMenu 组件。这个数据包含了整个菜单结构,每个菜单项可能有 children 属性,表示其子菜单。
3. 渲染菜单:组件的模板部分使用 v-for 指令遍历 menus 数组。对于每个 item(菜单项),组件会检查是否满足以下条件:
- item.children && item.children.length > 0:菜单项有子菜单。
- item.meta.showMenu:菜单项被标记为可显示。
4. 条件渲染:
- 如果上述条件为真,说明当前菜单项有子菜单且应该显示,组件会渲染一个 <a-sub-menu> 容器,并使用一个插槽(slot)来显示菜单标题。
- 组件接着递归地调用自己 <recursive-menu :menus="item.children" /> 来渲染子菜单。这是递归的关键步骤,因为它允许组件无限制地渲染任意深度的菜单结构。
5. 递归调用:对于每个子菜单项,步骤 3 和 4 会重复执行,直到渲染完所有层级的菜单。