P4-Vue3后台管理系统-顶部导航与左侧导航联动面包屑

1.概述

上篇文章实现了左侧边栏导航,这篇文章我们开发顶部导航。

2.顶部导航栏设置

2.1.设置导航栏背景色

  • 在Main.vue组建中设置Header导航栏背景色

2.1.Header添加按钮

  • 官网中找到Button按钮–图标按钮–复制图标按钮代码
  • vue element 点击右侧内容左侧导航失活 vue顶部导航栏 左侧菜单_ico

  • 应用到CommonHeader组件中
  • vue element 点击右侧内容左侧导航失活 vue顶部导航栏 左侧菜单_导航栏_02

  • 官网搜索图标替换按钮默认图标
  • vue element 点击右侧内容左侧导航失活 vue顶部导航栏 左侧菜单_导航栏_03


  • vue element 点击右侧内容左侧导航失活 vue顶部导航栏 左侧菜单_导航栏_04

2.2.添加头像下拉菜单

  • Element官网找到点击触发下拉菜单
  • 复制点击触发下拉菜单代码
  • 应用到CommonHeader组件中
  • CommonHeader组件完整代码
<template>
  <header>
    <div class="l-content">
      <!-- sezi设置按钮尺寸 -->
      <el-button type="primary" icon="el-icon-menu" size="mini"></el-button>
    </div>
    <!-- 设置导航栏右边头像 -->
    <div class="r-content">
      <el-dropdown trigger="click">
        <span class="el-dropdown-link">下拉菜单<i class="el-icon-arrow-down el-icon--right"></i></span>
        <el-dropdown-menu slot="dropdown">
          <el-dropdown-item icon="el-icon-plus">黄金糕</el-dropdown-item>
          <el-dropdown-item icon="el-icon-circle-plus">狮子头</el-dropdown-item>
          <el-dropdown-item icon="el-icon-circle-plus-outline">螺蛳粉</el-dropdown-item>
          <el-dropdown-item icon="el-icon-check">双皮奶</el-dropdown-item>
          <el-dropdown-item icon="el-icon-circle-check">蚵仔煎</el-dropdown-item>
        </el-dropdown-menu>
      </el-dropdown>
    </div>
  </header>
</template>

<script>
export default {}
</script>

<style lang="scss" scoped>
// 设置header为弹性容器
header {
  display: flex;
  // 设置弹性容器中元素水平方向居中
  align-items: center;
  height: 100%;
  // 设置元素两端对齐
  justify-content: space-between;
}
</style>

-CommonHeader组件导航栏效果

vue element 点击右侧内容左侧导航失活 vue顶部导航栏 左侧菜单_ide_05

2.3.添加下拉菜单头像

  • 添加下拉头像
  • 下拉头像代码
<template>
  <header>
    <div class="l-content">
      <!-- sezi设置按钮尺寸 -->
      <el-button type="primary" icon="el-icon-menu" size="mini"></el-button>
    </div>
    <!-- 设置导航栏右边头像 -->
    <div class="r-content">
      <el-dropdown trigger="click">
        <!-- img:用户头像是从data中动态获取,不是写死的。 -->
        <span class="el-dropdown-link"><img :src="userImg" class="user"/></span>
        <el-dropdown-menu slot="dropdown">
          <el-dropdown-item icon="el-icon-plus">黄金糕</el-dropdown-item>
          <el-dropdown-item icon="el-icon-circle-plus">狮子头</el-dropdown-item>
          <el-dropdown-item icon="el-icon-circle-plus-outline">螺蛳粉</el-dropdown-item>
          <el-dropdown-item icon="el-icon-check">双皮奶</el-dropdown-item>
          <el-dropdown-item icon="el-icon-circle-check">蚵仔煎</el-dropdown-item>
        </el-dropdown-menu>
      </el-dropdown>
    </div>
  </header>
</template>

<script>
export default {
  data() {
    return {
      userImg: require('../assets/images/user.png')
    }
  }
}
</script>

