Vue3 封装 Element Plus Menu 无限级菜单组件

本文分别使用 SFC(模板方式)和 tsx 方式对 Element Plus el-menu 组件进行二次封装,实现配置化的菜单,有了配置化的菜单,后续便可以根据路由动态渲染菜单。

1 数据结构定义

使用 element-plus el-menu 组件实现菜单,主要包括三个组件:

el-menu:整个菜单;
el-sub-menu:含有子菜单的菜单项;
el-sub-menu:没有子菜单的菜单项(最末级);

结合菜单的属性和展示效果,可以得到每个菜单项包括:菜单名称、菜单图标、菜单唯一标识、子菜单列表四个属性。于是可得到菜单项结构定义如下:

/**
 * 菜单项
 */
export interface MenuItem {
  /**
   * 菜单名称
   */
  title: string;
  /**
   * 菜单编码(对应 el-menu-item / el-sub-menu 组件的唯一标识 index 字段)
   */
  code: string;
  /**
   * 菜单的图标
   */
  icon?: string;
  /**
   * 子菜单
   */
  children?: MenuItem[]
}

传入 MenuItem 数组,使用该数组渲染出菜单。但有时候数据字段不一定为上面结构定义的属性名,如 菜单名称 字段,上面定义的属性为 title,但实际开发过程中后端返回的是 name,此时字段名不匹配。一种处理方式是前端开发获取到后台返回的数据后,遍历构造上述结构,由于是树形结构,遍历起来麻烦。另一种方式是由用户指定字段的属性名,分别指定菜单名称、菜单编码等分别对应用户传递数据的什么字段。所以需要再定义一个结构,由用户来配置字段名称。

首先定义菜单项字段配置的结构:

/**
 * 菜单项字段配置结构
 */
export interface MenuOptions {
  title?: string;
  code?: string;
  icon?: string;
  children?: string;
}

再定义菜单项结构默认字段名:

/**
 * 菜单项默认字段名称
 */
export const defaultMenuOptions: MenuOptions = {
  title: 'title',
  code: 'code',
  icon: 'icon',
  children: 'children'
}

2 使用 tsx 实现封装

通常使用 tsx 封装组件的结构如下:

import { defineComponent } from 'vue'

export default defineComponent({
  name: 'yyg-menu',

  // 属性定义
  props: {
  },

  setup (props, context) {
    console.log(props, context)

    return () => (
      yyg-menu
    )
  }
})

首先定义两个属性:菜单的数据、菜单数据的字段名。

// 属性定义
props: {
  data: {
    type: Array as PropType,
    required: true
  },
  menuOptions: {
    type: Object as PropType,
    required: false,
    default: () => ({})
  }
},

除了上面定义的两个属性, el-menu 中的属性我们也希望能够暴露出去使用:

el-menu 的属性太多,一个个定义不太现实,在 tsx 中可以使用 context.attrs 来获取。

context.attrs 会返回当前组件定义的属性之外、用户传入的其他属性,也就是返回没有在 props 中定义的属性。

setup递归 实现菜单的无限级渲染。封装函数 renderMenu,该函数接收一个数组,遍历数组:

  • 如果没有子节点,则使用 el-menu-item 渲染
  • 如果有子节点,先使用 el-sub-menu 渲染, el-sub-menu 中的内容又继续调用 renderMenu 函数继续渲染。

整个组件实现如下 infinite-menu.tsx

import { DefineComponent, defineComponent, PropType } from 'vue'
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
import { defaultMenuOptions, MenuItem, MenuOptions } from './types'

