开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第7天,点击查看活动详情
通过递归的方式实现侧边栏多层级导航组件,提供两种解决方法,本文章只提供实现思路,希望对大家有所帮助。
数据格式
let data2 = [
{
a: "导航1",
b: "1",
c: "Document",
},
{
a: "导航2",
b: "2",
c: "Document",
},
{
a: "导航3",
b: "3",
c: "Document",
d: [
{
a: "导航3-1",
b: "3-1",
c: "Document",
d: [
{
a: "导航3-1-1",
b: "3-1-1",
c: "Document",
d: [
{
a: "导航3-1-1-1",
b: "3-1-1-1",
c: "Document",
d: [
{
a: "导航3-1-1-1-1",
b: "3-1-1-1-1",
c: "Document",
},
],
},
],
},
],
},
],
},
];
调用组件
<el-row class="tac">
<el-col :span="12">
<!-- 多层导航(递归) -->
<h2 style="text-align:center;padding:10px;"> 组件递归 </h2>
<m-menu-recursion :data="data2"></m-menu-recursion>
</el-col>
<el-col :span="12">
<!-- 多层导航(tsx)注意数据的映射关系 -->
<h2 style="text-align:center;padding:10px;"> tsx递归 </h2>
<m-infinite-menu
:data="data2"
defaultActive="2"
name="a"
index="b"
icon="c"
children="d"
></m-infinite-menu>
</el-col>
</el-row>
方法一:组件递归的方式实现
# index.vue (父组件)
<template>
<!-- 导航开始 -->
<el-menu
default-active="2"
class="el-menu-vertical-demo"
@open="handleOpen"
@close="handleClose"
>
<menuItem v-for="item in data" :key="item.b" :item="item"></menuItem>
</el-menu>
<!-- 导航结束 -->
</template>
<script setup lang="ts">
import { onMounted, PropType } from "vue";
import menuItem from "./menuItem.vue";
let props = defineProps({
// 导航菜单的数据
data: {
type: Array as PropType<any[]>,
required: true,
}
});
onMounted(() => {
console.log("props.data", props.data, props);
});
const handleOpen = (key: string, keyPath: string[]) => {
console.log(key, keyPath);
};
const handleClose = (key: string, keyPath: string[]) => {
console.log(key, keyPath);
};
</script>
# menuItem.vue (子组件)
<template>
<el-menu-item v-if="!item.d || !item.d.length" :index="item.b">
<el-icon>
<component :is="`el-icon-${toLine(item.c)}`"></component>
</el-icon>
<span>{{ item.a }}</span>
</el-menu-item>
<el-sub-menu v-if="item.d && item.d.length" :index="item.b">
<template #title>
<el-icon>
<component :is="`el-icon-${toLine(item.c)}`"></component>
</el-icon>
<span>{{ item.a }}</span>
</template>
<-- 递归 -->
<menuItem v-for="ele in item.d" :key="ele.b" :item="ele"></menuItem>
</el-sub-menu>
</template>
<script lang="ts">
import { defineComponent } from "vue";
import { toLine } from "../../../utils"; // 处理icon图标
export default defineComponent({
name: "menuItem",
props: {
item: {
type: Object,
require: true,
default: () => {},
},
},
setup() {
return { toLine };
},
});
</script>
方法二:tsx的方式实现
# main.tsx
import { defineComponent, PropType, useAttrs } from 'vue'
import { MenuItem } from './types'
import * as Icons from '@element-plus/icons-vue'
import './styles/index.scss'
export default defineComponent({
props: {
// 数据
data: {
type: Array as PropType<MenuItem[]>,
required: true
},
// 标题键名(对应 a)
name: {
type: String,
default: 'name'
},
// 标识键名(对应 b)
index: {
type: String,
default: 'index'
},
// 图标键名(对应 c)
icon: {
type: String,
default: 'icon'
},
// 子菜单键名(对应 d)
children: {
type: String,
default: 'children'
},
},
setup(props, ctx) {
// 封装一个渲染无限层级菜单的方法
// 函数会返回一段jsx的代码
let renderMenu = (data: any[]) => {
return data.map((item: any) => {
// 每个菜单的图标
item.i = (Icons as any)[item[props.icon!]]
// 处理sub-menu的插槽
let slots = {
title: () => {
return <>
<item.i />
<span>{item[props.name]}</span>
</>
}
}
// 递归渲染children
if (item[props.children!] && item[props.children!].length) {
return (
// v-slots={slots} 插槽插入内容
<el-sub-menu index={item[props.index]} v-slots={slots}>
// 递归
{renderMenu(item[props.children!])}
</el-sub-menu>
)
}
// 正常渲染普通的菜单
return (
<el-menu-item index={item[props.index]}>
<item.i />
<span>{item[props.name]}</span>
</el-menu-item>
)
})
}
let attrs = useAttrs()
return () => {
return (
<el-menu
class="menu-icon-svg"
default-active={props.defaultActive}
router={props.router}
{...attrs}
>
{renderMenu(props.data)}
</el-menu>
)
}
}
})