封装一个树视图组件

一、背景

因小程序项目要展示地方产业链树状图,手机宽度有限,左右滑动也改变不了可视区域问题,所以设计了一个单列的树视图功能,因短时间内未找到符合条件风格的小程序插件及相关案例,所以避免浪费时间直接手写一个。

先附上封面图的VUE版本源码,开箱即用:
KTree

因为微信小程序代码不知道往哪放,所以修改了个VUE3的版本提交到github,道理都一样。

接着附上微信小程序实现效果:

微信图片_20211123172657.jpg

二、效果

这个实现思路相对比较清晰,用递归方法把选择的一级节点下所有子节点遍历出来,然后继续第一个节点往下遍历所有子节点,重复此动作直到找不到子级为止。

我是封装了一个组件,传入当前节点的子集,组件内部渲染子集列表,然后判断列表第一个节点下还有子集的话就组件自己调自己,然后传入第一个节点的子集,以此递归下去。

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%,以及靠左还是靠右。

切换节点的时候将当前选择节点的子集拿去递归渲染即可。

节点如下:

扫描二维码关注公众号,回复: 13317277 查看本文章

微信图片_20211123182519.png

单列处理

每次渲染组件时都要定位获取一个当前节点,以此来继续往下渲染它的子节点。

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

纵有疾风来,人生不言弃。风乍起,合当奋意向此生。

猜你喜欢

转载自juejin.im/post/7033956922201997325