需求
PS:写在前面,需求想要一个Tree 形结构展示当前的组织机构,最末层节点可以选择,层级明确。第一选择网上npm官网或者github 找找成型的东西
element-ui Tree 没有组织结构线js-tree 好看,但是适配Vue3 有点费劲,Vue2 倒是还好echart Tree 感觉有点类似xmind,不是想要的效果最好的就是在element-ui Tree 加上组织连线这就是最完美的效果。
方案选择
引入element-ui Tree,二次封装增加连线样式(实现简单,效果明显,效率高)。自己写一个Tree ※但是我选第二个,能了解Tree 组件实现原理,自己想要啥样的就写啥样的,哈哈哈哈。
Vue 递归组件
递归:自己调用自己,什么时候终止(没有子集就终止)// TreeWithSwitch 就是子组件<template> <div class="TreeWithSwitch" v-for="item in dataList" :key="item.code"> // 本级标题以及选择框展示 <div class="tree_content"> <span>{{ item.label }}</span> <van-switch size="18px" v-if="item.isLeaf" v-model="item.checked" /> </div> // 递归 判断是否有子集 <div class="node_children"> <tree-with-switch :data-list="item.children" v-if="item.children.length" /> </div> </div></template>
增加样式展示层级 .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; }}
通过伪类增加当前连接线样式 .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 绘制的横线和竖线,分析缺少的部分正是当前节点子集的这部分连接线.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 组件
# Tree 组<template> <div class="Tree" v-for="item in dataList"> <second-title :title="item.label" /> <tree-with-switch v-if="item.children" :data-list="item.children" /> </div></template><script setup lang="ts">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">.SecondTitle { margin-left: 5px;}</style>
OK,写到这里基本上样式问题已经解决了,接下来
最后一步,用你的组件的时候如果获取那些是选中的节点如何获取?
PS:子集不处理事件,无限向上抛出,最后有父级处理。
# TreeWithSwitch<template> <div class="TreeWithSwitch" v-for="item in dataList" :key="item.code"> <div class="tree_content"> <span>{{ item.label }}</span> // 增加选中事件 <van-switch size="18px" v-if="item.isLeaf" v-model="item.checked" @change="chooseTreeItem(item)" /> </div> // 子集选中事件 <div class="node_children"> <tree-with-switch :data-list="item.children" v-if="item.children.length" @chooseTreeItem="chooseChildrenItem"/> </div> </div></template><script setup lang="ts">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"> <second-title :title="item.label" /> // 增加绑定选中事件 <tree-with-switch v-if="item.children" :data-list="item.children" @chooseTreeItem="chooseTreeItem" /> </div></template><script setup lang="ts">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" />