Vue实战【调整Vue-element-admin中的菜单栏,并添加顶部模块菜单栏】

news2025/7/6 4:43:54

目录

  • 🌟前言
  • 🌟小伙伴们先看
  • 🌟实现思路
  • 🌟具体代码
  • 🌟最后

🌟前言

因为最近在整合公司的项目,需要把所有系统里的功能集成到一个项目里,这样就导致菜单栏目录会特别的多,不便于用户使用,体验效果极差。于是想到了一个方法,就是增加顶部导航栏,点击的时候让侧边菜单栏在显示相对应模块的所有菜单;这样的话就可以很大程度提升我们的用户体验啦。

🌟小伙伴们先看

在这里插入图片描述

🌟实现思路

嗯,干活前一定要先把思路理清楚,记在小本本上,画个图都行哈哈

  • 布局方面我需要在Navbar组件内添加一个导航组件以便我们去渲染顶部模块菜单;
  • 因为是动态路由所以我们可以:
    • 登录的时候让后端返回所有的当前用户下所有的菜单权限;
    • 登录时候只返回默认显示的菜单,每次点击的时候再去获取相应的模块菜单权限。

我这边用的是第一种方式,登陆的时候获取全部的存在vuex里,每次点击的时候再去处理相应的数据;小伙伴们也可以尝试一下第二种方式哦。

🌟具体代码

话不多说,直接开整。。。

<!--src/layout/components/Navbar.vue-->
<template>
  <div class="navbar">
    <hamburger :is-active="sidebar.opened" class="hamburger-container" @toggleClick="toggleSideBar" />

    <!--重点一:顶部menu-->
    <el-menu
      mode="horizontal"
      default-active="/"
      @select="handleSelect"
    >
      <el-menu-item v-for="item in menuList" :key="item.path" class="menuItem" :index="item.path">
        <icon :class="item.meta?item.meta.icon:''" />
        <span slot="title">{{ item.name }}</span>
      </el-menu-item>
    </el-menu>
    <div class="right-menu">
      <el-dropdown class="avatar-container" trigger="click">
        <div class="avatar-wrapper">
          <img :src="avatar+'?imageView2/1/w/80/h/80'" class="user-avatar">
          <i class="el-icon-caret-bottom" />
        </div>
        <el-dropdown-menu slot="dropdown" class="user-dropdown">
          <router-link to="/">
            <el-dropdown-item>
              Home
            </el-dropdown-item>
          </router-link>
          <a target="_blank" href="https://github.com/PanJiaChen/vue-admin-template/">
            <el-dropdown-item>Github</el-dropdown-item>
          </a>
          <a target="_blank" href="https://panjiachen.github.io/vue-element-admin-site/#/">
            <el-dropdown-item>Docs</el-dropdown-item>
          </a>
          <el-dropdown-item divided @click.native="logout">
            <span style="display:block;">Log Out</span>
          </el-dropdown-item>
        </el-dropdown-menu>
      </el-dropdown>
    </div>
  </div>
</template>

<script>
import { mapGetters } from 'vuex'
import Breadcrumb from '@/components/Breadcrumb'
import Hamburger from '@/components/Hamburger'

export default {
  components: {
    Breadcrumb,
    Hamburger
  },
  computed: {
    ...mapGetters([
      'sidebar',
      'avatar'
    ]),
    toIndex() { // 根据路径绑定到对应的一级菜单,防止页面刷新重新跳回第一个
      return '/' + this.$route.path.split('/')[1]
    }
  },
  // eslint-disable-next-line vue/order-in-components
  data() {
    return {
      menuList: [ // 水平一级菜单栏的菜单

      ]
    }
  },
  mounted() {
  // 初始化菜单数据
    this.initMenuList()
  },
  methods: {
  // 重点二:
  // 因为整个项目工程比较大,所以当时搭建了一个demo,菜单数据卸载了本地;
  // 大家在实现的时候可以通过上边第一种方法;
  // 后台获取回来数据以后通过 router.addRoutes(获取回来的菜单数组)方法;
  // 动态的挂载到我们的router上。
    initMenuList() {
      const menuList = ['/login', '/404']
      this.menuList = this.$router.options.routes.filter((v, i) => {
        return v.path !== menuList[i]
      })
    },
    // 重点三:
    // 根据当前惦记的顶部模块菜单去切换左侧菜单栏,把当前点击的菜单path存在vuex里
    // 我这边是存在了store/modules/user里边,这个没有要求,小伙伴们随意
    handleSelect(path) {
      this.$store.dispatch('user/setPath', path)
    },

    toggleSideBar() {
      this.$store.dispatch('app/toggleSideBar')
    },
    async logout() {
      await this.$store.dispatch('user/logout')
      this.$router.push(`/login?redirect=${this.$route.fullPath}`)
    }
  }
}
</script>

