Vue+Element实现左侧无限级菜单

本文展示了如何使用Vue.js和Element-UI库创建一个包含导航菜单、折叠功能以及响应式布局的管理界面。代码示例详细解释了组件的结构和交互,包括Header、Nav和NavItem组件的实现,以及路由配置。此外,还提到了在处理菜单折叠时可能遇到的 Maximum call stack size exceeded 错误及其解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Main.js:

<template>
  <div id="app">
    <el-container style="height: 100%;">
      <el-header style="height: 80px; position: fixed; z-index: 1; width: 100%;" :style="topBg">
        <Header />
      </el-header>
      <el-container style="padding-top: 80px; height: 100%;">
        <el-aside width="auto" :style="leftBg">
          <el-row class="tac">
            <el-col :span="24">
              <Nav />
            </el-col>
          </el-row>
        </el-aside>
        <el-main style="padding: 15px;">
          <router-view></router-view>
        </el-main>
      </el-container>
    </el-container>
  </div>
</template>
 
<script>
import Header from "./layout/Header";
import Nav from "./layout/Nav";
export default {
  name: "App",
  data() {
    return {
      isCollapse: false,
      leftBg: {
        background: "#001529"
      },
      topBg: {
        background: "#001529",
        height: "80px",
        fontSize: "32px",
        color: "#ffffff"
      }
    };
  },
  methods: {
    handleOpen(key, keyPath) {
      console.log(key, keyPath);
    },
    handleClose(key, keyPath) {
      console.log(key, keyPath);
    }
  },
  components: {
    Header,
    Nav
  }
};
</script>
 
<style>
#app {
  font-family: "Avenir", Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  color: #2c3e50;
  height: 100%;
}
.el-header,
.el-footer {
  background-color: #b3c0d1;
  color: #333;
  line-height: 80px;
}
.logo {
  height: 80px;
  line-height: 80px;
  font-size: 30px;
  font-family: "微软雅黑";
  font-weight: bold;
  text-shadow: 0 0 5px #000000;
  display: flex;
  align-items: center;
}
.logo img {
  margin: 0 5px 0 0;
  width: 35px;
  height: 35px;
}
.el-aside {
  background-color: #d3dce6;
  color: #333;
}
.el-aside a {
  text-decoration: none;
}
.el-menu {
  background: none;
  border-right: 0;
}
.el-submenu__title {
  background: none !important;
}
.el-menu-item-group .el-menu-item {
  padding-left: 52px !important;
}
.el-menu-item {
  background: rgb(0, 21, 41) !important;
}
.el-menu-item.is-active {
  color: #ffffff !important;
  background: #1890ff !important;
}
.el-main {
  background-color: #f0f2f5;
  color: #333;
  padding: 0;
}
body > .el-container {
  margin-bottom: 40px;
}
.el-container:nth-child(5) .el-aside,
.el-container:nth-child(6) .el-aside {
  line-height: 260px;
}
.el-container:nth-child(7) .el-aside {
  line-height: 320px;
}
.el-menu-vertical-demo:not(.el-menu--collapse) {
  width: 200px;
  min-height: 400px;
}
</style>

Nav.vue:

​
<template>
  <el-aside width="200px">
    <el-menu
      class="el-menu-vertical-demo"
      background-color="rgb(0, 21, 41)"
      text-color="#fff"
      active-text-color="#ffd04b"
      default-active="/main/data/tag"
      :unique-opened="true"
      @open="handleOpen"
      @close="handleClose"
      router
    >
      <NavItem
        v-for="v in this.$router.options.routes[2].children"
        :key="v.path"
        :item="v"
        :basePath="v.path"
      />
    </el-menu>
  </el-aside>
</template>

<script>
import NavItem from "./NavItem";
export default {
  methods: {
    handleOpen(key, keyPath) {
      console.log(key, keyPath);
    },
    handleClose(key, keyPath) {
      console.log(key, keyPath);
    }
  },
  components: {
    NavItem
  }
};
</script>

<style scoped>
</style>

​

NavItem.vue:

<template>
  <div>
    <el-menu-item :index="basePath" v-if="!item.children">
      <i :class="item.meta.icon"></i>
      <span slot="title">{{item.meta.title}}</span>
    </el-menu-item>
    <el-submenu :index="basePath" v-else>
      <template slot="title">
        <i :class="item.meta.icon"></i>
        <span>{{item.meta.title}}</span>
      </template>

      <NavItem v-for="child in item.children" :key="child.path" :item="child" :basePath="child.path" />
    </el-submenu>
  </div>
