Elemnt-UI + 递归组件实现后台管理系统左侧菜单

news2024/9/22 11:36:42

Elemnt-UI + 递归组件实现后台管理系统左侧菜单

在 Vue.js 中,允许你编写一个组件来表示一个节点,而这个节点可以包含多个子节点,每个子节点又可以是同样的组件。这种方式使得组件能够处理无限层级的嵌套结构。

应用场景

递归组件非常适合处理具有层级结构的数据,以下是几种常见的应用场景:

  • 多级菜单:如上所示,递归组件可以用来生成多级菜单,无论是水平还是垂直布局。
  • 文件目录树:文件系统中的目录结构通常是树状的,递归组件可以帮助你轻松地构建一个文件目录树。
  • 评论系统:许多网站的评论系统支持回复评论,形成一个树形结构,递归组件可以用来展示这些评论及其回复。
  • 组织架构图:公司内部的组织结构也可以用递归组件来表示,每个部门下面可以有子部门或员工。
  • 任务列表:在项目管理工具中,任务列表往往包含父任务和子任务,递归组件可以清晰地展现这种层次关系。

实现步骤

1. 安装 Element-UI
确保你的项目已经安装了 Element-UI。如果还没有安装,可以通过 npm 或 yarn 来安装:

npm install element-ui --save
# 或者
yarn add element-ui

然后在 main.js 文件中引入并使用 Element-UI:

import Vue from 'vue';
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';

Vue.use(ElementUI);

2. 设计菜单数据结构

先定义一个合理的菜单数据结构,通常是一个数组对象,其中每个对象代表一个菜单项,并且可以包含子菜单。例如:

import Layout from '@/Layout/index.vue'

export const routes = [
  {
    path: '/',
    name: 'redirect',
    component: Layout,
    hidden: true, // 隐藏菜单
    redirect: "/homePage", // 用户在地址栏输入 '/' 时会自动重定向到 /homePage 页面
  },
  {
    path: '/homePage',
    component: Layout,
    redirect: "/homePage/index",
    meta: {
      title: "首页",
    },
    children: [
      {
        path: 'index',
        name: 'homePageIndex',
        meta: {
          title: "首页",
        },
        component: () => import('@/views/homePage/index.vue')
      }
    ]
  },
  {
    path: '/login',
    component: () => import('@/views/login.vue'),
    hidden: true
  },
  {
    path: '/404',
    component: () => import('@/views/error/404.vue'),
    hidden: true
  },
  {
    path: '/401',
    component: () => import('@/views/error/401.vue'),
    hidden: true
  },
  {
    path: '/admin',
    meta: {
      title: "系统管理",
    },
    component: Layout,
    children: [
      {
        path: 'user',
        name: 'userIndex',
        meta: {
          title: "用户管理",
        },
        component: () => import('@/views/admin/user.vue')
      },
      {
        path: 'role',
        name: 'roleIndex',
        meta: {
          title: "权限管理",
        },
        component: () => import('@/views/admin/role.vue'),
        children: [
          {
            path: 'add',
            name: 'addRole',
            hidden: true,
            meta: {
              title: "添加角色",
            },
            component: () => import('@/views/admin/user/index.vue')
          },
          {
            path: 'update',
            name: 'updateRole',
            hidden: true,
            meta: {
              title: "编辑角色",
            },
            component: () => import('@/views/admin/role/index.vue')
          }
        ]
      }
    ]
  },
  {
    path: '/tableEcho',
    meta: {
      title: "表格管理",
    },
    component: Layout,
    children: [
      {
        path: 'test',
        name: 'tableEchoIndex',
        meta: {
          title: "表格测试",
        },
        component: () => import('@/views/tableEcho/index.vue'),
        children: [
          {
            path: 'add',
            name: 'addTable',
            hidden: true,
            meta: {
              title: "新增测试",
            },
            component: () => import('@/views/tableEcho/add.vue')
          }
        ]
      },
    ],
  },
]

上述示例:

  • 配置了四个路由器:根路径重定向、首页、系统管理、表格管理。
  • 根路径重定向: 访问根路径 '/' 时,会自动重定向到 /homePage
  • 首页菜单: 一个子菜单。
  • 系统管理: 两个子菜单, 两个三级菜单(不展示)。
  • 表格管理: 一个子菜单, 一个三级菜单(不展示)。

注意点:

每个菜单的一级路由的 component 都必须是 Layout 组件, Layout 组件用于定义整个系统页面的基本结构和布局,比如导航栏、侧边栏等。通过将所有的一级路由都指向 Layout 组件,可以确保无论用户访问哪个页面,都能看到一致的布局。

Layout 组件文件布局图片如下
在这里插入图片描述

3. 创建递归组件
创建一个递归组件来渲染菜单项。这个组件将根据传入的数据结构递归地生成菜单项。