<style lang="scss" scoped>
// 设置header为弹性容器
header {
  display: flex;
  // 设置弹性容器中元素水平方向居中
  align-items: center;
  height: 100%;
  // 设置元素两端对齐
  justify-content: space-between;
}
.r-content {
  .user {
    // 设置头像宽高
    width: 40px;
    height: 40px;
    // 设置头像为圆形
    border-radius: 50%;
  }
}
</style>

2.4.设置下拉菜单内容

删除多余的下拉内容,只保留两个下拉选项。同时改名为个人中心和退出。删除两个下拉项的icon图标

vue element 点击右侧内容左侧导航失活 vue顶部导航栏 左侧菜单_ide_06

3.设置左侧面包屑

3.1.添加面包屑样式

  • 复制Element官网面包屑基础用法的代码
  • 应用到CommonHeader组件

3.2.设置面包屑对齐方式

  • 设置水平居中

3.3.设置面包屑文字颜色

  • 查看面包屑内容CSS样式
  • 复制CSS的类选择器到CommonHeader组件中
  • 面包屑的内容改为白色

3.4.设置面包屑间距

  • 设置button按钮和面包屑的外边间距
  • vue element 点击右侧内容左侧导航失活 vue顶部导航栏 左侧菜单_ide_07

  • 设置margin后的效果
  • vue element 点击右侧内容左侧导航失活 vue顶部导航栏 左侧菜单_导航栏_08

3.5.删除多余的面包屑内容

vue element 点击右侧内容左侧导航失活 vue顶部导航栏 左侧菜单_ico_09

3.6.CommonHeader组件完整内容

<template>
  <header>
    <div class="l-content">
      <!-- sezi设置按钮尺寸 -->
      <el-button type="primary" icon="el-icon-menu" size="mini"></el-button>
      <!-- 设置面包屑 -->
      <el-breadcrumb separator="/">
        <el-breadcrumb-item :to="{ path: '/' }">首页</el-breadcrumb-item>
        <el-breadcrumb-item><a href="/">活动管理</a></el-breadcrumb-item>
      </el-breadcrumb>
    </div>
    <!-- 设置导航栏右边头像 -->
    <div class="r-content">
      <el-dropdown trigger="click">
        <!-- img:用户头像是从data中动态获取,不是写死的。 -->
        <span class="el-dropdown-link"><img :src="userImg" class="user"/></span>
        <el-dropdown-menu slot="dropdown">
          <el-dropdown-item>个人中心</el-dropdown-item>
          <el-dropdown-item>退出</el-dropdown-item>
        </el-dropdown-menu>
      </el-dropdown>
    </div>
  </header>
</template>

<script>
export default {
  data() {
    return {
      userImg: require('../assets/images/user.png')
    }
  }
}
</script>

<style lang="scss" scoped>
// 设置header为弹性容器
header {
  display: flex;
  // 设置弹性容器中元素水平方向居中
  align-items: center;
  height: 100%;
  // 设置元素两端对齐
  justify-content: space-between;
}
// 设置l-content为弹性容器
.l-content {
  display: flex;
  // 设置弹性容器中元素水平方向居中
  align-items: center;
  // 设置button按钮外右间距
  .el-button {
    margin-right: 20px;
  }
}
.r-content {
  .user {
    // 设置头像宽高
    width: 40px;
    height: 40px;
    // 设置头像为圆形
    border-radius: 50%;
  }
}
</style>
<style lang="scss">
.el-breadcrumb__inner a,
.el-breadcrumb__inner.is-link {
  color: #fff;
}
</style>

4.添加状态管理

通过Vuex来管理各个组件的状态。

4.1.新建状态管理文件

在store文件夹下新建tab.js文件来管理导航栏的组件状态

vue element 点击右侧内容左侧导航失活 vue顶部导航栏 左侧菜单_ide_10

4.2.导入tab文件模块

  • tab.js中写状态管理的内容
  • index.js导入tab模块

4.3.获取CommonAside点击的item

  • 获取菜单item方法思路
  • CommonAside组件内容
