前言
上节回顾
上一小节我们使用H5+CSS3实现了管理平台的架构布局,并且通过Vuex的使用,获取到前端数据本地化存储的username,绑定到右上角进行全局展示。还不了解上下文的同学可以回顾一下 使用Vue+Vuex+CSS3完成管理端响应式架构模板实战 。
本节介绍
本小节已经是专栏的第10篇博客了,我们将继续维护添加Vue-router的路由数据,这些路由都是后续实战业务的真实路由了,在添加部分路由的同时,我们将借助ElementUI的el-munu组件,来实现左侧菜单功能的实战。其实使用el-menu并非重点,我们只是做一个基本使用,重点是我们将自研实现一个自己的tg-menu组件,用来替换el-menu组件,块一起来看看吧。
目录
一、基于el-menu组件的菜单功能
1. 添加路由数据
先添加上一些真实业务数据,为后续的业务开发做准备。添加图书管理、书评管理、新书推荐、学生信息管理、学生申请管理几个菜单。
之前的博客说过,我们的这个《Vue + SpringBoot前后端分离项目实战》虽然是以校园图书借阅做为引子,但其实是参照了很多毕业设计做的需求融合整理,意在愿意跟着专栏学校去的同学,可以做到不仅学会了这个项目实战的知识,场景,更希望将知识融汇以后,不管后续做什么项目,什么毕业设计的题材,都可以在这基础上做修改,而修改的顶多就是一些字段名称,界面之类的,其实不管你什么需求,我们这个专栏里都已经包含了,这就是本专栏的优势之一。
{
path: '/layout',
component: () => import("@/views/Layout"),
children: [
{
path: '/index',
component: () => import("@/views/index/Index"),
meta: {
title: '首页'
},
fatherTitle: '首页',
fatherTitleCode: 0,
},
{
path: '/book-manager',
component: () => import("@/views/index/Index"),
meta: {
title: '图书管理'
},
fatherTitle: '图书管理',
fatherTitleCode: 1,
},
{
path: '/bookAppraise-manager',
component: () => import("@/views/index/Index"),
meta: {
title: '书评管理'
},
fatherTitle: '图书管理',
fatherTitleCode: 1,
},
{
path: '/goodBook-manager',
component: () => import("@/views/index/Index"),
meta: {
title: '新书推荐'
},
fatherTitle: '图书管理',
fatherTitleCode: 1,
},
{
path: '/studentInfo-manager',
component: () => import("@/views/index/Index"),
meta: {
title: '学生信息管理'
},
fatherTitle: '学生管理',
fatherTitleCode: 2,
},
{
path: '/studentApply-manager',
component: () => import("@/views/index/Index"),
meta: {
title: '学生申请管理'
},
fatherTitle: '学生管理',
fatherTitleCode: 2,
},
],
}
2. 路由属性的介绍
我们看路由都包含在了layout的模板路由组件之下,上一节也说过,后续几乎所有的业务都会在content模板下开发了。路由属性包含了以下几点:
path: 路由的url;
component: 路由所对应的组件,目前由于还没有讲到这些具体业务,所以先都对应到首页去,后边会不断迭代修改;
meta: 本路由在菜单中的具体展示
fatherTitle: 一级菜单的展示 (meta和fatherTitle共同完成业务组件面包屑)
fatherTitleCode: 所属一级菜单的code码值
3. 引入el-menu,开发菜单组件
el-menu为ElementUI的内置组件,所以无需单独引入,只是使用即可,我们在之前的博客已经引入了ElementUI组件。我们看目前的一级菜单应该是首页、图书管理和学生管理,而首页是一个无子菜单的一级菜单,而另外2个包含子级二级菜单,所以 /src/views/layout.vue 文件补充代码如下:
<aside class="aside-menu">
<el-menu
default-active="1">
<el-menu-item index="1">
<span slot="title">
<router-link to="/index">首页</router-link>
</span>
</el-menu-item>
<el-submenu index="2">
<template slot="title">
<span>图书管理</span>
</template>
<el-menu-item-group>
<el-menu-item index="1-1">
<router-link to="/book-manager">图书管理</router-link>
</el-menu-item>
<el-menu-item index="1-2">
<router-link to="/bookAppraise-manager">书评管理</router-link>
</el-menu-item>
<el-menu-item index="1-3">
<router-link to="/goodBook-manager">新书推荐</router-link>
</el-menu-item>
</el-menu-item-group>
</el-submenu>
<el-submenu index="3">
<template slot="title">
<span>学生管理</span>
</template>
<el-menu-item-group>
<el-menu-item index="2-1">
<router-link to="/studentInfo-manager">学生信息管理</router-link>
</el-menu-item>
<el-menu-item index="2-2">
<router-link to="/studentApply-manager">学生申请管理</router-link>
</el-menu-item>
</el-menu-item-group>
</el-submenu>
</el-menu>
</aside>
二、自研tg-menu组件的菜单
之前说过,遇到第三方组件,我们尽量自己去实现一下,一旦自己实现一套下来,好处是非常多的。有了自己的实现,自己多了一种解决问题的思路,这个时候如果再去读第三方组件的源码,肯定和之前的感悟是不同的。
1. 引入tg-menu组件
还是三步,template模板处的布局组件,TgMenu的引入,compnents的定义。
<tg-menu></tg-menu>
<script>
import { mapGetters } from "vuex";
import TgMenu from '@/components/tg-menu.vue';
import { userLogout } from '@/api/login.js';
export default {
name: 'Layout',
components: { TgMenu },
2. 引入路由数据,维护菜单组件数据
最初引入的路由数据,我们只取layout下的,才做为左侧菜单展示,而为了便于菜单的一级和二级甚至多级展示,我们利用到了上面讲到的fatherTitleCode,相同的fatherTitleCode则视为同一模块,最终我们需要将数据处理为这样的结构:
3. 无子级菜单和有子级菜单的区分
既然首页是无子级菜单的,而像图书管理这样的模块又有自己菜单,我们就要做一个区分,基于上一步的数据格式,这个二维数组,我们将数组子元素只有1个的视为无子级菜单,含有1个子元素以上的视为有子级菜单。同时绑定上需要跳转的path值,一级菜单的fatherTitle值,以及当前菜单的meta下的title值
4. 当前菜单的高亮展示
我们知道,当前菜单一定会处于一个选中状态,我们为其他添加了 cur-path-style 的class样式类,而且需要实时监测当前路由是什么,然后用当前的路由去匹配这些路由数据,匹配到了就是当前的路由啦,然后做一个vue的class类动态绑定即可。
5. tg-munu组件完整代码
添加 /src/components/tg-menu.vue 文件,添加如下代码:
<template>
<div class="tg-menu-box">
<div v-for="(mitem, mindex) in menu" :key="mindex" class="menu-item-box">
<template v-if="mitem.length === 1">
<p :class="['first-title', currentPath===mitem[0]['path'] ? 'cur-path-style' : '']">
<a :href="'/#' + mitem[0]['path']">
{
{ mitem[0]['fatherTitle'] }}
</a>
</p>
</template>
<template v-if="mitem.length > 1">
<p class="first-title" @click="handleMenuClick(mindex)">
<span>{
{ mitem[0]['fatherTitle'] }}</span>
<i class="el-icon-arrow-down"></i>
</p>
<ul v-if="menuBackObj[mindex]['showSon']">
<li v-for="sitem in mitem" :key="sitem.path"
:class="['menu-item-li', currentPath===sitem['path'] ? 'cur-path-style' : '']">
<a :href="'/#' + sitem['path']">{
{ sitem['meta']['title'] }}</a>
</li>
</ul>
</template>
</div>
</div>
</template>
<script>
import { routerData } from "../router/routesData";
export default {
name: 'TgMenu',
props: {
},
watch: {
$route: {
handler: function (route) {
this.currentPath = route.path;
},
immediate: true,
},
},
data () {
return {
menu: [],
menuBackObj: {},
currentPath: '',
}
},
created() {
// 获取会展示在菜单的路由数据
let menuListData = routerData.find((item) => {
return item.path === '/layout';
});
this.makeData(menuListData.children);
},
methods: {
handleMenuClick(index) {
let oldMenuBackObj = this.menuBackObj;
oldMenuBackObj[index]['showSon'] = !oldMenuBackObj[index]['showSon'];
this.menuBackObj = JSON.parse(JSON.stringify(oldMenuBackObj));
},
makeData(menuList) {
// 菜单数据结构整理
let menuShowArr = [];
menuList.forEach((item1) => {
if (!menuShowArr[item1.fatherTitleCode] || menuShowArr[item1.fatherTitleCode].length === 0) {
menuShowArr[item1.fatherTitleCode] = [];
this.menuBackObj[item1.fatherTitleCode] = {}; // 为一级目录添加控制属性
}
menuShowArr[item1.fatherTitleCode].push(item1);
this.menuBackObj[item1.fatherTitleCode] = {
showSon: false,
};
})
console.log(menuShowArr);
this.menu = JSON.parse(JSON.stringify(menuShowArr));
}
}
}
</script>
<style scoped lang="less">
.menu-item-box {
border-bottom: 1px solid #CCC;
}
.first-title {
display: flex;
justify-content: space-between;
align-items: center;
height: 40px;
cursor: pointer;
&>a, &>span {
margin-left: 30px;
color: #000;
}
[class^=el-icon-] {
margin-right: 20px;
}
}
.menu-item-li {
height: 40px;
line-height: 40px;
&>a {
margin-left: 50px;
color: #000;
}
}
.cur-path-style {
background: #EEEEFF;
a {
color: #409EFF;
}
}
</style>
6. tg-memu菜单组件包含的功能
一直到这里,我们的tg-menu组件就实现完成了,而且菜单功能也基本算实现完成了。那么我们的tg-menu菜单组件都包含哪些功能呢?
三、专栏进度汇报
我和天哥(天哥主页)利用工作业余时间开展专栏(同时这也是一个真实战项目)。是希望可以帮助初学者,或者毕业生做一个实战类型的项目,参加完蓝桥杯,不断学习了学校的各种专业课,然后可以将知识点串联一下,提前有个属于自己的实战项目。而且一旦跟着这个专栏的文章一篇一篇学下来,以后你会发现,真实工作中无非就这些东西,翻过来,滚过去,无非就是产品经理变着花样的来回拧。下面我把本专栏的进度给大家做一个汇报:
1. 目前专栏的博客进度
2. 前端部分的内容架构进展
3. 前端部分代码目录进展
四、读完本小节需要思考的几个问题
还是按照惯例,提出几个看完本小节,需要思考的小问题,很重要哦,可以说以上实战内容可以不看,但是这些小问题一定要思考:
- 做管理系统,左侧菜单你是如何与路由数据相结合的?
- 你是如何实现一个二级菜单的?多级菜单呢?
- 如何保证当前路由对应的菜单处于被选中状态?
好啦,这就是本小节的全部内容,为了便于与大家沟通,我为大家精心准备了投票环节,投个票吧。