Vue2集成ElementUI实现左侧菜单导航

文章目录

简介

在开发后台系统时,通过菜单进行导航是非常重要的一件事情,在前端开发过程中使用vue2+elementui可以快速搭建菜单导航,本文主要记录两个菜单的生成方式,通过在前端router/index.js中直接进行配置,后端返回菜单数据进行对应,可以通过后端返回的菜单数据控制权限;另一种是部门静态导航,然后再拼接动态导航,生成完成页面导航。

静态导航

安装element-ui,vue-router,vuex

复制代码
npm install elementui --S

npm install vue-router@3 --S

npm install vuex --S

编写router/index.js

router/index.js

复制代码
import Vue from 'vue';
import Router from 'vue-router';

Vue.use(Router);

const router = new Router({
  routes: [
    {
      path: '/',
      name: 'Home',
      component: () => import('@/views/Home.vue'),
      children: [
        {
          path: '/index',
          name: 'Index',
          component: () => import('@/views/Index.vue'),
        },
        {
          path: '/documents/note',
          name: 'NoteManagement',
          component: () => import('@/views/NoteManagement.vue'),
        },
        {
          path: '/documents/file',
          name: 'FileManagement',
          component: () => import('@/views/FileManagement.vue'),
        },
        {
          path: '/documents/newMarkdown',
          name: 'NewDocument',
          component: () => import('@/components/RichTextEditor.vue'), // 新增路由指向RichTextEditor.vue
        },
        {
          path: '/documents/newWord',
          name: 'NewWord',
          component: () => import('@/components/WordEditor.vue'),
        },
        {
          path: '/documents/newExcel',
          name: 'NewExcel',
          component: () => import('@/components/ExcelEditor.vue'),
        },
        {
          path: '/system/user',
          name: 'UserManagement',
          component: () => import('@/views/UserManagement.vue'),
        },
        {
          path: '/system/menu',
          name: 'MenuManagement',
          component: () => import('@/views/MenuManagement.vue'),
        },
        {
          path: '/system/role',
          name: 'RoleManagement',
          component: () => import('@/views/RoleManagement.vue'),
        },
        {
          path: 'system/company',
          name: 'CompanyManagement',
          component: () => import('@/views/CompanyManagement.vue'),
        },
        {
          path: '/system/dept',
          name: 'DeptManagement',
          component: () => import('@/views/DeptManagement.vue'),
        },
        {
          path: '/target',
          name: 'TargetManagement',
          component: () => import('@/views/TargetManage.vue'),
        },
        {
          path: '/targetTask',
          name: 'TargetTask',
          component: () => import('@/views/TargetTask.vue'), // 新增路由指向MonthTask.vue
        },
        {
          path: '/dayTask',
          name: 'DayTask',
          component: () => import('@/views/DayTask.vue'), // 新增路由指向DayTask.vue
        }
      ]
    },
    {
      path: '/login',
      name: 'Login',
      component: () => import('@/views/Login.vue'),
    },
    {
      path: '/register',
      name: 'Register',
      component: () => import('@/views/Register.vue'), // 更新注册路由
    },
  ],
});

// 导航守卫
// 使用 router.beforeEach 注册一个全局前置守卫,判断用户是否登陆
router.beforeEach((to, from, next) => {
  if (to.path === '/login') {
    next();
  } else {
    let token = localStorage.getItem('Authorization');
 
    if (token === null || token === '') {
      next('/login');
    } else {
      next();
    }
  }
});

export default router;

main.js中引入elementui,router

main.js

复制代码
import Vue from 'vue'
import App from './App.vue'
import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
import store from './store'
import router from './router/index'

// import mavonEditor from 'mavon-editor'
// import 'mavon-editor/dist/css/index.css';
// import mermaidItMarkdown from 'mermaid-it-markdown'
// mavonEditor.mavonEditor.getMarkdownIt().use(mermaidItMarkdown)
// Vue.use(mavonEditor)

Vue.config.productionTip = false