Sidebar / SidebarItem.vue

<div class="sidebar_item">
    <!-- 如果没有子菜单或只有一个二级的子菜单则直接渲染 -->
    <template
      v-if="hasOneShowingChild(item.children, item) && (!onlyOneChild.children || onlyOneChild.noShowingChildren)"
      class="item">
      <router-link v-if="onlyOneChild.meta" :to="resolvePath(onlyOneChild.path)">
        <el-menu-item :index="resolvePath(onlyOneChild.path)" class="title">
          <img src="" alt="">
          <span slot="title">{{ onlyOneChild.meta.title }}</span>
        </el-menu-item>
      </router-link>
    </template>

    <!-- 有子菜单 -->
    <el-submenu v-else :index="resolvePath(item.path)" popper-append-to-body>
      <template slot="title">
        <span slot="title" class="title">{{ item.meta.title }}</span>
      </template>
      <Sidebar-item v-for="child in item.children" :key="child.path" :is-nest="true" :item="child"
        :base-path="resolvePath(child.path)"></Sidebar-item>
    </el-submenu>
  </div>
</template>

<script>
import path from "path";

export default {
  name: "SidebarItem",
  data() {
    return {
      onlyOneChild: {
        children: [],
      },
    }
  },
  props: {
    item: {
      type: Object,
      required: true
    },
    basePath: {
      type: String,
      default: ''
    }
  },
  methods: {
    // 判断是否只有一个子菜单
    hasOneShowingChild(children = [], parent) {
      // console.log('parent::: ',children , parent);
      if (!children) {
        children = [];
      }

      // 过滤掉隐藏的菜单
      const showingChildren = children.filter((item) => {
        // 是否隐藏菜单
        if (item.hidden) {
          return false;
        } else {
          this.onlyOneChild = item;
          return true;
        }
      });

      // 当只有一个子路由时,默认显示子路由器
      if (showingChildren.length === 1) {
        return true;
      }

      // 如果没有子路由,则显示父级路由
      if (showingChildren.length === 0) {
        this.onlyOneChild = { ...parent, path: "", noShowingChildren: true };
        return true;
      }
      return false;
    },
    // 判断是否是外链
    isExternal(path) {
      return /^(https?:|mailto:|tel:)/.test(path);
    },
    // 路径拼接
    resolvePath(routePath) {
      if (this.isExternal(routePath)) {
        return routePath;
      }
      if (this.isExternal(this.basePath)) {
        return this.basePath;
      }
      return path.resolve(this.basePath, routePath);
    },
  }
}
</script>

<style lang="scss" scoped>
.sidebar_item {
  cursor: pointer;
  >a {
    height: 60px;
    line-height: 60px;
  }
}

::v-deep .el-submenu__title {
  height: 60px;
  line-height: 60px;
}

::v-deep .el-submenu__title:hover,
a :hover {
  background-color: #2a9cff !important;
}

::v-deep .active {
  background-color: #2a9cff !important;
}

.is-active {
  background-color: #2a9cff !important;

}

.title {
  font-size: 16px;
}
</style>

注意:

代码里导入的 path 模块需要安装 node-polyfill-webpack-plugin 模块, 原因是由于 webpack5 中移除了 nodejs 核心模块的 polyfill 自动引入, 所以需要下载插件手动引入

npm install node-polyfill-webpack-plugin --save
# 或者
yarn add node-polyfill-webpack-plugin

vue.config.js 配置

const { defineConfig } = require('@vue/cli-service');
const nodePolyfillWebpackPlugin = require("node-polyfill-webpack-plugin");

module.exports = defineConfig({
  configureWebpack: (config) => {
    // 由于webpack5中移除了nodejs核心模块的polyfill自动引入, 所以需要下载插件手动引入
  	config.plugins.push(new nodePolyfillWebpackPlugin());
  }
})

4. 使用递归组件

在主布局或需要显示菜单的地方使用 Menu 组件,并传递菜单数据:

Sidebar / index.vue

<template>
  <div style="padding-top: 30px;">
    <!-- 左侧菜单 -->
    <el-menu :default-active="index" class="memu" @open="handleOpen" @close="handleClose" background-color="#304156"
      text-color="#bfcbd9" active-text-color="#fff" @select="handleSelect">
      <Sidebar-item v-for="route in sidebarRouters" :key="route.path + index" :item="route" :base-path="route.path" />
    </el-menu>
  </div>
</template>

<script>
import SidebarItem from './SidebarItem.vue';
import { mapGetters } from "vuex";

export default {
  name: 'Sidebar',
  data() {
    return {
      index: this.$route.path,

    }
  },
  components: { SidebarItem },
  computed: {
    ...mapGetters(["sidebarRouters"]),
  },
  methods: {
    handleOpen(key, keyPath) {
      console.log('handleOpen::: ', key, keyPath);

    },
    handleClose(key, keyPath) {
      console.log('handleClose::: ', key, keyPath);

    },
    handleSelect(index, indexPath) {
      console.log('handleSelect::: ', index, indexPath);
    }
  }
}
</script>