<style lang="scss" scoped>
.navbar {
  display: flex;
  justify-content: space-between;
  height: 50px;
  overflow: hidden;
  position: relative;
  background: #fff;
  box-shadow: 0 1px 4px rgba(0,21,41,.08);

  .hamburger-container {
    line-height: 46px;
    height: 100%;
    float: left;
    cursor: pointer;
    transition: background .3s;
    -webkit-tap-highlight-color:transparent;

    &:hover {
      background: rgba(0, 0, 0, .025)
    }
  }

  .breadcrumb-container {
    float: left;
  }

  .right-menu {
    float: right;
    height: 100%;
    line-height: 50px;

    &:focus {
      outline: none;
    }

    .right-menu-item {
      display: inline-block;
      padding: 0 8px;
      height: 100%;
      font-size: 18px;
      color: #5a5e66;
      vertical-align: text-bottom;

      &.hover-effect {
        cursor: pointer;
        transition: background .3s;

        &:hover {
          background: rgba(0, 0, 0, .025)
        }
      }
    }

    .avatar-container {
      margin-right: 30px;

      .avatar-wrapper {
        margin-top: 5px;
        position: relative;

        .user-avatar {
          cursor: pointer;
          width: 40px;
          height: 40px;
          border-radius: 10px;
        }

        .el-icon-caret-bottom {
          cursor: pointer;
          position: absolute;
          right: -20px;
          top: 25px;
          font-size: 12px;
        }
      }
    }
  }
}
  .menuItem{
    height: 47px;
  }
</style>

// src/store/modules/user
import { login, logout, getInfo } from '@/api/user'
import { getToken, setToken, removeToken } from '@/utils/auth'
import { resetRouter } from '@/router'

const getDefaultState = () => {
  return {
    token: getToken(),
    name: '',
    avatar: '',
    // 定义以下两个状态
    menuList: [], // 动态路由
    path: '/' // 当前点击的菜单模块path
  }
}

const state = getDefaultState()

const mutations = {
  RESET_STATE: (state) => {
    Object.assign(state, getDefaultState())
  },
  SET_TOKEN: (state, token) => {
    state.token = token
  },
  SET_NAME: (state, name) => {
    state.name = name
  },
  SET_AVATAR: (state, avatar) => {
    state.avatar = avatar
  },
  // 定义SET_MENULIST方法用来保存我们的动态路由
  SET_MENULIST: (state, menuList) => {
    state.menuList = menuList
  },
   // 定义SET_MENULIST方法用来保存我当前点击的顶部模块菜单path
  SET_PATH: (state, path) => {
    state.path = path
  }
}

