需求介绍
最近写了一个搜索功能还挺有意思来记录一下
需求是当点击搜索时树形菜单所有匹配到的值展开高亮显示,详见下图效果
树形菜单+搜索查找功能
搜索功能
这里是写了一个页面,主页面app.vue调用组件treeList.vue;
js部分
<script setup>
import axios from "axios";
import {
reactive,
ref
} from "vue";
import {
Search
} from "@element-plus/icons-vue";
import treeList from "./components/tree.vue";
import { PiniaStore } from "./store/pinia";
// 状态容器 用的pinia
const pinia = PiniaStore();
//这个是输入框的值
const input = ref("");
//控制弹出框的显示隐藏
const show = ref(false);
//请求的数据
const tree = reactive({
arr: [],
Arr: [],
});
// 请求的数据
axios.get("/data.json").then((response) => {
// console.log("请求数据", response.data);
tree.arr = response.data.catalog;
tree.Arr = response.data.catalog;
});
// 查找搜索的字段
let num = 1;//是一个次数记录
const finds = (arr, field, original) => {
//这里使用正则匹配,只要存在该字段就会匹配成功
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初始
num = 1
//这一步是已经拿到匹配成功所有数据的id,去调用这个方法将他的上级都查找出来后续树形菜单显示
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) => {
if (raw != undefined && raw.length != 0) {
//我自己写的数据格式下级的id比上级id多一位数,所以每次对id进行切割来查找上级
let id = data.id.slice(0, num);
//全局状态就拿到这条数据上级的所有id包括自己
pinia.closeList.push(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);
}
} else {
num = 1;
}
};
// 搜索
const myKeydown = () => {
//每次触发搜索时将所有数据初始
tree.arr = [];
pinia.closeList = [];
pinia.searchs = [];
num = 1;
let arr = JSON.parse(JSON.stringify(tree.Arr));
if (input.value != "") {
finds(arr, input.value, arr);
} else {
tree.arr = JSON.parse(JSON.stringify(tree.Arr));
pinia.closeList = [];
pinia.searchs = [];
}
};
</script>
我这只是写了一个小功能所以axios直接引入没有挂全局,请求的是一个本地的json文件
用递归实现,用正则来匹配。
tree菜单
这个是treeList组件通过判断下级 来进行递归
<template>
<div>
<ul v-for="(item) in Arr" :key="item.id">
<li>
<div class="sorts" @click="onoff(item.id)">
<div v-if="item.children">
<svg v-if="value.includes(item.id)" t="1653203328175" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3138" width="10" height="10"><path d="M65.582671 288.791335l446.417329 446.41733 446.417329-446.41733z" p-id="3139"></path></svg>
<svg v-else t="1653203270268" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2985" width="10" height="10"><path d="M288.791335 65.582671l446.41733 446.417329-446.41733 446.417329z" p-id="2986"></path></svg>
</div>
<p :style="{color: checked.includes(item.id) ? '#23A8F2' : '#1E1F1F'}">
{{ item.name +`(${item?.children ? item.children.length : 0})`}}
</p>
</div>
</li>
<!-- 根据传入的值判断 是否需要在递归 -->
<tree-list
v-if="item.children && value.includes(item.id)"
:Arr="item.children"
:index="indexs"
:key="item.id"
/>
</ul>
</div>
</template>
// 点击事件
function onoff(id) {
//每次点击判断是否存在此id存在意味着已经展开则删除这一条就关闭
if (pinia.closeList.includes(id)) {
pinia.closeList.splice(pinia.closeList.indexOf(id));
} else {
pinia.closeList.push(id);
}
}
下面这个是treeList组件的完整代码
<template>
<div>
<ul v-for="(item) in Arr" :key="item.id">
<li>
<div class="sorts" @click="onoff(item.id)">
<div v-if="item.children">
<!-- 这两个svg是展开箭头 -->
<svg v-if="value.includes(item.id)" t="1653203328175" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3138" width="10" height="10"><path d="M65.582671 288.791335l446.417329 446.41733 446.417329-446.41733z" p-id="3139"></path></svg>
<svg v-else t="1653203270268" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2985" width="10" height="10"><path d="M288.791335 65.582671l446.41733 446.417329-446.41733 446.417329z" p-id="2986"></path></svg>
</div>
<p :style="{color: checked.includes(item.id) ? '#23A8F2' : '#1E1F1F'}">
{{ item.name +`(${item?.children ? item.children.length : 0})`}}
</p>
</div>
</li>
<!-- 根据传入的值判断 是否需要在递归 -->
<tree-list
v-if="item.children && value.includes(item.id)"
:Arr="item.children"
:index="indexs"
:key="item.id"
/>
</ul>
</div>
</template>
<script>
import {
toRefs,
computed
} from "vue";
import { PiniaStore } from '../store/pinia';
export default {
name: "tree-list",
props: {
Arr: { type: Array },
index: { type: Number }
},
setup(props) {
console.log(props);
// 传入下标是方便设置下级左边宽度
let indexs = props.index ? props.index : 0;
indexs += 1;
// 改变状态
const pinia = PiniaStore();
const checked = computed(()=>{
return pinia.searchs.length ? pinia.searchs : [];
})
const value = computed(()=>{
return pinia.closeList
})
// 点击事件
function onoff(id) {
if (pinia.closeList.includes(id)) {
pinia.closeList.splice(pinia.closeList.indexOf(id));
} else {
pinia.closeList.push(id);
}
}
return { ...toRefs(props), onoff, indexs,checked,value};
}
};
</script>
<style scoped>
.sorts{
width: auto;
}
.sorts div{
margin-right: 10px;
}
</style>
如有疑问可以点此链接查看完整版代码
https://gitee.com/sandwich-lili/vue3search-demo