• vue2虚拟滚动下拉选择器


    vue虚拟滚动下拉选择器

    目的

    为了解决 element-ui 中 el-select 组件在大数据量的情况下出现的性能问题(数据量太大,导致渲染过慢,或造成页面卡顿甚至于卡死) 。

    原理

    本组件时基于vue-virtual-scroll-list(一个基于vue2的虚拟滚动组件,通过不渲染可视区域以外的内容,显示虚拟的滚动条来提升页面性能)实现的下拉选中框,该下拉选择组件支持单选,多选,筛选等操作,若是选中了数据,再次打开会自动定位到数据的位置

    一.安装

    npm install vue-virtual-scroll-list --save
    
    • 1
    常用参数及方法
    常用参数
    参数名类型描述
    data-keyString|Function从每个数据对象中的“数据源”获取唯一键。或者用每个“数据源”调用一个函数,并返回它们的唯一键。它的值在“数据源”中必须是唯一的,用于标识条目的大小。
    data-sourcesArray[Object]为列表构建的源数组,每个数组数据必须是一个对象,并具有唯一的键ID(data-key)属性。
    data-componentComponentvue创建/声明的渲染项组件,它将使用’ data-sources ‘中的数据对象作为渲染道具,并命名为’source’。
    keepsNumber你希望虚拟列表在真正的dom中保持呈现多少项,默认30个。
    extra-props Object分配给不在数据源中的项目组件的额外道具。注意:索引和源都被占用在内部。
    常用方法

    可以通过ref方式调用以下这些方法:

    方法名描述
    reset将所有状态重置为初始状态。
    scrollToIndex(index)手动设置滚动位置为指定的索引。
    scrollToOffset(offset)手动设置滚动位置到指定的偏移量。

    上述是组件常用的参数和方法,其他参数及方法可 官方插件

    二.开始封装element的select组件

    在src/components中创建文件夹SelectV2

    1.创建index.vue文件
    <template>
      <div>
        <el-select
          :value="value"
          popper-class="virtualselect"
          filterable
          :filter-method="filterMethod"
          @visible-change="visibleChange"
          v-bind="$attrs"
          v-on="$listeners"
        >
          <vue-virtual-list
            ref="virtualList"
            class="virtualselect-list"
            :data-key="fields.value"
            :data-sources="dataSources"
            :data-component="itemComponent"
            :keeps="20"
            :extra-props="{
              label: fields.label,
              value: fields.value,
              rightLabel: fields.rightLabel
            }"
          ></vue-virtual-list>
        </el-select>
      </div>
    </template>
    
    <script>
    import VueVirtualList from 'vue-virtual-scroll-list';
    import itemComponent from './itemComponent';
    
    export default {
      name: 'select-v2',
      components: {
        'vue-virtual-list': VueVirtualList
      },
      model: {
        prop: 'value',
        event: 'change'
      },
      props: {
        // 下例列表数据
        data: {
          type: Array,
          default: () => []
        },
        // 字段参数配置,默认为label,value
        // 可尝试传入rightLabel字段,使其在右边也展示内容
        fields: {
          type: Object,
          default() {
            return {
              label: 'label',
              value: 'value',
            };
          }
        },
        // 绑定的默认值
        value: {
          type: [String, Array],
          default: () => []
        }
      },
      mounted() {
        this.init();
      },
      watch: {
        'data'() {
          this.init();
        }
      },
      data() {
        return {
          // 内容组件
          itemComponent,
          // 下拉列表中的数据
          dataSources: [],
          // 用来定位滚动到某个位置
          start: 0
        };
      },
      methods: {
        init() {
          if (!this.value || this.value.length === 0) {
            // 初始化数据
            this.dataSources = this.data;
          } else {
            // 回显问题
            // 单选时,存储当前的index
            // 多选时,取选中的所有值中下标的最小值
            this.dataSources = JSON.parse(JSON.stringify(this.data));
            if (typeof this.value === 'string') {
              const value = this.value
              for (let i = 0; i < this.dataSources.length; i++) {
                const element = this.dataSources[i];
                if (element[this.fields.value] === value) {
                  this.start = i
                  break;
                }
              }
            } else if (Array.isArray(this.value)) {
              let start = []
              const valueSet = new Set(this.value)
              for (let i = 0; i < this.dataSources.length; i++) {
                const element = this.dataSources[i];
                if (valueSet.has(element[this.fields.value])) {
                  start.push(i)
                }
              }
              this.start = Math.min(...start)
            }
          }
        },
        // 搜索
        filterMethod(query) {
          if (query !== '') {
            setTimeout(() => {
              this.$refs.virtualList.scrollToIndex(0);
              this.dataSources = [...this.data.filter((item) => {
                return this.fields.rightLabel
                    ? item[this.fields.label]
                        .indexOf(query) > -1 ||
                    item[this.fields.rightLabel]
                        .indexOf(query) > -1
                    : item[this.fields.label]
                    .indexOf(query) > -1;
              })];
            }, 100);
          } else {
            this.init();
          }
        },
        visibleChange(bool) {
          if (!bool) {
            this.$refs.virtualList.reset();
            this.init();
          } else {
            // 定位到选中的位置
            this.$nextTick(() => {
              this.$refs.virtualList.scrollToIndex(this.start);
            })
          }
        }
      }
    };
    </script>
    <style lang="scss">
    .virtualselect {
      // 设置最大高度
      &-list {
        max-height: 245px;
        overflow-y: auto;
      }
    
      .el-scrollbar .el-scrollbar__bar.is-vertical {
        width: 0 !important;
      }
    }
    </style>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    2.同目录下创建itemComponent.vue文件
    <template>
      <div>
        <el-option
            :key="label + value"
            :label="source[label]"
            :value="source[value]"
        >
          <span>{{ source[label] }}</span>
          <span v-if="rightLabel" style="float:right;color:#939393">{{
              source[rightLabel]
            }}</span>
        </el-option>
      </div>
    </template>
    
    <script>
    export default {
      name: 'item-component',
      props: {
        // index of current item
        // 每一行的索引
        index: {
          type: Number
        },
        // 每一行的内容
        source: {
          type: Object,
          default() {
            return {};
          }
        },
        // 需要显示的名称
        label: {
          type: String
        },
        // 绑定的值
        value: {
          type: String
        },
        // 右侧显示绑定的值,为空则不显示
        rightLabel: {
          type: String,
          default: ''
        }
      },
      mounted() {}
    };
    </script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48

    三.使用用例

    创建测试页面
    <template>
      <div class="cw-select">
        <select-v2
            :data="list"
            v-model="value"
            placeholder="请选择下拉数据"
            clearable
            multiple
            @change="selectChange"
        ></select-v2>
      </div>
    </template>
    
    <script>
    import SelectV2 from '@/components/SelectV2'
    export default {
      name: 'demo',
      components: {
        'select-v2': SelectV2
      },
      data() {
        return {
          list: [],
          // 下拉框选择的默认值,可以时字符串和数组
          value: []
        };
      },
      mounted() {
        this.list = [];
        // 创建20000条测试数据
        for (let i = 0; i < 20000; i++) {
          this.list.push({ value: i, label: '测试' + i + '' });
        }
      },
      methods: {
        selectChange(val) {
          console.log('下拉框选择的值', val);
        }
      }
    };
    </script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
  • 相关阅读:
    一次关于关系抽取(RE)综述调研的交流心得
    绿色计算产业发展白皮书:2022年OceanBase助力蚂蚁集团减排4392tCO2e
    SpringBoot使用DevTools实现后端热部署
    vue3+Cesium 遇到的问题及解决办法(持续更新)
    Java面试题汇总(持续更新.....)
    kafka概要设计---Kafka从入门到精通(三)
    【splishsplash】PBDWrapper分析
    Anaconda全网最全conda命令行(新建、复制、重命名、删除、国内源加速等)
    OpenCV 中的轮廓-查找轮廓的不同特征,例如面积,周长,重心,边界框等。
    车载电子电器架构 —— 电气架构开发计划
  • 原文地址:https://blog.csdn.net/qq_40015826/article/details/133034753