注释很详细开箱即用

可配置单选、多选,可配置允许选择任意节点或只允许选择最底层节点
在这里插入图片描述

<template>
	<view class="container1">
		<scroll-view :scroll-y="true">
			<view class="container1-page" v-for="(item, index) in arrays" :key="item.id">
				<view v-if="item.children?.length" class="department">
					<view class="department-header" @click="toggleExpand(item)">
						<view class="department-header-left">
							<up-icon v-if="isExpanded(item)" name="arrow-down-fill" size="12" color="#bfbfbf" class="icon-expand" />
							<up-icon v-else name="play-right-fill" size="12" color="#bfbfbf" class="icon-expand" />
							<span class="node-title">{{ item.name }}</span>
						</view>
						<view v-if="!props.onlyLeaf" class="checkbox checkbox-parent" :class="{ 'checkbox-checked': isSelected(item) }" @click.stop="toggleSelection(item)">
							<view class="checkbox-inner"></view>
						</view>
					</view>
					<jcTree v-if="isExpanded(item)" class="indent-child" :arrays="item.children" @toggleSelection="getData" />
				</view>
				<view v-else class="employee">
					<view class="employee-content">
						<up-icon name="tags" size="19" color="#bfbfbf" class="icon-tags" />
						<span class="node-title">{{ item.name }}</span>
					</view>
					<view class="checkbox" :class="{ 'checkbox-checked': isSelected(item) }" @click="toggleSelection(item)">
						<view class="checkbox-inner"></view>
					</view>
				</view>
			</view>
		</scroll-view>
	</view>
</template>

<script lang="ts" setup>
	import { ref, defineEmits } from "vue";
	// 组件配置(解决递归组件名称问题)
	defineOptions({
		name: "jcTree",
	});
	const props = defineProps({
		arrays: {
			// 树形数据结构,格式示例:[{id:'',name:'',children:[]}]
			type: Array,
			default: () => [],
		} as any,
		isCheckbox: {
			// 选择模式控制:true=多选模式,false=单选模式
			type: Boolean,
			default: false,
		},
		onlyLeaf: {
			//是否可以选择任意层级:true=只允许选择叶子节点,false=允许选择任意层级
			type: Boolean,
			default: false, // 默认只允许选择叶子节点
		},
	});
	const selectedEmployees = ref<any[]>([]); // 已选节点集合
	const expandedNodes = ref<any[]>([]); // 展开节点ID集合
	const emits = defineEmits(["toggleSelection"]);
	/**
	 * 切换节点选中状态
	 * @param node - 当前操作的节点对象
	 *
	 * 逻辑说明:
	 * - 多选模式:切换节点的选中状态
	 * - 单选模式:始终只保留最后一个选中节点
	 */
	const toggleSelection = (node: any) => {
		if (props.isCheckbox) {
			const index = selectedEmployees.value.findIndex((emp) => emp.id === node.id);
			if (index !== -1) {
				selectedEmployees.value.splice(index, 1);
			} else {
				selectedEmployees.value.push({ id: node.id, name: node.name });
			}
		} else {
			selectedEmployees.value = [{ id: node.id, name: node.name }];
		}
		emits("toggleSelection", selectedEmployees.value);
	};
	/**
	 * 处理子节点事件
	 * @param item - 来自子组件的事件数据
	 *
	 * 设计说明:
	 * - 多选模式:操作最后一个节点保持多选特性
	 * - 单选模式:始终操作第一个节点保持单选特性
	 */
	const getData = (item: any) => {
		props.isCheckbox ? toggleSelection(item[item.length - 1]) : toggleSelection(item[0]);
	};
	/**
	 * 判断节点是否被选中
	 * @param node - 需要检测的节点对象
	 */
	const isSelected = (node: any) => {
		return selectedEmployees.value.some((emp) => emp.id === node.id);
	};

	/**
	 * 切换节点展开状态
	 * @param node - 需要切换展开状态的节点
	 *
	 * 实现方式:
	 * - 通过维护expandedNodes数组记录已展开节点ID
	 */
	const toggleExpand = (node: any) => {
		const index = expandedNodes.value.indexOf(node.id);
		index === -1 ? expandedNodes.value.push(node.id) : expandedNodes.value.splice(index, 1);
	};
	/**
	 * 判断节点是否展开
	 * @param node - 需要检测的节点对象
	 */
	const isExpanded = (node: any) => {
		return expandedNodes.value.includes(node.id);
	};
</script>

<style lang="scss" scoped>
	.container1 {
		overflow-y: auto;
		&-page {
			.department {
				font-weight: bold;
				&-header {
					display: flex;
					align-items: center;
					justify-content: space-between;
					cursor: pointer;
					color: #747272;
					margin: 12px 0;

					&-left {
						display: flex;
						align-items: center;

						.icon-expand {
							margin-right: 5px;
						}
					}
					.checkbox-parent {
						margin-left: 10px;
					}
				}
			}

			.employee {
				display: flex;
				align-items: center;
				justify-content: space-between;
				cursor: pointer;
				color: #747272;
				margin: 12px 0;
				&-content {
					display: flex;
					align-items: center;

					.icon-tags {
						margin-right: 5px;
					}
				}
			}
			.node-title {
				font-size: 14px;
			}
			.indent-child {
				margin-left: 10px;
			}
		}
	}
	.checkbox {
		width: 18px;
		height: 18px;
		border: 1px solid #3c9cff;
		border-radius: 2px;
		display: flex;
		align-items: center;
		justify-content: center;
		&-inner {
			width: 11px;
			height: 11px;
			border-radius: 1px;
		}

		&-checked {
			.checkbox-inner {
				background-color: #3c9cff;
			}
		}
	}
</style>


在父组件中使用

<tyTree  style="max-height: 500px" :arrays="data" @toggleSelection="toggleSelection" />
const data = ref([
		{
			id: "1",
			name: "A",
			children: [
				{
					id: "1-1",
					name: "A-1",
					children: [],
				},
				{
					id: "1-2",
					name: "A-2",
					children: [],
				},
			],
		},
		{
			id: "2",
			name: "B",
			children: [
				{
					id: "2-1",
					name: "B-1",
					children: [],
				},
				{
					id: "2-2",
					name: "B-2",
					children: [],
				},
			],
		},
		{
			id: "3",
			name: "C",
			children: [
				{
					id: "3-1",
					name: "C-1",
					children: [
						{
							id: "3-1-1",
							name: "C-1-1",
							children: [],
						},
						{
							id: "3-1-2",
							name: "C-1-2",
							children: [],
						}
					],
				},
				{
					id: "3-2",
					name: "C-2",
					children: [],
				},
			],
		},
		{ id: "4", name: "d", children: [] },
	]);
	const toggleSelection = (data: any) => {
		//处理数据
	};

更多推荐