前端-Vue3递归组件&自定义Tree

Xiao_zuo_ya 2024-07-20 15:33:06 阅读 54

需求

PS:写在前面,需求想要一个Tree 形结构展示当前的组织机构,最末层节点可以选择,层级明确。第一选择网上npm官网或者github 找找成型的东西

element-ui Tree 没有组织结构线js-tree 好看,但是适配Vue3 有点费劲,Vue2 倒是还好echart Tree 感觉有点类似xmind,不是想要的效果

最好的就是在element-ui Tree 加上组织连线这就是最完美的效果。

方案选择

引入element-ui Tree,二次封装增加连线样式(实现简单,效果明显,效率高)。自己写一个Tree ※

但是我选第二个,能了解Tree 组件实现原理,自己想要啥样的就写啥样的,哈哈哈哈。

Vue 递归组件

递归:自己调用自己,什么时候终止(没有子集就终止)

<code>// TreeWithSwitch 就是子组件

<template>

<div class="TreeWithSwitch" v-for="item in dataList" :key="item.code">code>

// 本级标题以及选择框展示

<div class="tree_content">code>

<span>{ { item.label }}</span>

<van-switch size="18px" v-if="item.isLeaf" v-model="item.checked" />code>

</div>

// 递归 判断是否有子集

<div class="node_children">code>

<tree-with-switch :data-list="item.children" v-if="item.children.length" />code>

</div>

</div>

</template>

在这里插入图片描述

增加样式展示层级

<code>.TreeWithSwitch {

line-height: 30px;

padding-left: 20px;

margin-left: 25px;

.tree_content {

display: flex;

align-items: center;

justify-content: space-between;

padding-right: 10px;

height: 35px;

font-size: 13px;

white-space: nowrap;

outline: 0;

position: relative;

}

}

在这里插入图片描述

通过伪类增加当前连接线样式

<code>.TreeWithSwitch {

position: relative;

line-height: 30px;

padding-left: 20px;

margin-left: 25px;

.tree_content {

display: flex;

align-items: center;

justify-content: space-between;

padding-right: 10px;

height: 35px;

font-size: 13px;

white-space: nowrap;

outline: 0;

position: relative;

&::before {

position: absolute;

top: 50%;

left: -19px;

display: block;

width: 17px;

border-top: 1px dashed #43484b;

content: '';

}

&::after {

content: '';

border-left: 1px dashed #43484b;

width: 1px;

height: 30px;

position: absolute;

left: -21px;

top: -11px;

}

}

}

在这里插入图片描述

可以看到很多空缺的部分,上一次绘制的是在每个层级的:before :after 绘制的横线和竖线,分析缺少的部分正是当前节点子集的这部分连接线

在这里插入图片描述

<code>.TreeWithSwitch {

position: relative;

line-height: 30px;

padding-left: 20px;

margin-left: 25px;

&:last-child {

.node_children {

&::after {

display: none;

}

}

}

.node_children {

position: relative;

&::after {

content: '';

border-left: 1px dashed #43484b;

position: absolute;

height: 100%;

left: -21px;

top: -11px;

}

}

.tree_content {

display: flex;

align-items: center;

justify-content: space-between;

padding-right: 10px;

height: 35px;

font-size: 13px;

white-space: nowrap;

outline: 0;

position: relative;

&::before {

position: absolute;

top: 50%;

left: -19px;

display: block;

width: 17px;

border-top: 1px dashed #43484b;

content: '';

}

&::after {

content: '';

border-left: 1px dashed #43484b;

width: 1px;

height: 30px;

position: absolute;

left: -21px;

top: -11px;

}

}

}

在这里插入图片描述

5. 但是可以发现所有的Tree的最外层是没有margin-left:20px 的,也没有上图的多余的部分,那怎么办呢,找了下ElementUI tree 的源码,他把第一层级拿出来了,然后才是递归组件,OK,那我们在封装一个Tree 组件

<code># Tree 组

<template>

<div class="Tree" v-for="item in dataList">code>

<second-title :title="item.label" />code>

<tree-with-switch v-if="item.children" :data-list="item.children" />code>

</div>

</template>

<script setup lang="ts">code>

import type TreeItem from '@/components/public/Tree/TreeItem';

import SecondTitle from '@/components/public/appTitle/SecondTitle.vue';

import TreeWithSwitch from '@/components/public/Tree/TreeWithSwitch.vue';

const props = defineProps({

dataList: {

type: Array<TreeItem>,

required: true

}

});

</script>

<style scoped lang="less">code>

.SecondTitle {

margin-left: 5px;

}

</style>

在这里插入图片描述

OK,写到这里基本上样式问题已经解决了,接下来

最后一步,用你的组件的时候如果获取那些是选中的节点如何获取?

PS:子集不处理事件,无限向上抛出,最后有父级处理。

<code># TreeWithSwitch

<template>

<div class="TreeWithSwitch" v-for="item in dataList" :key="item.code">code>

<div class="tree_content">code>

<span>{ { item.label }}</span>

// 增加选中事件

<van-switch size="18px" v-if="item.isLeaf" v-model="item.checked" @change="chooseTreeItem(item)" />code>

</div>

// 子集选中事件

<div class="node_children">code>

<tree-with-switch :data-list="item.children" v-if="item.children.length" @chooseTreeItem="chooseChildrenItem"/>code>

</div>

</div>

</template>

<script setup lang="ts">code>

import type TreeItem from '@/components/public/Tree/TreeItem';

defineProps({

dataList: {

type: Array<TreeItem>,

required: true

}

});

// 子集向上抛出事件

const emits = defineEmits(['chooseTreeItem']);

const chooseTreeItem = (item: TreeItem) => {

emits('chooseTreeItem', item);

};

// 子集的子集继续向上排除(这里就是逐级传递的)

const chooseChildrenItem = (item: TreeItem) => {

chooseTreeItem(item);

};

</script>

Tree 组件

<template>

<div class="Tree" v-for="item in dataList">code>

<second-title :title="item.label" />code>

// 增加绑定选中事件

<tree-with-switch v-if="item.children" :data-list="item.children" @chooseTreeItem="chooseTreeItem" />code>

</div>

</template>

<script setup lang="ts">code>

import type TreeItem from '@/components/public/Tree/TreeItem';

import SecondTitle from '@/components/public/appTitle/SecondTitle.vue';

import TreeWithSwitch from '@/components/public/Tree/TreeWithSwitch.vue';

import { ref } from 'vue';

const props = defineProps({

dataList: {

type: Array<TreeItem>,

required: true

},

// 定义v-model绑定的参数

chooseItemList: {

type: [],

required: false

}

});

// 保存全部选中的节点

const selectedTreeNode = ref([]);

// 值更新抛出事件

const emits = defineEmits(['update:chooseItemList']);

const chooseTreeItem = (item: TreeItem) => {

// 节点是否选中,选中数组新增,取消选中数组删除

if (item.checked) {

selectedTreeNode.value.push(item.code);

} else {

let index = selectedTreeNode.value.indexOf(item.code);

if (index > -1) {

selectedTreeNode.value.splice(index, 1);

}

}

// 绑定值更新

emits('update:chooseItemList', selectedTreeNode.value);

};

</script>

调用组件

// :data-list Tree 的数据

// v-model:chooseItemList 选中的值

<tree :data-list="hiddenItemList" v-model:chooseItemList="chooseHiddenItemList" />code>

在这里插入图片描述



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。