组件递归实现多级菜单

750 阅读2分钟

递归组件

递归组件在项目中的多级菜单功能再好用不过,它像普通函数进行递归一样,解决了一些不确定层级的场景

说到不确定层级的菜单,我们先看下大概的数据结构, 如下数组:

export default [
  {
    id: 1,
    father_id: 0,
    status: 1,
    name: '生命科学竞赛',
    _child: [
      {
        id: 2,
        father_id: 1,
        status: 1,
        name: '野外实习类',
        _child: [
          { id: 3, father_id: 2, status: 1, name: '植物学' },
          { id: 4, father_id: 2, status: 1, name: '动物学' },
          { id: 5, father_id: 2, status: 1, name: '微生物学' },
          { id: 6, father_id: 2, status: 1, name: '生态学' }
        ]
      },
      {
        id: 7,
        father_id: 1,
        status: 1,
        name: '科学研究类',
        _child: [
          { id: 8, father_id: 7, status: 1, name: '植物学与植物生理学' },
          { id: 9, father_id: 7, status: 1, name: '动物学与动物生理学' },
          { id: 10, father_id: 7, status: 1, name: '微生物学' },
          { id: 11, father_id: 7, status: 1, name: '生态学' },
          {
            id: 21,
            father_id: 7,
            status: 1,
            name: '农学',
            _child: [
              { id: 22, father_id: 21, status: 1, name: '植物生产类' },
              { id: 23, father_id: 21, status: 1, name: '动物生产类' },
              { id: 24, father_id: 21, status: 1, name: '动物医学类' }
            ]
          },
          {
            id: 41,
            father_id: 7,
            status: 1,
            name: '药学'
          },
          { id: 55, father_id: 7, status: 1, name: '其他' }
        ]
      },
      { id: 71, father_id: 1, status: 1, name: '添加' }
    ]
  },
  {
    id: 56,
    father_id: 0,
    status: 1,
    name: '考研相关',
    _child: [
      { id: 57, father_id: 56, status: 1, name: '政治' },
      { id: 58, father_id: 56, status: 1, name: '外国语' }
    ]
  },
  {
    id: 65,
    father_id: 0,
    status: 1,
    name: '找工作',
    _child: [
      { id: 66, father_id: 65, status: 1, name: '招聘会' },
      { id: 67, father_id: 65, status: 1, name: '简历' }
    ]
  },
  {
    id: 70,
    father_id: 0,
    status: 1,
    name: '其他',
    _child: [
      {
        id: 72,
        father_id: 70,
        status: 1,
        name: '新增的根级12311111'
      }
    ]
  }
]

实现方案

  • 递归组件遍历,利用v-if在层级不存在child情况下中止递归
  • 给予每个层级菜单depth进行样式区别
  • 设置每个层级菜单active当前选中菜单记录
  • 默认参数actives设置,每个层级利用depth在数组(如:[1,2,3])默认值中获取自身默认值,若无则当前层级active为整个层级数组的第一个对象
  • 每个层级的选中都会通过active的重新赋值,进而改变其子层级的data数组,直到最底层,所以,我们只记录最低层级的active变化向上传递改变,直到引用组件的最外层

代码实现

<template>
  <div class="wrap">
    <div class="menu-wrap">
      <div
        v-for="item in data"
        :key="item.id"
        :class="`menu-item menu-${depth} ${active.id == item.id ? 'acitve' : ''}`"
        @click="active = item"
      >
        <span class="title">{{ item.name }}</span>
      </div>
    </div>
    <next-menu
      v-if="subMenu && subMenu.length"
      :data="subMenu"
      :actives="actives"
      :depth="depth + 1"
      @change="nextCompchange"
    />
  </div>
</template>

<script>
export default {
  name: 'NextMenu',
  props: {
    depth: { type: Number, default: () => 1 },
    data: { type: Array, default: () => [] },
    actives: { type: Array, default: () => [] }
  },
  data () {
    return {
      active: {},
      subMenu: []
    }
  },
  watch: {
    data: {
      handler: function () {
        const activeId = this.actives[this.depth - 1]
        const defaultValue = this.data[0] || {}
        this.active = activeId
          ? this.data.find(item => item.id === activeId) || defaultValue
          : defaultValue
      },
      immediate: true
    },
    active: {
      handler: function () {
        this.subMenu = this.active._child
        if (!this.subMenu || this.subMenu.length <= 0) {
          this.$emit('change', [this.active.id])
        }
      },
      immediate: true
    }
  },
  methods: {
    nextCompchange (actives) {
      this.$emit('change', [this.active.id].concat(actives))
    }
  }
}
</script>

<style lang="less" scoped>
.menu-wrap {
  display: flex;
  flex-wrap: wrap;
  .menu-item {
    margin-right: 10px;
  }
  .acitve {
    color: red;
  }
}
.menu-1 {
  font-size: 18px;
  color: #111111;
  font-weight: bold;
}
.menu-2 {
  font-size: 16px;
}
.menu-3 {
  font-size: 14px;
}
</style>

需要注意的点:当click后改变active通过最底层emit传递当前的active.id,然后在父层级里面通过@change="nextCompchange"接收子层级传过来的active.id,这样再拼接上自身的选中id即[this.active.id].concat(actives),最终整合所有选中id,得到所要结果[1,2,4]