Vue.use(ElementUI)

new Vue({
  store,
  router,
  render: h => h(App),
}).$mount('#app')

编写左侧导航

复制代码
 <!-- 第二部分:导航栏和内容显示区域 -->
    <div class="main-content">
      <el-menu
        :default-active="activeMenu"
        @select="handleMenuSelect"
        background-color="#545c64"
        text-color="#fff"
        active-text-color="#ffd04b"
        :collapse="isCollapse"
      >
        <el-menu-item
          v-for="item in filteredMenuItems"
          :key="item.menuPath"
          :index="item.menuPath"
        >
          <i :class="item.menuIcon"></i>
          <span slot="title">{{ item.menuName }}</span>
        </el-menu-item>
        <el-submenu
          v-for="item in menuItemsWithChildren"
          :key="item.menuPath"
          :index="item.menuPath"
        >
          <template #title>
            <i :class="item.menuIcon"></i>
            <span slot="title">{{ item.menuName }}</span>
          </template>

          <el-menu-item
            v-for="child in item.children"
            :key="child.menuPath"
            :index="child.menuPath"
          >
            <i :class="child.menuIcon"></i>
            <span slot="title">{{ child.menuName }}</span>
          </el-menu-item>
        </el-submenu>
      </el-menu>
		//路由出口
      <div class="content">
        <router-view></router-view>
      </div>
    </div>

返回的菜单数据

菜单数据时根据用户id请求后端菜单权限后返回的菜单数据

复制代码

动态导航

安装vue-router、elementui步骤与静态导航相同

编写router/index.js

复制代码
import Vue from "vue";
import VueRouter from "vue-router";

Vue.use(VueRouter);

const constantRoutes = [
    {
        path: "/login",
        name: "Login",
        component: () => import("@/views/Login.vue")
    },
    {
        path: "/home",
        name: "Home",
        component: () => import("@/views/Home.vue"),
        children: []
    },
    // {
    //     path: "*",
    //     name: "NotFound",
    //     component: () => import("@/views/NotFound.vue")
    // },
]

const createRouter = () => new VueRouter({
    mode: "hash",
    routes: constantRoutes
})

const router = createRouter();

//路由重置方法
export function resetRouter() {
    const newRouter = createRouter();
    router.matcher = newRouter.matcher; // 重置路由
}

//动态加载路由方法
export const addDynamicRoutes = (menus) => {
  debugger;
  const routes = [];
  //1.转换菜单为路由配置
  const asyncRoutes = coverMenusToRoutes1(routes,menus);
  //2. 添加嵌套路由到Home
  asyncRoutes.forEach(route => {
    // debugger;
    // if (route.name !== '') {
    //   router.addRoute("Home", route);
    // }
    router.addRoute("Home", route);
  });
}

//菜单转换路由方法
const coverMenusToRoutes = (menus) => {
  if (!menus) return [];
  const routes = [];
  menus.forEach(menu => {
    const route = {
      path: menu.path,
      name: menu.path.slice(1),
      meta: {title: menu.name,icon: menu.icon},
      component: resolveComponent(menu.component),
    };
    if (menu.children && menu.children.length > 0) {
      route.children = coverMenusToRoutes(menu.children);
    }
    routes.push(route);
  })
  return routes;
}

const coverMenusToRoutes1 = (routes,menus) => {
  if (!menus) return [];
  // const routes = [];
  menus.forEach(menu => {
    if (menu.component.length > 0){
      const route = {
        path: menu.path,
        name: menu.path.slice(1),
        meta: {title: menu.name,icon: menu.icon},
        component: resolveComponent(menu.component),
      };
      routes.push(route);
    }
    if (menu.children && menu.children.length > 0) {
      coverMenusToRoutes1(routes,menu.children);
    }
  })
  return routes;
}

