vue3 递归无限分类树型菜单+搜索功能

本文介绍了如何在Vue3中创建一个无限滚动的树形结构菜单,并实现搜索功能。首先,定义了数据结构,然后创建组件处理递归展示数据。通过Pinia管理状态,实现节点的展开与关闭。接着,实现了搜索功能,遍历数据并高亮显示匹配项。同时,展示了完整的代码实现过程。
摘要由CSDN通过智能技术生成

我们先来看一下大致实现效果,数据可以无限向下增加,搜索关键字会自动展开数据

vue3树形结构菜单+搜索

首先我这个需要自己设计数据源,一定要先搞清楚数据是什么结构才能顺利开展下一步(有接口的同学可以忽略这一步~)

其中children顾名思义,子节点,相当于树的分叉,就这样一级一级的生成,就是递归生成后的树形结构数据

[{
	id: '1',
	name: '两栖',
	children: [{
		id: '11',
		name: '鸟类',
		children: [{
			id: '111',
			name: '飞行',
			children: [{
				id: '1111',
				name: '小鸟',
			}]
		}]
	}]
}, {
	id: '2',
	name: '2',
	children: [{
		id: '22',
		name: '22',
		children: [{
			id: '222',
			name: '222',
		}]
	}]
}]

明白数据结构之后,我们就正式开始啦~

1、在components中创建xx.vue文件,通过props来传值

<template>
	<ul v-for="(item) in data" :key="item.id">
		<li>
			<div @click="open(item.id)">
				<p>{{item.name}}</p>
			</div>
		</li>
		<DefaultCompoent v-if="item.children&&value.includes(item.id)" :data="item.children" :key="item.id"></DefaultCompoent>
	</ul>
</template>

<script>
	export default {
		name: 'DefaultCompoent',
		props: {
			data: { type: Array }
		}
	}
</script>

2、在app.vue或者其他你想使用的页面引入刚刚定义的组件

<template>
	<section>
		<DefaultCompoent :data="tree.Arr"></DefaultCompoent>
	</section>
</template>

<script setup>
    import {ref,reactive} from "vue";
    import DefaultCompoent from '/src/components/DefaultTree.vue';//引入自定义文件,路径一定要引对!!!

    //数据
    const tree = reactive({
		Arr: [{
				id: '1',
				name: '两栖',
				children: [{
					id: '11',
					name: '鸟类',
					children: [{
						id: '111',
						name: '飞行',
						children: [{
							id: '1111',
							name: '小鸟',
						}]
					}]
				}]
			},
			{
				id: '2',
				name: '2',
				children: [{
					id: '22',
					name: '22',
					children: [{
						id: '222',
						name: '222',
					}]
				}]
			}
		],
	});
</script>

目前,我们的数据已经引好,就是下面的效果

 3、接下来,我们需要使用Pinia,关于Pinia的介绍以及引用,请移步我另一篇博客

https://blog.csdn.net/m0_64397933/article/details/125185496?spm=1001.2014.3001.5502

给页面的数据添加点击事件,传本条数据对应的id

// 改变状态
const pinia = PiniaStore();
const checked = computed(() => {
	return pinia.searchs.length ? pinia.searchs : [];
})
const value = computed(() => {
	return pinia.closeList
})
// 打开关闭点击事件
const open = (id) => {
	console.log(id);
	if (pinia.closeList.includes(id)) {
	pinia.closeList.splice(pinia.closeList.indexOf(id));
} else {
	pinia.closeList.push(id);
    }
};

4、搜索功能

<template>
	<!-- 搜索 -->
	<el-input v-model="input" placeholder="请输入搜索内容" @change="changes" />
	<router-view />
</template>

<script setup>
// 查找字段
	var num = 1;
	const finds = (arr, field, original) => {
		console.log(arr);
		//正则匹配,只要存在该字段就会匹配成功
		let reg = new RegExp(field);
		for (let i = 0; i < arr.length; i++) {
			let res = field,
				raw = original;
			if (reg.test(arr[i].name)) {
				pinia.searchs.push(arr[i].id);
				num = 1; //每次匹配成功初始num
				unfold(original, arr[i]);
				if (arr[i].children) {
					// 若有匹配成功的字段就继续
					finds(arr[i].children, res, raw);
				}
			} else {
				//若匹配不成功,并且还有下级则再次调用自己并将下级数组传过去
				if (arr[i].children != undefined && arr[i].children.length != 0) {
					finds(arr[i].children, res, raw);
				}
			}
		}
	};
	// 将匹配到的数据id分割拿到这条数据的所有上级id
	const unfold = (raw, data) => {
		console.log(raw, data);
		if (raw != undefined && raw.length != 0) {
			// let news = [...raw];
			let id = data.id.slice(0, num); //切割id
			// console.log(id);
			pinia.closeList.push(id); //全局状态就拿到这条数据上级的所有id包括自己
			let res = raw.findIndex((val) => val.id == id);
			if (res != -1 && raw[res].children != undefined) {
				//这条数据还有下级则继续分割id
				num += 1;
				let obj = data;
				unfold(raw[res].children, obj);
			}
			tree.arr = raw;
		} else {
			num = 1;
		}
	};
	const changes = () => {
		// console.log(input.value)
		// 初始数据
		tree.arr = [];
		pinia.closeList = [];
		pinia.searchs = [];
		num = 1;
		let arr = JSON.parse(JSON.stringify(tree.Arr));
		console.log(arr);
		if (input.value != "") {
			finds(arr, input.value, arr);
		} else {
			tree.arr = JSON.parse(JSON.stringify(tree.Arr));
			pinia.closeList = [];
			pinia.searchs = [];
		}
	};
    //监听输入框的值,当输入框的值为空时,数据显示默认状态
	watch(input, (newName, oldName) => {
	  console.log(newName);
	  if(newName==''){
		  pinia.closeList = [];
		  pinia.searchs = [];
	  }
	});
