VueJSJQuery&JS&BootStrap前端

vue合家福实例(3):根据路由生成菜单

2018-09-07  本文已影响509人  碧波之心

本章的最终效果

最终效果图
这章的开发在admin子项目中进行。这里我们在project/admin中创建文件App.vue。内容如vue合家福实例(2):使用element-ui el-scrollbar中的,建立一个项目的页面框架。统一的菜单和顶部状态栏。
在开发中,要增加一个页面。我是先写一个简单的组件(.vue)文件,然后配置路由。接下去是修改菜单(这样的情况下,一般菜单会建立一个组件)。那么,如果路由配置好后就可以增加到菜单中,即菜单是根据路由生成的,这样修改路由就不用再去改菜单的代码。想想挺酷的。
目录结构:
目录结构
我们在红框中的文件目录进行工作。
BhLayout>src>menu-item.vue (一个菜单项组件,对element-ui的菜单组件进行再封装)
菜单的代码在App.vue中,routers.js是路由配置文件。

App.vue:

<script>
import _ from 'lodash'
import {BhMenuItem} from '@/components/BhLayout' // 加入菜单项组件
export default {
  name: 'app',
  components: {
    BhMenuItem
  },
  data () {
    return {
      isCollapse: false,
      asideWidth: '230px',
      vmenus: [],
      defActive: this.activePath()
    }
  },
  created () {},
  watch: {
    $route () {
      this.defActive = this.activePath()
      this.setPageTitle()
    }
  },
  methods: {
    // 改变菜单栏的宽度
    changeCollapse () {
      this.isCollapse = !this.isCollapse
      this.$emit('collapse-change', this.isCollapse)
      if (this.isCollapse) {
        this.asideWidth = '65px'
      } else {
        this.asideWidth = '230px'
      }
    },
    // 设置页面标题
    setPageTitle () {
      let path = this.$route.path
      let pathArr = _.split(path, '/')
      let l = pathArr.length
      let menus = this.$router.options.routes
      if (_.isEmpty(menus) || l < 2) {
        return
      }
      let ts = ['vue全家福']
      let i = 1
      let children = menus
      while (i < l) {
        path = _.join(_.slice(pathArr, 0, i + 1), '/')
        let index = _.findIndex(children, menu => {
          let menuPath = menu.path
          let i = menuPath.indexOf('/:')
          if (i > 0) {
            menuPath = menuPath.substring(0, i)
          }
          return menuPath === path
        })
        if (index < 0) {
          break
        }
        if (!children[index]) {
          break
        }
        if (children[index]['text']) {
          ts.push(children[index]['text'])
        }
        if (!children[index]['children'] || _.isEmpty(children[index]['children'])) {
          break
        }
        children = children[index]['children']
        i++
      }
      this.title = _.clone(ts)
    },
    // 计算菜单当前选中的路径
    activePath () {
      let path = this.$route.path
      let pathArr = _.split(path, '/')
      let l = pathArr.length
      if (l <= 2) {
        return path
      }
      let menus = this.$router.options.routes
      if (_.isEmpty(menus)) {
        return path
      }
      let i = 1
      let children = menus
      while (i < l) {
        path = _.join(_.slice(pathArr, 0, i + 1), '/')
        let index = _.findIndex(children, {'path': path})
        if (index < 0) {
          break
        }
        if (!children[index]) {
          break
        }
        if (children[index]['hasChildren'] === false || !children[index]['children'] || _.isEmpty(children[index]['children'])) {
          break
        }
        children = children[index]['children']
        i++
      }
      return path
    }
  }
}
</script>

<template>
  <el-container>
    <!-- 菜单栏 -->
    <el-aside :width="asideWidth">
      <el-scrollbar class="default-scrollbar" wrap-class="default-scrollbar__wrap" view-class="default-scrollbar__view">
        <div :class="isCollapse ? 'menu-collapsed' : 'menu-expanded'">
          <el-menu
            :default-active="defActive"
            class="el-menu-vertical-demo"
            unique-opened
            router
            :collapse="isCollapse">
            <bh-menu-item :menu="item" :key="item.name" v-for="item in $router.options.routes" v-if="item.menu"></bh-menu-item>
          </el-menu>
        </div>
      </el-scrollbar>
    </el-aside>
    <el-container>
      <!-- 右边上面的栏目 -->
      <el-header class="clear">
        <div class="collapse-btn" @click.prevent="changeCollapse">
          <i class="fas fa-bars" :class="{ rotate90: isCollapse }"></i>
        </div>
      </el-header>
      <!-- 路由容器 -->
      <router-view></router-view>
    </el-container>
  </el-container>