const actions = {
  // user login
  login({ commit }, userInfo) {
    const { username, password } = userInfo
    return new Promise((resolve, reject) => {
      login({ username: username.trim(), password: password }).then(response => {
        const { data } = response
        commit('SET_TOKEN', data.token)
        setToken(data.token)
        resolve()
      }).catch(error => {
        reject(error)
      })
    })
  },
  
  //定义两个actions 方法用来执行我们上边定义的SET_MENULIST和SET_PATH
  setMenuList({ commit }, menuList) {
    commit('SET_MENULIST', menuList)
  },
  setPath({ commit }, path) {
    commit('SET_PATH', path)
  },

  // get user info
  getInfo({ commit, state }) {
    return new Promise((resolve, reject) => {
      getInfo(state.token).then(response => {
        const { data } = response

        if (!data) {
          return reject('Verification failed, please Login again.')
        }

        const { name, avatar } = data

        commit('SET_NAME', name)
        commit('SET_AVATAR', avatar)
        resolve(data)
      }).catch(error => {
        reject(error)
      })
    })
  },

  // user logout
  logout({ commit, state }) {
    return new Promise((resolve, reject) => {
      logout(state.token).then(() => {
        removeToken() // must remove  token  first
        resetRouter()
        commit('RESET_STATE')
        resolve()
      }).catch(error => {
        reject(error)
      })
    })
  },

  // remove token
  resetToken({ commit }) {
    return new Promise(resolve => {
      removeToken() // must remove  token  first
      commit('RESET_STATE')
      resolve()
    })
  }
}

export default {
  namespaced: true,
  state,
  mutations,
  actions
}


动态菜单和path都存好了以后我们就可以根据当前惦记的path去动态的渲染我们的侧边栏啦

<!--src/layout/components/Sidebar/index.vue-->
<template>
  <div :class="{'has-logo':showLogo}">
    <logo v-if="showLogo" :collapse="isCollapse" />
    <el-scrollbar wrap-class="scrollbar-wrapper">
      <el-menu
        :default-active="activeMenu"
        :collapse="isCollapse"
        :background-color="variables.menuBg"
        :text-color="variables.menuText"
        :unique-opened="false"
        :active-text-color="variables.menuActiveText"
        :collapse-transition="false"
        mode="vertical"
      >
        <sidebar-item v-for="route in menuList" :key="route.path" :item="route" :base-path="route.path" />
      </el-menu>
    </el-scrollbar>
  </div>
</template>

<script>
import { mapGetters } from 'vuex'
import Logo from './Logo'
import SidebarItem from './SidebarItem'
import variables from '@/styles/variables.scss'

export default {
  components: { SidebarItem, Logo },
  computed: {
    ...mapGetters([
      'sidebar'
    ]),
    routes() {
      return this.$router.options.routes
    },
    activeMenu() {
      const route = this.$route
      const { meta, path } = route
      // if set path, the sidebar will highlight the path you set
      if (meta.activeMenu) {
        return meta.activeMenu
      }
      return path
    },
    showLogo() {
      return this.$store.state.settings.sidebarLogo
    },
    variables() {
      return variables
    },
    isCollapse() {
      return !this.sidebar.opened
    }
  },
  watch: {
  // 因为每次点击顶部菜单的时候path都会改变,所以我们要对它进行监听;
  // 通过数组的filter方法去过滤出来我们想要的菜单数组就可以啦。
    '$store.state.user.path': {
      handler: function(newVal, oldVal) {
        console.log('新值' + newVal, '旧值' + oldVal)
        console.log('vuex里存的菜单', this.$store.state.user.menuList)
        this.menuList = this.$store.state.user.menuList.filter(v => {
          return newVal === v.path
        })
      }
    }
  },
  mounted() {
  // 页面渲染时候获取一下vuex里的menuList,因为刚才在vuex里定义的path默认给了'/';
  // 所以第一次进来的时候默认显示的首页
    console.log('当前path', this.$store.state.user.path)
    this.$store.dispatch('user/setMenuList', this.$router.options.routes)
    this.menuList = this.$store.state.user.menuList.filter(v => {
      return this.$store.state.user.path === v.path
    })
  },
  // eslint-disable-next-line vue/order-in-components
  data() {
    return {
      menuList: []
    }
  }
}
</script>

🌟最后

当我们接到新需求的时候,一定要仔细分析把逻辑梳理清楚了;复杂的话我们可以画一下流程图以便我们更好的去写代码;万变不离其宗,思路最重要。小伙伴们如果有更好的思路,可以一起交流,共同进步。

✨原创不易,还希望各位大佬支持一下!
👍 点赞,你的认可是我创作的动力!
⭐️ 收藏,你的青睐是我努力的方向!
✏️ 评论,你的意见是我进步的财富!

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

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

