vue树形组件实现思路

2021-07-17  本文已影响0人  咕嘟咕嘟li

效果图如下:


树形组件效果图

父组件调用树形组件代码:

// menu 要展示菜单的数据
// depth 记录层级的深度,计算文字缩进的像素
// chapterId 传递进来展示默认选中项
// iconUrl 可选,展开折叠图标, 默认是加减号样式
// theme 可选,主题,用来修改样式  menu-catalog-normal
<choose-question-menu :menu="chapterData" :chapter-id="chapterId" 
    class="m-left-question-menu m-tree-menu-wrap"
    @menu="chapterListHandler" />

父组件要做的主要步骤:

  1. 引入树形组件
  2. 父组件获取的数据结构如下,把数据处理好后传给树形组件:
{
  "result": [{
      "objectId": "5f97b67697ae1535c05faef1",
      "children": [{
          "objectId": "5f97b87497ae1535c05fafb6",
          "children": [{
            "objectId": "5f97bd5d97ae1535c05fb191",
            "name": "夯实基础"
          }],
          "name": "第1节 空间和时间"
        },
        {
          "objectId": "5f97b88d97ae1535c05fafbc",
          "children": [{
            "objectId": "5f97bd8697ae1535c05fb1aa",
            "name": "夯实基础"
          }],
          "name": "第2节  质点和位移"
        }
      ],
      "name": "第1章   运动的描述"
    },
    {
      "objectId": "5f97b70497ae1535c05faf21",
      "children": [{
        "objectId": "5f97b93397ae1535c05fb002",
        "children": [{
          "objectId": "5f97bdf897ae1535c05fb1f0",
          "name": "夯实基础"
        }],
        "name": "第1节  速度变化规律"
      }],
      "name": "第2章   匀变速直线运动"
    },
    {
      "objectId": "5f97b77f97ae1535c05faf50",
      "children": [{
          "objectId": "5f97b9ed97ae1535c05fb05f",
          "children": [{
            "objectId": "5f97beae97ae1535c05fb27e",
            "children": [{
              "objectId": "5f97beb297ae1535c05fb281",
              "name": "夯实基础"
            }],
            "name": "第1课时  合力与分力"
          }],
          "name": "第1节  科学探究:力的合成"
        },
        {
          "objectId": "5f97b9fd97ae1535c05fb06a",
          "children": [{
            "objectId": "5f97bec197ae1535c05fb28b",
            "name": "夯实基础"
          }],
          "name": "第2节  力得分解"
        }
      ],
      "name": "第3章   力与平衡"
    },
    {
      "objectId": "5f97b7a897ae1535c05faf68",
      "children": [{
        "objectId": "5f97bab697ae1535c05fb0c5",
        "name": "章末检测卷(一)"
      }],
      "name": "章末检测卷"
    }
  ],
  "resultCode": 0,
  "errorCode": -1
}
  1. 主要方法

3-1. 构造数据,给数据添加isOpen开关以及楼层floor

    chapterDataMenuFlag ({val, floor}) {
      if (!(val instanceof Array)) { return false }
      val.map((item, i) => {
        if (item.children) {
          if (i === 0) {
            item.isOpen = true // 默认第一层都是展开状态
          } else {
            item.isOpen = false
          }
          // 判断是第几层
          if (!item.floor) {
            item.floor = `${floor}-${item.objectId}`
          }
          this.chapterDataMenuFlag({val: item.children, floor: item.floor})
        }
      })
      return val
    }

3-2. 渲染后点击展开折叠

    // val是获取到并构造好的数据,checkedData为当前点击项的数据
    menuHandler (val, checkedData) {
      if (!(val instanceof Array)) { return false }
      val.map(item => {
        if (item.children) {
          let currentFloor = checkedData.floor.split('-').pop()
          let totalFloor = checkedData.floor.split('-')
          if (checkedData.floor && currentFloor === item.objectId) {
            // 当前选中的节点进行展开和折叠
            item.isOpen = !checkedData.isOpen
          } else if (checkedData.floor && totalFloor.includes(item.objectId)) {
            // 当前选中的所有父节点都是展开的
            item.isOpen = true
          }
          if (!totalFloor.includes(item.objectId)) {
            // 当前选中的根父节点的相邻节点都折叠
            item.isOpen = false
          }
          this.menuHandler(item.children, checkedData)
        }
      })
    }

