vue后台系统顶部一级侧边多级联动

2,833 阅读3分钟

这是我参与更文挑战的第5天,活动详情查看: 更文挑战

前言

最近在做后台系统的时候遇到了一个设计,就是顶部是菜单的一级,侧边栏是菜单的二级或者更多。可是在我百度了一圈后发现这种设计比较成熟或者现成的几乎没有。感觉很奇怪啊,这种应该是常见的设计咋就没有这样现成的呢。所以自己就实现了一个。今天重新来给大家说说,并且做一个简易的基础功能,大概的样子就长这样: 1.jpg

图画的有点丑,毕竟不是专业的

点击顶部菜单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,大家有其他需求可以自行修改即可。最后把所以代码串起来,写点样式就可以看到效果了。

效果

将组件和代码合并就是可以看到效果了,效果如下: 2.gif

总结

其实是个很简单的功能,稍微注意就好了,大家可以根据自己的需求去扩充和丰富就行了。