这是我参与更文挑战的第5天,活动详情查看: 更文挑战
前言
最近在做后台系统的时候遇到了一个设计,就是顶部是菜单的一级,侧边栏是菜单的二级或者更多。可是在我百度了一圈后发现这种设计比较成熟或者现成的几乎没有。感觉很奇怪啊,这种应该是常见的设计咋就没有这样现成的呢。所以自己就实现了一个。今天重新来给大家说说,并且做一个简易的基础功能,大概的样子就长这样:
图画的有点丑,毕竟不是专业的
点击顶部菜单1就是现实下面的菜单,点击菜单2就替换侧边菜单为菜单2下面的子菜单。
分析
其实通过界面就能看出来布局很简单,总体分为三个部分,顶部、侧边栏、内容区域。这里只是做一个简单的功能实现,界面美化就不在这里赘述了。
实现
新建项目
这里采用Vue、Element-UI来实现,所以新建项目就略过,和其他vue项目一样的。
顶部组件实现
在项目组件目录中新建header组件来实现顶部菜单组件。其实顶部没有什么可说的,就是一个菜单,直接使用elementui的菜单组件来就是。关键代码如下:
<el-menu mode="horizontal" @select="handleSelect" :default-active="activeRoute()">
<el-menu-item class="list-item" :index="item.url" v-for="item in items" :key="item.url">
<i :class="item.icon"></i>
<span slot="title">{{ item.name }}</span>
</el-menu-item>
</el-menu>
export default {
data() {
return {
items: []
}
},
created() {
this.getUserInfo()
// 设置最开始没有点击任何菜单时的默认侧边栏菜单处理
this.defaultMenu('/'+this.$route.path.split('/')[1])
},
methods: {
// 根据路径绑定到对应的一级菜单,防止页面刷新重新跳回第一个
activeRoute() {
return '/'+this.$route.path.split('/')[1]
},
getUserInfo() {
// 直接模拟请求接口获取菜单
var datas = [
{
name:'菜单1',
url:'/menu-1',
children:[
{
name:'菜单1-1',
url:'/menu-1/child-1',
children:[
{
name:'菜单1-1-1',
url:'/menu-1/child-1/sub-1'
},
{
name:'菜单1-1-2',
url:'/menu-1/child-1/sub-2'
}
]
},
{
name:'菜单1-2',
url:'/menu-1/child-2',
children:[
{
name:'菜单1-2-1',
url:'/menu-1/child-2/sub-1'
},
]
}
]
},
{
name:'菜单2',
url:'/menu-2',
children:[
{
name:'菜单2-1',
url:'/menu-2/child-1',
children:[
{
name:'菜单2-1-1',
url:'/menu-2/child-1/sub-1'
}
]
},
{
name:'菜单2-2',
url:'/menu-2/child-2'
}
]
},
{
name:'菜单3',
url:'/menu-3',
children:[
{
name:'菜单3-1',
url:'/menu-3/child-1',
children:[
{
name:'菜单3-1-1',
url:'/menu-3/child-1/sub-1'
}
]
}
]
}
]
this.items = datas
},
handleSelect(index){
this.defaultMenu(index)
this.$router.push(index)
},
defaultMenu(index){
let route = this.items.find(item=>item.url === index)
this.$store.commit("changeValue", {
name: "menus",
value: route
})
}
}
}
这样我们的顶部菜单就搞定了。上面菜单是没有icon的,这里我就不使用icon了,大家可以自行添加图标就好了。
侧边栏菜单组件实现
侧边栏的菜单目前我们只是实现结构,真正的数据需要后面处理的。关键代码如下:
<div class="sidebar">
<el-scrollbar class="scroll-wrapper">
<el-menu class="sidebar-el-menu" :default-active="$route.path" unique-opened router>
<subItem :items="items" />
</el-menu>
</el-scrollbar>
</div>
import subItem from "./subitem"
export default {
props:['items'],
components: {
subItem
}
}
这里就使用常规的递归来实现多级菜单的,subItem组件代码如下:
<template>
<div>
<template v-for="item in items">
<el-submenu :index="item.url" v-if="item.children && item.children.length > 0" :key="item.url">
<template slot="title">
<span>{{ item.name }}</span>
</template>
<subitem :items="item.children" />
</el-submenu>
<el-menu-item :index="item.url" v-else :key="item.url">
<span slot="title">{{ item.name }}</span>
</el-menu-item>
</template>
</div>
</template>
<script>
export default {
name:"subitem",
props:{
items:Array
}
}
</script>
侧边栏菜单也就完成了,这里呢菜单的数组是需要传递过来的。
定义路由
现在我们就需要定义路由了,这里根据设计看,是有三级的,所以需要设计一个三级的路由,路由代码如下:
{
path: '/',
component: () => import('../layouts/framework.vue'),
meta: {
title: '自述文件'
},
children: [
{
path: "/menu-1",
component: () => import("@/layouts/children.vue"),
meta: {
title: "菜单1"
},
redirect: "/menu-1/child-1/sub-1",
children: [
{
path: "child-1",
component: () => import("@/layouts/page.vue"),
meta: {
title: "菜单1-1"
},
children: [
{
path: "sub-1",
component: () => import("@/views/menu111.vue"),
meta: {
title: "菜单1-1-1"
}
},
{
path: "sub-2",
component: () => import("@/views/menu112.vue"),
meta: {
title: "菜单1-1-2"
}
}
]
}
]
},
{
path: "/menu-2",
component: () => import("@/layouts/children.vue"),
meta: {
title: "菜单2"
},
redirect: "/menu-2/child-1/sub-1",
children: [
{
path: "child-1",
component: () => import("@/layouts/page.vue"),
meta: {
title: "菜单2-1"
},
children: [
{
path: "sub-1",
component: () => import("@/views/menu211.vue"),
meta: {
title: "菜单2-1-1"
}
}
]
},
{
path: "child-2",
component: () => import("@/layouts/page.vue"),
meta: {
title: "菜单2-2"
},
children: [
{
path: "sub-1",
component: () => import("@/views/menu221.vue"),
meta: {
title: "菜单2-2-1"
}
}
]
}
]
},
{
path: "/menu-3",
component: () => import("@/layouts/children.vue"),
meta: {
title: "菜单3"
},
redirect: "/menu-3/child-1/sub-1",
children: [
{
path: "child-1",
component: () => import("@/layouts/page.vue"),
meta: {
title: "菜单3-1"
},
children: [
{
path: "sub-1",
component: () => import("@/views/menu311.vue"),
meta: {
title: "菜单3-1-1"
}
}
]
}
]
}
]
}
路由也定义完了,接下来就是实现路由中的几个布局据组件了。
布局组件实现
首先就是framework组件
这个就是最外层的组件了,其实就是只包含了顶部菜单的布局组件了,代码如下:
<template>
<el-container class="main">
<myHeader />
<router-view />
</el-container>
</template>
<script>
import myHeader from '@/components/header/index.vue'
export default {
components: {
myHeader
}
}
</script>
侧边栏菜单布局组件
接下来就是侧边栏的菜单布局组件了,这里也就是包含了菜单组件在里面,当前啦,重点的数据传递就是在这里了。代码如下:
<template>
<el-container>
<mySidebar :items="items" />
<el-container>
<router-view />
</el-container>
</el-container>
</template>
<script>
import mySidebar from '@/components/sidebar/index.vue'
export default {
components: {
mySidebar
},
data() {
return {
items:[]
}
},
watch:{
$route(){
this.items = this.$store.state.menus.children
}
},
created() {
this.items = this.$store.state.menus.children
}
}
</script>
最后一级就是具体页面的布局组件
这一级的组件就不说了,就是一个router-view,大家有其他需求可以自行修改即可。最后把所以代码串起来,写点样式就可以看到效果了。
效果
将组件和代码合并就是可以看到效果了,效果如下:
总结
其实是个很简单的功能,稍微注意就好了,大家可以根据自己的需求去扩充和丰富就行了。