//动态解析组件路由
function resolveComponent(component) {
  if (!component) return undefined;
  return () => import(`@/views/${component}`);
}
// 导航守卫
// 使用 router.beforeEach 注册一个全局前置守卫,判断用户是否登陆
router.beforeEach((to, from, next) => {
    if (to.path === '/login') {
      next();
    } else {
      let token = localStorage.getItem('token');
      if (token === null || token === '') {
        next('/login');
      } else {
        next();
      }
    }
  });

export default router;

动态导航需要特别注意路径问题,如果路径不正确会导致菜单无法正常显示,因为在项目中返回的菜单数据时树形结构,在处理菜单数据时如果按树形结构嵌套再添加到Home路由的children列表中,菜单无法正常的显示,后面修改了处理逻辑,把有组件的菜单添加到Home路由的children列表后,菜单可以正常显示,需要特别注意

左侧菜单

通过for循环生成

复制代码
<template>
  <el-menu
    :default-active="defaultActive"
    class="el-menu"
    @open="handleOpen"
    @close="handleClose"
    @select="handleSelect"
    :collapse="isCollapse"
    background-color="#545c64"
    text-color="#fff"
    active-text-color="#ffd04b"
  >
    <span><i :class="collapseClass" @click="changeMenu"></i></span>
    <template v-for="item in menuList">
      <el-submenu
        v-if="item.children && item.children.length"
        :index="item.path"
        :key="item.id"
      >
        <template slot="title">
          <i :class="item.icon"></i>
          <span slot="title">{{ item.name }}</span>
        </template>
        <template v-for="child in item.children">
          <el-submenu v-if="child.children && child.children.length" :index="child.path" :key="child.id">
            <template slot="title">
              <i :class="child.icon"></i>
              <span slot="title">{{child.name}}</span>
            </template>
            <el-menu-item v-for="ch in child.children" :index="ch.path" :key="ch.id">
              <i :class="ch.icon"></i>
              <span slot="title">{{ch.name}}</span>
            </el-menu-item>
          </el-submenu>
          <!-- v-for="child in item.children" -->
          <el-menu-item v-else
            :index="child.path"
            :key="child.id"
          >
            <i :class="child.icon"></i>
            <span slot="title">{{ child.name }}</span>
          </el-menu-item>
        </template>
      </el-submenu>
      <el-menu-item v-else :index="item.path" :key="item.id">
        <i :class="item.icon"></i>
        <span slot="title">{{ item.name }}</span>
      </el-menu-item>
    </template>
  </el-menu>
</template>

<script>
export default {
  data() {
    return {
      collapseClass: "el-icon-s-fold",
      isCollapse: false,
      defaultActive: "1-4-1",
      menuList: [],
    };
  },
  mounted() {
    //获取动态菜单
    this.createMenuList();
  },
  methods: {
    createMenuList() {
      console.log(this.$store.state.menus);
      this.menuList = this.$store.state.menus;
    },
    handleOpen(key, keyPath) {
      console.log(key, keyPath);
    },
    handleClose(key, keyPath) {
      console.log(key, keyPath);
    },
    changeMenu() {
      this.isCollapse = !this.isCollapse;
      if (this.isCollapse) {
        this.collapseClass = "el-icon-s-unfold";
      } else {
        this.collapseClass = "el-icon-s-fold";
      }
    },
    handleSelect(index) {
      this.activeMenu = index;
      if (this.$route.path !== index) {
        // 检查当前路径是否与目标路径相同
        this.$router.push(index);
      }
    },
  },
};
</script>

<style>
.el-menu:not(.el-menu--collapse) {
  width: 220px;
  /* height: 100vh; */
  overflow: hidden;
}
.el-menu {
  width: 60px;
  /* height: 100vh; */
  overflow: hidden;
}
</style>

通过for循环+递归生成

菜单生成子组件

