select筛选器+大数据手动切割渲染(vue3+ts+elementPlus)

145 阅读2分钟

我们在经常开发时,除了会遇到分页请求select之前,还会偶尔遇到后端一次性返回几十万行的数据,这种情况下就需要我们手动切割加载渲染了。

组件相关的代码:

<template>
  <el-select
    ref="selectScroll" v-model="chooseShowValue" clearable filterable :filter-method="filterMethod"
    v-scrollChange="loadMore" @visible-change="visibleChange" @change="changeData" :placeholder="placeholderValue"
    :disabled="curDisabled">
    <div :class="classSelectRef">
      <el-option v-for="(item, index) in selecOptions" :key="index" :label="item[showLabel]" :value="item[showValue]">
        {{ item[showLabel] }}
      </el-option>
    </div>
  </el-select>
</template>

<script lang="ts" setup>
import { onActivated, watch, onMounted, toRaw, ref, Directive, DirectiveBinding } from 'vue'
const emit = defineEmits(['update:chooseValue', 'change'])

const dom = ref()
const selectScroll = ref(null)

const selecOptions = ref()
const options = ref()
const rangeNumber = ref(10)
const chooseShowValue = ref('f99c1b07-bd7b-4c66-a1de-33c10bf80291')
const initSelectData = ref(null)
const initValue = ref('')
const placeholderValue = ref('')
const timeOut = ref()
const queryRef = ref('')
const curDisabled = ref(false)
const curResOptions: any = ref()

const classSelectRef = ref('')

const props = defineProps({
  chooseValue: {
    type: String,
    default: () => ''
  },
  showValue: {
    type: String,
    default: () => 'value'
  },
  showLabel: {
    type: String,
    default: () => 'label'
  },
  resOptions: {
    type: Array,
    default: () => []
  },
  changeParam: {
    type: Object,
    default: () => { }
  },
  placeholder: {
    type: String,
    default: '请选择'
  },
  disabled: {
    type: Boolean,
    default: () => false
  },
  classSelect: {
    type: String,
    default: 'labelSelectCpmBox'
  }
})

onMounted(() => {
  classSelectRef.value = props.classSelect + (new Date().getTime()).toString()
  chooseShowValue.value = props.chooseValue
  placeholderValue.value = props.placeholder
  curResOptions.value = props.resOptions
  options.value = props.resOptions
})

// computed: {
//   chooseComputedValue() {
//     return JSON.parse(JSON.stringify(this.chooseValue));
//   }
// },
const loadMore = () => {
  // n是默认初始展示的条数会在渲染的时候就可以获取,具体可以打log查看
  // elementui下拉超过7条才会出滚动条,如果初始不出滚动条无法触发loadMore方法
  // return () => (this.rangeNumber += 5
  // this.tmpOption = this.resOptions.splice(0,this.rangeNumber)
  // );
  // return () => {
  rangeNumber.value += 5
  selecOptions.value = options.value.slice(0, rangeNumber.value)
  // };
}

// 防抖
const debounce = (fn: any, delay: number) => {
  if (timeOut.value) {
    clearTimeout(timeOut.value)
  }
  timeOut.value = setTimeout(() => {
    fn()
  }, delay)
}

const filterMethodByQuery = () => {
  if (queryRef.value) {
    const filterArr = curResOptions.value.filter((item: any) => {
      const tmpData = toRaw(item)
      const tmpLabel = tmpData[props.showLabel]
      const tmpValue = tmpData[props.showValue]
      const query = queryRef.value
      return tmpLabel.includes(query)
      // return tmpLabel.includes(query) || tmpValue.includes(query)
    })
    options.value = filterArr
    selecOptions.value = options.value.slice(0, rangeNumber.value)
  } else {
    options.value = curResOptions.value
  }
  chooseShowValue.value = queryRef.value
  changeData()
}

const filterMethod = (query = '') => {
  if (query) {
    queryRef.value = query
    debounce(filterMethodByQuery, 500)
  } if (queryRef.value !== query) {
    queryRef.value = query
    debounce(filterMethodByQuery, 500)
  }
}

// 下拉框出现时,调用过滤方法
const visibleChange = (flag: any) => {
  if (flag) {
    filterMethod()
  }
}

const changeData = () => {
  emit('update:chooseValue', chooseShowValue.value)
  emit('change', chooseShowValue.value, props.changeParam)
}

const showInitData = () => { }

/* 滚动监听函数 */
const scrollAddEventFn = (e: any) => {
  const self = e.target as any
  if (self.scrollHeight - self.scrollTop <= self.clientHeight) {
    console.log('分页查询')
    loadMore()
  }
}

const vScrollChange: Directive = (el, binding: DirectiveBinding) => {
  /* 在数据渲染完之后的回调 */
  /* 初始化滚动监听 (由于 dom 渲染未完成,所以需要开启一个 timeout 在 1s 后实现监听) */
  const parentDom = document.querySelectorAll('.el-select-dropdown__wrap.el-scrollbar__wrap.el-scrollbar__wrap--hidden-default') as any
  setTimeout(() => {
    parentDom.forEach((e: any, idx: number) => {
      const className = '.' + toRaw(classSelectRef.value)
      // if (e.querySelector('.labelSelectCpmBox') && e.querySelector('.labelSelectCpmBox').children && e.querySelector('.labelSelectCpmBox').children.length > 0) {
      if (e.querySelector(className) && e.querySelector(className).children && e.querySelector(className).children.length > 0) {
        dom.value = parentDom[idx]
        dom.value.addEventListener('scroll', scrollAddEventFn, false)
      }
    })
  }, 1000)
}

watch(
  () => props.chooseValue,
  (newValue) => {
    chooseShowValue.value = JSON.parse(JSON.stringify(newValue))
  },
  {
    deep: true,
    immediate: true
  }
)

watch(
  () => props.placeholder,
  (newValue) => {
    placeholderValue.value = JSON.parse(JSON.stringify(newValue))
  },
  {
    deep: true,
    immediate: true
  }
)

watch(
  () => props.resOptions,
  (newValue) => {
    curResOptions.value = JSON.parse(JSON.stringify(newValue))
    options.value = JSON.parse(JSON.stringify(newValue))
    if (newValue.length > 10) {
      selecOptions.value = JSON.parse(JSON.stringify(newValue)).slice(0, rangeNumber.value)
    } else {
      selecOptions.value = JSON.parse(JSON.stringify(newValue))
    }
    showInitData()
  },
  {
    deep: true,
    immediate: true
  }
)

watch(
  () => props.disabled,
  (newValue) => {
    curDisabled.value = JSON.parse(JSON.stringify(newValue))
  },
  {
    deep: true,
    immediate: true
  }
)
</script>