vue-cli3 + elementui 搭建后台管理界面(五)-递归生成侧栏路由

1 定义多级菜单

修改 src/router/index.js 的 / 路由

  {
    path: '/',
    redirect: '/dashboard',
    name: 'Container',
    component: Container,
    children: [
      {path: 'dashboard', name: '首页', component: Dashboard, 
        children: [
          {path: 'dashboard1', name: '首页1', component: Dashboard,},
          {path: 'dashboard2', name: '首页2', component: Dashboard,
            children: [
              {path: 'dashboard21', name: '首页21', component: Dashboard,},
              {path: 'dashboard22', name: '首页22', component: Dashboard, },
            ] },
        ]
      },
      {path: 'article', name: '文章', component: Article, },
    ]
  }

2 抽出Sidebar组件

生成的递归路由放在侧边栏,因此抽取 sidebar 组件,sidebar 包含logo和 递归路由
再抽取 SidebarItem 为单独的路由组件,方便递归调用

2.1 Sidebar

Sidebar 接收collapse、路由数组,同时引入 SidebarItem 组件

子组件传入:

  • barIdx: 当前路由的索引,用来定位子菜单
  • subroute: 路由对象
  • fatherpath: 父路径,如 /、 /a/b
<template>
  <div>
    <div class="app-side-logo">
      <img src="@/assets/logo.png"
          :width="collapse ? '60' : '60'"
          height="60" />
    </div>

    <el-menu class="el-menu-vertical-demo"
          :default-active="defaultActive"
            router
            :collapse="collapse"
            >
      <SidebarItem v-for="(item, idx) in routes" 
        :subroute="item"
        :fatherpath="fatherPath"
        :barIdx="idx" :key="idx" />
    </el-menu>
  </div>
</template>

<script>
import SidebarItem from './SidebarItem'
export default {
  naem: "Sidebar",
  components: {
    SidebarItem
  },
  props: {
    collapse: {
      type: Boolean
    },
    routes: {
      type: Array
    }
  },
  computed: {
     // 首次进入页面时展开当前页面所属的菜单
    defaultActive(){
      return this.$route.path
    },
    fatherPath(){
      // 这里直接获取路由配置的 '/' 项
      return this.$router.options.routes[1].path
    }
  }
}
</script>

<style>

</style>

2.2 SidebarItem

SidebarItem 接收路由、父路由path、父级idx,然后递归调用自身

<template>
  <!-- 如果当前 subroute 有子节点 -->
  <el-submenu v-if="!subroute.hidden && subroute.children && subroute.children.length > 0"
    :index="genPath(fatherpath, subroute.path)">
    <!-- 创建菜单分组 -->
    <template slot="title">
      <i class="el-icon-menu"></i>
      <span slot="title">{{subroute.name}}</span>
    </template>

    <!-- 递归调用自身,直到 subroute 不含子节点 -->
    <SidebarItem v-for="(submenu, subidx) in subroute.children"
      :subroute="submenu"
      :fatherpath="genPath(fatherpath, subroute.path)"
      :barIdx="subidx" 
      :key="barIdx + '-' + subidx" 
      />
  </el-submenu>

  <!-- 当前节点不含子节点且非隐藏 -->
  <el-menu-item style="font-weight: 400"
    v-else-if="!subroute.hidden"
    :index="genPath(fatherpath, subroute.path)"
    >{{subroute.name}}
  </el-menu-item>

  <el-menu-item style="font-weight: 400"
    v-else
    :index="genPath(fatherpath, subroute.path)"
    >{{ subroute.name }}
  </el-menu-item>
</template>

<script>
export default {
  name: 'SidebarItem',
  props: {
    subroute: {
      type: Object
    },
    barIdx: {
      type: [String, Number]
    },
    fatherpath: {
      type: String
    }
  },
  computed: {
    // 默认激活的路由, 用来激活菜单选中状态
    defaultActive: function(){
      return this.$route.path
    },
  },
  methods: {
    // 生成侧边栏路由,格式: /a/b/c
    genPath: function(){
      let arr = [ ...arguments ]
      let path = arr.map(v => {
          while (v[0] === '/'){
            v = v.substring(1)
          }
          while(v[-1] === '/'){
            v = v.substring(0, v.length)
          }
        return v 
      }).join('/')
      path = path[0] === '/' ? path : '/'+path
      return path
    },
    handleOpen: function(key, keyPath) {
      console.log(key, keyPath)
    },
    handleClose: function(key, keyPath) {
      console.log(key, keyPath)
    }
  },
  mounted: function(){
    console.log('sidebar routes: ', this.routes)
  }
}
</script>

<style>
</style>

3 项目结构

此时 src 的目录结构

│  App.vue
│  main.js
├─assets
│      logo.png
├─components
│      HelloWorld.vue
│      Sidebar.vue
│      SidebarItem.vue
├─container
│      Container.vue
├─router
│      index.js
├─styles
│      index.scss
└─views
    │  TheLogin.vue
    ├─article
    │      index.vue
    └─dashboard
            index.vue

4 修改容器配置

src/container/Container.vue 引入 Sidebar 组件

<template>
<!-- ... -->
<el-aside class="app-side app-side-left"
          :class="isCollapse ? 'app-side-collapsed' : 'app-side-expanded'">
  <Sidebar :collapse="isCollapse" :routes="$router.options.routes[1].children"/>
</el-aside>
<!-- ... -->
</template>
<script>
import Sidebar from '@/components/Sidebar'
export default {
  name: 'Container',
  components: {
    Sidebar
  },
/** ... */
</script>

页面效果


image
image

推荐阅读更多精彩内容