<template>
  <el-menu default-active="2" class="el-menu-vertical-demo" background-color="#33adf0" text-color="#fff" active-text-color="#ffd04b">
    <!-- 配置没有二级菜单的菜单路由内容 
        - 通过v-for循环遍历 noChildren 数据对象,分别取出:
          - 菜单路由item.path
          - 菜单icon
          - 菜单title名称
    -->
    <el-menu-item :index="item.path" v-for="item in noChildren" :key="item.path" @click="clickMenu(item)">
      <i :class="'el-icon-' + item.icon"></i>
      <span slot="title">{{ item.label }}</span>
    </el-menu-item>
    <!-- 配置有二级菜单的菜单路由内容
          - 通过v-for循环遍历 hasChildren 数据对象,分别取出:
            - 一级菜单的index
            - 一级菜单icon
            - 一级菜单名字
            通过遍历当前一级菜单的二级菜单数据对象,分别取出:
              - 二级菜单index
              - 二级菜单名字label
              - 二级菜单icon
     -->
    <el-submenu :index="item.label" v-for="(item, index) in hasChildren" :key="index">
      <template slot="title">
        <i :class="'el-icon-' + item.icon"></i>
        <span>{{ item.label }}</span>
      </template>
      <el-menu-item-group>
        <el-menu-item :index="subItem.path" v-for="(subItem, subIndex) in item.children" :key="subIndex" @click="clickMenu(subItem)">
          {{ subItem.label }}
        </el-menu-item>
      </el-menu-item-group>
    </el-submenu>
  </el-menu>
</template>

<script>
export default {
  computed: {
    // 获取菜单对象中不包含子级的菜单
    noChildren() {
      return this.asideMenu.filter(item => !item.children)
    },
    // 获取菜单对象中包含子级的菜单
    hasChildren() {
      return this.asideMenu.filter(item => item.children)
    }
  },
  data() {
    return {
      // 配置菜单路由
      asideMenu: [
        {
          path: '/',
          name: 'home',
          label: '首页',
          icon: 's-home'
        },
        {
          path: '/video',
          name: 'video',
          label: '视频管理',
          icon: 'video-play'
        },
        {
          path: '/user',
          name: 'user',
          label: '用户管理',
          icon: 'user'
        },
        // 设置子级菜单路由
        {
          label: '其他',
          icon: 'user',
          children: [
            {
              path: '/page1',
              name: 'page1',
              label: '页面1',
              icon: 'setting'
            },
            {
              path: '/page2',
              name: 'page2',
              label: '页面2',
              icon: 'setting'
            }
          ]
        }
      ]
    }
  },
  methods: {
    // clickMenu方法获取用户点击左侧导航栏的item状态
    clickMenu(item) {
      // 调用store中的tab.js文件定义的selectMenu方法
      this.$store.commit('selectMenu', item)
    }
  }
}
</script>

<style lang="scss" scoped>
.el-menu {
  height: 100%;
  border: none;
}
</style>
  • 通过vue插件查看事件给绑定的方法传递参数值

4.4.ComonHeader面包屑接收菜单item数据

  • 面包屑获取菜单item原理
  • 面包屑获取菜单item代码
<template>
  <header>
    <div class="l-content">
      <!-- sezi设置按钮尺寸 -->
      <el-button type="primary" icon="el-icon-menu" size="mini"></el-button>
      <!-- 设置面包屑 -->
      <el-breadcrumb separator="/">
        <el-breadcrumb-item :to="{ path: '/' }">首页</el-breadcrumb-item>
        <el-breadcrumb-item>
          <!-- 从计算属性的current中获取用户点击菜单的label,然后在面包屑中展示该菜单的label -->
          <a href="/">{{ current.label }}</a>
        </el-breadcrumb-item>
      </el-breadcrumb>
    </div>
    <!-- 设置导航栏右边头像 -->
    <div class="r-content">
      <el-dropdown trigger="click">
        <!-- img:用户头像是从data中动态获取,不是写死的。 -->
        <span class="el-dropdown-link"><img :src="userImg" class="user"/></span>
        <el-dropdown-menu slot="dropdown">
          <el-dropdown-item>个人中心</el-dropdown-item>
          <el-dropdown-item>退出</el-dropdown-item>
        </el-dropdown-menu>
      </el-dropdown>
    </div>
  </header>
