【VUE3】基于Vue3和Element Plus的递归组件实现多级导航栏

文章目录

  • 前言
  • 一、递归的意义
  • 二、递归组件的实现——基于element-plus UI的多级导航栏
    • 2.1 element-plus Menu菜单官方示例
    • 2.2 接口定义
    • 2.3 组件递归
    • 2.4 父组件封装递归组件
  • 三、完整代码——基于element-plus UI的多级导航栏
    • 3.1 组件架构
    • 3.2 types.ts
    • 3.3 menuTreeItem.vue
    • 3.4 index.vue
    • 3.5 组件调用与运行结果
  • 总结


前言

在日常工作中,我能经常能碰到那种自引用类似的数据结构。比方说树形的导航栏结构,我们期望Vue渲染时递归遍历组件树,直到叶子节点。一个组件在其自身的模板中调用自身的组件,这便是Vue中的递归组件。


一、递归的意义

想象一个这样的一个对象数组。每个对象有基本的path路径,name名称,label标签,icon图标。这四个属性是必须的,个别对象本身有children属性包含它的子菜单内容。并且children属性本身也是对应一个包含path,name,label,icon四个基本属性和children可选属性的对象。

这样的话我们就构建了一个典型的自引用类似的数据结构,相互之间互相嵌套。相信大家已经发现了,在我们不知道嵌套深度的情况下,是没法通过循环完整解析出全部数据。并且递归的逻辑复用性是优于循环。这样的结果也会使递归在代码居然简洁性,灵活性,方便扩展性。

二、递归组件的实现——基于element-plus UI的多级导航栏

完整代码在第三节

2.1 element-plus Menu菜单官方示例

我们接下来要开发的多级导航栏是基于element-plus的menu菜单组件。观察官方代码结构,组件最由el-menu标签包裹,el-sub-menu为一级菜单,内嵌的el-menu-item为一级菜单的子内容。但el-sub-menu里面也可以继续添加el-sub-menu标签,作为一个二级菜单。实现组件树的结构。

树状导航栏代码如下:

<el-menu>                 <!-- 最外层菜单容器 -->├─ <el-sub-menu>         <!-- 一级菜单(可展开) -->│   ├─ 菜单标题(含图标和文本)│   └─ <el-sub-menu>       <!-- 二级菜单(嵌套在一级菜单内) -->│       ├─ 二级菜单标题│       └─ <el-menu-item>  <!-- 二级菜单子项 -->├─ <el-menu-item>          <!-- 一级菜单子项(非展开项) -->└─ <el-menu-item>          <!-- 一级菜单子项 -->
</el-menu>

完整实例代码

<template><el-row class="tac"><el-col :span="12"><h5 class="mb-2">Default colors</h5><el-menudefault-active="2"class="el-menu-vertical-demo"@open="handleOpen"@close="handleClose"><el-sub-menu index="1"><template #title><el-icon><location /></el-icon><span>Navigator One</span></template><el-menu-item-group title="Group One"><el-menu-item index="1-1">item one</el-menu-item><el-menu-item index="1-2">item two</el-menu-item></el-menu-item-group><el-menu-item-group title="Group Two"><el-menu-item index="1-3">item three</el-menu-item></el-menu-item-group><el-sub-menu index="1-4"><template #title>item four</template><el-menu-item index="1-4-1">item one</el-menu-item></el-sub-menu></el-sub-menu><el-menu-item index="2"><el-icon><icon-menu /></el-icon><span>Navigator Two</span></el-menu-item><el-menu-item index="3" disabled><el-icon><document /></el-icon><span>Navigator Three</span></el-menu-item><el-menu-item index="4"><el-icon><setting /></el-icon><span>Navigator Four</span></el-menu-item></el-menu></el-col></el-row>
</template><script lang="ts" setup>
import {Document,Menu as IconMenu,Location,Setting,
} from '@element-plus/icons-vue'
const handleOpen = (key: string, keyPath: string[]) => {console.log(key, keyPath)
}
const handleClose = (key: string, keyPath: string[]) => {console.log(key, keyPath)
}
</script>

2.2 接口定义

定义组件的对象数组结构。path路径,name名称,label标签,icon图标,这四个属性是必须的。个别对象本身有children属性包含它的子菜单内容。

export interface NavTreeMenuItem {path: string;name?: string;label: string;icon: string;children?: NavTreeMenuItem[];
}

2.3 组件递归

