vue2 自定义虚拟滚动select组件(VirtualScrollSelectPopover)

 场景

项目需要在表格中,插入input、select下拉列表等元素。当数据量过大时,会导致浏览器渲染超负荷,甚至内存过大直接崩溃,而此时的select下拉框还没加载渲染完成。

插件

        vue-virtual-scroller 文档

        demo

        演示效果

结合个人项目使用场景,输出如下代码:

用法:

<virtual-scroll-select
   :list="dataIdLists"
   name="dataid"
   key-name="id"
   :dataValues.sync="scope.row.dataid"
   @change="changeDataId(scope.$index, 'dataid', scope.row.dataid, scope.row)">
</virtual-scroll-select>

实现: 

HTML

<template>
  <div class="virtual-select">
    <el-popover
      ref="popover"
      placement="bottom"
      popper-class="virtual-select-popover"
      :visible-arrow="false"
      @show="onShow"
      @hide="onHide"
      v-model="isVisible"
      trigger="focus">
      <!-- manual(手动开启关闭,配合v-model) focus -->
      <div class="scroll-container" :style="{'--scrollHeight': scrollHeight}">
        <RecycleScroller
          ref="list"
          class="scroller"
          :key="pageModeFullPage"
          :items="filteredOptions"
          :item-size="null"
          :page-mode="false"
          size-field="optionHeight"
          :key-field="keyName"
          itemClass="option-box"
        >
          <template #default="{ item }">
            <div
              class="option"
              :class="{'active-option': item[keyName] === selectedItem}"
              @click="selectItem(item)"
            >
              <span>{{ item[keyName] }}</span>
            </div>
          </template>
          <template #empty v-if="filteredOptions.length === 0">
            <div class="scroller-empty">
              暂无数据
            </div>
          </template>
        </RecycleScroller>
      </div>
    </el-popover>
    <v-input
      v-popover:popover
      v-model="searchText"
      placeholder="请选择"
      @input="onSearch">
    </v-input>
  </div>
</template>

 JS

<script>
import { RecycleScroller } from 'vue-virtual-scroller'
import 'vue-virtual-scroller/dist/vue-virtual-scroller.css'
import VInput from '@/components/VInput'
import _ from 'lodash'

export default {
  components: {
    RecycleScroller,
    VInput
  },
  props: {
    list: {
      type: Array,
      required: true
    },
    dataValues: {
      type: String,
      default: ''
    },
    keyName: {
      type: String,
      default: ''
    }
  },
  data () {
    return {
      searchText: '',
      filteredOptions: [],
      isDropdownVisible: true,
      selectedItem: '',
      pageMode: true,
      pageModeFullPage: true,
      isVisible: false
    }
  },
  computed: {
    scrollHeight () {
      // 根据数据量调整下拉popover显示高度
      // return '200px'
      return this.filteredOptions.length > 6
        ? '200px'
        : (this.filteredOptions.length === 0
          ? '40px'
          : this.filteredOptions.length * 32 + 'px'
        )
    }
  },
  watch: {
    list: {
      handler: function (nv, ov) {
        // console.log('hd', nv)
        this.filteredOptions = nv
      },
      deep: true
    },
    dataValues: function (nv) {
      this.selectedItem = nv
      this.searchText = nv
    }
  },
  methods: {
    onShow () {
      // this.btnClick()
      const vm = this
      this.$nextTick(() => {
        const i = vm.list.findIndex(item => {
          return item[vm.keyName] === vm.dataValues
        })
        console.log(vm.dataValues)
        console.log(i)
        /**
          * 用于打开popover时,回显,滚动到当前已选择数据位置;
            插件内置scrollToItem()方法
          */
        vm.$refs.list.scrollToItem(i)
      })
    },
    onSearch: _.debounce(function (newValue) {
      // console.log(newValue)
      this.$emit('update:dataValues', newValue)
      this.filteredOptions = this.list.filter(option =>
        option[this.keyName].toLowerCase().includes(this.searchText.toLowerCase())
      )
      // console.log(this.filteredOptions)
    }, 300),
    selectItem (item) {
      console.log('Selected:', item)
      this.selectedItem = item[this.keyName]
      this.searchText = item[this.keyName]
      this.isVisible = false
      this.$emit('update:dataValues', item[this.keyName])
      this.$emit('change', item)
      // 这里可以添加逻辑来处理选中项,比如关闭下拉列表、更新外部状态等
    },
    onHide () {
      if (this.searchText !== this.selectedItem) {
        // 当用户输入搜索过,但未选择任意数据,则恢复至上次绑定;
        this.searchText = this.selectedItem
        this.$emit('update:dataValues', this.selectedItem)
      }
      this.filteredOptions = this.list
    }
  },
  mounted () {
    this.selectedItem = this.dataValues
    this.searchText = this.dataValues
    this.filteredOptions = this.list
  }
}
</script>
<style lang="less" scoped>
.virtual-select {
  width: 100%;
  position: relative;
}
.scroll-container {
  // overflow-y: auto;
  height: var(--scrollHeight);
  // max-height: 200px;
  background: #fefefe;
  .scroller {
    width: 100%;
    height: 100%;
    ::v-deep .option-box {
      border-bottom: 1px solid #e9eaf0;
      font-size: 14px;
      cursor: pointer;
      height: 32px;
      line-height: 19px;
      padding: 6px 12px;
      box-sizing: border-box;
      .option {
        color: #616163;
        text-align: center;
        background: #fff;
        box-sizing: border-box;
        white-space: nowrap;
        overflow: hidden;
        text-overflow: ellipsis;
        box-sizing: border-box;
        border-radius: 4px;
        &:hover {
          color: #fff;
          background: #6d71f9;
          // .option {
          //   color: #616163;
          // }
        }
        span {
          display: inline-block;
          width: 100%;
          height: 100%;
          padding: 0 10px;
          box-sizing: border-box;
        }
      }
      .active-option {
        color: #fff;
        background: #6d71f9;
      }
    }
    .scroller-empty {
      padding: 10px 0;
      margin: 0;
      text-align: center;
      color: #999;
      font-size: 14px;
    }
    /** 滚动条样式调整 */
    &::-webkit-scrollbar {
      width: 6px;
      height: 6px;
      transition: background-color .2s linear, width .2s ease-in-out;
    }
    &::-webkit-scrollbar-thumb {
      border-radius: 10px;
      background: rgba(0,0,0,0.1);
    }
    &::-webkit-scrollbar-thumb:hover {
      width: 10px;
      background: rgba(0,0,0,0.3);
    }
    &::-webkit-scrollbar-track {
      border-radius: 10px;
      background: rgba(0,0,0,0.2);
    }
    &::-webkit-scrollbar-thumb:active {
      width: 10px;
      background: rgba(0,0,0,.6);
    }
  }
}

.fade-enter-active, .fade-leave-active {
  transition: opacity .5s;
}
.fade-enter, .fade-leave-to /* .fade-leave-active below version 2.1.8 */ {
  opacity: 0;
}
</style>
<style lang="less">
.virtual-select-popover {
  margin: 0;
  border-color: #e9eaf0;
  border-radius: 0;
  box-shadow: 0px 0px 10px 0px rgba(168, 174, 187, 0.25);
  border: 1px solid #E4E7ED;
  padding: 0;
  box-sizing: border-box;
}
</style>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值