vue樹形組件實(shí)現(xiàn)思路

效果圖如下:


樹形組件效果圖

父組件調(diào)用樹形組件代碼:

// menu 要展示菜單的數(shù)據(jù)
// depth 記錄層級(jí)的深度,計(jì)算文字縮進(jìn)的像素
// chapterId 傳遞進(jìn)來展示默認(rèn)選中項(xiàng)
// iconUrl 可選,展開折疊圖標(biāo), 默認(rèn)是加減號(hào)樣式
// 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. 父組件獲取的數(shù)據(jù)結(jié)構(gòu)如下,把數(shù)據(jù)處理好后傳給樹形組件:
{
  "result": [{
      "objectId": "5f97b67697ae1535c05faef1",
      "children": [{
          "objectId": "5f97b87497ae1535c05fafb6",
          "children": [{
            "objectId": "5f97bd5d97ae1535c05fb191",
            "name": "夯實(shí)基礎(chǔ)"
          }],
          "name": "第1節(jié) 空間和時(shí)間"
        },
        {
          "objectId": "5f97b88d97ae1535c05fafbc",
          "children": [{
            "objectId": "5f97bd8697ae1535c05fb1aa",
            "name": "夯實(shí)基礎(chǔ)"
          }],
          "name": "第2節(jié)  質(zhì)點(diǎn)和位移"
        }
      ],
      "name": "第1章   運(yùn)動(dòng)的描述"
    },
    {
      "objectId": "5f97b70497ae1535c05faf21",
      "children": [{
        "objectId": "5f97b93397ae1535c05fb002",
        "children": [{
          "objectId": "5f97bdf897ae1535c05fb1f0",
          "name": "夯實(shí)基礎(chǔ)"
        }],
        "name": "第1節(jié)  速度變化規(guī)律"
      }],
      "name": "第2章   勻變速直線運(yùn)動(dòng)"
    },
    {
      "objectId": "5f97b77f97ae1535c05faf50",
      "children": [{
          "objectId": "5f97b9ed97ae1535c05fb05f",
          "children": [{
            "objectId": "5f97beae97ae1535c05fb27e",
            "children": [{
              "objectId": "5f97beb297ae1535c05fb281",
              "name": "夯實(shí)基礎(chǔ)"
            }],
            "name": "第1課時(shí)  合力與分力"
          }],
          "name": "第1節(jié)  科學(xué)探究:力的合成"
        },
        {
          "objectId": "5f97b9fd97ae1535c05fb06a",
          "children": [{
            "objectId": "5f97bec197ae1535c05fb28b",
            "name": "夯實(shí)基礎(chǔ)"
          }],
          "name": "第2節(jié)  力得分解"
        }
      ],
      "name": "第3章   力與平衡"
    },
    {
      "objectId": "5f97b7a897ae1535c05faf68",
      "children": [{
        "objectId": "5f97bab697ae1535c05fb0c5",
        "name": "章末檢測(cè)卷(一)"
      }],
      "name": "章末檢測(cè)卷"
    }
  ],
  "resultCode": 0,
  "errorCode": -1
}
  1. 主要方法

3-1. 構(gòu)造數(shù)據(jù),給數(shù)據(jù)添加isOpen開關(guān)以及樓層floor

    chapterDataMenuFlag ({val, floor}) {
      if (!(val instanceof Array)) { return false }
      val.map((item, i) => {
        if (item.children) {
          if (i === 0) {
            item.isOpen = true // 默認(rèn)第一層都是展開狀態(tài)
          } else {
            item.isOpen = false
          }
          // 判斷是第幾層
          if (!item.floor) {
            item.floor = `${floor}-${item.objectId}`
          }
          this.chapterDataMenuFlag({val: item.children, floor: item.floor})
        }
      })
      return val
    }

3-2. 渲染后點(diǎn)擊展開折疊

    // val是獲取到并構(gòu)造好的數(shù)據(jù),checkedData為當(dāng)前點(diǎn)擊項(xiàng)的數(shù)據(jù)
    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) {
            // 當(dāng)前選中的節(jié)點(diǎn)進(jìn)行展開和折疊
            item.isOpen = !checkedData.isOpen
          } else if (checkedData.floor && totalFloor.includes(item.objectId)) {
            // 當(dāng)前選中的所有父節(jié)點(diǎn)都是展開的
            item.isOpen = true
          }
          if (!totalFloor.includes(item.objectId)) {
            // 當(dāng)前選中的根父節(jié)點(diǎn)的相鄰節(jié)點(diǎn)都折疊
            item.isOpen = false
          }
          this.menuHandler(item.children, checkedData)
        }
      })
    }

3-3. 如果需要展開第一項(xiàng)里最深的一項(xiàng),并設(shè)置

    // 返回最深的一項(xiàng)的id,這個(gè)id后面要傳遞到樹形組件當(dāng)中
    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后,保存當(dāng)前選中的chapterId
          tempObj.chapterId = v.objectId
          tempObj.knowledgeIdToggle = v
        }
      }
      return tempObj
    },

遞歸樹形組件代碼

<!--
 * @Descripttion: 遞歸菜單
  props參數(shù)如下:
  menu 要展示菜單的數(shù)據(jù)
  depth 記錄層級(jí)的深度,計(jì)算文字縮進(jìn)的像素
  chapterId 傳遞進(jìn)來展示默認(rèn)選中項(xiàng)
  iconUrl 可選,展開折疊圖標(biāo), 默認(rèn)是加減號(hào)樣式
  theme 可選,主題,用來修改樣式 目前只有一個(gè) 'menu-catalog-theme'

  與父組件交互的事件:
  @menu 傳遞當(dāng)前點(diǎn)擊項(xiàng)的數(shù)據(jù)給父組件
  示例:
  <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: ''
    },
    // 可選,展開折疊圖標(biāo)
    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>

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容