vue3中实现递归组件是组件在其模板中直接调用自身。比如我定义了一个组件名称叫menuTreeItem.vue,我就可以在该vue文件内直接调用menuTreeItem。这是基于这主要归功于Vue 3 单文件组件的编译时处理机制和 < script setup> 的特性。

在menuTreeItem.vue的el-sub-menu节点里判断children属性是否有值,并且没有超过最大递归深度。

值得注意的是,使用递归方法要注意无限递归这个漏洞风险,需要设置最大深度,避免无限递归

menuTreeItem.vue文件片段

<template><template v-for="item in treeData" :key="item.path" :index="item.path"><el-sub-menu v-if="item.children && item.children.length > 0 && currentDepth < maxDepth"  :index="item.path"><!--  --><menuTreeItem :tree-data="item.children"  :current-depth="currentDepth + 1":max-depth="maxDepth"></menuTreeItem><!--  --></el-sub-menu></template>
</template>

2.4 父组件封装递归组件

前文我们把需要递归的组件封装成单独子组件,最后嵌套在父组件里,防止不必要的内容被遍历渲染。

<template><el-aside :style="{ width: menuWidth }"><h3 class="mb-2" v-once>后台系统</h3><div class="sidebar-menu"><el-menu default-active="2" class="my-el-menu" @open="handleOpen" @close="handleClose"><menuTreeItem :tree-data="treeData" :max-depth="5"></menuTreeItem></el-menu></div></el-aside>
</template>

三、完整代码——基于element-plus UI的多级导航栏

3.1 组件架构

navigationAside              ├─ index.vue			<!-- 核心组件 -->├─ menuTreeItem.vue		<!-- 递归组件 -->└─ types.ts         	<!-- 接口类型 -->
</el-menu>

3.2 types.ts

export interface NavTreeMenuItem {path: string;name?: string;label: string;icon: string;url?: string;children?: NavTreeMenuItem[];
}

3.3 menuTreeItem.vue

<template><template v-for="item in treeData" :key="item.path" :index="item.path"><el-sub-menu v-if="item.children && item.children.length > 0 && currentDepth < maxDepth"  :index="item.path"><template #title><component class="icons" :is="item.icon"></component><span>{{ item.label }}</span></template><MenuTreeItem :tree-data="item.children" :isRoot="false" :current-depth="currentDepth + 1":max-depth="maxDepth"></MenuTreeItem></el-sub-menu><el-menu-item v-else :index="item.path"><component class="icons" :is="item.icon"></component><span>{{ item.label }}</span></el-menu-item></template>
</template><script lang="ts" setup>
import { useRouter } from 'vue-router'
const router = useRouter()
import {Document,Menu as IconMenu,Location,Setting,
} from '@element-plus/icons-vue'
import { NavTreeMenuItem } from './types'const props = withDefaults(defineProps<{treeData: NavTreeMenuItem[];isRoot?: boolean;currentDepth?: number; // 当前深度maxDepth?: number;    // 最大深度
}>(), {isRoot: true,currentDepth: 1, // 默认当前深度为1maxDepth: 3 // 默认最大深度为3
})
</script><style lang="less" scoped>
:deep(.icons) {height: 18px;margin-right: 5px;width: 18px;
}
</style>

3.4 index.vue

<template><el-aside :style="{width: menuWidth}"><h3 v-if="!isCollapse" class="mb-2" v-once>天津城安远传系统</h3><h3  v-else class="mb-2" v-once>远传</h3><div class="sidebar-menu"><el-menu  default-active="2"class="my-el-menu" :collapse="isCollapse"@open="handleOpen" @close="handleClose" ><menuTreeItem :tree-data="treeData" :max-depth="5"></menuTreeItem></el-menu></div></el-aside>
</template><script lang="ts" setup>
import { ref, computed } from 'vue'
import { useRouter } from 'vue-router'
import {useWebSettingDataStore} from '../../stores'import menuTreeItem from './menuTreeItem.vue'
const router = useRouter()
import {Document,Menu as IconMenu,Location,Setting,
} from '@element-plus/icons-vue'
import { NavTreeMenuItem } from './types'const props = defineProps<{treeData: NavTreeMenuItem[];isRoot?: boolean;
}>();const store = useWebSettingDataStore()
const isCollapse = computed(() => store.state.isCollapse);
const menuWidth = computed(() => store.state.isCollapse ? '64px' : '180px')const handleOpen = (key: string, keyPath: string[]) => {console.log("open")console.log(key, keyPath)
}
const handleClose = (key: string, keyPath: string[]) => {console.log("close")console.log(key, keyPath)
}
</script><style lang="less" scoped>
.el-menu {border-right: none;height: 100%;
}.el-aside {display: flex;flex-direction: column;//border-right: 1px solid #e4e7ed;background-color: #304156;h3 {line-height: 13px;color: #fff;text-align: center;flex-shrink: 0;/* 防止被压缩 */}
}.sidebar-menu {flex: 1;overflow-y: auto;padding: 10px 0;
}/* 可选:美化滚动条 */
.sidebar-menu::-webkit-scrollbar {width: 4px;
}.sidebar-menu::-webkit-scrollbar-thumb {background-color: #bfcbd9;border-radius: 3px;
}.my-el-menu{--el-menu-bg-color: #304156;--el-menu-text-color: #bfcbd9;--el-menu-hover-bg-color: #263445;--el-menu-active-color: #409eff;
}
</style>

