ant-design-vue 动态生成导航菜单(1~2级)

由于近来公司业务繁忙,以至于没时间去接着做antd的项目。emmm…后面应该还会忙些时间!

antd的路由导航还是有些坑的,对习惯了element的小伙伴来说不那么理想。这里先记录一下如何通过路由来动态生成1~2级导航菜单了(之后会出三级及以上导航菜单爬坑日志)。注: 此文为过渡文,没怎么上心,表达不明之处还请见谅,另外,欢迎打扰~~~~项目在线地址 (有用的话,记得给个start哦)

  1. antd的a-menu标签内部不能使用除template外的其他任何标签包裹 a-menu-item 以及 a-sub-menu标签,一旦使用就会报错!也因此没法组件化二级菜单(这里先不讲函数式组件)
  2. 既然是动态生成二级导航菜单,当然就不能使用定义路由、子路由的数组来维护,太繁琐,每次新增一个路由还得手动去添加。这样根本就算不得动态了。在这里博主参考的是花裤衩大佬的vue-element-admin开源项目所使用的方法。通过在定义路由的时候同时写入hidden(是否加入动态生成的导航菜单)以及meta(动态生成title及icon),代码如下:
{
  path: '/',
  component: Layout,
  redirect: '/dashboard',
  children: [
    {
      path: 'dashboard',
      name: 'Home',
      component: () => import('@/views/dashboard/index'),
      meta: { title: 'Dashboard', icon: 'user' }
    }
  ]
}
  1. 导航菜单(这里在后期有更新(在做三级及以上路由的时候有大幅更新),先放个最初版本的,在下一篇文章会贵出最终版)
<a-menu theme="dark" mode="inline" :selected-keys="currentSelectMenu" :open-keys="openKeys" @click="menuClick" @openChange="onOpenChange">
  <template v-for="item in routers">
    <template v-if="!item.hidden && item.children">
      <!-- 只有一个子菜单: 一级菜单 -->
      <a-menu-item v-if="item.children.length === 1" :key="item.path === '/' ? `${item.path}${item.children[0].path}`: `${item.path}/${item.children[0].path}`">
        <a-icon :type="item.children[0] && item.children[0].meta && item.children[0].meta.icon" />
        <router-link style="display: inline-block" :to="{path: item.path === '/' ? `${item.path}${item.children[0].path}` : `${item.path}/${item.children[0].path}`}">
          {{ generateTitle(item.children[0].meta.title) }}
        </router-link>
      </a-menu-item>
      <!-- submenu -->
      <a-sub-menu v-else :key="item.path">
        <span slot="title">
          <a-icon :type="item.meta && item.meta.icon" />
          <span v-if="item.meta">{{ generateTitle(item.meta.title) }}</span>
        </span>
        <template v-for="subItem in item.children">
          <a-menu-item v-if="!subItem.children" :key="item.path + '/' + subItem.path">
            <a-icon v-if="subItem.meta.icon" :type="subItem.meta && subItem.meta.icon" />
            <router-link style="display: inline-block" :to="item.path + '/' + subItem.path">
              {{ generateTitle(subItem.meta.title) }}
            </router-link>
          </a-menu-item>
        </template>
      </a-sub-menu>
    </template>
  </template>
</a-menu>

这里涉及到的a-menu属性啥的就不详细赘述了,官方文档挺好的(主要是表达能力着实不行…),这里说一下所用到的属性及事件吧。

  • selected-keys: 当前选中的菜单项,值的类型为字符串数组。这里主要用于刷新界面后保持选中的状态;
// 当前选中的菜单项(这里直接返回path即可)
currentSelectMenu() {
  return [this.$route.path]
}

open-keys:当前展开的 SubMenu 菜单项 key 数组,这里的key数组必须与父节点菜单的key值相对应。实现openKeys与父节点菜单的key的方法有很多,最初采用的是将其存入浏览器的sessionStorage(有些麻烦,后期在做三级菜单的时候移除了)。openChange:SubMenu 展开/关闭的回调,主要是为了控制当前submenu菜单是否展开/关闭;

// 通过菜单栏的点击事件menuClick将当前菜单的key存入sessionStorage
menuClick({ item, key, keyPath }) {
  // length为1则说明没有子菜单
  if (keyPath.length === 1) {
    this.openKeys = []
    window.sessionStorage.clear()
  } else window.sessionStorage.setItem('open-menu-key', keyPath.pop()) // 将副路由存入sessionStorage,展开激活submenu菜单
},

