近期有需求希望树形控件可以带线,UI可能觉得这样逼格会高点。无奈我们的ElTree组件不带这个功能,于是手动基于ElTree来扩展一个,方便平时开发中使用。
先看下组件扩展后的样子





嗯,这是目前扩展后的功能,有了上图的样子后,就得定义使用姿势,然后再看如何实现,先看下使用姿势
js
<template>
<el-line-tree
style="max-width: 600px"
:data="data"
@node-click="handleNodeClick"
/>
</template>
主要使用姿势和ElTree一样,只是新增了几个属性控制线的颜色和弧度。
代码全部黏贴,比较浪费空间,可以点这个文档地址去查看
有了上面的ui及使用姿势,接下来就是看看如何实现
- 因为要保留ElTree组件的所有功能,且扩张其功能,我们先把$attrs绑定给el-tree,然后把el-tree的default和empty插槽给开放出来,然后重写default插槽即可,这样就把el-tree组件的所有功能都继承了。
js
<template>
<el-tree
:class="ns.b()"
:style="treeStyle"
v-bind="$attrs"
:expand-on-click-node="expandOnClickNode"
>
<template #default="{ node, data }">
<div
:class="nsNode.e('collapse')"
@click.stop="handleExpandIconClick(node)"
>
<slot name="collapse" v-bind="{ node, data }">
<el-icon
:class="[
nsNode.is('leaf', node.isLeaf),
{
expanded: !node.isLeaf,
},
]"
size="15"
v-bind="iconProps"
>
<component :is="icon ? icon : node.expanded ? Expand : PutAway" />
</el-icon>
</slot>
</div>
<div
:class="[
nsNode.e('content'),
showContentLine ? nsNode.m('content-line') : '',
]"
:level="node.level"
>
<slot v-bind="{ node, data }">
<span>{{ node.label }}</span>
</slot>
</div>
</template>
<template #empty>
<slot name="empty" />
</template>
</el-tree>
</template>
<script lang="ts" setup>
import { computed } from 'vue'
import { ElIcon, ElTree, useNamespace } from 'element-plus'
import * as IconsVue from '@element-plus/components/icons-vue'
import { lineTreeEmits, lineTreeProps } from './line-tree'
const { Expand, PutAway } = IconsVue
defineOptions({
name: 'ElLineTree',
inheritAttrs: false,
})
const ns = useNamespace('line-tree')
const nsNode = useNamespace('line-tree-node')
const props = defineProps(lineTreeProps)
defineEmits(lineTreeEmits)
const treeStyle = computed(() => {
const prefix = `--${ns.namespace.value}`
return {
[`${prefix}-line-tree-line-color`]: props.lineColor,
[`${prefix}-line-tree-line-radius`]: props.lineRadius,
[`${prefix}-line-tree-collapse-width`]: props.collapseWidth,
}
})
const handleExpandIconClick = (node: any) => {
if (node.isLeaf || !props.expandOnClickNode) return
node.expanded ? node.collapse() : node.expand()
}
</script>
一键省流,上面代码主要给自定义节点中添加了一个collapse元素和内容元素,这个collapse元素很重要,因为后续的连线需要基于它来计算位置,其他不用管,因为主要是css连的线,后面才是重点
css连线
- 先看下el-tree的dom结构

- .el-tree-node元素包含 .el-tree-node__content元素和 .el-tree-node__children元素

-
.el-tree-node__content元素包含三角箭头,checkbox,loading, .el-line-tree-node__collapse, .el-line-tree-node__content这5个元素,只不过checkbox,loading元素没渲染出来。
-
注意.el-line-tree-node__collapse, .el-line-tree-node__content元素是我们新增的元素。
-
.el-tree-node__children元素又包含.el-tree-node元素,这也是递归渲染出来的结果
- 先画折线

- 根据border-left和border-bottom画出2条线
- 宽度需要为折叠器的一半,left需要偏移折叠器折叠器一半且-1,因为线本身有1px的宽度
- bottom则为节点高度减去折叠器高度,其实我认为50%更好,这还得根据不同节点高度去尝试
- 这样就把这个折线画好了,但是你会发现有断层,接下来就把断层补上
- 断层线补上

- 给.el-tree-node__children元素添加伪元素
- 主要说下left值,为折叠器的一半,-1是为了平衡border-left值为1的效果
- 所以到这里,线已出来了,也知道了折叠器元素宽高的重要性了
总结
这个组件本质就是通过css伪元素设置的,并没有修改el-tree组件的源代码,其实我先前的做法是通过js控制的。 还有希望el-tree组件能够开放一个折叠的插槽就完美了
在上面再包一层插槽不香吗,你都想到了让用户传入icon来换图标,咋不再做的更通用点呢?
当然element-plus组件确实帮助我们开发节省了大量时间,还是非常感谢elp团队的。
由于平时开发中,也基于element-plus组件开发了一些通用组件,有element-plus组件没有的组件,也有增强版的组件(比如上述的line-tree组件)。该组件库叫 element-plus-x
喜欢的同学,希望可以给个小星星,谢谢