3.5 组件调用与运行结果

<commNavigationAside :tree-data="navTreeData"></commNavigationAside>
import commNavigationAside from "@/components/navigationAside/index.vue"

在这里插入图片描述


总结

本文基于 Vue3 和 Element Plus,通过递归组件实现了可动态渲染的多级导航栏,利用自引用数据结构和深度控制避免无限循环,同时结合 TypeScript 规范数据类型并优化组件封装。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.pswp.cn/web/83703.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

思科资料-ACL的基础配置-详细总结

一、ACL技术 1、定义 访问控制列表访问控制列表使用包过滤技术&#xff0c;在路由器上读取第三层及第四层包头中的信息如源地址&#xff0c;目的地址&#xff0c;源端口&#xff0c;目的端口等&#xff0c;根据预先定 义好的规则对包进行过滤&#xff0c;从而达到访问控制的目…

GitHub 上 PAT 和 SSH 的 7 个主要区别:您应该选择哪一个?

在代码仓库和像 Github 这样的版本控制系统中,有时您需要安全高效地访问您的仓库。随着对更安全实践的需求日益增长,开发人员一直在寻找最高效、最安全的方式来与 Github 交互。为了解决这个问题,我们将探讨两种常用的方法:个人访问令牌 (PAT) 和安全 Shell (SSH) 密钥。本…

Vue 事件修饰符详解

Vue 事件修饰符详解 事件修饰符是 Vue 中处理 DOM 事件细节的强大工具。下面我将通过一个交互式示例全面解析各种事件修饰符的用法和原理。 <!DOCTYPE html> <html lang"zh-CN"> <head><meta charset"UTF-8"><meta name"…

初探Qt信号与槽机制

3.3 按键响应 - 初识信号与槽 3.3.1 信号与槽基本介绍 提出疑问&#xff0c;界面上已经有按键了&#xff0c;怎么操作才能让用户按下按键后有操作上的反应呢&#xff1f; 在 Qt 中&#xff0c; 信号和槽机制 是一种非常强大的事件通信机制。这是一个重要的概念&#xff0…

Android音视频流媒体基础总结

流媒体开发中&#xff0c;流媒体系统的实现从数据采集、编码封装、传输分发、接收解码播放都有哪些技术和实现&#xff0c;流媒体和本地音视频又有哪些差异&#xff1f; 影像系统开发&#xff0c;流媒体方向和普通的多媒体影像系统开发有一定差异。 相同点在于图像多媒体处理…

疫菌QBD案例