export default defineComponent({
  name: 'yyg-menu-tsx',

  // 属性定义
  props: {
    data: {
      type: Array as PropType,
      required: true
    },
    menuOptions: {
      type: Object as PropType,
      required: false,
      default: () => ({})
    }
  },

  setup (props, context) {
    console.log(props, context)

    // 合并默认的字段配置和用户传入的字段配置
    const options = {
      ...defaultMenuOptions,
      ...props.menuOptions
    }

    // 渲染图标
    const renderIcon = (icon?: string) => {
      if (!icon) {
        return null
      }
      const IconComp = (ElementPlusIconsVue as { [key: string]: DefineComponent })
      return (

      )
    }

    // 递归渲染菜单
    const renderMenu = (list: any[]) => {
      return list.map(item => {
        // 如果没有子菜单,使用 el-menu-item 渲染菜单项
        if (!item[options.children!] || !item[options.children!].length) {
          return (

              {renderIcon(item[options.icon!])}
              {item[options.title!]}

          )
        }

        // 有子菜单,使用 el-sub-menu 渲染子菜单
        // el-sub-menu 的插槽(title 和 default)
        const slots = {
          title: () => (
            <>
              {renderIcon(item[options.icon!])}
              {item[options.title!]}
            </>
          ),
          default: () => renderMenu(item[options.children!])
        }

        return
      })
    }

    return () => (

        {renderMenu(props.data)}

    )
  }
})

3 使用 SFC 实现菜单封装

SFC 即 Single File Component,可以理解为 .vue 文件编写的组件。上面使用 tsx 可以很方便使用递归,模板的方式就不太方便使用递归了,需要使用两个组件来实现。

infinite-menu-item.vue


    {{ item[menuOptions.title] }}

      {{ item[menuOptions.title] }}

import { defineProps, PropType } from 'vue'
import { MenuOptions } from './types'
import * as ElementPlusIconsVue from '@element-plus/icons-vue'

defineProps({
  item: {
    type: Object,
    required: true
  },
  menuOptions: {
    type: Object as PropType<MenuOptions>,
    required: true
  }
})

infinite-menu-sfc.vue


import InfiniteMenuItem from './infinite-menu-item.vue'
import { defineProps, onMounted, PropType, ref } from 'vue'
import { defaultMenuOptions, MenuItem, MenuOptions } from './types'

const props = defineProps({
  data: {
    type: Array as PropType<MenuItem[]>,
    required: true
  },
  menuOptions: {
    type: Object as PropType<MenuOptions>,
    required: false,
    default: () => ({})
  }
})

const options = ref({})

onMounted(() => {
  options.value = {
    ...defaultMenuOptions,
    ...props.menuOptions
  }
})

4 测试组件

menu-mock-data.ts

export const mockData = [{
  title: '系统管理',
  id: 'sys',
  logo: 'Menu',
  children: [{
    title: '权限管理',
    id: 'permission',
    logo: 'User',
    children: [
      { title: '角色管理', id: 'role', logo: 'User' },
      { title: '资源管理', id: 'res', logo: 'User' }
    ]
  }, {
    title: '字典管理', id: 'dict', logo: 'User'
  }]
}, {
  title: '营销管理', id: '2', logo: 'Menu'
}, {
  title: '测试',
  id: 'test',
  logo: 'Menu',
  children: [{
    title: '测试-1',
    id: 'test-1',
    logo: 'Document',
    children: [{ title: '测试-1-1', id: 'test-1-1', logo: 'Document', children: [{ title: '测试-1-1-1', id: 'test-1-1-1', logo: 'Document' }]}, { title: '测试-1-2', id: 'test-1-2', logo: 'Document' }]
  }]
}]

      tsx

      sfc

import YygInfiniteMenuTsx from '@/components/infinite-menu'
import YygInfiniteMenuSfc from '@/components/infinite-menu-sfc.vue'
import { mockData } from '@/views/data/menu-mock-data'

const menuOptions = { title: 'title', code: 'id', icon: 'logo' }

.menu-demo {
  display: flex;

  > div {
    width: 250px;
    margin-right: 30px;
  }
}

总结:

感谢你阅读本文,如果本文给了你一点点帮助或者启发,还请三连支持一下,点赞、关注、收藏,作者会持续与大家分享更多干货