</template>

<script>
export default {
  name: "NavItem",
  data() {
    return {};
  },
  props: ["item", "basePath"]
};
</script>

<style scoped>
.el-menu--collapse > div > .el-submenu > .el-submenu__title span {
  height: 0;
  width: 0;
  overflow: hidden;
  visibility: hidden;
  display: inline-block;
}
.el-menu--collapse
  > div
  > .el-submenu
  > .el-submenu__title
  .el-submenu__icon-arrow {
  display: none;
}
</style>

注意:

1、最外层div不可省略,否则鼠标在菜单项上移动时会报错;

2、<span slot="title"></span>不可省略,否则菜单收缩时鼠标放在菜单项上不会有文字标签链接;

Element-ui使用el-menu垂直布局递归生成菜单及菜单折叠后hover报错Maximum call stack size exceeded的解决方案_贵宾哥Cyril的博客-优快云博客

Header.js:

<template>
    <el-row style="height: 80px;">
      <el-col :span="6" class="logo">
        <img :src="require('../../assets/logo.svg')" />VUE AntD管理系统
      </el-col>
      <el-col :span="4" style="padding-top: 10px;">
        <el-radio-group v-model="isCollapse" style="margin-bottom: 20px;">
          <el-radio-button :label="false">展开</el-radio-button>
          <el-radio-button :label="true">收起</el-radio-button>
        </el-radio-group>
      </el-col>
      <el-col :span="13" :offset="1">
        <el-dropdown style="float: right;">
          <span class="el-dropdown-link" style="color: #ffffff;">
            user001
            <i class="el-icon-arrow-down el-icon--right"></i>
          </span>
          <el-dropdown-menu slot="dropdown">
            <el-dropdown-item icon="el-icon-edit">修改密码</el-dropdown-item>
            <el-dropdown-item icon="el-icon-setting">设置默认密级</el-dropdown-item>
            <el-dropdown-item icon="el-icon-circle-close" @click.native="loginout()">退出</el-dropdown-item>
          </el-dropdown-menu>
        </el-dropdown>
      </el-col>
    </el-row>
</template>

<script>
export default {
  data() {
    return {
      isCollapse: false,
      topBg: {
        background: "#001529",
        height: "80px",
        fontSize: "32px",
        color: "#ffffff"
      }
    };
  }
};
</script>

<style scoped>
.logo {
  height: 80px;
  line-height: 80px;
  font-size: 30px;
  font-family: "微软雅黑";
  font-weight: bold;
  text-shadow: 0 0 5px #000000;
  display: flex;
  align-items: center;
}

.logo img {
  margin: 0 5px 0 0;
  width: 35px;
  height: 35px;
}
</style>

 router.js

import Vue from 'vue'
import Router from 'vue-router'

Vue.use(Router);