复制代码
<template>
  <el-menu
    :default-active="defaultActive"
    class="el-menu"
    @open="handleOpen"
    @close="handleClose"
    @select="handleSelect"
    :collapse="isCollapse"
    background-color="#545c64"
    text-color="#fff"
    active-text-color="#ffd04b"
  >
    <template v-for="item in menuList">
      <el-submenu v-if="item.children && item.children.length" :index="item.path" :key="item.id">
      <template slot="title">
        <i :class="item.icon"></i>
        <span slot="title">{{item.name}}</span>
      </template>
      <!-- 递归调用 -->
      <AsideMenu :menuList="item.children"></AsideMenu>
    </el-submenu>
    <el-menu-item v-else :index="item.path" :key="item.id">
        <i :class="item.icon"></i>
        <span slot="title">{{item.name}}</span>
    </el-menu-item>
    </template>
  </el-menu>
</template>

<script>
export default {
  name: "AsideMenu", //name必须要有,要和递归调用的名称保持一致
  components: {
  },
  props: {
    menuList: [],
    // eslint-disable-next-line vue/require-prop-type-constructor
    isCollapse: false,
  },
  data() {
    return {
      // collapseClass: "el-icon-s-fold",
      // isCollapse: false,
      defaultActive: "1-4-1",
    };
  },
  methods: {
    handleOpen(key, keyPath) {
      console.log(key, keyPath);
    },
    handleClose(key, keyPath) {
      console.log(key, keyPath);
    },
    handleSelect(index) {
      debugger;
      this.activeMenu = index;
      console.log(index);
      console.log(this.$route);
      console.log(this.$router.getRoutes());
      if (this.$route.path !== index) {
        // 检查当前路径是否与目标路径相同
        this.$router.push(index);
      }
    },
  },
};
</script>

<style>
.el-menu:not(.el-menu--collapse) {
  width: 220px;
  /* height: 100vh; */
  overflow: hidden;
  border-right: none; /* 隐藏右侧的边框 */
}
.el-menu {
  width: 60px;
  /* height: 100vh; */
  overflow: hidden;
  border-right: none;
}
</style>

子组件名称是必须要有的,递归调用时按照名称进行递归调用。在这个项目中子组件名称为:AsideMenu,递归调用时使用 AsideMenu来引用自身

调用菜单生成的父组件

复制代码
<template>
  <div class="sidebar">
    <span><i :class="collapseClass" @click="changeMenu"></i></span>
    <!-- 调用菜单生成组件 -->
     <aside-menu :menuList="menuList" :isCollapse="isCollapse"  />
  </div>
</template>
<script>
import AsideMenu from '@/components/AsideMenu.vue';
export default {
  components: {
    AsideMenu
  },
  data() {
    return {
      collapseClass: "el-icon-s-fold",
      isCollapse: false,
      menuList: []
    };
  },
  mounted() {
    //获取动态菜单
    this.createMenuList();
  },
  methods: {
    createMenuList() {
      console.log(this.$store.state.menus);
      this.menuList = this.$store.state.menus;
    },
    changeMenu() {
      this.isCollapse = !this.isCollapse;
      if (this.isCollapse) {
        this.collapseClass = "el-icon-s-unfold";
      } else {
        this.collapseClass = "el-icon-s-fold";
      }
    },
  }
};
</script>

<style scoped>
.sidebar {
    /* border: 1px solid red; */
    background-color:#545c64;
}
</style>

store/index.js

使用vuex保存用户id,token,菜单列表,权限信息,角色信息

复制代码
import Vue from "vue";
import vuex from "vuex";

Vue.use(vuex);

const store = new vuex.Store({
    state: {
        //用户id
        userId: {},
        //用户token
        token: "",
        //用户角色
        role: "",
        //用户权限
        permission: "",
        //用户菜单
        menus: [],
        //用户路由
    },
    getters: {
        //获取用户id
        getUserId(state) {
            return state.userId;
            
        },
        //获取用户token
        getToken(state) {
            return state.token;
        },
        //获取用户角色
        getRole(state) {
            return state.role;
        },
        //获取用户权限
        getPermission(state) {
            return state.permission;
       },
        //获取用户菜单
        getMenus(state) {
            return state.menus;
        },
    },
    mutations: {
        //设置用户id
        setUserId(state, userId) {
            state.userId = userId;
            localStorage.setItem("userId", userId);
        },
        //设置用户token
        setToken(state, token) {
            state.token = token;
            localStorage.setItem("token", token);
        },
        //设置用户角色
        setRole(state, role) {
            state.role = role;
        },
        //设置用户权限
        setPermission(state, permission) {
            state.permission = permission;
        },
        //设置用户菜单
        setMenus(state, menus) {
            state.menus = menus;
            localStorage.setItem("menus", menus);
        },
    }
})

