el-cascader懒加载+多选+回显+远程搜索(vue3+element plus)

el-cascader懒加载+多选+回显+远程搜索(vue3+element plus)

el-cascader引用地址

<template>
<el-cascader v-model="dataVal" 
:debounce="600" 
:options="options" 
:props="propsLazy" clearable 
:teleported="false"
 :filterable="false" 
 :before-filter="beforeFilter"
 :filter-method="filterMethod"
@change="valChange" />
</template>

<script setup>
import { computed, nextTick, onMounted, reactive, ref, watch } from "vue";
let dataVal=ref([])
let options=ref([])//懒加载虚拟拼接的数据,打印时看不到懒加载的数据
let optionsRaw=ref([])//实际自己拼接的数据
let searchShow=ref(false)//true:搜索后选中数据,防止optionsRaw继续添加数据,而导致无法选中
//懒加载方法
const loadOptions = async (node, resolve) => {
  if (node.value && !searchShow.value) {
  //getTypeData是获取数据的接口方法,parent_id是根据父级id获取其子级的数据组
  //如[{id:xx,label:xx,child_num:xxx}]
    const fetchedOptions = await getTypeData({ parent_id: node.value });
    optionsRaw.value = setOptions(
      node.pathValues,//父级id数组如:[1,2]
      optionsRaw.value,
      0,
      fetchedOptions
    );
    resolve(fetchedOptions);
  }
  resolve();
};
const propsLazy = {
  checkStrictly: true,
  lazy: true,
  lazyLoad: loadOptions,
  value: "id",
  leaf: "leaf",
  multiple: true,
  checkStrictly: true,
};
//给父级拼接子级数据children
const setOptions = (idArr, arr, num, addData) => {
  for (let item of arr || []) {
    if (item.id == idArr[num]) {
      if (num == idArr.length - 1) {
        item["children"] = JSON.parse(JSON.stringify(addData));
      } else {
        item = setOptions(idArr, item["children"], num + 1, addData);
      }

      break;
    }
  }
  return arr;
};

//回显
const getDataOpen = async (val) => {
 options.value = await getTypeData();//获取最开头一级下拉数据
 optionsRaw.value = JSON.parse(JSON.stringify(options.value));
 dataVal.value=val//再赋值,后面会自动触发loadOptions 补充已选的子级数据
};
//获取对应的数据
const getTypeData = async (param = {}) => {
  let list = [];
  await api.$getCategory(param).then((res) => {
    if (res.Status == 0) {
    //res.data后端返回的数据结构是[{id:xx,label:xx,child_num:xxx}]
      list = getChildren(res.data || []);
    }
  });
  return list;
};
//遍历得到想要的数据结构
const getChildren = (arrData) => {
  if (arrData && arrData.length > 0) {
    let data = arrData.map((el) => {
      let obj = {
        id: el.id,
        label: el.label,
        leaf: !(Number(el.child_num) > 0),//用于判断,是否有子级数据
        child_num: el.child_num,//child_num是子级的数组长度
      };
      if (el.children && el.children?.length > 0) {
        obj["children"] = getChildren(el.children);
      }
      return obj;
    });
    return data;
  } else {
    return arrData;
  }
};

//搜索
const beforeFilter = async (val) => {
  searchShow.value = true;
  /***
  注意:搜索后端返回的数据结构是
  [{id:1级,label:xx,child_num:xxx, children:[{id:2级,label:目标搜索名字,child_num:xxx}]}]
  这里把对应目标的子级返回时,会连它前面的所有的父级层层都返回,但父级的同级数据是不会返回的,
  所以后面有个坑,就是把这个搜索后的数据拼接后,点击对应的父级时(如1级),
  不会懒加载对应所有的子级数据,所有后面会用到点击展开函数expandChang
  **/
  let obj = await getTypeData({search:val});
  obj.map((item) => {
    for (let el of optionsRaw.value) {
      if (el.id == item.id) {
        el = addItem(el, item);
        return;
      }
    }
  });
  options.value = JSON.parse(JSON.stringify(optionsRaw.value));
};

//把远程搜索插入到对应的子级
const addItem = (goal = {}, obj = {}) => {
  if (obj.children && obj.children.length > 0) {
    if ((goal.children || []).length == 0) {
      goal.children = JSON.parse(JSON.stringify(obj.children));
    } else {
      obj.children.map((item) => {
        for (let el of goal.children) {
          if (el.id == item.id) {
            el = addItem(el, item);
            return;
          }
        }
      });
    }
  }
  return goal;
};
const filterMethod = (node, val) => {
  return node.label.toLowerCase().includes(val.toLowerCase());
};
//下拉框折叠/收起
const visibleChange = async (bool) => {
  if (!bool) {
    if (searchShow.value) {
      searchShow.value = false;
    }
  }
};
//点击展开下一级触发,val是父级id集合
const expandChange = (val) => {
  let childArr = JSON.parse(JSON.stringify(optionsRaw.value));
  val.forEach(async (el, index) => {
    let res = getItem(el, childArr);
    childArr = res.childArr;
    if (index == val.length - 1) {
      if (res.child_num > childArr.length) {
        const fetchedOptions = await getTypeData({ parent_id: el });
//多级不完整的子级数据时,这里点击最前面的子级,加载补充后,它后面子级数据的会自动触发loadOptions,补充完整
        options.value = setOptions( val, options.value, 0, fetchedOptions );
        categoryOptionsRaw.value = JSON.parse(JSON.stringify(categoryOptions.value));
      }
    }
  });
};
const getItem = (val, arr) => {
  let childArr = [];
  let child_num = 0;
  for (let item of arr) {
    if (val == item.id) {
      childArr = JSON.parse(JSON.stringify(item.children || []));
      child_num = item.child_num;
      break;
    }
  }
  return { childArr, child_num };
};
</script>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值