<style lang="scss" scoped>
.memu {
  display: inline-block;
  text-align: left;
  width: 100%;
}
</style>

实现效果
在这里插入图片描述

面包屑导航栏实现参考文档: https://blog.csdn.net/a123456234/article/details/142070954

总结

Element-UI 结合递归组件的方式,用于构建后台管理系统的左侧菜单,主要是通过以下步骤实现的:

  • 配置路由(Routes): 首先,如同前述代码片段所示,定义一系列路由对象构成的数组 routes,这些对象不仅描述了各页面路径(path)、页面名称(name)、对应组件(component),还包括了嵌套的子路由(children)以及元信息(如页面标题)等,从而形成了多级菜单的结构基础。

  • 递归组件(Recursive Component): 利用 Vue 中的递归组件技术,创建一个菜单组件,该组件根据传入的路由配置(通常是 routes 数组)自动生成菜单项。递归的逻辑在于:组件内根据当前路由的 children 属性判断是否有子菜单,如果有,则继续渲染子菜单组件,直至没有更多子节点,以此实现无限级菜单的展现。

  • Element-UI 风格: 在实现递归菜单组件时,利用 Element-UI 的 UI 组件库(如 el-menu、el-submenu、el-menu-item 等),为菜单项赋予统一且美观的样式,实现折叠、展开、激活状态等交互效果,增强用户体验。

总结而言,Element-UI 与递归组件结合的方式,使得后台管理系统的左侧菜单能够高效地根据路由配置动态渲染多层级菜单,同时确保了菜单的一致性和美观性,提升了系统的可维护性和用户体验。

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

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

相关文章

2013年

B D B C D 分支结点是非叶结点 B 47 C A C C D D C A C

2010-2022年各省乡村振兴新质生产力相关变量数据(40+指标)

2010-2022年各省乡村振兴新质生产力相关变量数据&#xff08;40指标&#xff09; 1、时间&#xff1a;2010-2022年 2、来源&#xff1a;统计年鉴、能源统计年鉴、农村统计年鉴、人口和就业统计年鉴、城乡建设统计年鉴以及各省份统计年鉴 3、指标&#xff1a;省份、年份、分地…

信号量(二值信号量和计数信号量)和互斥量

信号量 信号量&#xff08;Semaphore&#xff09; 是一种实现任务间通信的机制&#xff0c; 可以实现任务之间同步或临界资源的互斥访问&#xff0c; 常用于协助一组相互竞争的任务来访问临界资源。 在多任务系统中&#xff0c; 各任务之间需要同步或互斥实现临界资源的保护&a…

图神经网络介绍3

1. 图同构网络&#xff1a;Weisfeiler-Lehman 测试与图神经网络的表达力 本节介绍一个关于图神经网络表达力的经典工作&#xff0c;以及随之产生的另一个重要的模型——图同构网络。图同构问题指的是验证两个图在拓扑结构上是否相同。Weisfeiler-Lehman 测试是一种有效的检验两…

第二期: 第二节 , 逻辑编程 , gpio

1 首先就是 看原理图&#xff1a; 这里有两个 &#xff2c;&#xff25;&#xff24; 核心板的原理图。 可以看到 是这个脚。 &#xff12; 然后就是 查看数据手册。 从 数据手册可以看出 &#xff0c;一共有这么多的 gpio 组&#xff0c; 但是这些 组 是有复用的&#xf…

多文件编程实现链表创建,插入,输出(上)

linklist.c #include "linklist.h" //创建空的链表&#xff0c;为头结点在堆区分配空间 linklist_t *creat_empty_linklist() {linklist_t *head NULL;head (linklist_t *) malloc(sizeof(linknode_t));if(NULL head){printf("malloc is fail!\n");ret…

网格参数的应用和数学基础

引言 对于任意两个拓扑结构相似的表面&#xff0c;可以计算它们之间的一一对应映射。如果其中一个表面由三角形网格表示&#xff0c;那么计算这种映射的问题被称为网格参数化。映射到的表面通常被称为参数域。表面网格与各种域之间的参数化在计算机图形学和几何处理中有广泛的应…

移动WEB开发(第二天)_flex布局

移动WEB开发&#xff08;第二天&#xff09;_flex布局 移动web开发——flex布局1.0传统布局和flex布局对比1.1传统布局1.2 flex布局1.3 建议 2.0 flex布局原理3.0 父项常见属性3.1 flex-direction设置主轴的方向3.2 justify-content 设置主轴上的子元素排列方式3.3 flex-wrap设…

9月美联储决策前哨战——美国CPI数据来袭

