vue 手写 Tree 组件 (递归)

947 阅读1分钟

效果图

1657157168571.jpg

问题来源 (项目中需要tree组件进行单选&不包含的隐藏选择框&最多选择5个)

treeData.vue

<template>
	<div>
		<div class="list-item" v-for="(item, index) in list" :key="index">
			<div class="dsplay">
				<div v-if="item.type!=0" @click="showFn(item)" class="word mt20">
					<i v-if="item.children" :class="[!item.show?'el-icon-arrow-right':'el-icon-arrow-down']" />
					<el-checkbox-group v-model="checkedCities" @change="handleCheckedCitiesChange" :max="5">
						<div class="item-name word" @click="showFn(item)">
							<el-tooltip :disabled="item.name.length < 10" class="item" effect="dark"
								:content="item.name" placement="top-start">
								<el-checkbox :label="item.id" :key="item.id">{{item.name}}
								</el-checkbox>
							</el-tooltip>

						</div>
					</el-checkbox-group>
				</div>
				<div @click="showFn(item)" class="dsplay" v-else>
					<i v-if="item.children" :class="[!item.show?'el-icon-arrow-right':'el-icon-arrow-down']" />
					<el-tooltip :disabled="item.name.length < 10" class="item" effect="dark" :content="item.name"
						placement="top-start">
						<div class="word">
							{{item.name}}
						</div>
					</el-tooltip>

				</div>
			</div>

			<div v-if="item.children&&item.children.length" v-show="item.show" class="children-item">
				<tree-data :list="item.children" v-on="$listeners" :data="checkedCities"></tree-data>
			</div>
		</div>
	</div>
</template>
<script>
	export default {
		data() {
			return {
				checkedCities: []
			}
		},
		name: "treeData",// 给当前组件取个名字,方便递归的时候自己调用自己
		props: {
			list: {
				type: Array,
				default: []
			},
			data: {
				type: Array,
				default: () => [],
			},
		},
		watch: {
			data: {
				handler(id) {
					this.checkedCities = id;
				},
				immediate: true
			}
		},
		methods: {
			showFn(v) {
				this.$set(v, 'show', !v.show);
			},
			handleCheckedCitiesChange(id) {
				this.$emit('ChangeDate', id)
			},
		},
	};
</script>

<style scoped lang="scss">
	.dsplay {
		display: flex;
		align-items: center;
		font-size: 14px;
		font-family: PingFangSC-Regular, PingFang SC;
		font-weight: 400;
		color: rgba(0, 0, 0, 0.8500);
		width: 200px;
	}

	::v-deep .word {
		overflow: hidden;
		text-overflow: ellipsis;
		white-space: nowrap;
		margin-left: 10px;
		-webkit-box-orient: vertical;


		.el-checkbox {
			display: flex;
			align-items: center;
		}

		.el-checkbox__label {
			font-size: 14px;
			font-family: PingFangSC-Regular, PingFang SC;
			font-weight: 400;
			color: rgba(0, 0, 0, 0.85);
			width: 200px;
			overflow: hidden;
			text-overflow: ellipsis;
			white-space: nowrap;
			-webkit-box-orient: vertical;
		}
	}

	.mt20 {
		margin-top: -20px;
	}

	.list-item {
		margin-top: 20px;
	}

	.item-name {
		margin-top: 20px;
	}

	.children-item {
		margin-left: 24px;
	}
</style>

使用组件

参数: list => 当前树结构  (下方有图)
      data => 当前选中的元素的id集合 (下方图每个节点都有id,可用于后期数据回显)
      ChangeDate ()=> fun  (子向父传参)
     
 <tree-data :list="schoolList" :data="checkedCities" @ChangeDate="ChangeDate" />
 
 //引入注册
 import treeData from "./treeData.vue";
 components: { treeData }
数组结构

1657156623419.jpg

        methods: {
            TreeChecked(data, old) {
                for (let i = 0; i < data.length; i++) {
                    let obj = data[i];
                    for (let s = 0; s < old.length; s++) {
                        if (obj.id == old[s]) {
                            this.checkedArr.push(obj);
                            this.checkedCities.push(obj.id);
                        }
                    }
                    if (obj.children) {
                        this.TreeChecked(obj.children, old)
                    }
                }
            },
            // 后台返回的初始化默认第一次选中的数据id 集合
            getOptionList() {
                getOptionList({ chartType: this.tableData.chartType, optionType: this.tableData.optionsType }).then(res => {
                    this.getList = res.data.data;
                })
            },
            ChangeDate(e) {
                this.checkedArr = [];
                this.checkedCities = [];
                 //Tree data集合  e 是子组件传来的选中项 [1,2,3] ID集合
                this.TreeChecked(this.schoolList, e)
            },
            //查找父节点
            familyTree(Data, id) {
                let temp = []
                let forFn = function (arr, id) {
                    for (let i = 0; i < arr.length; i++) {
                        let item = arr[i]
                        if (item.id === id) {
                            temp.push(item)
                            forFn(Data, item.parentId)
                            break
                        } else {
                            if (item.children) {
                                forFn(item.children, id)
                            }
                        }
                    }
                }
                forFn(Data, id)
                return temp
            },
            // 接口数据
            showDialog(status) {
                this.dialogVisibleSchool = status;
                this.checkedCities = [];
                this.checkedArr = [];
                this.parentData = [];
                getOrgTreeWithSchool().then(res => {
                    this.title = res.data.data.name;
                    setTimeout(() => {
                        this.getList.forEach((item) => {
                            //拿到id所有父级的集合
                            let b = this.familyTree(res.data.data.children, item.targetId)
                            b.forEach((x) => {
                                //x 需要回显的数据标识
                                if (x.id == item.targetId) {
                                    x.x = item.targetId
                                }
                                this.parentData.push(x)
                            })
                        })
                        this.TreeData(res.data.data.children, this.parentData)
                    }, 100);
                })
            },
            TreeData(data, old) {
                for (let i = 0; i < data.length; i++) {
                    let obj = data[i];
                    for (let s = 0; s < old.length; s++) {
                        //id相同展开
                        if (obj.id == old[s].id) {
                            this.$set(obj, 'show', true)
                        }
                        //id相同展开 &&  接口返回的数据push
                        if (obj.id == old[s].id && obj.x) {
                            this.checkedArr.push(obj);
                            this.checkedCities.push(obj.id);
                        }
                    }
                    if (obj.children) {
                        this.TreeData(obj.children, old)
                    }
                }
                this.schoolList = data;
            },
            //右侧删除 X 
            deleteCheckedArr(item) {
                let isChecked = this.checkedArr.findIndex(e => e.id === item.id),
                    id = this.checkedCities.findIndex(e => e === item.id);
                this.checkedArr.splice(isChecked, 1)
                this.checkedCities.splice(id, 1)
            },
            submit() {
                console.log(this.checkedArr);
                if (this.checkedArr.length === 0) {
                    this.$message.error('请先选择学校')
                    return
                }
                this.$emit('submitSchool', this.checkedArr)
            }
        },
    }

项目中遇到的问题

Vue中递归组件时的阻止冒泡事件和$emit失效问题解决

解决方法为给递归的子组件添加 v-on="$listeners",如下:

1657158183593.jpg

就完美解决啦~

有什么问题 可以留言一起讨论哦~