export default store;

main.js中引入store

复制代码
import Vue from 'vue'
import App from './App.vue'
import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
import router from './router'
import store from './store'

Vue.use(ElementUI)

Vue.config.productionTip = false

new Vue({
    store,
    router,
    render: h => h(App),
}).$mount('#app')

登录页面代码

登录页面存储用户信息、菜单信息、并动态加载路由

复制代码
<!-- eslint-disable vue/multi-word-component-names -->
<template>
  <el-row type="flex" justify="center" align="middle" style="height: 100vh;">
    <el-col :xs="24" :sm="12" :md="8" :lg="6">
      <el-card class="box-card">
        <div class="clearfix">
          <img src="@/assets/logo.png" class="logo" />
          <h2>欢迎登录</h2>
        </div>
        <el-form :model="loginForm" ref="loginForm" :rules="loginRules" label-width="100px">
          <el-form-item label="用户名" prop="username">
            <el-input v-model="loginForm.username" prefix-icon="el-icon-user" autocomplete="off"></el-input>
          </el-form-item>
          <el-form-item label="密码" prop="password">
            <el-input type="password" v-model="loginForm.password" prefix-icon="el-icon-lock" show-password autocomplete="off"></el-input>
          </el-form-item>
          <el-form-item>
            <el-button type="primary" @click="submitForm">登录</el-button>
            <el-button @click="resetForm">重置</el-button>
          </el-form-item>
        </el-form>
      </el-card>
    </el-col>
  </el-row>
</template>
 
<script>
import { Message } from 'element-ui';
import http from '../request/http'
import { resetRouter,addDynamicRoutes } from '@/router/index'
export default {
  data() {
    return {
      loginForm: {
        username: '',
        password: ''
      },
      loginRules: {
        username: [{ required: true, message: '请输入用户名', trigger: 'blur' }],
        password: [{ required: true, message: '请输入密码', trigger: 'blur' }]
      }
    };
  },
  methods: {
    submitForm() {
      this.$refs.loginForm.validate((valid) => {
        if (valid) {
          // 这里可以添加提交到服务器的逻辑
          http.post('/user/login',this.loginForm,{
                headers: {
                  "Content-Type": "application/json;charset=UTF-8",
                },
              }).then((res) => {
            if (res.data.code === 200) {
              // 登录成功,可以进行后续操作,如跳转到主页
              //保存用户id、token、菜单列表
              // console.log(res.data.data);
              const userId = res.data.data.user.id;
              const token = res.data.data.user.token;
              this.$store.commit('setUserId', userId);
              this.$store.commit('setToken', token);
              this.$store.commit('setMenus', res.data.data.menus);
              //创建动态路由
              //1.重置路由
              resetRouter();
              //2.添加动态路由
              const menus = res.data.data.menus;
              addDynamicRoutes(menus);
              this.$router.push('/home');
            } else {
              // 登录失败,可以提示错误信息
              Message.error(res.msg);

            }
          });
        } else {
          console.log('表单验证失败!');
          return false;
        }
      });
    },
    resetForm() {
      this.$refs.loginForm.resetFields();
    }
  }
};
</script>
 
<style scoped>
.box-card {
  /* border: 1px solid red; */
  width: 100%; /* 或者具体宽度 */
  border-radius: 10px; /* 圆角 */
  box-shadow: 0 0 10px rgba(0,0,0,0.1); /* 阴影 */
  background-color:aliceblue;
}

