el-select远程查询大数据量分段加载

目录

    • el-select远程查询大数据量分段加载
      • 1.问题:
      • 2.解决思路
      • 3.代码
      • 4.优化

el-select远程查询大数据量分段加载

1.问题:

最近使用elementUI的el-select组件的远程查询功能,发现在大量数据(1w条以上)的时候出现页面卡死的情况,经过排查,产生问题的原因是前端渲染造成的。

2.解决思路

1.在查找解决方案的时候看到一个解决办法是加载前50条,效果是这样的:
el-select远程查询大数据量分段加载_第1张图片
2.上面那个方法已经解决了页面卡死的问题了,但是只显示了50条结果,其他的结果无法显示。
顺着这个思路,我给最后一行(“只显示前50条…”)加了一个点击事件,鼠标点击的时候就多显示20条,
效果是这样的:
el-select远程查询大数据量分段加载_第2张图片
el-select远程查询大数据量分段加载_第3张图片
3.再升级一下,可不可以不用点击?
那就得加鼠标滚动事件,通过鼠标滚动加载更多数据,效果与第2点一样,只是不用点击就可以加载了。

3.代码

组件代码:


<template>
  <div>
    <el-form ref="remoteForm" :model="remoteForm" :rules="formRule">
      <el-form-item prop="result">
        <el-tooltip ref="toolTip" :content="remoteForm.result?remoteForm.result:placeholder" popper-class="remote-tooltip" placement="top" effect="light">
          <el-select
            v-if="hasRemote"
            id="remote-search"
            ref="remoteSearch"
            v-model="remoteForm.result"
            filterable
            remote
            clearable
            reserve-keyword
            :placeholder="placeholder"
            :size="size"
            class="remote-search"
            :remote-method="remoteMethod"
            :loading="loading"
            :title="result"
            @input.native="input"
            @change="change"
            @focus="focus"
            @mousewheel="mouseWheel"
          >
            <el-option
              v-for="item in numberList_"
              :key="item.value"
              :label="item.label"
              :value="item.value"
            />
            <div v-if="numberList.length > 9 && numberList_.length !== numberList.length" ref="moreDiv" class="search-keyword">
              <span @click="loadMore">加载更多...span>
            div>
            <div v-else class="search-keyword-bottom">
              <span>到底啦~span>
            div>
          el-select>
        el-tooltip>
      el-form-item>
    el-form>
  div>
template>

<script>
/**
 * 1.如何清除表单?
 *   使用v-if属性对组件进行创建和销毁。使用方法:传入:has-remote="hasRemote",
 *   hasRemote为true是创建组件,表单重置时使hasRemote=false;
 *   *如果是在对话框中,讲对话框的visible的值给has-remote即可
 */
