首页 > 文章列表 > Vue3 Element-plus和el-menu无限级菜单组件怎么封装

Vue3 Element-plus和el-menu无限级菜单组件怎么封装

Vue3 Element-plus el-menu
154 2023-05-16

Vue3 Element-plus和el-menu无限级菜单组件怎么封装

对于element中提供给我们的el-menu组件最多可以实现三层嵌套,如果多一层数据只能自己通过变量去加一层,如果加了两层、三层这种往往是行不通的,所以只能进行封装

效果图

 一、定义数据

MenuData.ts

export default [

    {

        id: "1",

        name: "第一级菜单",

        level: '1',

        child: [

            {

                id: "11",

                name: "第二级菜单",

                level: '1-1',

                child: [

                    {

                        id: "111",

                        name: "第三级菜单",

                        level: '1-1-1',

                        child: [

                            {

                                id: "1111",

                                name: "第四级菜单",

                                level: '1-1-1-1',

                                child: [

                                    {

                                        id: "11111",

                                        name: "第五级菜单",

                                        level: '1-1-1-1-1',

                                        child: []

                                    }

                                ]

                            }

                        ]

                    }]

            }

        ]

    },

    {

        id: "2",

        name: "第一级同级菜单",

        level: '2',

        child: []

    }

 

]

二、封装组件 

封装思想:

 1.对本身组件进行循环使用,如果有子集使用本身组件 把child数据传给自己

 2.如果没有子集 使用 el-menu-item

以下代码对setup( )函数和setup语法糖分别做了实现 

setup语法糖

<template>

  <el-menu

    :default-active="defaultActive"

    :unique-opened="true"

    class="el-menu-vertical-demo"

  >

    <template v-for="item in menu">

      <!-- 如果有子集 -->

      <template v-if="item.child && item.child.length > 0">

        <el-sub-menu

          :key="item.id"

          :index="item.level"

          :disabled="item.meta?.disabled"

          :popper-append-to-body="false"

        >

          <template #title>

            <i :class="[item.meta?.icon]"></i>

            <!-- 添加空格 表示下级-->

            <span> {{ generateSpaces(item.level) }} </span>

            <span slot="title"> {{ item.name }}</span>

          </template>

          <MenuTree

            :menu="item.child"

            :defaultActive="defaultActive"

            @clickItem="clickItemHandle"

          />

        </el-sub-menu>

      </template>

      <!-- 如果没有子集 -->

      <template v-else>

        <el-menu-item

          :key="item.id"

          :index="item.level"

          :disabled="item.meta?.disabled"

          :popper-append-to-body="false"

          @click="clickItemHandle(item)"

        >

          <i :class="[item.meta?.icon]"></i>

          <!-- 添加空格 表示下级-->

          <span> {{ generateSpaces(item.level) }} </span>

          <span slot="title">{{ item.name }}</span>

        </el-menu-item>

      </template>

    </template>

  </el-menu>

</template>

 

<script lang="ts" name="MenuTree" setup>

// 把下面代码变成setup语法糖的形式 

import type { PropType } from "vue";

import type { MenuItem } from "@/types/lesson";

// type 为了方便写成这样 可以根据自己项目设定type

 defineProps({

  menu: {

    type: Array as unknown as PropType<any[]>,

    required: true,

    default: () => [],

  },

  defaultActive: {

    type: String as unknown as PropType<string>,

    required: true,

    default: [],

  },

});

 

const emit = defineEmits(["update-active-path", "clickItem"]);

 

// 返回的空格字符串 用于显示菜单层级 

const generateSpaces = (level: string) => {

  let str = "";

  level.split("")  .filter((it) => it != "-") .forEach(() => {

      str += " ";

  });

  return str;

};

 

// 点击当前菜单项

const clickItemHandle = (item: MenuItem) => {

  emit("clickItem", item);

};

</script>

 

<style scoped lang="less">

.el-menu {

  width: 288px;

}

</style>

setup函数

<template>

  <el-menu :default-active="defaultActive"  :unique-opened="true"   class="el-menu-vertical-demo"  >

    <template v-for="item in menu">

        <template v-if="item.child && item.child.length > 0">

          <el-sub-menu 

            :key="item.id"

            :index="item.level"

            :disabled="item.meta?.disabled"

            :popper-append-to-body="false"

          >

            <template #title>

              <i :class="[item.meta?.icon]"></i>

              <!-- 添加空格 表示下级-->

              <span> {{ generateSpaces(item.level) }} </span>

              <span slot="title"> {{ item.name }}</span>

            </template>

            <MenuTree :menu="item.child" :defaultActive="defaultActive"  @clickItem="clickItemHandle"  />

          </el-sub-menu>

        </template>

        <template v-else>

          <el-menu-item 

            :key="item.id"

            :index="item.level"

            :disabled="item.meta?.disabled"

            :popper-append-to-body="false"

            @click="clickItemHandle(item)"

          >

            <i :class="[item.meta?.icon]"></i>

            <!-- 添加空格 表示下级-->

            <span> {{ generateSpaces(item.level) }} </span>

            <span slot="title">{{ item.name }}</span>

          </el-menu-item>

        </template>

      </template>

  </el-menu>

</template>

 

<script lang="ts">

import { defineComponent, toRefs } from 'vue';

import type { PropType } from 'vue'

import type {MenuItem} from '@/types/lesson'

export default defineComponent({

  name: 'MenuTree',

  props: {

    menu: {

      type: Array as unknown as PropType<any[]>,

      required: true,

      default: () => [],

    },

    defaultActive: {

      type: String as unknown as PropType<string>,

      required: true,

      default: '',

    },

  },

  emits: ['update-active-path','clickItem'],

  setup(props, context) {

    const { menu, defaultActive } = toRefs(props);

 

    const generateSpaces = (level:string) => {

      let str = ''

      level.split('').filter(it=>it!='-').forEach(() => {

        str += ' '

      })

      return str

    }

 

    const clickItemHandle = (item:MenuItem) => {

      context.emit('clickItem', item)

    }

 

   

    return {

      clickItemHandle,

      menu,

      defaultActive,

      generateSpaces,

    }

  },

});

</script>

 

<style scoped lang="less">

  .el-menu {

    width: 288px;

  }

</style>

 type就不补充了 可根据自己项目定义,可临时改成any

三、使用组件

<template>

 <MenuTree

        :menu="menuList"

        :defaultActive="defaultActive"

        @clickItem="handleMenuClick"

        :update-click="handleMenuClick"

      />

</template>

 

<script setup lang="ts">

import MenuTree from "./components/MenuTree.vue";

import type {MenuItem} from '@/types/lesson'

import menuData from './MenuData'

 

const defaultActive = ref<string>(''); // "1-1-1-1" 默认选中的数据

const menuList = ref(menuData)

 

const handleMenuClick = (item:MenuItem) => {

  console.log('父组件',item);

};

</script>

补充default-active变量,如果一开始想默认点开第一层的数据 就需要找规律啦

拿到所有的level,通过接口方式返给你 自己平铺拿到所有的level也好 

例如数据格式:

let arr = [  "1-1",

  "1-1-1",

  "1-1-1-1",

  "1-1-1-2",

  "1-1-1-3",

  "1-1-1-4",

  "1-1-1-5",

  "1-1-1-6",

  "1-1-2",

  "1-1-2-1"

]

 想要的结果就是 最长且相同数字最多的元素 1-1-1-1

arr.sort((a,b)=> b.split('-').length - a.split('-').length)[0]

使用split防止有些字符串是10、11 两位数字的