随着本周关键CPI数据的即将发布&#xff0c;市场正翘首以待&#xff0c;这将是美联储在9月17日至18日议息会议前获取的最后一块重要经济拼图。鉴于美联储官员已进入传统的政策静默期&#xff0c;8月份的CPI报告无疑将成为交易员们评估未来货币政策走向的重要标尺。 欧洲央行降…

python列表判断是否为空的三种方式

#列表是否为空判断 a[]一&#xff1a; if a:print(not null) else:print(null)二&#xff1a; b len(a) if b 0:print(null) else:print(not null)三&#xff1a; if not a:print(null) else:print(not null)运行结果&#xff1a;

Day9 | Java框架 | SpringBoot

Day9 | Java框架 | SpringBoot SpringBoot简介入门程序概述起步依赖 基础配置配置文件格式&#xff1a;3种yaml语法规则yaml数据读取三种格式 多环境启动配置文件参数命令行参数多环境开发控制&#xff1a;Maven & SpringBoot 多环境兼容 配置文件分类&#xff1a;4种 整合…

Qt+FFmpeg开发视频播放器笔记(三):音视频流解析封装

音频解析 音频解码是指将压缩的音频数据转换为可以再生的PCM(脉冲编码调制)数据的过程。 FFmpeg音频解码的基本步骤如下: 初始化FFmpeg解码器(4.0版本后可省略): 调用av_register_all()初始化编解码器。 调用avcodec_register_all()注册所有编解码器。 打开输入的音频流:…

k8s以及prometheus

#生成控制器文件并建立控制器 [rootk8s-master ~]# kubectl create deployment bwmis --image timinglee/myapp:v1 --replicas 2 --dry-runclient -o yaml > bwmis.yaml [rootk8s-master ~]# kubectl expose deployment bwmis --port 80 --target-port 80 --dry-runclient…

【深海王国】初中生也能画的电路板?目录合集

Hi٩(๑ ^ o ^ ๑)۶, 各位深海王国的同志们&#xff0c;早上下午晚上凌晨好呀~辛勤工作的你今天也辛苦啦 (o゜▽゜)o☆ 今天大都督为大家带来系列文章《初中生也能画的电路板》&#xff0c;帮你一周内快速入门PCB设计&#xff0c;手把手教你从元器件库添加、电路原理图绘制、…

如何解决在idea中的hadoop日志错误

在idea中操作hadoop的时候&#xff0c;每次运行代码都会发现有个日志错误&#xff0c;虽然不影响程序运行&#xff0c;但是无法打印日志。这是缺少依赖&#xff0c;和windows上缺少log4j的文件 解决方案&#xff1a; 1、导入slf4j依赖 2、导入hadoop中的log4j文件 1、从hado…

校园安全无小事,EasyCVR视频综合管理平台助力智慧校园视频监控系统全面升级

随着信息技术的飞速发展&#xff0c;智慧校园作为教育信息化的重要载体&#xff0c;正逐步成为提升校园安全管理、优化教育资源配置、增强师生互动体验的关键手段。其中&#xff0c;高效、智能的视频监控系统作为智慧校园不可或缺的一部分&#xff0c;扮演着至关重要的角色。TS…

Benvista PhotoZoom Pro / Classic 9.0.2 Win/mac + Plug-in中文破解版

对数码照片放大的质量不满意&#xff1f; 使用 BenVista PhotoZoom Classic9 调整图像大小&#xff0c;并通过我们屡获殊荣的独特 S-Spline 技术获得出色的效果&#xff01; 更高质量&#xff1a;PhotoZoom Classic9 专门用于在保持质量的同时放大照片。 该软件配备了 BenVista…

C++核心编程和桌面应用开发 第一天

目录 1.C的编程方式 2.双冒号::运算符 3.命名空间 3.1作用 3.2命名空间内的东西 3.3注意事项 4.using的用法 4.1using的声明 4.2using编译指令 5.C相较于C的增强 5.1全局变量检测增强 5.2函数检测增强 5.3类型转换检测增强 5.4结构体增强 5.5三目运算符增强 5.…

如何高效阅读论文呢???

论文题目《多模态数据融合研究综述》&#xff0c;介绍了多模态数据融合技术以及对齐方法&#xff0c;最后是展望以及未来的发展。 泛读这篇论文不在状态&#xff0c;求助前辈们该如何高效的阅读论文呢&#xff1f;&#xff08;我是刚入学的研一学生&#xff0c;对于读论文这块…

【H2O2|全栈】关于CSS(2)CSS基础(二)

目录 CSS基础知识 前言 准备工作 选择器的组合 盒模型 示例网页代码 后代选择器 亲代选择器 相邻兄弟选择器 后续兄弟选择器 多个元素选择器 通配符选择器 优先级 其他应用 伪类 锚链接的属性 列表的属性 list-style-type list-style-position list-style…