export default {
  name: 'RemoteSearch',
  props: {
    numberList: { // 完整的查询结果
      default: () => [],
      type: Array
    },
    size: {
      default: 'mini',
      type: String
    },
    placeholder: {
      default: null,
      type: [Number, String]
    },
    hasRemote: {
      default: true,
      type: Boolean
    }
  },
  data() {
    return {
      loading: false,
      options: [],
      list: [],
      searchData: '',
      remoteForm: {
        result: ''
      },
      formRule: {},
      numberList_: [],
      i: 1
    }
  },
  computed: {
    result() {
      return this.remoteForm.result
    }
  },
  watch: {
    numberList() {
      this.numberList_ = this.numberList.slice(0, 10) // 先加载10条结果
      this.i = 1
    },
    result(newValue, oldValue) {
      this.searchData = newValue
    },
    hasRemote() {
      if (!this.hasRemote) {
        this.$refs.remoteForm.resetFields() // 组件销毁时重置输入框
        // window.removeEventListener('mousewheel', this.mouseWheel, false) // 销毁鼠标滚动事件
      }
    }
  },
  mounted() {
    console.log('组件挂载')
    window.addEventListener('mousewheel', this.mouseWheel)
  },
  destroyed() {
    window.removeEventListener('mousewheel', this.mouseWheel, false) // 组件销毁时销毁鼠标滚动事件
  },
  methods: {
    remoteMethod(query) {
      if (query !== '') {
        this.loading = true
        setTimeout(() => {
          this.loading = false
          this.options = this.numberList_.filter(item => {
            return item.label.toLowerCase()
              .indexOf(query.toLowerCase()) > -1
          }).slice(0, 10)
        }, 200)
      } else {
        this.options = []
      }
    },
    loadMore: function() {
      this.i++
      this.numberList_ = this.numberList.slice(0, 10 * this.i) // 多加载10条结果
    },
    input: function(searchData) {
      this.remoteForm.result = searchData.target.value
      this.$emit('input', searchData)
    },
    change: function(data) {
      this.$emit('change', data)
    },
    focus: function() {
      this.$emit('focus')
    },
    select: function(data) {
      this.$emit('select')
    },
    mouseWheel: function() {
      if (this.$refs.remoteSearch && this.$refs.remoteSearch.$refs && this.$refs.remoteSearch.$refs.scrollbar) {
        const scrollHeight = Math.round(this.$refs.remoteSearch.$refs.scrollbar.moveY) // 滚动条滚动的距离
        console.log(scrollHeight);
        console.log(36 * this.i - 1);
        if (scrollHeight && scrollHeight !== 0 && scrollHeight > 36 * this.i) {
          this.loadMore()
        }
      }
    }
  }
}
script>
<style lang="scss">
.remote-search {
  width: 100%;
  .el-input__inner {
    padding: 2px 5px;
  }
}
.remote-search {
  input::-webkit-input-placeholder {
    color: #616161a1;
  }
  input::-moz-input-placeholder {
    color: #6161618c;
  }
  input::-ms-input-placeholder {
    color: #61616173;
  }
}
.remote-tooltip {
  padding: 5px!important;
}
.search-keyword {
  text-align: center;
  padding: 5px;
  color: rgb(133, 133, 133);
  font-size: 12px;
  cursor: pointer;
  :hover {
    color: rgb(71, 159, 218);
  }
}
.search-keyword-bottom {
  text-align: center;
  padding: 5px;
  color: rgb(133, 133, 133);
  font-size: 12px;
}
style>

调用:

changeResultDlgVisible 是所在表单是否显示(值为false和true) startNumber 默认回显的数据
resultNumberInput()是访问后台接口,获取查询结果
numberList是接口返回的查询结果,格式如下,须有value和label两个字段
在这里插入图片描述

<remote-search
  :result="startNumber"
  :number-list="numberList"
  :placeholder="startNumber"
  :has-remote="changeResultDlgVisible"
  size="small"
  @input="resultNumberInput(startNumber, 'paperNumber', $event.target.value)"
  @change="startNumber=$event;$forceUpdate"
  @focus="numberList = []"
/>

就可以啦~

4.优化

上面是每次输入都会访问后台,感觉挺浪费的,可以给input输入事件添加防抖,隔段时间再访问后台,下面直接给代码:
1.创建一个js文件,存放防抖函数,我将它放在common目录下,新建文件debounce.js

/**
* 防抖函数
* @param func 用户传入的防抖函数
* @param wait 等待的时间
* @param immediate 是否立即执行
*/
export const debounce = function(func, wait = 50, immediate = false) {
  console.log(func);
  // 缓存一个定时器id
  let timer = null
  let result
  const debounced = function(...args) {
    // 如果已经设定过定时器了就清空上一次的定时器
    if (timer) {
      clearTimeout(timer)
    }
    if (immediate) {
      const callNow = !timer
      // 等待wait的时间间隔后,timer为null的时候,函数才可以继续执行
      timer = setTimeout(() => {
        timer = null
        result = func.apply(this, args)
      }, wait)
      // 未执行过,执行
      if (callNow) result = func.apply(this, args)
    } else {
      // 开始一个定时器,延迟执行用户传入的方法
      timer = setTimeout(() => {
        // 将实际的this和参数传入用户实际调用的函数
        func.apply(this, args)
      }, wait)
    }
    return result
  }
  debounced.cancel = function() {
    clearTimeout(timer)
    timer = null
  }
  // 这里返回的函数时每次用户实际调用的防抖函数
  return debounced
}

2.引入

import { debounce } from '@/common/debounce'

3.使用到input事件上

input: debounce(function(searchData) {
      const _this = this
        _this .remoteForm.result = searchData.target.value
      	_this .$emit('input', searchData)
    }, 500, false),
    ```

你可能感兴趣的:(element,vue,el-select,远程搜索)