vue3 无限分类树形菜单 +支持搜索定位节点

vue3 无限分类树形菜单 + 搜索

需求介绍

   	最近写了一个搜索功能还挺有意思来记录一下
	
	需求是当点击搜索时树形菜单所有匹配到的值展开高亮显示,详见下图效果

树形菜单+搜索查找功能

搜索功能

这里是写了一个页面,主页面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

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值