// 在created生命周期获取,初始化展开submenu
const openKeys = window.sessionStorage.getItem('open-menu-key')
if (openKeys) this.openKeys = [openKeys]

// openChange:SubMenu 展开/关闭的回调,主要是为了控制当前submenu菜单是否展开/关闭
// 子菜单展开/关闭的回调
onOpenChange(openKeys) {
  const latestOpenKey = openKeys.find(key => this.openKeys.indexOf(key) === -1)
  this.openKeys = this.rootSubmenuKeys.indexOf(latestOpenKey) === -1 ? openKeys : latestOpenKey ? [latestOpenKey] : []
}

最后,附上全部代码

<template>
  <div class="sidebar-container">
    <a-layout-sider v-model="isCollapsed" :trigger="null" collapsible>
      <div class="logo" />
      <a-menu theme="dark" mode="inline" :selected-keys="currentSelectMenu" :open-keys="openKeys" @click="menuClick" @openChange="onOpenChange">
        <template v-for="item in routers">
          <template v-if="!item.hidden && item.children">
            <!-- menu -->
            <a-menu-item v-if="item.children.length === 1" :key="item.path === '/' ? `${item.path}${item.children[0].path}`: `${item.path}/${item.children[0].path}`">
              <a-icon :type="item.children[0] && item.children[0].meta && item.children[0].meta.icon" />
              <router-link style="display: inline-block" :to="{path: item.path === '/' ? `${item.path}${item.children[0].path}` : `${item.path}/${item.children[0].path}`}">
                {{ generateTitle(item.children[0].meta.title) }}
              </router-link>
            </a-menu-item>
            <!-- submenu -->
            <a-sub-menu v-else :key="item.path">
              <span slot="title">
                <a-icon :type="item.meta && item.meta.icon" />
                <span v-if="item.meta">{{ generateTitle(item.meta.title) }}</span>
              </span>
              <template v-for="subItem in item.children">
                <a-menu-item v-if="!subItem.children" :key="item.path + '/' + subItem.path">
                  <a-icon v-if="subItem.meta.icon" :type="subItem.meta && subItem.meta.icon" />
                  <router-link style="display: inline-block" :to="item.path + '/' + subItem.path">
                    {{ generateTitle(subItem.meta.title) }}
                  </router-link>
                </a-menu-item>
              </template>
            </a-sub-menu>
          </template>
        </template>
      </a-menu>
    </a-layout-sider>
  </div>
</template>

<script>
// 国际化导航菜单title
import { generateTitle } from './breadcrumb/index.js'
export default {
  name: 'SubMenu',
  props: {
    collapsed: [Boolean],
    routers: {
      type: Array,
      default: () => []
    }
  },
  data() {
    return {
      isCollapsed: false,
      onlyOneChild: null,
      openKeys: [''],
      rootSubmenuKeys: ['/form', '/about'] // submenu数组
    }
  },
  computed: {
    // 当前选中的菜单项
    currentSelectMenu() {
      return [this.$route.path]
    },
    router() {
      return this.$route.path
    }
  },
  watch: {
    collapsed: {
      handler: function(val) {
        this.isCollapsed = val
      }
    }
  },
  created() {
    // 获取当前副路由(需要展开)
    const openKeys = window.sessionStorage.getItem('open-menu-key')
    if (openKeys) this.openKeys = [openKeys]
  },
  methods: {
    generateTitle,
    // 点击菜单触发事件
    menuClick({ item, key, keyPath }) {
      // length为1则说明没有子菜单
      if (keyPath.length === 1) {
        this.openKeys = []
        window.sessionStorage.clear()
      } else window.sessionStorage.setItem('open-menu-key', keyPath.pop()) // 将副路由存入sessionStorage,展开激活submenu菜单
    },
    // 子菜单展开/关闭的回调
    onOpenChange(openKeys) {
      const latestOpenKey = openKeys.find(key => this.openKeys.indexOf(key) === -1)
      this.openKeys = this.rootSubmenuKeys.indexOf(latestOpenKey) === -1 ? openKeys : latestOpenKey ? [latestOpenKey] : []
    }
  }
}
</script>

<style lang="less" scoped>
.logo {
  height: 32px;
  background: rgba(255, 255, 255, 0.2);
  margin: 16px;
}
</style>
  • 2
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值