美文网首页VueJSJQuery&JS&BootStrap前端
vue合家福实例(3):根据路由生成菜单

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

作者: 碧波之心 | 来源:发表于2018-09-07 11:26 被阅读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;
    }
    

    最终效果


    效果一

    点击图标后收缩菜单。


    效果二

    相关文章

      网友评论

      • eb00aba16180:你好资讯一个相关问题,这个功能实现后,我如何让一个页面不显示在左边菜单中?现在的需求是我要在别的页面调用这个页面,但不能让他显示在菜单列表里
        碧波之心:修改成:menu: false 就不会显示在菜单中
        碧波之心:{
        path: '/dashboard',
        name: 'dashboard',
        text: '仪表盘',
        menu: true,
        icon: 'fas fa-tachometer-alt',
        component: resolve => require(['../views/Dashboard'], resolve)
        }

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

      本文链接:https://www.haomeiwen.com/subject/vdiqgftx.html