目录
- 🌟前言
- 🌟小伙伴们先看
- 🌟实现思路
- 🌟具体代码
- 🌟最后
🌟前言
因为最近在整合公司的项目,需要把所有系统里的功能集成到一个项目里,这样就导致菜单栏目录会特别的多,不便于用户使用,体验效果极差。于是想到了一个方法,就是增加顶部导航栏,点击的时候让侧边菜单栏在显示相对应模块的所有菜单;这样的话就可以很大程度提升我们的用户体验啦。
🌟小伙伴们先看
🌟实现思路
嗯,干活前一定要先把思路理清楚,记在小本本上,画个图都行哈哈
- 布局方面我需要在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>
🌟最后
当我们接到新需求的时候,一定要仔细分析把逻辑梳理清楚了;复杂的话我们可以画一下流程图以便我们更好的去写代码;万变不离其宗,思路最重要。小伙伴们如果有更好的思路,可以一起交流,共同进步。
✨原创不易,还希望各位大佬支持一下!
👍 点赞,你的认可是我创作的动力!
⭐️ 收藏,你的青睐是我努力的方向!
✏️ 评论,你的意见是我进步的财富!