</template>

<script>
// 导入vuex状态管理模块
import { mapState } from 'vuex'
export default {
  computed: {
    // 获取store下tab.js文件selectMenu方法中currentMenu对象数据
    ...mapState({
      current: state => state.tab.currentMenu
    })
  },
  data() {
    return {
      userImg: require('../assets/images/user.png')
    }
  }
}
</script>

<style lang="scss" scoped>
// 设置header为弹性容器
header {
  display: flex;
  // 设置弹性容器中元素水平方向居中
  align-items: center;
  height: 100%;
  // 设置元素两端对齐
  justify-content: space-between;
}
// 设置l-content为弹性容器
.l-content {
  display: flex;
  // 设置弹性容器中元素水平方向居中
  align-items: center;
  // 设置button按钮外右间距
  .el-button {
    margin-right: 20px;
  }
}
.r-content {
  .user {
    // 设置头像宽高
    width: 40px;
    height: 40px;
    // 设置头像为圆形
    border-radius: 50%;
  }
}
</style>
<style lang="scss">
.el-breadcrumb__inner a,
.el-breadcrumb__inner.is-link {
  color: #fff;
}
</style>

4.5.去除首页面包屑

当点击首页时候会重复显示首页,这个时候我们就需要将首页过滤掉

vue element 点击右侧内容左侧导航失活 vue顶部导航栏 左侧菜单_导航栏_11

  • CommonHeader面包屑添加判断是否展示菜单
  • tab.js中判断当前传入的菜单名称是否为home

4.6. 设置当前激活菜单的面包屑样式

  • 查看面包屑菜单样式CSS类选择器
  • CommonHeader组件中设置该面包屑样式
  • 设置面包屑的效果

4.7.设置面包屑完整代码

  • tab.js文件
export default {
  state: {
    menu: [],
    currentMenu: null
  },
  mutations: {
    selectMenu(state, val) {
      // 判断当前菜单name是否为home,如果是则将currentMenu属性设置为null,如果不是则将val赋值给currentMenu属性
      val.name === 'home' ? (state.currentMenu = null) : (state.currentMenu = val)
    }
  },
  actions: {},
  modules: {}
}
  • CommonAside.vue组件
<template>
  <el-menu default-active="2" class="el-menu-vertical-demo" background-color="#33adf0" text-color="#fff" active-text-color="#ffd04b">
    <!-- 配置没有二级菜单的菜单路由内容 
        - 通过v-for循环遍历 noChildren 数据对象,分别取出:
          - 菜单路由item.path
          - 菜单icon
          - 菜单title名称
    -->
    <el-menu-item :index="item.path" v-for="item in noChildren" :key="item.path" @click="clickMenu(item)">
      <i :class="'el-icon-' + item.icon"></i>
      <span slot="title">{{ item.label }}</span>
    </el-menu-item>
    <!-- 配置有二级菜单的菜单路由内容
          - 通过v-for循环遍历 hasChildren 数据对象,分别取出:
            - 一级菜单的index
            - 一级菜单icon
            - 一级菜单名字
            通过遍历当前一级菜单的二级菜单数据对象,分别取出:
              - 二级菜单index
              - 二级菜单名字label
              - 二级菜单icon
     -->
    <el-submenu :index="item.label" v-for="(item, index) in hasChildren" :key="index">
      <template slot="title">
        <i :class="'el-icon-' + item.icon"></i>
        <span>{{ item.label }}</span>
      </template>
      <el-menu-item-group>
        <el-menu-item :index="subItem.path" v-for="(subItem, subIndex) in item.children" :key="subIndex" @click="clickMenu(subItem)">
          {{ subItem.label }}
        </el-menu-item>
      </el-menu-item-group>
    </el-submenu>
  </el-menu>
</template>