let router = new Router({
  mode: 'hash',
  routes: [
    {
      path: '/',
      name: 'index',
      redirect: '/login'
    },
    {
      path: '/login',
      name: 'login',
      component: () => import("@/pages/login/Index"),
      meta: {
        title: 'Login'
      }
    },
    {
      path: '/main',
      alias: '/main',
      name: 'main',
      component: () => import("@/components/Main"),
      meta: {
        title: 'Main'
      },
      children: [
        {
          path: '/main/data',
          alias: '/data',
          name: 'data',
          component: () => import("@/pages/statistics/Index"),
          meta: {
            title: '首页',
            icon: 'el-icon-upload',
            requireAuth: true
          },
          children: [
            {
              path: '/main/data/tag',
              alias: '/tag',
              name: 'tag',
              component: () => import('@/pages/statistics/Tag'),
              meta: {
                title: '分析',
                requireAuth: true
              }
            },
            {
              path: '/main/button',
              alias: '/button',
              name: 'button',
              component: () => import('@/pages/statistics/StandardList'),
              meta: {
                title: '标准列表',
                requireAuth: true
              }
            }
          ]
        },
        {
          path: '/main/form',
          alias: '/form',
          name: 'form',
          component: () => import('@/pages/form/Index'),
          meta: {
            title: '表单',
            icon: 'el-icon-eleme',
            requireAuth: true
          },
          children: [
            {
              path: '/main/form/validate',
              alias: '/validate',
              name: 'validate',
              component: () => import('@/pages/form/Validate'),
              meta: {
                title: '基础表单',
                requireAuth: true
              }
            },
            {
              path: '/main/form/step',
              alias: '/step',
              name: 'Step',
              component: () => import('@/pages/form/step'),
              meta: {
                title: '分步表单',
                requireAuth: true
              }
            },
            {
              path: '/main/form/senior',
              alias: '/senior',
              name: 'senior',
              component: () => import('@/pages/form/senior'),
              meta: {
                title: '高级表单',
                requireAuth: true
              }
            }
          ]
        },
        {
          path: '/main/list',
          alias: '/list',
          name: 'list',
          component: () => import('@/pages/list/Index'),
          meta: {
            title: '反馈',
            icon: 'el-icon-upload',
            requireAuth: true
          },
          children: [
            {
              path: '/main/list/card',
              alias: '/card',
              name: 'card',
              component: () => import('@/pages/list/CardList'),
              meta: {
                title: '卡片列表',
                requireAuth: true
              }
            }
          ]
        },
        {
          path: '/main/dashbord',
          alias: '/dashbord',
          name: 'dashbord',
          component: () => import('@/pages/dashbord/Index'),
          meta: {
            title: '分析',
            icon: 'el-icon-upload',
            requireAuth: true
          },
          children: [
            {
              path: '/main/dashbord/analysis',
              alias: '/analysis',
              name: 'analysis',
              component: () => import('../pages/dashbord/analysis'),
              meta: {
                title: '分析页',
                requireAuth: true
              }
            }
          ]
        }
      ]
    }
  ]
});

export default router

// router.beforeEach((to, from, next) => {
//   let islogin = localStorage.getItem("islogin");
//   islogin = Boolean(Number(islogin));

//   if (to.path == "/login") {
//     if (islogin) {
//       next("/validate");
//     } else {
//       next();
//     }
//   } else {
//     // requireAuth:可以在路由元信息指定哪些页面需要登录权限
//     if (to.meta.requireAuth && islogin) {
//       next();
//     } else {
//       next("/login");
//     }
//   }
// })

完整代码:

https://gitee.com/duansamve/vue-element-management.git

环境配置 Node 下载地址http://nodejs.cn/ 安装文件下有一个绿色的图片交node.exe 点击运行 输入node -v进行检测是否安装成功 使用vue-cli(脚手架)搭建项目 vue-cli是vue官方提供的用域搭建基于vue+webpack_es6项目的脚手架工具 在线文档:https://github.com/vuejs/vue-cli 操作: 1.npm install -g vue-cli:全局下载工具 2.vue init webpack 项目名:下载基于webpack模板项目 3.cd 项目名:进入项目目录 4.npm install :下载项目依赖的所有模块 5.npm run dev :运行项目 6.访问项目:localhost:8080 项目目录结构 src assets:存放照片、css、js css js img components:存放组件 lib:存放模拟数据 router:配置路由 store:存放vuex vuex的安装:cd x项目目录 cnpm install vuex --save views:存放所有单页面 配置访问端口号: 根目录下有一个config文件夹,看名字就知道与配置有关,打开config目录下的index.js dev: { env: require('./dev.env'), port: 8092, autoOpenBrowser: true, assetsSubDirectory: 'static', assetsPublicPath: '/', proxyTable: {}, } 项目目录下:https://blog.csdn.net/weixin_39378691/article/details/83784403 1.安装elementUI:cd进入项目根目录,npm i element-ui -S 2.引入elementUI组件(main.js文件中) import Element from 'element-ui' import 'element-ui/lib/theme-chalk/index.css' Vue.use(Element, { size: 'small' }) 项目目录下:https://blog.csdn.net/weixin_41432849/article/details/81988058 1.安装jquery:cd进入项目根目录, npm install jquery --save 2.在项目 build 里的webpack.base.conf.js 里加载webpack文件,注意,要放在配置文件第一行; const webpack = require('webpack') 3.在module.exports的最后加入 , plugins:[ new webpack.ProvidePlugin({ $:"jquery", jQuery:"jquery", jquery:"jquery", "window.jQuery":"jquery" }) ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值