效果图如下:

父组件调用树形组件代码:
// 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" />
父组件要做的主要步骤:
- 引入树形组件
- 父组件获取的数据结构如下,把数据处理好后传给树形组件:
{
"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
}
- 主要方法
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>
网友评论