• el-menu使用递归组件实现多级菜单组件


    vue3+element plus版:点击此处

    1. 效果:

    image
    image image

    2. 实现:

    创建外层菜单AsideMenu.vue组件和子菜单项AsideSubMenu.vue组件,在AsideSubMenu中进行递归操作。

    AsideMenu.vue文件内容如下:

    <template>
    <aside class="wrap">
    <el-menu
    :default-active="activeMenu"
    router
    :class="'menu-left'"
    :default-openeds="openedsArr"
    text-color="#fff"
    >
    <AsideSubMenu :menuData="menuData">AsideSubMenu>
    el-menu>
    aside>
    template>
    <script>
    import AsideSubMenu from "./AsideSubMenu.vue";
    export default {
    name: "AsideMenu",
    components: {
    AsideSubMenu,
    },
    props: {
    menuData: {
    type: Array,
    },
    },
    computed: {
    activeMenu() {
    const route = this.$route;
    const { meta, path } = route;
    // 此处添加判断的原因见说明
    if (meta.matchPath) {
    return meta.matchPath;
    } else {
    return path;
    }
    },
    // 设置默认展开菜单项
    openedsArr() {
    // const arr = this.menuData.map((item) => {
    // return item.title;
    // });
    // return arr;
    return [];
    },
    },
    };
    script>

    判断高亮状态的activeMenu方法中的判断matchPath属性可以让多个路由不同的页面匹配同一个菜单高亮状态,因为菜单高亮状态是根据路由地址匹配的。如果两个不同的路由页面想公用同一个菜单高亮状态(如详情页面和列表页)就可以使用该方法实现。在router文件里设置meta对象,添加matchPath属性设置为想要共用的高亮状态的页面的路由地址。(有点绕😂)

    样式根据需求修改,示例中的样式如下(此处包含深浅两种主题的菜单样式):

    点击查看代码
    <style scoped>
    aside {
    height: 100%;
    text-align: center;
    }
    .el-menu {
    padding: 16px;
    box-sizing: border-box;
    }
    /* ---------- 深色 ---------- */
    .menu-left,
    .menu-left /deep/ .el-menu {
    min-height: 100%;
    background-color: #222653;
    }
    .menu-left .icon {
    width: 20px;
    margin-right: 9px;
    }
    .menu-left /deep/.el-submenu__title,
    .menu-left /deep/.el-menu-item {
    box-sizing: border-box;
    font-size: 14px;
    font-family: PingFangSC-Regular, PingFang SC;
    text-align: left;
    color: #b3c0e7 !important;
    background-color: #222653 !important;
    }
    .menu-left /deep/.el-submenu__title i {
    color: #b3c0e7 !important;
    }
    .menu-left /deep/.el-submenu__title {
    height: 54px;
    line-height: 54px;
    /* padding-left: 36px !important; */
    }
    .menu-left /deep/ .el-menu-item {
    height: 52px;
    line-height: 52px;
    }
    .menu-left /deep/.el-submenu .el-menu-item {
    padding-left: 45px !important;
    }
    /* 外层高亮 */
    .menu-left /deep/.el-submenu.is-active,
    .menu-left /deep/.el-submenu.is-active .el-menu-item,
    .menu-left /deep/.el-submenu.is-active .el-submenu__title,
    .menu-left /deep/.el-menu-item.is-active {
    background-color: #4880ff !important;
    }
    .menu-left /deep/.el-submenu.is-active {
    border-radius: 10px;
    overflow: hidden;
    }
    .menu-left /deep/ .el-menu-item.is-active {
    border-radius: 10px;
    }
    .menu-left /deep/ .el-menu--inline .el-menu-item.is-active,
    .menu-left /deep/ .el-submenu.noIcon {
    border-radius: 0;
    }
    .menu-left /deep/ .el-submenu.noIcon .el-submenu__title {
    padding-left: 45px !important;
    }
    .menu-left /deep/ .el-submenu.noIcon .el-menu-item {
    padding-left: 58px !important;
    }
    .menu-left /deep/.el-submenu.is-active > .el-submenu__title,
    .menu-left /deep/.el-submenu.is-active > .el-submenu__title i {
    color: #ffffff !important;
    }
    /* 内层高亮 */
    .menu-left /deep/.el-menu-item:focus,
    .menu-left /deep/.el-menu-item:hover,
    .menu-left /deep/.el-menu-item.is-active {
    color: #ffffff !important;
    font-weight: 500;
    }
    .menu-left /deep/.el-menu-item.is-disabled {
    padding-left: 45px !important;
    color: #ffffff !important;
    }
    /* ---------- 浅色 ---------- */
    .menu-left-light {
    height: 100%;
    background-color: #f8f8f8;
    }
    .menu-left-light .icon {
    width: 20px;
    margin-right: 9px;
    }
    .menu-left-light /deep/.el-submenu__title,
    .menu-left-light /deep/.el-menu-item {
    box-sizing: border-box;
    font-size: 16px;
    font-family: PingFangSC-Regular, PingFang SC;
    text-align: left;
    color: #333333;
    }
    .menu-left-light /deep/.el-submenu__title {
    height: 52px;
    line-height: 52px;
    padding-left: 56px !important;
    }
    .menu-left-light /deep/.el-submenu__title:hover {
    background-color: #ffffff !important;
    }
    .menu-left-light /deep/.el-submenu__icon-arrow {
    right: 85px;
    }
    .menu-left-light /deep/.el-submenu__title i {
    color: #333333;
    }
    .menu-left-light /deep/.el-menu-item {
    height: 40px;
    line-height: 40px;
    padding-left: 82px !important;
    border-left: 4px solid #ffffff;
    }
    .menu-left-light /deep/.el-menu-item:focus,
    .menu-left-light /deep/.el-menu-item:hover,
    .menu-left-light /deep/.el-menu-item.is-active {
    background: rgba(31, 65, 219, 0.1) !important;
    color: #1f41db !important;
    border-color: #1f41db;
    }
    .menu-left-light /deep/.el-menu-item.is-disabled {
    background: #ffffff !important;
    color: #333333 !important;
    }
    style>

    AsideMenu.vue文件内容如下:

    <template>
    <div>
    <template v-for="item in menuData">
    <el-submenu
    :key="item.path"
    v-if="item.children && item.children.length > 0"
    :index="item.path"
    :class="item.icon ? '' : 'noIcon'"
    >
    <template slot="title">
    <img
    class="icon mr-r-10"
    :src="
    curRoute.indexOf(item.path) != -1 ? item.iconActive : item.icon
    "
    />
    <span>{{ item.title }}span>
    template>
    <AsideSubMenu :menuData="item.children">AsideSubMenu>
    el-submenu>
    <el-menu-item
    :key="item.id"
    v-else
    :index="item.path"
    :disabled="item.disabled"
    >
    <template slot="title">
    <img
    class="icon mr-r-10"
    :src="
    curRoute.indexOf(item.path) != -1 ? item.iconActive : item.icon
    "
    />
    <span>{{ item.title }}span>
    template>
    el-menu-item>
    template>
    div>
    template>

    判断如果有子菜单则进行遍历操作。同时此处根据是否有icon给el-submenu动态添加了一个类名:class="item.icon ? '' : 'noIcon'",这么做是由于高亮状态下的.el-submenu添加了圆角效果,在存在多层子菜单嵌套的情况下如果不清除圆角效果则会出现问题(见下图)。这个状态下不好用选择器选中需要操作的元素,因此根据是否有icon这个区别进行了区分。如果是整个菜单都没有icon的情况的话,那暂时还没想好应对策略。😂

    image

    <script>
    import AsideSubMenu from "./AsideSubMenu.vue";
    export default {
    name: "AsideSubMenu",
    components: {
    AsideSubMenu,
    },
    props: {
    menuData: {
    type: Array,
    default: () => {
    return [];
    },
    },
    },
    computed: {
    curRoute() {
    return this.$route.path;
    },
    },
    };
    script>

    3. 使用组件:

    • 添加路由配置;
    • 引入并挂载组件;
    • 传入菜单数据;

    代码如下:

    <template>
    <el-container class="container">
    <el-aside width="320px">
    <AsideMenu :menuData="menuData">AsideMenu>
    el-aside>
    <el-main>
    <keep-alive :exclude="[]">
    <router-view>router-view>
    keep-alive>
    el-main>
    el-container>
    template>
    <script>
    import AsideMenu from '@/components/AsideMenu.vue';
    export default {
    name: 'MenuTest',
    components: {
    AsideMenu
    },
    data() {
    return {
    menuData: [
    {
    title: '菜单一',
    path: '/menutest/menu1',
    icon: require('@/assets/icons/apply.svg'),
    iconActive: require('@/assets/icons/apply_active.svg'),
    children: [
    {
    title: '子菜单一',
    path: '/menutest/menu1/menu1-1',
    // disabled: true,
    },
    {
    title: '子菜单二',
    path: '/menutest/menu1/menu1-2'
    }
    ]
    },
    {
    title: '菜单二',
    path: '/menutest/menu2',
    icon: require('@/assets/icons/apply.svg'),
    iconActive: require('@/assets/icons/apply_active.svg'),
    children: [
    {
    title: '子菜单一',
    path: '/menutest/menu2/menu2-1'
    },
    {
    title: '子菜单二',
    path: '/menutest/menu2/menu2-2',
    children: [
    {
    title: '孙子菜单一',
    path: '/menutest/menu2/menu2-2/menu2-1-1'
    },
    {
    title: '孙子菜单二',
    path: '/menutest/menu2/menu2-2/menu2-2-2'
    }
    ]
    },
    {
    title: '子菜单三',
    path: '/menutest/menu2/menu2-3'
    }
    ]
    },
    {
    title: '菜单三',
    path: '/menutest/menu3',
    icon: require('@/assets/icons/apply.svg'),
    iconActive: require('@/assets/icons/apply_active.svg'),
    }
    ]
    };
    }
    };
    script>
    <style scoped>
    .container {
    min-height: 800px;
    }
    .el-main {
    padding: 32px 40px;
    box-sizing: border-box;
    background: #f5f5fa;
    overflow-y: auto;
    }
    style>

    示例中的路由配置如下:

    {
    path: "/menutest",
    name: "Menu",
    component: () => import("../views/MenuTest.vue"),
    redirect: "/menutest/menu1",
    children: [{
    path: '/menutest/menu1',
    component: () => import("../views/menuPages/menu1.vue"),
    children: [{
    path: '/menutest/menu1/menu1-1',
    component: () => import("../views/menuPages/menu1-1.vue"),
    },
    {
    path: '/menutest/menu1/menu1-2',
    component: () => import("../views/menuPages/menu1-2.vue"),
    }
    ]
    },
    {
    path: '/menutest/menu2',
    component: () => import("../views/menuPages/menu2.vue"),
    children: [{
    path: '/menutest/menu2/menu2-1',
    component: () => import("../views/menuPages/menu2-1.vue"),
    },
    {
    path: '/menutest/menu2/menu2-2',
    component: () => import("../views/menuPages/menu2-2.vue"),
    children: [{
    path: '/menutest/menu2/menu2-2/menu2-1-1',
    component: () => import("../views/menuPages/menu2-1-1.vue"),
    },
    {
    path: '/menutest/menu2/menu2-2/menu2-2-2',
    component: () => import("../views/menuPages/menu2-1-2.vue"),
    }
    ]
    },
    {
    path: '/menutest/menu2/menu2-3',
    component: () => import("../views/menuPages/menu2-3.vue"),
    }
    ]
    },
    {
    path: '/menutest/menu3',
    component: () => import("../views/menuPages/menu3.vue"),
    }
    ]
    }
  • 相关阅读:
    03 【资源处理】
    eclipseTomcat配置
    Python之函数
    (计算机组成原理)第二章数据的表示和运算-第一节3:无符号数的表示和运算
    开发一个Android应用:从零到一的实践指南
    GitHub:ViT-pytorch相关学习-视觉分类方向-1
    零基础学习Linux系统计划任务cron
    linux001--初次体验vmware虚拟机
    2016-2023全国MPA国家A类线趋势图:浙大MPA要高多少?
    进程与计划任务
  • 原文地址:https://www.cnblogs.com/lpkshuai/p/17310220.html