vue.js

关注公众号 jb51net

关闭
首页 > 网络编程 > JavaScript > javascript类库 > vue.js > Element后台管理系统左侧菜单

Element-UI结合递归组件实现后台管理系统左侧菜单

作者:你不讲 wood

在Vue.js中使用递归组件可以方便地构建多层级的菜单结构,递归组件适用于处理具有嵌套关系的数据,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

在 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')
          }
        ]
      },
    ],
  },
]

上述示例:

注意点:

每个菜单的一级路由的 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>

实现效果

在这里插入图片描述

总结

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

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

到此这篇关于Elemnt-UI + 递归组件实现后台管理系统左侧菜单的文章就介绍到这了,更多相关Elemnt-UI + 递归组件实现后台管理系统左侧菜单内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文