我们先来看一下大致实现效果,数据可以无限向下增加,搜索关键字会自动展开数据
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>
如有不对,或者更好的方法,欢迎指出~