.el-row {
    background-image: url('../assets/pic01.jpg');
    background-size: cover;
    background-position: center;
    background-repeat: no-repeat;
}

.logo {
  width: 80px;
  height: 80px;
}
</style>

菜单返回数据

复制代码
{
    "code": 200,
    "message": "请求成功",
    "data": {
        "menus": [
            {
                "id": "1913479834787434497",
                "parentId": null,
                "name": "系统管理",
                "path": "",
                "component": "",
                "perms": null,
                "type": 1,
                "icon": "el-icon-s-tools",
                "orderNum": 0,
                "visible": false,
                "createTime": null,
                "updateTime": null,
                "rf1": null,
                "rf2": null,
                "rf3": null,
                "rf4": null,
                "rf5": null,
                "children": [
                    {
                        "id": "1913484019050274818",
                        "parentId": "1913479834787434497",
                        "name": "菜单管理",
                        "path": "/menu",
                        "component": "MenuManage.vue",
                        "perms": null,
                        "type": 1,
                        "icon": "el-icon-menu",
                        "orderNum": 0,
                        "visible": false,
                        "createTime": null,
                        "updateTime": null,
                        "rf1": "系统管理",
                        "rf2": null,
                        "rf3": null,
                        "rf4": null,
                        "rf5": null,
                        "children": null
                    },
                    {
                        "id": "1913488214084083714",
                        "parentId": "1913479834787434497",
                        "name": "二级菜单",
                        "path": "",
                        "component": "",
                        "perms": null,
                        "type": 1,
                        "icon": "el-icon-location",
                        "orderNum": 1,
                        "visible": false,
                        "createTime": null,
                        "updateTime": null,
                        "rf1": "系统管理",
                        "rf2": null,
                        "rf3": null,
                        "rf4": null,
                        "rf5": null,
                        "children": [
                            {
                                "id": "1913488799369846786",
                                "parentId": "1913488214084083714",
                                "name": "三级菜单",
                                "path": "/san",
                                "component": "SanManage.vue",
                                "perms": null,
                                "type": 1,
                                "icon": "el-icon-star-on",
                                "orderNum": 0,
                                "visible": false,
                                "createTime": null,
                                "updateTime": null,
                                "rf1": "二级菜单",
                                "rf2": null,
                                "rf3": null,
                                "rf4": null,
                                "rf5": null,
                                "children": null
                            }
                        ]
                    }
                ]
            }
        ],
        "user": {
            "id": "123456",
            "username": "admin",
            "password": "$2a$10$0uPlhwgy.OlkV20pRJ/9Wu8OJ61OfbcMqMXf60qI4qsahlxJD4iUq",
            "nickname": "wangcheng",
            "avatar": null,
            "email": null,
            "mobile": null,
            "status": 1,
            "deptId": null,
            "createTime": null,
            "updateTime": null,
            "token": "eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiIzMWMwOTgzNzQ2MTQ0ZGNiYmJhZTgwZmJhYzNkNWFjMSIsImlhdCI6MTc0NTE5NjAxNCwic3ViIjoiMTIzNDU2IiwiZXhwIjoxNzQ1ODAwODE0fQ.OyZaKaHRfu0PfMSubrnU1qLDOQHfisdQkTvByCMeIes",
            "roles": null
        }
    }
}

总结

项目开发过程中动态菜单生成、动态路由的正确配置是困难点。

动态菜单的递归调用

递归调用最主要的是对自身的调用,要保持名称和调用自身组件的一致性。

动态路由

动态路由要注意路径问题,不要因为菜单返回树形结构,后期处理的路由也是树形结构,造成子路由里面多层嵌套,无法正常渲染菜单。

记录菜单处理的另外一种方式

MenuTreeOne.vue