<script>
export default {
  computed: {
    // 获取菜单对象中不包含子级的菜单
    noChildren() {
      return this.asideMenu.filter(item => !item.children)
    },
    // 获取菜单对象中包含子级的菜单
    hasChildren() {
      return this.asideMenu.filter(item => item.children)
    }
  },
  data() {
    return {
      // 配置菜单路由
      asideMenu: [
        {
          path: '/',
          name: 'home',
          label: '首页',
          icon: 's-home'
        },
        {
          path: '/video',
          name: 'video',
          label: '视频管理',
          icon: 'video-play'
        },
        {
          path: '/user',
          name: 'user',
          label: '用户管理',
          icon: 'user'
        },
        // 设置子级菜单路由
        {
          label: '其他',
          icon: 'user',
          children: [
            {
              path: '/page1',
              name: 'page1',
              label: '页面1',
              icon: 'setting'
            },
            {
              path: '/page2',
              name: 'page2',
              label: '页面2',
              icon: 'setting'
            }
          ]
        }
      ]
    }
  },
  methods: {
    // clickMenu方法获取用户点击左侧导航栏的item状态
    clickMenu(item) {
      // 调用store中的tab.js文件定义的selectMenu方法
      this.$store.commit('selectMenu', item)
    }
  }
}
</script>

<style lang="scss" scoped>
.el-menu {
  height: 100%;
  border: none;
}
</style>
  • CommonHeader.vue组件
<template>
  <header>
    <div class="l-content">
      <!-- sezi设置按钮尺寸 -->
      <el-button type="primary" icon="el-icon-menu" size="mini"></el-button>
      <!-- 设置面包屑 -->
      <el-breadcrumb separator="/">
        <el-breadcrumb-item :to="{ path: '/' }">首页</el-breadcrumb-item>
        <!-- 通过判断current是否为null设置面包屑是否显示菜单名称 -->
        <el-breadcrumb-item :to="current.path" v-if="current">
          <!-- 从计算属性的current中获取用户点击菜单的label,然后在面包屑中展示该菜单的label -->
          {{ current.label }}
        </el-breadcrumb-item>
      </el-breadcrumb>
    </div>
    <!-- 设置导航栏右边头像 -->
    <div class="r-content">
      <el-dropdown trigger="click">
        <!-- img:用户头像是从data中动态获取,不是写死的。 -->
        <span class="el-dropdown-link"><img :src="userImg" class="user"/></span>
        <el-dropdown-menu slot="dropdown">
          <el-dropdown-item>个人中心</el-dropdown-item>
          <el-dropdown-item>退出</el-dropdown-item>
        </el-dropdown-menu>
      </el-dropdown>
    </div>
  </header>
</template>

<script>
// 导入vuex状态管理模块
import { mapState } from 'vuex'
export default {
  computed: {
    // 获取store下tab.js文件selectMenu方法中currentMenu对象数据
    ...mapState({
      current: state => state.tab.currentMenu
    })
  },
  data() {
    return {
      userImg: require('../assets/images/user.png')
    }
  }
}
</script>

<style lang="scss" scoped>
// 设置header为弹性容器
header {
  display: flex;
  // 设置弹性容器中元素水平方向居中
  align-items: center;
  height: 100%;
  // 设置元素两端对齐
  justify-content: space-between;
}
// 设置l-content为弹性容器
.l-content {
  display: flex;
  // 设置弹性容器中元素水平方向居中
  align-items: center;
  // 设置button按钮外右间距
  .el-button {
    margin-right: 20px;
  }
}
.r-content {
  .user {
    // 设置头像宽高
    width: 40px;
    height: 40px;
    // 设置头像为圆形
    border-radius: 50%;
  }
}
</style>
<style lang="scss">
.el-breadcrumb__item {
  // 设置面包屑首页样式
  .el-breadcrumb__inner {
    color: #fff;
  }
  // 设置面包屑首页后面菜单样式
  &:last-child {
    .el-breadcrumb__inner {
      color: #f4f4f4;
    }
  }
}
</style>