一、背景
因小程序项目要展示地方产业链树状图,手机宽度有限,左右滑动也改变不了可视区域问题,所以设计了一个单列的树视图功能,因短时间内未找到符合条件风格的小程序插件及相关案例,所以避免浪费时间直接手写一个。
先附上封面图的VUE版本
源码,开箱即用:
KTree
因为微信小程序代码不知道往哪放,所以修改了个VUE3
的版本提交到github,道理都一样。
接着附上微信小程序实现效果:
二、效果
这个实现思路相对比较清晰,用递归方法把选择的一级节点下所有子节点遍历出来,然后继续第一个节点往下遍历所有子节点,重复此动作直到找不到子级为止。
我是封装了一个组件,传入当前节点的子集,组件内部渲染子集列表,然后判断列表第一个节点下还有子集的话就组件自己调自己,然后传入第一个节点的子集,以此递归下去。
HTML结构
<template>
<div class="c-viewTree-container">
<div class="tree-node">
<div :class="{'node-item': true, 'node-hide': !item.label}" v-for="(item, index) in treeData" :key="item.id">
<div class="item-vertical-top">
<div class="vertical-line"></div>
<i class="el-icon-caret-bottom"></i>
<div :class="{'vertical-transverse': true, 'none-transverse-last': item.lastLineHide, 'none-transverse-first': item.firstLineHide}" v-if="!item.only && treeData.length > 1"></div>
</div>
<div class="item-content">
<div :class="{'content-box': true, 'constnt-pointer': item.children && item.children.length > 0}" @click="treeChange(item, index)">
<div class="item-text">{{item.label}}</div>
<div :class="{'item-icon': true, 'item-icon-active': targetTree.id === item.id}">
<i class="iconfont">√</i>
</div>
</div>
</div>
<div class="item-vertical-bottom" v-if="targetTree.id === item.id && targetTree.children && targetTree.children.length > 0">
<div class="vertical-line"></div>
</div>
</div>
</div>
<CKTree v-if="targetTree.children && targetTree.children.length > 0" :treeData="targetTree.children" @KTreeChange="viewClick">
</div>
</template>
复制代码
点、线、三角
都是使用节点样式组成(本来想着伪类样式更合适,但也懒得改了)
,布局很扎实,所以不用它会担心散架!(我也曾担心过)
。
点、三角
的话判断下级有节点就渲染。
线
的话判断前后有没有兄弟来控制长度100%
还是50%
,以及靠左还是靠右。
切换节点的时候将当前选择节点的子集拿去递归渲染即可。
节点如下:
单列处理
每次渲染组件时都要定位获取一个当前节点,以此来继续往下渲染它的子节点。
if (props.treeData && props.treeData.length > 0) {
let attr = props.treeData.findIndex((item:any) => { return item.id })
state.targetIndex = 0
if (attr > -1) {
state.targetIndex = attr
treeRegular(props.treeData[attr])
} else {
state.targetIndex = 0
}
} else {
state.targetTree = {}
}
复制代码
treeRegular
方法是用来处理拼装子节点数量保证对齐,以及点、线
的绘制逻辑。
targetIndex
是当前节点集合
需要继续向下展示子集
的下标。
先判断如果当前选中节点的子集合
长度大过当前节点及兄弟节点
的长度时,判断出第一个树节点
和最后一个树节点
的线长度为50%
并且分别靠右
和靠左
。
const treeRegular:Function = (res:any) => {
if (res && res.children && res.children.length > 0) {
let attr = JSON.parse(JSON.stringify(res))
if (props.treeData.length > attr.children.length) {
...
}
attr.children.forEach((item: any, index: any) => {
if (!attr.children[index].id && attr.children[index - 1]) {
attr.children[index - 1].lastLineHide = true
}
if (!attr.children[index].id && attr.children[index + 1]) {
attr.children[index + 1].firstLineHide = true
}
})
state.targetTree = attr
} else {
state.targetTree = {}
}
}
复制代码
接着如果当前选中节点的子集合
长度小于当前节点及兄弟节点
的长度时,为了调整对齐位置,先按传入的当前节点及兄弟节点
长度设置一个空集合attr.children
。
const treeRegular:Function = (res:any) => {
...
attr.children = []
for(let i=0; i < chainLen; i++) {
attr.children.push({})
}
...
}
复制代码
如果当前选中节点的子集合
的长度为1
时,设置一个横向线
隐藏的标识only
,然后按targetIndex
的下标给子集合
相同位置插入唯一的那个节点。
const treeRegular:Function = (res:any) => {
...
let attr = JSON.parse(JSON.stringify(res))
res.children[0].only = true
attr.children.splice(state.targetIndex, 1, res.children[0])
...
}
复制代码
如果当前选中节点的子集合
的长度大于1
时,则遍历将子集的每一项下标
进行判断,如果子集合长度当前集合下标targetIndex
小于当前集合的长度
,则往空集合attr.children
下标位置targetIndex
的右边依次插入子节点
,否则就往空集合attr.children
下标位置targetIndex
的左边依次插入子节点
。
const treeRegular:Function = (res:any) => {
...
let dataLen = res.children.length
for(let i=0; i < dataLen; i++) {
if ((dataLen + state.targetIndex) < chainLen) {
attr.children.splice(state.targetIndex + i, 1, res.children[i])
} else {
attr.children.splice(chainLen - (i + 1), 1, res.children[i])
}
}
...
}
复制代码
最后将新创建的需要展示的子集传入下一层组件中。
完整代码如下
const treeRegular:Function = (res:any) => {
if (res && res.children && res.children.length > 0) {
let attr = JSON.parse(JSON.stringify(res))
if (props.treeData.length > attr.children.length) {
let dataLen = res.children.length
let chainLen = props.treeData.length
attr.children = []
for(let i=0; i < chainLen; i++) {
attr.children.push({})
}
if (dataLen === 1) {
res.children[0].only = true
attr.children.splice(state.targetIndex, 1, res.children[0])
} else {
for(let i=0; i < dataLen; i++) {
if ((dataLen + state.targetIndex) < chainLen) {
attr.children.splice(state.targetIndex + i, 1, res.children[i])
} else {
attr.children.splice(chainLen - (i + 1), 1, res.children[i])
}
}
}
}
attr.children.forEach((item: any, index: any) => {
if (!attr.children[index].id && attr.children[index - 1]) {
attr.children[index - 1].lastLineHide = true
}
if (!attr.children[index].id && attr.children[index + 1]) {
attr.children[index + 1].firstLineHide = true
}
})
state.targetTree = attr
} else {
state.targetTree = {}
}
}
复制代码
结语
当前层级小于上一层时,为了对齐所以按上一级长度创建空集合,然后计算位置生成一个新的子集合进行下一层渲染。这里后续需要改进。
别问我为什么不用canvas
,问就是菜
。
喜欢的点点star
。
纵有疾风来,人生不言弃。风乍起,合当奋意向此生。