相关文章

【JavaScript】手撕前端面试题:事件委托 | 判断URL是否合法 | 全排列

&#x1f5a5;️ NodeJS专栏&#xff1a;Node.js从入门到精通 &#x1f5a5;️ 博主的前端之路&#xff08;源创征文一等奖作品&#xff09;&#xff1a;前端之行&#xff0c;任重道远&#xff08;来自大三学长的万字自述&#xff09; &#x1f5a5;️ TypeScript知识总结&…

【Axure】Axure RP 9下载、安装、授权、汉化

目录一、Axure RP 9 下载二、Axure RP 9 安装三、Axure RP 9 授权四、Axure RP 9 汉化一、Axure RP 9 下载 1、Axure RP 9 下载地址&#xff1a;https://www.axure.com/release-history/rp9 2、其他版本下载地址 ①登录axure官网:https://www.axure.com/ ②拉到最下面找到相关…

很好看的爱心表白代码(动态)

分享几个好看的爱心表白代码❤️爱心代码❤️&#xff08;C语言&#xff09;❤️流动爱心❤️&#xff08;htmlcssjs&#xff09;❤️线条爱心❤️&#xff08;htmlcssjs&#xff09;❤️biu表白爱心❤️&#xff08;htmlcssjs&#xff09;❤️matlab爱心函数❤️&#xff08;需…

Vue3+TS+Vite 入门指南

最近尝试上手 Vue3TSVite&#xff0c;对比起 Vue2 有些不适应&#xff0c;但还是真香~ 上手前先说下 Vue3 的一些变化吧~ Vue3 的变化 Vue3 带来的变化主要有以下几个方面&#xff1a; 使用层面 对比起 Vue2 启动速度快很多&#xff0c;新项目从 1s 升级到不到 500msvite.co…

Element-UI新手学习记录(一)

Layout 布局 通过基础的 24 分栏&#xff0c;迅速简便地创建布局。 span的作用 一行默认24个span&#xff0c;属性放在el-col中决定此元素占据多少span gutter属性 放在el-row中&#xff0c;给各个块之前设置间隔&#xff0c;但是是割的代码块的宽度。 offset属性 放在el…

小程序页面之间数据传递的四种方法

近期再使用小程序开发的时候遇到小程序页面和页面之间的数据传递问题。总结一下大致有以下几种方式实现页面数据传递。 最常见的就是路由传参&#xff0c;使用场景主要是页面汇总的少量数据的传递。以下都以Tarovue示例&#xff0c;原生、react或者uniapp同理&#xff0c;替换…

Pinia(二)了解和使用Store

Store Store 是保存状态(state)和业务逻辑的实体, store 不应该与我们的组件绑定. 换句话说, store 就是全局状态.store 有三个关键概念, 分别是 state, getters 和 actions, 这与 Vue 组件中的 data, computed 和 methods 是相对应的概念. 定义 store 通过 defineStore 函数…

Vue页面路由参数的传递和获取

文章目录1. 通过动态路由参数传递2. 通过query字符串传递3. props 隐式传递vue 页面路由切换时传参的方式有如下几种&#xff1a; 动态路由参数 它隐藏字段信息&#xff0c;相对于来说较安全&#xff0c;同时地址栏中的地址也相对较短 它必须是先定义后使用&#xff0c;一般用…

关于嵌套使用 iFrame 出现 Refused to display in aframe 拒绝连接访问 和 ‘X-Frame-Options‘ to ‘SAMEORIGIN‘ 的解决方案【已解决】

目录问题描述原因分析问题解决总结今天在迁移旧项目时&#xff0c;出现了如下错误提示&#xff1a; Refused to display in a frame because it set X-Frame-Options to SAMEORIGIN问题描述 当前项目是一个生产环境正常运行的项目&#xff0c;由于我们要迁移服务器并且部署 k…

Pro2:修改div块的颜色

什么是JavaScript&#xff1f;实现目标实现代码实现效果实现方法&#x1f49b;作者主页&#xff1a;静Yu &#x1f9e1;简介&#xff1a;CSDN全栈优质创作者、华为云享专家、阿里云社区博客专家&#xff0c;前端知识交流社区创建者 &#x1f49b;社区地址&#xff1a;前端知识交…