Original: https://www.cnblogs.com/youyacoder/p/16701222.html
Author: 程序员优雅哥(/同)
Title: Vue3 封装 Element Plus Menu 无限级菜单组件

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/578485/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • linux中软件的安装方式

    linux中软件的安装方式 四种方式 ​ 源码编译安装 ​ rpm安装 ​ yum安装 解压、配置(hadoop、hive等) 1.源码编译安装 1.为了编译nginx源码 yum…

    Linux 2023年6月11日
    088
  • rabbitmq-安装部署及基础操作

    yum 安装 rabbitmq # centos7 编译安装rabbitmq 在安装RabbitMQ中需要注意:1、RabbitMQ依赖于Erlang,需要先安装Erlang2、E…

    Linux 2023年6月14日
    0101
  • 全新UI西游H5决战天宫游戏详细图文架设教程

    前言 想体验经典Q版西游霸服快乐吗?想体验满级VIP的尊贵吗?想体验一招秒杀的爽快吗?各种极品炫酷时装、坐骑、翅膀、宠物通通给你,就在全新UI西游H5决战天宫! 本文讲解决战天宫架…

    Linux 2023年6月7日
    091
  • 【Prometheus+Grafana系列】监控MySQL服务

    前言 前面的一篇文章已经介绍了 docker-compose 搭建 Prometheus + Grafana 服务。当时实现了监控服务器指标数据,是通过 node_exporter…

    Linux 2023年6月7日
    083
  • 软件定义网络第一次作业

    配置结果 如何pip解决下载过慢问题 实验环境配置 环境安装截图如下 安装环境过程中一些问题的解决 github连接不上 在hosts文件中加上以下语句 140.82.114.3 …

    Linux 2023年6月7日
    093
  • 二进制插入

    二进制插入__牛客网 (nowcoder.com) 题意为 让m插入到n的第j位到第i位 方法1: class BinInsert { public: int binInsert(…

    Linux 2023年6月13日
    087
  • 树莓派Raspiberry 编译Linux实时内核PREEMPT-RT 实战

    树莓派4B 实时内核(Preempt_RT)的配置和编译https://blog.csdn.net/zlp_zky/article/details/114994444 基本按照这个…

    Linux 2023年6月7日
    093
  • Gumbel_Softmax 概要

    解决argmax不可导,无法进行反向传播的问题 出现的原因: argmax(x,y)不可导的根本原因是其向量空间不是光滑的,有尖锐的点和面;而是某些任务中,argmax会被插入到反…

    Linux 2023年6月7日
    098
  • [ Linux ] 设置服务器开机自启端口

    https://www.cnblogs.com/yeungchie/ 需要用到的工具: crontab iptables crontab.set SHELL=/bin/bash P…

    Linux 2023年6月7日
    097
  • KMP分析证明

    引用后缀的目的: “ABBABA” 如果说ABA里面组成的AB是答案组成部分的开头,那么AB后面的字符一定是和模式串开头的第三个字符一样,如果不一样一定不是…

    Linux 2023年6月7日
    061
  • 备用链接

    win10 stick note 地址 https://www.partitionwizard.com/partitionmanager/where-are-sticky-note…

    Linux 2023年6月7日
    079
  • MIT6.824 Lab2调试过程

    2021-12-12 21:50 测试了5次,通过了并发用例。运行并发的用例的时候,会报第6个entry没有被三个节点中的任意一个apply。 看了看日志,发现第6个entry一直…

    Linux 2023年6月7日
    087
  • WEB自动化-08-Cypress 接口测试

    8 接口测试 在服务和服务、系统和系统之间进行通信时,常常会使用到接口。通过接口测试,可以在项目早期更快发现问题。接口有很多类型,而现阶段使用的接口是基于HTTP协议的接口。 8….

    Linux 2023年6月7日
    0108
  • Docker存储卷

    Docker存储卷 1、COW机制 Docker镜像由多个只读层叠加而成,启动容器时,Docker会加载只读镜像层并在镜像栈顶部添加一个读写层。 如果运行中的容器修改了现有的一个已…

    Linux 2023年6月7日
    072
  • Linux机器自建账号并赋予sudo权限,同时修改远程端口

    默认使用root账号来操作Linux有一定风险,因此需要自建账号并赋予sudo权限,方便使用 登录为root用户后,创建账号 adduser <username>&lt…

    Linux 2023年6月6日
    0119
  • 关于飞书事件订阅功能的应用

    此项目源码我也是站在巨人的肩膀上进行一个二次应用,感谢这位大神的共享 附上源码链接–Feishu-Event-Subscribe: 【实验】飞书的事件订阅 主要是通讯录…

    Linux 2023年6月14日
    076
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球