复制代码
<template>
  <div class="custom-menu-tree">
    <template v-for="item in menuList">
      <el-submenu
        v-if="item.children && item.children.length"
        :index="item.path"
        :key="item.id"
      >
        <template slot="title">
          <i :class="item.icon"></i>
          <span slot="title">{{ item.name }}</span>
        </template>
        <!-- 递归调用 -->
        <AsideMenuOne :menuList="item.children"></AsideMenuOne>
      </el-submenu>
      <el-menu-item v-else :index="item.path" :key="item.id">
        <i :class="item.icon"></i>
        <span slot="title">{{ item.name }}</span>
      </el-menu-item>
    </template>
  </div>
</template>

<script>
export default {
  name: "AsideMenuOne",
  props: {
    menuList: [],
  },
  data() {
    return {};
  },
  mounted() {},
  methods: {},
};
</script>

<style scoped>
.custom-menu-tree {
  height: 100%;
  display: flex;
  flex-direction: column;
}
</style>

AsideMenuOne.vue

复制代码
<template>
  <el-menu
    :default-active="defaultActive"
    class="el-menu"
    @open="handleOpen"
    @close="handleClose"
    @select="handleSelect"
    :collapse="isCollapse"
    background-color="#545c64"
    text-color="#fff"
    active-text-color="#ffd04b"
  >
    <span><i :class="collapseClass" @click="changeMenu"></i></span>
    <menu-tree-one :menuList="menuList"></menu-tree-one>
  </el-menu>
</template>

<script>
import MenuTreeOne from '@/components/MenuTreeOne.vue';
export default {
  components: { MenuTreeOne },
  data() {
    return {
      collapseClass: "el-icon-s-fold",
      isCollapse: false,
      defaultActive: "1-4-1",
      menuList: [],
    };
  },
  mounted() {
    //获取动态菜单
    this.createMenuList();

  },
  methods: {
    createMenuList() {
      console.log(this.$store.state.menus);
      this.menuList = this.$store.state.menus;
    },
    handleOpen(key, keyPath) {
      console.log(key, keyPath);
    },
    handleClose(key, keyPath) {
      console.log(key, keyPath);
    },
    changeMenu() {
      this.isCollapse = !this.isCollapse;
      if (this.isCollapse) {
        this.collapseClass = "el-icon-s-unfold";
      } else {
        this.collapseClass = "el-icon-s-fold";
      }
    },
    handleSelect(index) {
      this.activeMenu = index;
      if (this.$route.path !== index) {
        // 检查当前路径是否与目标路径相同
        this.$router.push(index);
      }
    },
  },
};
</script>

<style>
.el-menu:not(.el-menu--collapse) {
  width: 220px;
  /* height: 100vh; */
  overflow: hidden;
}
.el-menu {
  width: 60px;
  /* height: 100vh; */
  overflow: hidden;
}
</style>
相关推荐
星空寻流年2 分钟前
css3新特性第五章(web字体)
前端·css·css3
加油乐8 分钟前
JS计算两个地理坐标点之间的距离(支持米与公里/千米)
前端·javascript
小小小小宇8 分钟前
前端在 WebView 和 H5 环境下的缓存问题
前端
懒羊羊我小弟12 分钟前
React JSX 语法深度解析与最佳实践
前端·react.js·前端框架
冷冷清清中的风风火火16 分钟前
关于敏感文件或备份 安全配置错误 禁止通过 URL 访问 Vue 项目打包后的 .gz 压缩文件
前端·vue.js·安全
纪元A梦27 分钟前
华为OD机试真题——数据分类(2025A卷:100分)Java/python/JavaScript/C++/C语言/GO六种最佳实现
java·javascript·c++·python·华为od·go·华为od机试题
小行星12528 分钟前
前端根据后端返回的excel二进制文件流进行导出下载
前端·excel
Moment37 分钟前
前端远程面试全记录:项目、思维、管理一个不落 😔😔😔
前端·javascript·面试
snakeshe10101 小时前
React Lane模型:优先级与批处理的解耦革命
前端
闲不住的李先森1 小时前
使用 ts-enum-next 优雅的管理 typeScript enum
前端·typescript·代码规范