vue3 Select单、多选下拉选择器懒加载组件封装

当下拉选择器含大量的数据时,影响性能问题,因而以懒加载的方式作为性能优化;
该下拉选择器包含功能:物理搜索、触底懒加载、适用单选和多选;

其中要点:

  • 选择器下拉自定义类名,以标识并查找当前下拉
  • 选择器下拉需做滚动监听,此处是用指令
  • 监听选中值,并回调父组件以及选中时需赋值
  • 选择器筛选时,需将筛选值加入展示数据里并去重,以及初始化时,需将选中值加入展示数据中
<template>
  <yh-select v-model="selectVal" placeholder="请选择" filterable clearable :data-class="className" :popper-class="className" v-selectScroll="scrollChange" :filter-method="filtereMethod" :multiple="multiple" collapse-tags collapse-tags-tooltip :reserve-keyword="false" @change="changeVal">
    <yh-option v-for="(item, index) in selectParams.showData" @click="clickOption(item.value)" :key="index" :value="item.value" :label="item.label" :title="item.label" />
  </yh-select>
</template>
const props = defineProps({
  value: { default: null },
  optionsData: { default: [] },
  className: { default: 'lazyselect' },
  multiple: { default: false },
});

const emit = defineEmits(['returnValue']);

let selectVal = ref();

//默认下拉的分页数
let defaultPageNum = ref(200);

//下拉参数
let selectParams = ref({
  pageSize: 1,//当前页码
  pageNumber: defaultPageNum.value,//默认显示条数
  allData: [],//全部数据
  showData: [],//展示数据
  activeFilter: false,//搜索状态
});

//下拉选中值监听
watch(
  () => props.value,
  (to, from) => {
    selectVal.value = to;
    addValueSelect();
  }
);

onMounted(() => {
  selectParams.value = {
    pageSize: 1,
    pageNumber: defaultPageNum.value,
    total: 0,
    allData: props.optionsData || [],
    showData: props.optionsData?.length ? props.optionsData?.slice(0, props.optionsData.length < defaultPageNum.value ? props.optionsData.length : defaultPageNum.value) : [],
    activeFilter: false,
  };
  addValueSelect();
  selectVal.value = props.multiple ? selectVal.value?.split(',') || [] : selectVal.value || '';
});

//点击选项,非多选时执行
const clickOption = (e) => {
  if (!props.multiple) {
    selectParams.value.activeFilter = false;
    selectVal.value = e;
    emit('returnValue', selectVal.value);
  }
};

//添加已选的下拉选项
const addValueSelect = () => {
  const value = selectVal.value;
  if (![null, undefined].includes(value) && value !== '' && !selectParams.value.showData?.find((i) => i.value == value) && selectParams.value.allData.length) {
    const item = selectParams.value.allData.find((i) => i.value === value);
    if (item) {
      selectParams.value.showData.push(item);
    }
  }
};

//滚动下拉
const scrollChange = () => {
  const { allData, showData, pageNumber } = selectParams.value;
  if (showData.length < allData.length && !selectParams.value.activeFilter) {
    selectParams.value.pageSize += 1;
    const addData = selectParams.value.pageSize * pageNumber;
    selectParams.value.showData = addData > allData.length ? allData : allData.slice(0, addData);
  }
  addValueSelect();
};


//下拉搜索
const filtereMethod = (e) => {
  const { allData, showData } = selectParams.value;
  if (e !== '') {
    if (!props.multiple) {
      selectVal.value = e;
    }
    const filterArr = allData.filter((i) => i.label.toLowerCase().includes(e?.toLowerCase()));
    selectParams.value.showData = filterArr;
    selectParams.value.activeFilter = true;
  } else {
    selectParams.value.showData = reSetOptions(allData.slice(0, defaultPageNum.value).concat(showData), 'value');
    if (selectParams.value.activeFilter) {
      changeVal(e);
    }
    selectParams.value.activeFilter = false;
  }
};

//数组去重
const reSetOptions = (arr, name) => {
  let obj = {};
  return arr.reduce((cur, next) => {
    obj[next[name]] ? '' : (obj[next[name]] = true && cur.push(next));
    return cur;
  }, []);
};

//下拉值修改
const changeVal = (e) => {
  selectParams.value.activeFilter = false;
  selectVal.value = e;
  emit('returnValue', selectVal.value);
};

代码要点解析:

  1. data-class 需与popper-class 一致;
  2. select触底时进行翻页和数据更新处理;
  3. 修改下拉值时回调父组件并传值;
  4. 搜索关键字时,需赋值给选择器选中值,不然将会得到键盘点下的最后一个字,而不是全部值。其次是,此处赋值关键字后,将会出现一个场景:搜索关键字,但是鼠标聚焦非下拉处,下拉已收起,未能清空选中值,因此,此处在单选时增加了一个option的点击事件,用于标识选中的值;

select触底指令:

import type { Directive, DirectiveBinding } from 'vue';

//下拉滚动触底监听指令
const selectScroll: Directive = {
  mounted(el, binding) {
    const className = `.${el.dataset.class}`, element = document.querySelector(className)?.querySelector(".el-select-dropdown .el-select-dropdown__wrap");
    element?.addEventListener("scroll", () => {
      scrollSelect(element, binding);
    })
  },
  beforeUnmount(el, binding) {
    const className = `.${el.dataset.class}`, element = document.querySelector(className)?.querySelector(".el-select-dropdown .el-select-dropdown__wrap");
    element?.removeEventListener("scroll", () => {
      scrollSelect(element, binding);
    })
  }
}

const scrollSelect = (element, binding) => {
  const { scrollTop, scrollHeight, clientHeight } = element,
    scrollDistance = scrollHeight - scrollTop - clientHeight;
  if (scrollDistance <= 0) {
    binding.value();
  }
}
  • 5
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值