</template>

<style scoped>
.collapse-btn {
  float: left;
  font-size: 24px;
  cursor: pointer;
}
.rotate90 {
  filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=1);
  -moz-transform: rotate(90deg);
  -o-transform: rotate(90deg);
  -webkit-transform: rotate(90deg);
  transform: rotate(90deg);
}
</style>

补充说明:$router.options.routes(如果在js中是this.$router.options.routes)。取出当前路由内容,即src>project>admin>router>routers.js文件中的内容
菜单是通过$router.options.routes(项目路由)自动生成的。下面是路由(routers.js)的内容:

import Wrap from '@/components/Wrap.vue'

const routers = [{
  path: '/',
  name: 'home',
  text: '首页',
  menu: true,
  icon: 'fas fa-home',
  component: resolve => require(['../views/Home'], resolve)
}, {
  path: '/dashboard',
  name: 'dashboard',
  text: '仪表盘',
  menu: true,
  icon: 'fas fa-tachometer-alt',
  component: resolve => require(['../views/Dashboard'], resolve)
}, {
  path: '/user',
  name: 'user',
  text: '用户管理',
  menu: true,
  hasChildren: true,
  icon: 'fas fa-user-cog',
  component: Wrap,
  children: [{
    path: '/user/permission',
    name: 'user_permission',
    text: '权限管理',
    menu: true,
    component: resolve => require(['../views/user/Permission'], resolve)
  }, {
    path: '/user/role',
    name: 'user_role',
    text: '角色管理',
    menu: true,
    component: resolve => require(['../views/user/Role'], resolve)
  }, {
    path: '/user/user',
    name: 'user_user',
    text: '用户管理',
    menu: true,
    component: resolve => require(['../views/user/User'], resolve)
  }]
}]

export default routers

补充说明:Wrap.vue文件只是一个简单的路由容器,内容如下。因为vue-cli3项目生成的,如果写component: { template: '<router-view></router-view>' } 会报异常。我的方案是建立一个组件,引入。

<script>
export default {
  name: 'Wrap'
}
</script>

<template>
  <router-view></router-view>
</template>

生成菜单的组件BhMenuItem,关键内容如下:

<template>
  <component v-bind:is="currentItemComponent" :index="menu.path" :key="menu.path">
    <i :class="menu.icon" v-if="menu.icon && !hc"></i><span v-if="!hc" slot="title">{{menu.text}}</span>
    <template slot="title" v-if="hc">
      <i :class="menu.icon" v-if="menu.icon"></i><span slot="title">{{menu.text}}</span>
    </template>
    <!-- 这里用了递归生成菜单项 -->
    <bh-menu-item :menu="child" :key="child.name" v-for="child in menu.children" v-if="hc && child.menu"></bh-menu-item>
  </component>
</template>

<script>
export default {
  name: 'BhMenuItem',
  props: {
    menu: Object
  },
  data () {
    return {
      hc: false
    }
  },
  computed: {
    currentItemComponent: function () {
      return this.hasChildren() ? 'el-submenu' : 'el-menu-item'
    }
  },
  methods: {
    hasChildren () {
      this.hc = this.menu.hasChildren !== false && this.menu.children && this.menu.children.length > 0
      return this.hc
    }
  }
}
</script>

补充说明:菜单的结构是一棵树,可以一层层深入。在这里判断如果菜单项没有子菜单的话就用element-ui的el-menu-item组件,如果还有子菜单,则用el-menu-item。最后如果有子菜单,递归使用组件。
在App.vue用BhMenuItem组件生成菜单树。完成后,菜单如图:


生成的菜单

发现菜单的图标样式不合理,这是font awesome图标。下章补充font awesome图标使用方法,在这里与element-ui 图标的padding不一样。在全局样式表中加入样式:

.el-menu .fa,
.el-menu .fas,
.el-menu .far {
  margin-right: 10px;
}

最终效果


效果一

点击图标后收缩菜单。


效果二
上一篇下一篇

猜你喜欢

热点阅读