vue-virtual-scroll-list(可单选、多选、搜索查询、创建条目)

发布时间:2024年01月12日

element-ui-解决下拉框数据量过多问题(vue-virtual-scroll-list)_element-ui下拉框数据太多如何优化-CSDN博客

的升级版

参考链接:封装el-select,实现虚拟滚动,可单选、多选、搜索查询、创建条目-CSDN博客

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
        :placeholder="placeholderParams"
        :multiple="isMultiple"
        :allow-create="allowCreate"
        @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: {
        // // 父组件传的值
        // selectData: {
        //   type: Object,
        //   default() {
        //     return {}
        //   }
        // },
        // 数组
        list: {
          type: Array,
          default() {
            return []
          }
        },
        // 显示名称
        label: {
          type: String,
          default: ''
        },
        // 标识
        value: {
          type: String,
          default: ''
        },
        // 是否拼接label | value
        isConcat: {
          type: Boolean,
          default: false
        },
        // 拼接label、value符号
        concatSymbol: {
          type: String,
          default: ' | '
        },
        // 显示右边
        isRight: {
          type: Boolean,
          default: false
        },
        // 加载条数
        keepsParams: {
          type: Number,
          default: 10
        },
        // 绑定的默认值
        bindValue: {
          type: [String, Array],
          default() {
            if (typeof this.bindValue === 'string') return ''
            return []
          }
        },
        // 是否多选
        isMultiple: {
          type: Boolean,
          default: false
        },
        placeholderParams: {
          type: String,
          default: '请选择'
        },
        // 是否允许创建条目
        allowCreate: {
          type: Boolean,
          default: false
        }
      },
      data() {
        return {
          itemComponent: ElOptionNode,
          selectArr: [],
          defaultValue: null // 绑定的默认值
        }
      },
      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 {
            // 回显问题
            // 由于只渲染固定keepsParams(10)条数据,当默认数据处于10条之外,在回显的时候会显示异常
            // 解决方法:遍历所有数据,将对应回显的那一条数据放在第一条即可
            this.selectArr = JSON.parse(JSON.stringify(this.list))
            let obj = {}
            if (typeof this.defaultValue === 'string' && !this.isMultiple) {
              if (this.allowCreate) {
                const arr = this.selectArr.filter(val => {
                  return val[this.value] === this.defaultValue
                })
                if (arr.length === 0) {
                  const item = {}
                  // item[this.value] = `Create-${this.defaultValue}`
                  item[this.value] = this.defaultValue
                  item[this.label] = this.defaultValue
                  item.allowCreate = true
                  this.selectArr.push(item)
                  this.$emit('selChange', item)
                } else {
                  this.$emit('selChange', arr[0])
                }
              }
              // 单选
              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) {
              if (this.allowCreate) {
                this.defaultValue.map(v => {
                  const arr = this.selectArr.filter(val => {
                    return val[this.value] === v
                  })
                  if (arr?.length === 0) {
                    const item = {}
                    // item[this.value] = `Create-${v}`
                    item[this.value] = v
                    item[this.label] = v
                    item.allowCreate = true
                    this.selectArr.push(item)
                    this.$emit('selChange', item)
                  } else {
                    this.$emit('selChange', arr[0])
                  }
                })
              }
              // 多选
              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)
                  }
                })
              }
            }
          }
        },
        // 搜索
        filterMethod(query) {
          if (!validatenull(query?.trim())) {
            this.$refs.virtualList.scrollToIndex(0) // 滚动到顶部
            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>
    .virtualselect {
        // 设置最大高度
        &-list {
            max-height:245px;
            overflow-y:auto;
        }
        .el-scrollbar .el-scrollbar__bar.is-vertical {
            width: 0;
        }
    }
    
</style>

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: {
          type: Number,
          default: 0
        },
        // 每一行的内容
        source: {
          type: Object,
          default() {
            return {}
          }
        },
        // 需要显示的名称
        label: {
          type: String,
          default: ''
        },
        // 绑定的值
        value: {
          type: String,
          default: ''
        },
        // 是否拼接label | value
        isConcat: {
          type: Boolean,
          default: false
        },
        // 拼接label、value符号
        concatSymbol: {
          type: String,
          default: ' | '
        },
        // 右侧是否显示绑定的值
        isRight: {
          type: Boolean,
          default() {
            return false
          }
        }
      },
      methods: {
        concatString(a, b) {
          a = a || ''
          b = b || ''
          if (this.isConcat) {
            // return a + ((a && b) ? ' | ' : '') + b
            return a + ((a && b) ? this.concatSymbol : '') + b
          }
          return a
        }
      }
    }
  </script>
   

2.使用:

第一层组件:insuplc.vue

<template>
  <cw-select
      v-model="value"
      :list="list"
      label="name"
      value="value"
      :class="classname" 
      :disabled="disabled" 
      :placeholder-params="placeholder"
      :keeps-params="10"
      :is-concat="false"
      :is-multiple="isMultiple"
      :is-right="false"
      :allow-create="false"
      @change="selectChange"
    />
</template>

<script>
  import CwSelect from '@/components/virtualScrollList/select.vue'
  export default {
      name:'insuplc',
      components: {
          CwSelect
      },
      props: {
        isMultiple: {
            type: Boolean,
            default: false
        },
        defaultValue: {
        },
        disabled: {
          type: Boolean
        },
        classname: {
          type: Object
        },
        placeholder: {
          type: String
        },
      },
      computed: {
        insuplcList() {//医保区划
          return this.$store.state.app.ybDictMap.yb_insuplc
        },
      },
      watch: {
        defaultValue:{
          handler(newVal) {
            this.value = newVal
          },
          immediate: true,
          deep: true
        }
      },
      data() {
          return {
            value:this.defaultValue,
            list:[]
          };
      },
      mounted() {
        this.list = JSON.parse(JSON.stringify(this.insuplcList))
      },
      methods: {
          selectChange(val) {
              this.$emit('change', {value:val})
          }
      }
  };
</script>

第二层:

        <insuplc :isMultiple="true" style="display:inline-block;width: 100%;" placeholder='请选择' :defaultValue="form.insuplc" @change="(val) => changeInsuplc(val)" />

文章来源:https://blog.csdn.net/xiuxialala/article/details/135414981
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。