Encapsulate el-select, realize virtual scrolling, single selection, multiple selection, search query

This component has been changed based on the following code: too much drop-down data in element, resulting in slow loading of the list, using a virtual list method

1. Problem description

A drop-down box in the form, due to too much data, will cause the page to freeze due to the large amount of data when selecting, so the el-select is re-encapsulated to realize virtual scrolling.

Second, the implementation is as follows:

It seems that all the data is loaded, but in fact only 10 pieces of (modifiable) data set by myself are loaded.

?

Step1: npm install vue-virtual-scroll-list –save

Step2: Create two files

?

1. Select.vue

<template>
  <div>
    <el-select
      popper-class="virtualselect"
      class="virtual-select-custom-style"
      :popper-append-to-body="false"
      :value="defaultValue"
      filterable
      :filter-method="filterMethod"
      default-first-option
      clearable
      :multiple="isMultiple"
      @visible-change="visibleChange"
      v-on="$listeners"
      @clear="clearChange"
    >
      <virtual-list
        ref="virtualList"
        class="virtualselect-list"
        :data-key="value"
        :data-sources="selectArr"
        :data-component="itemComponent"
        :keeps="keepsParams"
        :extra-props="{
          label: label,
          value: value,
          isRight: isRight,
          isConcat: isConcat,
          concatSymbol: concatSymbol
        }"
      ></virtual-list>
    </el-select>
  </div>
</template>
<script>
  import {
    validatenull
  } from '@/utils'
  import virtualList from 'vue-virtual-scroll-list'
  import ElOptionNode from './el-option-node'
  export default {
    components: {
      'virtual-list': virtualList
    },
    model: {
      prop: 'bindValue',
      event: 'change'
    },
    props: {
      // // The value passed by the parent component
      // selectData: {
      // type: Object,
      // default() {
      // return {}
      // }
      // },
      // array
      list: {
        type: Array,
        default() {
          return []
        }
      },
      // display name
      label: {
        type: String,
        default: ''
      },
      // ID
      value: {
        type: String,
        default: ''
      },
      // whether to concatenate label | value
      isConcat: {
        type: Boolean,
        default: false
      },
      // Stitching label and value symbols
      concatSymbol: {
        type: String,
        default: '|'
      },
      // show the right
      isRight: {
        type: Boolean,
        default: false
      },
      // load number
      keepsParams: {
        type: Number,
        default: 10
      },
      // default value for binding
      bindValue: {
        type: [String, Array],
        default() {
          if (typeof this. bindValue === 'string') return ''
          return []
        }
      },
      // Whether to select multiple
      isMultiple: {
        type: Boolean,
        default: false
      }
    },
    data() {
      return {
        itemComponent: ElOptionNode,
        selectArr: [],
        defaultValue: null // default value for binding
      }
    },
    watch: {
      'list'() {
        this.init()
      },
      bindValue: {
        handler(val, oldVal) {
          this.defaultValue = this.bindValue
          if (validatenull(val)) this. clearChange()
          this.init()
        },
        immediate: false,
        deep: true
      }
    },
    mounted() {
      this.defaultValue = this.bindValue
      this.init()
    },
    methods: {
      init() {
        if (!this.defaultValue || this.defaultValue?.length === 0) {
          this.selectArr = this.list
        } else {
          // echo the problem
          // Since only fixed keepsParams (10) pieces of data are rendered, when the default data is beyond 10 pieces, an exception will be displayed when echoing
          // Solution: Traverse all the data, and put the piece of data corresponding to the echo in the first piece
          this.selectArr = JSON.parse(JSON.stringify(this.list))
          let obj = {}
          if (typeof this.defaultValue === 'string' & amp; & amp; !this.isMultiple) {
            // radio
            for (let i = 0; i <this. selectArr. length; i ++ ) {
              const element = this. selectArr[i]
              if (element[this.value]?.toLowerCase() === this.defaultValue?.toLowerCase()) {
                obj = element
                this.selectArr?.splice(i, 1)
                break
              }
            }
            this.selectArr?.unshift(obj)
          } else if (this. isMultiple) {
            // multiple choice
            for (let i = 0; i <this. selectArr. length; i ++ ) {
              const element = this. selectArr[i]
              this.defaultValue?.map(val => {
                if (element[this.value]?.toLowerCase() === val?.toLowerCase()) {
                  obj = element
                  this.selectArr?.splice(i, 1)
                  this.selectArr?.unshift(obj)
                }
              })
            }
          }
        }
      },
      // search
      filterMethod(query) {
        if (!validatenull(query?.trim())) {
          this.$refs.virtualList.scrollToIndex(0) // scroll to the top
          setTimeout(() => {
            this.selectArr = this.list.filter(item => {
              return this.isRight || this.isConcat
                ? (item[this.label].trim()?.toLowerCase()?.indexOf(query?.trim()?.toLowerCase()) > -1 || item[this.value]?.toLowerCase()? .indexOf(query?.trim()?.toLowerCase()) > -1)
                : item[this.label]?.toLowerCase()?.indexOf(query?.trim()?.toLowerCase()) > -1
            })
          }, 100)
        } else {
          setTimeout(() => {
            this.init()
          }, 100)
        }
      },
      visibleChange(bool) {
        if (! bool) {
          this.$refs.virtualList.reset()
          this.init()
        }
      },
      clearChange() {
        if (typeof this. defaultValue === 'string') {
          this.defaultValue = ''
        } else if (this. isMultiple) {
          this.defaultValue = []
        }
        this. visibleChange(false)
      }
    }
  }