html里面使用axios发送请求

html里面使用axios 效果展示&#xff1a; 代码展示&#xff1a; <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><meta name…

Vue项目实战 —— 后台管理系统( pc端 ) —— Pro最终版本

前期回顾 开源项目 —— 原生JS实现斗地主游戏 ——代码极少、功能都有、直接粘贴即用_js斗地主_0.活在风浪里的博客-CSDN博客JS 实现 斗地主网页游戏https://blog.csdn.net/m0_57904695/article/details/128982118?spm1001.2014.3001.5501 通用版后台管理系统&#xff0c;如果…

前端vben框架中表格table问题汇总

1.template中的代码 <BasicTable register"registerTable" :canResize"false"><!-- 表格左边的插槽 --><template #tableTitle><div class"btn-group"><a-buttonghosttype"primary"preIcon"ant-desi…

【vue】vue脚手架以及vite介绍

内容一、VUECLI二、关于cli的原理三、vite四、使用vite五、vite处理css、less、postcss、ts六、vite对vue的支持&#xff1a;七、预打包&#xff1a;八、关于vite打包九、真实项目中不会一直使用npx的十、ESBuild十一、vite脚手架&#xff0c;一、VUECLI 先安装这个工具&#…

基于AI分词模型,构建一个简陋的Web应用

文章目录前言1. 效果展示2. 应用设计3. 实现3.1. lac分词模型的服务化部署3.2 使用Flask构建app4. 小结前言 内容纯属个人经验&#xff0c;若有不当或错误之处&#xff0c;还请见谅&#xff0c;欢迎指出。 文中大致介绍了&#xff0c;如何快捷地使用PaddleHub服务化部署一个简…

vue 时间格式总结及转换

vue框架中我们常常用el-date-picker标签来显示和选择时间&#xff0c;那么&#xff0c;常见的时间的格式包含年-月-日&#xff08;yyyy-MM-dd&#xff09;、年-月-日 时-分-秒&#xff08;yyyy-MM-dd HH-mm-ss&#xff09;、标准时间格式以及时间戳。那么今天我们就来总结一下常…

Vue的开发常用的工具有哪些?

相比其他大型框架&#xff0c;Vue更加灵活&#xff0c;开发者既可以选择使用Vue来开发一个全新项目&#xff0c;也可以将Vue引入到一个现有的项目中。代码简洁、上手容易&#xff0c;深受开发者青睐。本节我们将对Vue的开发环境以及常用工具的使用进行讲解。 1.Visual Studio …

web前端面试高频考点——Vue3.x新增API(生命周期,ref、toRef 和 toRefs 的理解和最佳使用方式)

系列文章目录 内容参考链接JavaScript 面试高频考点HTML、CSS、JavaScript、ES6、AJAX、HTTP 面试考点 Vue2.x 面试高频考点Vue2.x 面试高频考点Vue3.x新增API生命周期&#xff0c;ref、toRef 和 toRefs 的理解和最佳使用方式Vue3.x升级的重要功能emits属性、生命周期、多事件…

React初体验-Hello React的组件化方式-React入门小案例

文章目录React初体验Hello React案例演练Hello React案例升级Hello React的组件化组件化的方式数据依赖事件绑定其他案例练习电影列表展示计数器的案例React初体验 接下来我们通过Hello React的案例, 来体验一下React开发模式, 以及jsx的语法 Hello React案例演练 第一步: 先引…

〖大前端 - 基础入门三大核心之CSS篇⑲〗- CSS精灵的使用 与 background综合属性

说明&#xff1a;该文属于 大前端全栈架构白宝书专栏&#xff0c;目前阶段免费开放&#xff0c;购买任意白宝书体系化专栏可加入TFS-CLUB 私域社区。福利&#xff1a;除了通过订阅"白宝书系列专栏"加入社区获取所有付费专栏的内容之外&#xff0c;还可以通过加入星荐…