效果圖如下:

樹形組件效果圖
父組件調(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" />
父組件要做的主要步驟:
- 引入樹形組件
- 父組件獲取的數(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
}
- 主要方法
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>