使用Vue+Vue-router+el-menu实现菜单功能实战

 

前言

上节回顾

上一小节我们使用H5+CSS3实现了管理平台的架构布局,并且通过Vuex的使用,获取到前端数据本地化存储的username,绑定到右上角进行全局展示。还不了解上下文的同学可以回顾一下 使用Vue+Vuex+CSS3完成管理端响应式架构模板实战 。

本节介绍

本小节已经是专栏的第10篇博客了,我们将继续维护添加Vue-router的路由数据,这些路由都是后续实战业务的真实路由了,在添加部分路由的同时,我们将借助ElementUI的el-munu组件,来实现左侧菜单功能的实战。其实使用el-menu并非重点,我们只是做一个基本使用,重点是我们将自研实现一个自己的tg-menu组件,用来替换el-menu组件,块一起来看看吧。

目录

前言

上节回顾

本节介绍

一、基于el-menu组件的菜单功能

1. 添加路由数据

扫描二维码关注公众号,回复: 14813632 查看本文章

2. 路由属性的介绍 

 3. 引入el-menu,开发菜单组件

二、自研tg-menu组件的菜单

1. 引入tg-menu组件

2. 引入路由数据,维护菜单组件数据

3. 无子级菜单和有子级菜单的区分

4. 当前菜单的高亮展示

5. tg-munu组件完整代码

6. tg-memu菜单组件包含的功能 

三、专栏进度汇报

1. 目前专栏的博客进度

2. 前端部分的内容架构进展

3. 前端部分代码目录进展

四、读完本小节需要思考的几个问题


一、基于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. 目前专栏的博客进度

Vue + SpringBoot前后端分离项目实战 - 前端部分

CSDN链接直达:https://blog.csdn.net/xingyu_qie/category_12222258.html

1. 手把手带你做一套毕业设计-征程开启
2. 我应该把毕业设计做到什么程度才能过关?
3. 做毕业设计,前端部分你需要掌握的6个核心技能
4. 基于Vue+Vue-cli+webpack搭建渐进式高可维护性前端实战项目
5. 基于Vue+Less+axios封装+ElementUI搭建项目底层支撑实战
6. 使用Vue+vue-router+路由守卫实现路由鉴权功能实战
7. 使用Vue+el-form+form-validate实现管理端登录接口联调前准备工作实战
8. 使用Vue+axios+Vuex实现登录后前端数据本地化存储实战
9. 使用Vue+Vuex+CSS3完成管理端响应式架构模板实战

SpringBoot+Vue前后端分离项目实战 - 服务端部分

CSDN链接直达:https://blog.csdn.net/scm_2008/category_12236048.html

1. 基于SpringBoot+SpringCloud+Vue前后端分离项目实战 --开篇
2. 1-1. JDK8 安装教程、环境变量配置
3. 1-2. Maven 安装、仓库配置
4. 1-3. 开发环境安装(全)
5. 2-1. Maven 三层项目结构搭建
6. 2-2. SpringBoot API开发详解 --SpringMVC注解+封装结果+支持跨域+打包
7. 2-3. Maven依赖加载不进来?依赖加载失败?你值得掌握如何排查的方法
8. 2-4. 实战Git常用操作(IDEA界面+命令)
9. 3-1. SpringBoot项目集成【用户身份认证】实战 【技术选型篇】基于Session、Token、JWT怎么选?
10. 3-2. SpringBoot项目集成【用户身份认证】实战 【实战核心篇】基于JWT生成和校验Token
11. 3-3. SpringBoot项目集成【用户身份认证】实战 【全流程篇】基于JWT+双重检查的登录+登出+拦截器
12. 4-1. centos7安装和卸载mysql5.7

2. 前端部分的内容架构进展

3. 前端部分代码目录进展

四、读完本小节需要思考的几个问题

还是按照惯例,提出几个看完本小节,需要思考的小问题,很重要哦,可以说以上实战内容可以不看,但是这些小问题一定要思考:

  1. 做管理系统,左侧菜单你是如何与路由数据相结合的?
  2. 你是如何实现一个二级菜单的?多级菜单呢?
  3. 如何保证当前路由对应的菜单处于被选中状态?

好啦,这就是本小节的全部内容,为了便于与大家沟通,我为大家精心准备了投票环节,投个票吧。 

猜你喜欢

转载自blog.csdn.net/xingyu_qie/article/details/130050161