</script>

下面页面完整代码给大家~

components文件下xx.vue源码

<template>
	<ul v-for="(item) in data" :key="item.id">
		<li>
			<div @click="open(item.id)">
				<p>{{item.name}}</p>
			</div>
		</li>
		<DefaultCompoent v-if="item.children&&value.includes(item.id)" :data="item.children" :key="item.id"></DefaultCompoent>
	</ul>
</template>

<script>
	import {computed} from "vue";
	import {PiniaStore} from "/src/store/index.js";
	export default {
		name: 'DefaultCompoent',
		props: {
			data: { type: Array }
		},
		setup(props) {
			console.log(props);
			// 改变状态
			const pinia = PiniaStore();
			const checked = computed(() => {
				return pinia.searchs.length ? pinia.searchs : [];
			})
			const value = computed(() => {
				return pinia.closeList
			})
			// 打开关闭点击事件
			const open = (id) => {
				console.log(id);
				if (pinia.closeList.includes(id)) {
					pinia.closeList.splice(pinia.closeList.indexOf(id));
				} else {
					pinia.closeList.push(id);
				}
			};
			return {
				open,
				event,
				checked,
				value
			}
		}
	}
</script>

<style>
</style>

app.vue源码

<template>
	<section>
		<DefaultCompoent :data="tree.Arr"></DefaultCompoent>
	</section>
	<!-- 搜索 -->
	<el-input v-model="input" placeholder="请输入搜索内容" @change="changes" />
	<router-view />
</template>

<script setup>
	import {ref,reactive,watch} from "vue";
	import DefaultCompoent from '/src/components/DefaultTree.vue';
	import {PiniaStore} from "/src/store/index.js";
	const pinia = PiniaStore();
	const input = ref(''); //输入框
	const tree = reactive({
		Arr: [{
				id: '1',
				name: '两栖',
				children: [{
					id: '11',
					name: '鸟类',
					children: [{
						id: '111',
						name: '飞行',
						children: [{
							id: '1111',
							name: '小鸟',
						}]
					}]
				}]
			},
			{
				id: '2',
				name: '2',
				children: [{
					id: '22',
					name: '22',
					children: [{
						id: '222',
						name: '222',
					}]
				}]
			}
		],
	});
	// 查找字段
	var num = 1;
	// console.log(data);
	const finds = (arr, field, original) => {
		console.log(arr);
		//正则匹配,只要存在该字段就会匹配成功
		let reg = new RegExp(field);
		for (let i = 0; i < arr.length; i++) {
			let res = field,
				raw = original;
			if (reg.test(arr[i].name)) {
				pinia.searchs.push(arr[i].id);
				num = 1; //每次匹配成功初始num
				unfold(original, arr[i]);
				if (arr[i].children) {
					// 若有匹配成功的字段就继续
					finds(arr[i].children, res, raw);
				}
			} else {
				//若匹配不成功,并且还有下级则再次调用自己并将下级数组传过去
				if (arr[i].children != undefined && arr[i].children.length != 0) {
					finds(arr[i].children, res, raw);
				}
			}
		}
	};
	// 将匹配到的数据id分割拿到这条数据的所有上级id
	const unfold = (raw, data) => {
		console.log(raw, data);
		if (raw != undefined && raw.length != 0) {
			// let news = [...raw];
			let id = data.id.slice(0, num); //切割id
			// console.log(id);
			pinia.closeList.push(id); //全局状态就拿到这条数据上级的所有id包括自己
			let res = raw.findIndex((val) => val.id == id);
			if (res != -1 && raw[res].children != undefined) {
				//这条数据还有下级则继续分割id
				num += 1;
				let obj = data;
				unfold(raw[res].children, obj);
			}
			tree.arr = raw;
		} else {
			num = 1;
		}
	};
	const changes = () => {
		// console.log(input.value)
		// 初始数据
		tree.arr = [];
		pinia.closeList = [];
		pinia.searchs = [];
		num = 1;
		let arr = JSON.parse(JSON.stringify(tree.Arr));
		console.log(arr);
		if (input.value != "") {
			console.log(arr);
			finds(arr, input.value, arr);
		} else {
			tree.arr = JSON.parse(JSON.stringify(tree.Arr));
			pinia.closeList = [];
			pinia.searchs = [];
			console.log(tree.arr);
		}
	};
    //监听输入框的值,当输入框的值为空时,数据显示默认状态
	watch(input, (newName, oldName) => {
	  console.log(newName);
	  if(newName==''){
		  pinia.closeList = [];
		  pinia.searchs = [];
	  }
	});
</script>

<style>

</style>

如有不对,或者更好的方法,欢迎指出~

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值