3-3. 如果需要展开第一项里最深的一项,并设置

    // 返回最深的一项的id,这个id后面要传递到树形组件当中
    loopForId (val, tempObj) {
      if (!(val instanceof Array)) { return false }
      tempObj = tempObj || {}
      let v = val[0]
      if (v) {
        if (v.children) {
          return this.loopForId(v.children, tempObj)
        } else {
          // 没有children后,保存当前选中的chapterId
          tempObj.chapterId = v.objectId
          tempObj.knowledgeIdToggle = v
        }
      }
      return tempObj
    },

递归树形组件代码

<!--
 * @Descripttion: 递归菜单
  props参数如下:
  menu 要展示菜单的数据
  depth 记录层级的深度,计算文字缩进的像素
  chapterId 传递进来展示默认选中项
  iconUrl 可选,展开折叠图标, 默认是加减号样式
  theme 可选,主题,用来修改样式 目前只有一个 'menu-catalog-theme'

  与父组件交互的事件:
  @menu 传递当前点击项的数据给父组件
  示例:
  <choose-question-menu :menu="menu" @menu="lessonMenuHandler"
    :chapter-id="currentCatalog.objectId" theme="menu-catalog-them"
    :icon-url="iconUrl"
    class="m-tree-menu-wrap"/>
-->
<template>
  <div :class="theme">
    <div v-for="(item2, index2) in menu" :key="index2">
      <div :class="['m-cursor', {'m-chapter-list': depth < 1},
        {'m-chapter-knowledge': depth > 0}, {'active': chapterId===item2.objectId}]"
        :title="item2.name" @click="toggleChildren(item2)"
        :style="indent">
        <template v-if="item2.children">
          <div class="m-switch-icon" v-if="item2.children && item2.isOpen" >
            <div class="g-row-flex-center" style="width: 100%;height:100%;"><img :src="iconUrl.openIcon" alt="展开"></div>
          </div>
          <div class="m-switch-icon" v-else>
             <div class="g-row-flex-center" style="width: 100%;height:100%;"><img :src="iconUrl.closeIcon" alt="合起"></div>
          </div>
        </template>
        {{item2.name}}
        <div style="display: none;">{{item2 && item2.isOpen}}</div>
      </div>
      <template v-if="item2.isOpen">
        <choose-question-menu :menu="item2.children" :depth="depth + 1"
          :icon-url="iconUrl" :theme="theme"
         :chapter-id="chapterId" @menu="toggleChildren"/>
      </template>
    </div>
  </div>
</template>
<script>
export default {
  name: 'ChooseQuestionMenu',
  props: {
    menu: {
      type: Array | undefined,
      required: true
    },
    depth: {
      type: Number,
      default: 0
    },
    chapterId: {
      type: String,
      default: ''
    },
    // 可选,展开折叠图标
    iconUrl: {
      type: Object,
      default: () => {
        return {
          openIcon: '/static/images/bk_icon_mlzk.png',
          closeIcon: '/static/images/bk_icon_mlzd.png'
        }
      }
    },
    // 可选,主题,用来修改样式
    theme: {
      type: String,
      default: ''
    }
  },
  computed: {
    indent () {
      return {'text-indent': `${this.depth * 34}px`}
    }
  },
  data () {
    return {
      currentId: true
    }
  },
  methods: {
    toggleChildren (item) {
      this.$emit('menu', item)
    }
  }
}
</script>

<style lang="scss">
// css代码就不放了
@import '~@/style/menu-tree';
</style>

上一篇 下一篇

猜你喜欢

热点阅读