</script>
<style lang="scss" scoped>
.virtual-select-custom-style ::v-deep .el-select-dropdown__item {
  // Set the maximum width, beyond the ellipsis, the mouse hover display
  // options need to write :title="source[label]"
  width: 250px;
  display: inline-block;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.virtualselect {
  // set max height
   &-list {
    max-height: 245px;
    overflow-y: auto;
  }
}
::-webkit-scrollbar {
  width: 6px;
  height: 6px;
  background-color: transparent;
  cursor: pointer;
  margin-right: 5px;
}
::-webkit-scrollbar-thumb {
  background-color: rgba(144,147,153,.3) !important;
  border-radius: 3px !important;
}
::-webkit-scrollbar-thumb:hover{
  background-color: rgba(144,147,153,.5) !important;
}
::-webkit-scrollbar-track {
  background-color: transparent !important;
  border-radius: 3px !important;
  -webkit-box-shadow: none !important;
}
::v-deep.el-select__tags {
  flex-wrap: unset;
  overflow: auto;
}
</style>

2. el-option-node.vue

<template>
  <el-option
    :key="label + value"
    :label="concatString(source[label], source[value])"
    :value="source[value]"
    :disabled="source.disabled"
    :title="concatString(source[label], source[value])"
  >
    <span>{<!-- -->{ concatString(source[label], source[value]) }}</span>
    <span
      v-if="isRight"
      style="float:right;color:#939393"
    >{<!-- -->{ source[value] }}</span>
  </el-option>
</template>
<script>
  export default {
    name: 'ItemComponent',
    props: {
      // index of each row
      index: {
        type: Number,
        default: 0
      },
      // content of each line
      source: {
        type: Object,
        default() {
          return {}
        }
      },
      // name to be displayed
      label: {
        type: String,
        default: ''
      },
      // bound value
      value: {
        type: String,
        default: ''
      },
      // whether to concatenate label | value
      isConcat: {
        type: Boolean,
        default: false
      },
      // Stitching label and value symbols
      concatSymbol: {
        type: String,
        default: '|'
      },
      // Whether to display the bound value on the right
      isRight: {
        type: Boolean,
        default() {
          return false
        }
      }
    },
    methods: {
      concatString(a, b) {
        a = a || ''
        b = b || ''
        if (this. isConcat) {
          // return a + ((a & amp; & amp; b) ? ' | ' : '') + b
          return a + ((a & amp; & amp; b) ? this. concatSymbol : '') + b
        }
        return a
      }
    }
  }
</script>

3. utils index.js

/**
 * Determine whether it is empty
 */
export function validatenull(val) {
  if (typeof val === 'boolean') {
    return false;
  }
  if (typeof val === 'number') {
    return false;
  }
  if (val instanceof Array) {
    if (val. length===0) return true;
  } else if (val instanceof Object) {
    if (JSON. stringify(val) === '{}') return true;
  } else {
    if (val==='null' || val===null || val==='undefined' || val===undefined || val==='') return true;
    return false;
  }
  return false;
}

4. Use of components

// import component
import VirtualSelect from '@/components/common/VirtualSelect/Select'


// register component
components: {
  Virtual Select
},


// use

          <el-table-column
            align="left"
            width="200"
            label="Service customer name"
            prop="testCode"
          >
            <template slot-scope="scope">
              <el-form-item
                label=""
                :prop="`tableData[${scope.$index}].testCode`"
                :rules="tableRules.select_required"
              >
                <virtual-select
                  v-model="scope.row.testCode"
                  :list="testCodeOptions"
                  label="testName"
                  value="testCode"
                  placeholder="Please select drop-down data"
                  :keeps-params="10"
                  :is-concat="false"
                  :is-multiple="false"
                />
              </el-form-item>
            </template>
          </el-table-column>

Summarize:

  • The value of the list is the data of the drop-down value set of its own selection box.
  • label and value and v-model are the same as the original el-select attribute. Others can be used in the same way. For example, if the clearable is to be cleared, write it directly, and the disabled is the same. You can add it as you want to add the original el-select attribute. It can basically meet the general needs.
  • keepsParams displays the number of scrolling loads
  • isMultiple is multiple selection

  • bindValue Binding default value (defaultValue)

  • isRight Whether to display the value (code) value on the right

  • isConcat Whether to concatenate label and value

  • concatSymbol concatenates the symbol of label and value