本文是《A-VAX: Applying Quality by Design to Vaccines》第七个研究的R语言解决方案。 使用带两个中心点的二水平析因设计。运行10次实验。结果是分辨度为III的设计。 A <- c(25,25,15,15,15,25,25,20,15,20) B <- c(12,8,8,12,8,12,8,10,12,10) C <- c(35,15,15…

Linux部署elasticsearch 单机版

Linux部署elasticsearch 1、下载安装包 Elasticsearch 7.8.0 | Elastic 2、安装步骤 2.1、上传安装包到服务器opt目录 2.2、解压 #目录创建/opt/module cd /opt mkdir module tar -zxvf elasticsearch-7.8.0-linux-x86_64.tar.gz -C /opt/module mv elasticsearch-7.8.0 …

IDEA高效快捷键指南

1. 编辑类快捷键 编辑快捷键是最常用的一类&#xff0c;可以帮助我们快速操作代码&#xff1a; 快捷键 功能描述 Mac Windows 热度 psvm Tab 生成 main 方法 psvm Tab psvm Tab ⭐⭐⭐⭐⭐ sout Tab 生成 System.out.println() 输出语句 sout Tab sout Tab…

【论文写作参考文献地址】

参考文献地址 论文的各种参考文献地址国家哲学社会科学文献中心国家科技图书文献中心 论文的各种参考文献地址 国家哲学社会科学文献中心 资源免费!!! 整体配色就是红色&#xff0c;主页轮播有些实时新闻。 博主个人感受&#xff0c;对于计算机类的收录不是特别的充足 国家科…

华为OD机考-货币单位换算-字符串(JAVA 2025B卷)

纯暴力解法 import java.util.*; public class ExchangeMoney {public static void main(String[] args) {Scanner scanner new Scanner(System.in);while(scanner.hasNextLine()){int count Integer.parseInt(scanner.nextLine());List<String> strings new ArrayLi…

系统学习·PHP语言

由于之前没系统的学习PHP语言&#xff0c;都是在做题时遇到不会的才去查&#xff0c;后来发现这样的效率非常低&#xff0c;审代码别人一眼扫出漏洞&#xff0c;而我还需要去查一下这行代码的意思&#xff0c;那个函数的作用&#xff0c;查当然要查&#xff0c;但连简单的语法都…

leetcode2-两数相加

leetcode 2 思路 链表特性利用&#xff1a;由于数字按逆序存储&#xff0c;个位在链表头部&#xff0c;因此可以直接从前往后遍历链表进行逐位相加 比如题目中的 2->4->3 和 5->6->4 其实可以直接按位从第一位开始往后相加&#xff0c;就得到啦708 这样比把数字…

Java的Arrays.sort():排序算法与优化分析

文章目录 前言一、基本类型数组&#xff1a;双轴快速排序关键优化策略 二、对象数组&#xff1a;TimSort关键优化策略 三、性能对比总结总结 前言 在Java中&#xff0c;Arrays.sort()是开发者最常用的排序方法之一。但你是否思考过它的底层实现&#xff1f;本文将基于OpenJDK …

软件测试质量的“防”与“治”

引言: 想象一下,你正在建造一座摩天大楼。你是愿意在打地基时就严格检查材料规格和设计图纸(主动防患),还是等到大楼封顶后才开始拿着锤子敲敲打打找裂缝(被动补救)?软件世界亦是如此!今天,我们就来聊聊软件测试这个“质量守护神”的两大战略思维和三大实战招式,让你…

TDengine 如何从 2.x 迁移到 3.0

本节讲述如何通过 Explorer 界面创建数据迁移任务&#xff0c;从旧版 TDengine2 迁移数据到 TDengine 3.0 集群。 功能概述 taosX 通过 SQL 查询源集群数据&#xff0c;并把查询结果写入到目标数据库。具体实现上&#xff0c;taosX 以一个子表的一个时间段的数据作为查询的基…

免下载苹果 IPA 文件重签名工具:快速更换应用名称和 BID的教程

在iOS设备的使用和开发过程中&#xff0c;我们有时需要对IPA文件进行重签名&#xff0c;以便更换应用名称、Bundle ID&#xff08;软件包标识符&#xff09;或其他相关信息。这一过程通常需要使用到特定的工具&#xff0c;然而&#xff0c;市面上的一些工具可能需要下载和安装&…

Python全栈开发:前后端分离项目架构详解

文章目录 技术栈选择后端技术栈前端技术栈 项目整体结构详细目录结构说明后端架构&#xff08;backend/&#xff09;1. 应用核心&#xff08;app/&#xff09;2. 数据层&#xff08;models/&#xff09;3. API模式层&#xff08;schemas/&#xff09;4. API路由层&#xff08;a…

微信小程序使用图片实现红包雨功能

微信小程序红包雨功能实现&#xff1a;从组件封装到页面调用的完整实践 先看示例截图&#xff1a; 一、背景与技术选型 在微信小程序营销活动中&#xff0c;红包雨是一种极具吸引力的互动形式。实现红包雨效果主要有 Canvas 和图片两种方案&#xff1a; &#xff08;1&…

Python day31

浙大疏锦行 数据拆分的基本框架&#xff0c;拆分后让项目结构更加清晰

Chapter10-XXE

文章目录 1.XXE介绍1.1 XXE产生的原因1.1.1 什么是XML&#xff1f;1.1.2 什么是XML实体1.1.3 什么是文档类型定义&#xff08;document type definition&#xff09;1.1.4 什么是XML自定义实体1.1.5 什么是XML外部实体 2.XXE攻击类型2.1 利用XXE检索文件2.2 利用XXE执行SSRF攻击…