目录

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

前言

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

小伙伴们先看

实现思路

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

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

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

具体代码

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

<!--src/layout/components/Navbar.vue--><template><div class="navbar"><hamburger :is-active="sidebar.opened" class="hamburger-container" @toggleClick="toggleSideBar" /><!--重点一:顶部menu--><el-menumode="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" /> /><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-componentsdata() {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/userimport { 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方法用来保存我当前点击的顶部模块菜单pathSET_PATH: (state, path) => {state.path = path}}const actions = {// user loginlogin({ commit }, userInfo) {const { username, password } = userInforeturn new Promise((resolve, reject) => {login({ username: username.trim(), password: password }).then(response => {const { data } = responsecommit('SET_TOKEN', data.token)setToken(data.token)resolve()}).catch(error => {reject(error)})})},//定义两个actions 方法用来执行我们上边定义的SET_MENULIST和SET_PATHsetMenuList({ commit }, menuList) {commit('SET_MENULIST', menuList)},setPath({ commit }, path) {commit('SET_PATH', path)},// get user infogetInfo({ commit, state }) {return new Promise((resolve, reject) => {getInfo(state.token).then(response => {const { data } = responseif (!data) {return reject('Verification failed, please Login again.')}const { name, avatar } = datacommit('SET_NAME', name)commit('SET_AVATAR', avatar)resolve(data)}).catch(error => {reject(error)})})},// user logoutlogout({ commit, state }) {return new Promise((resolve, reject) => {logout(state.token).then(() => {removeToken() // must removetokenfirstresetRouter()commit('RESET_STATE')resolve()}).catch(error => {reject(error)})})},// remove tokenresetToken({ commit }) {return new Promise(resolve => {removeToken() // must removetokenfirstcommit('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.$routeconst { meta, path } = route// if set path, the sidebar will highlight the path you setif (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-componentsdata() {return {menuList: []}}}</script>

最后

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

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