前言:若依框架中前端菜单默认是在左侧展示,但是有些需求需要将菜单放在顶部。虽然框架自带的布局设置中有【开启 TopNav】功能,但该设置项也仅仅是将一级菜单置于顶部,其余菜单仍是在左侧展示,这可能并不是我们想要的效果。
示例项目已上传gitee,地址:gitee.com/SuperLucife…
预期效果
实现步骤
1.将src/layout/components/Sidebar目录下的指定文件复制到src/components/TopNav目录下
2.修改src/components/TopNav/index.vue(建议直接替换)
<template>
<el-menu
id="menu"
:default-active="activeMenu"
:unique-opened="true"
:collapse-transition="false"
mode="horizontal"
menu-trigger="hover"
>
<sidebar-item
v-for="(route, index) in visibleRouters"
:key="route.path + index"
:item="route"
:base-path="route.path"
/>
<!-- 顶部菜单超出数量折叠 -->
<el-submenu v-if="moreRouters.length" index="more">
<template slot="title">
<svg-icon icon-class="table" />
<span>更多菜单</span>
</template>
<sidebar-item
class="nest-menu"
v-for="(route, index) in moreRouters"
:key="route.path + index"
:item="route"
:base-path="route.path"
/>
</el-submenu>
</el-menu>
</template>
<script>
import { mapGetters } from "vuex";
import { deepClone } from "@/utils/index";
import SidebarItem from "./SidebarItem.vue";
export default {
props: {
width: {
type: Number,
default: 0,
},
},
components: { SidebarItem },
data() {
return {
// 顶部栏初始数
visibleNumber: 5,
menuKey: new Date().getTime(),
};
},
computed: {
...mapGetters(["sidebarRouters", "moduleList", "moduleCode"]),
theme() {
return this.$store.state.settings.theme;
},
// 所有的路由信息
routers() {
// 过滤掉隐藏的路由
return this.sidebarRouters.filter((item) => !item.hidden);
},
// 可视菜单路由
visibleRouters() {
const length = this.routers.length;
if (length <= this.visibleNumber) {
return deepClone(this.routers);
}
return this.routers.slice(0, this.visibleNumber - 1);
},
// 更多菜单路由
moreRouters() {
const length = this.routers.length;
if (length <= this.visibleNumber) {
return [];
}
return this.routers.slice(this.visibleNumber - 1, length);
},
// 默认激活的菜单
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;
},
},
watch: {
width: {
handler(newVal) {
this.setVisibleNumber(newVal);
// 更新菜单组件key,避免当前菜单被折叠后,再次出现时未高亮显示
this.menuKey = new Date().getTime();
},
immediate: true,
},
},
mounted() {},
methods: {
// 根据宽度计算设置显示栏数
setVisibleNumber(width) {
// 112是取的菜单项最大宽度,此处还可进行优化
this.visibleNumber = parseInt(width / 112);
},
},
};
</script>
<style lang="scss">
.el-menu-item {
padding: 0 10px;
color: #303133 !important;
&:hover {
background-color: #e8f4ff !important;
}
&.is-active {
color: #1890ff !important;
}
.svg-icon {
margin-left: 0 !important;
margin-right: 5px;
}
}
.el-submenu {
&.is-active {
.el-submenu__title {
color: #1890ff !important;
}
}
.el-submenu__title {
display: flex;
align-items: center;
padding: 0 10px;
color: #303133 !important;
&:hover {
background-color: #e8f4ff !important;
}
.svg-icon {
margin-left: 0 !important;
margin-right: 5px;
}
.el-submenu__icon-arrow {
position: initial;
margin-left: 5px;
margin-top: 1px;
}
}
}
.el-menu--popup {
.el-submenu__icon-arrow {
margin-left: auto !important;
}
}
</style>
3.安装元素尺寸监听插件element-resize-detector
npm install element-resize-detector@1.2.4
4.在src/directive/module目录下新建wResize.js文件,文件内容如下:
// 元素尺寸监听插件
import ElementResizeDetectorMaker from 'element-resize-detector'
import debounce from 'lodash/debounce'
/**
* 元素宽度监听
* 使用:v-wResize="onWidthResize"
* onWidthResize({width})
*/
export default {
bind(el, binding) {
let width = 0
const erd = ElementResizeDetectorMaker()
// 防抖
const debounceHandler = element =>
debounce(() => {
let offsetWidth = element.offsetWidth
if (width !== offsetWidth) {
width = offsetWidth
binding.value({ width })
}
}, 100)()
erd.listenTo(el, debounceHandler)
el.__vueErd__ = erd
},
// 销毁时移除detector
unbind(el) {
el.__vueErd__.uninstall(el)
},
}
5.注册v-wResize指令
import wResize from './module/wResize'
Vue.directive('wResize', wResize)
6.修改src/layout/components/navbar.vue
- 修改模板内容
<div v-wResize="onMenuContResize" id="navbar-left" class="navbar__left">
<top-nav
v-if="menuContWidth"
id="topmenu-container"
class="topmenu-container"
:width="menuContWidth"
/>
</div>
- 添加变量menuContWidth
data() {
return {
menuContWidth: 0,
};
},
- 添加方法onMenuContResize
// 菜单容器宽度变化回调
onMenuContResize({ width }) {
this.menuContWidth = width;
},
- 修改样式(直接替换)
.navbar {
height: 50px;
overflow: hidden;
position: relative;
background: #fff;
box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
display: flex;
&__left {
flex: 1;
overflow: hidden;
margin-left: 10px;
margin-right: 20px;
}
.hamburger-container {
line-height: 46px;
height: 100%;
float: left;
cursor: pointer;
transition: background 0.3s;
-webkit-tap-highlight-color: transparent;
&:hover {
background: rgba(0, 0, 0, 0.025);
}
}
.breadcrumb-container {
float: left;
}
.topmenu-container {
display: inline-flex;
align-items: center;
height: 50px;
border: none;
}
.errLog-container {
display: inline-block;
vertical-align: top;
}
.right-menu {
flex-shrink: 0;
margin-left: auto;
height: 100%;
display: flex;
align-items: center;
&:focus {
outline: none;
}
.right-menu-item {
display: inline-flex;
align-items: center;
padding: 0 8px;
height: 100%;
font-size: 18px;
color: #5a5e66;
&.hover-effect {
cursor: pointer;
transition: background 0.3s;
&:hover {
background: rgba(0, 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;
}
}
}
}
}
7.修改src/layout/index.vue
-
隐藏侧边栏
-
修改固定header的样式
8.修改src/asssets/styles/sidebar.scss
9.修改src/layout/components/Settings/index.vue,隐藏布局设置中的【开启 TopNav】、【显示 Logo】