Vue2 el-checkbox 虚拟滚动解决多选框全选卡顿问题 - 高性能处理大数据量选项列表

news2025/4/23 6:47:30

一、背景

在我们开发项目中,经常会遇到需要展示大量选项的多选框场景,比如权限配置、数据筛选等。当选项数量达到几百甚至上千条时,传统的渲染方式全选时会非常卡顿,导致性能问题。本篇文章,记录我使用通过虚拟滚动实现大数据量全选卡顿问题~封装成组件啦可以直接用!

二、效果图

在这里插入图片描述

三、功能特点

  • 虚拟滚动:只渲染可视区域的选项,大幅提升性能
  • 搜索过滤:支持选项实时搜索
  • 全选/反选:一键操作所有选项
  • 默认选中:支持初始化选中项
  • 性能优化:使用节流和防抖处理滚动和搜索

四、组件virtual-checkbox.vue完整代码

<template>
  <div class="virtual-checkbox">
    <el-input 
      v-if="showSearch"
      v-model="keyword" 
      prefix-icon="el-input__icon el-icon-search" 
      type="text" 
      placeholder="搜索" 
      @input="seachKey">
    </el-input>
    <el-checkbox v-model="checkAll" :style="`height:${itemH}px`" class="check-all-box" :indeterminate="isIndeterminate" @change="handleCheckAllChange">
      全选
    </el-checkbox>
    <div ref="scrollBox" :style="`width:${viewW}px;height:${viewH}px;line-height:${itemH}px;overflow-y:auto`" @scroll="handleScroll">
      <div :style="`height:${scrollH}px;min-height:${viewH - 22}px`" class="list">
        <el-checkbox-group v-if="searchOptions.length" v-model="checkedList" :style="`transform:translateY(${offsetY}px)`" @change="handleCheckChange">
          <el-checkbox v-for="item in viewOptions" :key="item.value" :label="item.value" :style="`height:${itemH}px`" @change="handleCheckChange">
            {{ item.label }}
          </el-checkbox>
        </el-checkbox-group>
        <div v-else class="empty-text" :style="`height:${viewH - 22}px`">
          暂无数据
        </div>
      </div>
    </div>
  </div>
</template>

<script>
import { throttle, debounce } from 'lodash'
/**
 * @component VirtualCheckbox
 * @description 虚拟滚动多选框组件,用于处理大数据量的选项列表。
 * 实现了以下功能:
 * 1. 虚拟滚动:只渲染可视区域的选项,优化性能
 * 2. 搜索过滤:支持选项搜索
 * 3. 全选/反选:支持一键全选/反选
 * 4. 默认选中:支持默认值回显
 */
export default {
  props: {
    // 所有选项数据数组,格式:[{label: '选项名', value: '选项值'}]
    options: {
      type: Array,
      default: function () { return [] }
    },
    // 默认选中项的值数组
    defaultChecked: {
      type: Array,
      default: function () { return [] }
    },
    // 虚拟列表可视区域高度(像素)
    viewH: {
      type: Number,
      default: function () { return 200 }
    },
    // 虚拟列表可视区域宽度(像素)
    viewW: {
      type: Number,
      default: function () { return 300 }
    },
    // 每个选项的高度(像素)
    itemH: {
      type: Number,
      default: function () { return 20 }
    },
    // 是否显示搜索框
    showSearch: {
      type: Boolean,
      default: true
    }
  },
  data() {
    return {
      checkAll: false,
      isIndeterminate: false,
      searchOptions: [], // 搜索后的数据
      checkedList: [], // 当前选中的数据
      viewOptions: [], // 显示区域的数据
      keyword: '', // 搜索关键字
      offsetY: 0 // 偏移量
    }
  },
  computed: {
    scrollH() {
      return this.searchOptions.length * this.itemH
    },
    // 计算可视区域需要显示的选项数量
    visibleCount() {
      return Math.floor(this.viewH / this.itemH) + 1
    },
    // 计算当前显示区域的起始索引
    startIndex() {
      return Math.floor(this.offsetY / this.itemH)
    }
  },
  watch: {
    // 监听默认勾选变化 渲染勾选
    defaultChecked: {
      handler(val) {
        this.checkedList = val
        this.handleCheckAllIndeterminate()
      },
      deep: true,
      immediate: true
    }
  },
  beforeDestroy() {
    // 清理防抖和节流函数
    if (this.throttledScroll) {
      this.throttledScroll.cancel()
    }
    if (this.debouncedSearch) {
      this.debouncedSearch.cancel()
    }
  },
  created() {
    this.initData()
    // 创建节流函数
    this.throttledScroll = throttle(this.handleScrollContent, 10)
    // 创建防抖函数
    this.debouncedSearch = debounce(this.handleSearch, 300)
  },
  methods: {
    /**
     * 处理单个选项的选中状态变化
     * @emits change - 触发选中数据变化事件
     */
    handleCheckChange() {
      this.handleCheckAllIndeterminate()
      this.$emit('change', this.getCheckedData())
    },

    /**
     * 处理全选/取消全选
     * @param {Boolean} val - 是否全选
     * @emits change - 触发选中数据变化事件
     */
    handleCheckAllChange(val) {
      this.checkedList = val ? this.options.map(item => item.value) : []
      this.isIndeterminate = false
      this.$emit('change', this.getCheckedData())
    },
    // 处理全选是否选中或者半选
    handleCheckAllIndeterminate() {
      this.checkAll = this.checkedList.length === this.options.length
      this.isIndeterminate = this.checkedList.length > 0 && this.checkedList.length < this.options.length
    },
    // 滚动事件
    handleScroll(e) {
      this.throttledScroll(e)
    },

    handleScrollContent(e) {
      let scrollTop = e.target.scrollTop
      this.offsetY = scrollTop - scrollTop % this.itemH
      this.viewOptions = this.searchOptions.slice(
        this.startIndex,
        this.startIndex + this.visibleCount
      )
    },
    // 搜索
    seachKey() {
      this.debouncedSearch()
    },
    // 搜索具体实现
    /**
     * 搜索过滤
     * @description 支持对选项label的模糊搜索,大小写不敏感
     */
    handleSearch() {
      if (this.keyword) {
        this.searchOptions = this.options.filter(item =>
          String(item.label).toLowerCase().includes(this.keyword.toLowerCase())
        )
      } else {
        this.searchOptions = JSON.parse(JSON.stringify(this.options))
      }
      this.viewOptions = this.searchOptions.slice(0, Math.floor(this.viewH / this.itemH) + 1)
      this.initScroll()
    },
    // 重置滚动
    initScroll() {
      const scrollBox = this.$refs.scrollBox
      if (scrollBox) {
        scrollBox.scrollTop = 0  // 将 scrollTop 设置为 0,确保每次弹出时滚动条回到顶部
        this.offsetY = 0
      }
    },
    // 初始化数据
    initData() {
      this.keyword = ''
      this.checkAll = false
      this.isIndeterminate = false
      this.checkedList = [...this.defaultChecked]
      this.searchOptions = this.options.length ? JSON.parse(JSON.stringify(this.options)) : []
      this.viewOptions = this.searchOptions.slice(0, Math.floor(this.viewH / this.itemH) + 1)
      this.initScroll()
      this.handleCheckAllIndeterminate()
      this.$emit('change', this.getCheckedData())
    },

    // 重置所有状态
    reset() {
      this.initData()
    },

    /**
     * 获取当前选中的数据
     * @returns {Object} 包含选中项的值数组和完整数据数组
     * @returns {Array} checkedValues - 选中项的value数组
     * @returns {Array} checkedItems - 选中项的完整数据数组
     */
    getCheckedData() {
      return {
        // 选中项的value数组
        checkedValues: this.checkedList,
        // 选中项的完整数据数组
        checkedItems: this.options.filter(item => this.checkedList.includes(item.value))
      }
    }
  }
}
</script>

<style lang="scss" scoped>
  ::v-deep .el-checkbox-group {
    display: flex;
    flex-direction: column;
    .el-checkbox {
      display: block;
    }
  }
  .check-all-box {
    margin-top: 10px;
  }
  .empty-text {
    color: #ccc;
    font-size: 12px;
    text-align: center;
    display: flex;
    justify-content: center;
    align-items: center;
  }
</style>

五、使用示例

<template>
  <div class="check-box">
    <div class="title">
      全选案例
    </div>
    <VirtualCheckbox :options="options" :default-checked="defaultCheckList" :view-h="500" :item-h="30" @change="change"></VirtualCheckbox>
  </div>
</template>

<script>
import VirtualCheckbox from './virtual-checkbox.vue'
export default {
  components: { VirtualCheckbox },
  data() {
    return {
      defaultCheckList: [], // 默认选中项
      checkList: [], // 当前选中项
      options: [] // 所有选项
    }
  },
  created() {
    this.getOptions()
  },
  methods: {
    getOptions() {
      const data = []
      for (let i = 1; i < 1000; i++) {
        data.push({
          value: i,
          label: '选项' + i
        })
      }
      this.options = data
      this.defaultCheckList = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
    },
    change(val) {
      this.checkList = val.checkedValues // 当前选中的id集合
    }
  }
}
</script>

<style lang="scss" scoped>
  .check-box {
    border: 1px solid red;
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    .title {
      font-size: 30px;
      font-weight: bold;
      margin-bottom: 10px;
    }
  }
</style>

六、 注意事项

  1. 项目记得下载lodash,组件使用了lodash的防抖节流
  2. options 数据格式必须符合 {label: string, value: string|number} 的格式
  3. itemH 需要与实际选项高度一致,否则可能导致滚动计算错误
  4. 组件销毁时会自动清理节流和防抖函数

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2340578.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

KUKA机器人KR 3 D1200 HM介绍

KUKA KR 3 D1200 HM是一款小型机器人&#xff0c;型号中HM代表“Hygienic Machine&#xff08;卫生机械&#xff09;用于主副食品行业”&#xff0c;也是一款并联机器人。用于执行高速、高精度的抓取任务。这款机器人采用食品级不锈钢设计&#xff0c;额定负载为3公斤&#xff…

【MATLAB第117期】#源码分享 | 基于MATLAB的SSM状态空间模型多元时间序列预测方法(多输入单输出)

【MATLAB第117期】#源码分享 | 基于MATLAB的SSM状态空间模型多元时间序列预测方法&#xff08;多输入单输出&#xff09; 引言 本文使用状态空间模型实现失业率递归预测&#xff0c;状态空间模型&#xff08;State Space Model, SSM&#xff09;是一种用于描述动态系统行为的…

【Linux】线程ID、线程管理、与线程互斥

&#x1f4da; 博主的专栏 &#x1f427; Linux | &#x1f5a5;️ C | &#x1f4ca; 数据结构 | &#x1f4a1;C 算法 | &#x1f310; C 语言 上篇文章&#xff1a; 【Linux】线程&#xff1a;从原理到实战&#xff0c;全面掌握多线程编程&#xff01;-CSDN博客 下…

【锂电池SOH估计】RF随机森林锂电池健康状态估计,锂电池SOH估计(Matlab完整源码和数据)

目录 效果一览程序获取程序内容代码分享研究内容基于随机森林(RF)的锂电池健康状态(SOH)估计算法研究摘要1. 引言2. 锂电池SOH评估框架3. 实验与结果分析4. 未来研究方向6. 结论效果一览 程序获取 获取方式一:文章顶部资源处直接下载:【锂电池SOH估计】RF随机森林锂电池…

【Pytorch 中的扩散模型】去噪扩散概率模型(DDPM)的实现

介绍 广义上讲&#xff0c;扩散模型是一种生成式深度学习模型&#xff0c;它通过学习到的去噪过程来创建数据。扩散模型有很多变体&#xff0c;其中最流行的通常是文本条件模型&#xff0c;它可以根据提示生成特定的图像。一些扩散模型&#xff08;例如 Control-Net&#xff0…

121.在 Vue3 中使用 OpenLayers 实现去掉鼠标右键默认菜单并显示 Feature 信息

🎯 实现效果 👇 本文最终实现的效果如下: ✅ 地图初始化时绘制一个多边形; ✅ 鼠标 右键点击地图任意位置; ✅ 若命中 Feature,则弹出该图形的详细信息; ✅ 移除浏览器默认的右键菜单,保留地图交互的完整控制。 💡 整个功能基于 Vue3 + OpenLayers 完成,采用 Com…

进阶篇 第 6 篇:时间序列遇见机器学习与深度学习

进阶篇 第 6 篇&#xff1a;时间序列遇见机器学习与深度学习 (图片来源: Tara Winstead on Pexels) 在上一篇中&#xff0c;我们探讨了如何通过精心的特征工程&#xff0c;将时间序列预测问题转化为机器学习可以处理的监督学习任务。我们学习了如何创建滞后特征、滚动统计特征…

【音视频】音频解码实战

音频解码过程 ⾳频解码过程如下图所示&#xff1a; FFmpeg流程 关键函数 关键函数说明&#xff1a; avcodec_find_decoder&#xff1a;根据指定的AVCodecID查找注册的解码器。av_parser_init&#xff1a;初始化AVCodecParserContext。avcodec_alloc_context3&#xff1a;为…

DOCA介绍

本文分为两个部分&#xff1a; DOCA及BlueField介绍如何运行DOCA应用&#xff0c;这里以DNS_Filter为例子做大致介绍。 DOCA及BlueField介绍&#xff1a; 现代企业数据中心是软件定义的、完全可编程的基础设施&#xff0c;旨在服务于跨云、核心和边缘环境的高度分布式应用工作…

# 利用迁移学习优化食物分类模型:基于ResNet18的实践

利用迁移学习优化食物分类模型&#xff1a;基于ResNet18的实践 在深度学习的众多应用中&#xff0c;图像分类一直是一个热门且具有挑战性的领域。随着研究的深入&#xff0c;我们发现利用预训练模型进行迁移学习是一种非常有效的策略&#xff0c;可以显著提高模型的性能&#…

洗车小程序系统前端uniapp 后台thinkphp

洗车小程序系统 前端uniapp 后台thinkphp 支持多门店 分销 在线预约 套餐卡等

HCIP(综合实验2)

1.实验拓补图 2.实验要求 1.根据提供材料划分VLAN以及IP地址&#xff0c;PC1/PC2属于生产一部员工划分VLAN10,PC3属于生产二部划分VLAN20 2.HJ-1HJ-2交换机需要配置链路聚合以保证业务数据访问的高带宽需求 3.VLAN的放通遵循最小VLAN透传原则 4.配置MSTP生成树解决二层环路问题…

Linux mmp文件映射补充(自用)

addr一般为NULL由OS指明&#xff0c;length所需长度&#xff08;4kb对齐&#xff09;&#xff0c;prot&#xff08;权限&#xff0c;一般O_RDWR以读写&#xff09;&#xff0c; flag&#xff08;MAP_SHARED(不刷新到磁盘上&#xff0c;此进程独有)和MAP_PRIVATE&#xff08;刷新…

单元测试学习笔记(一)

自动化测试 通过测试工具/编程模拟手动测试步骤&#xff0c;全自动半自动执行测试用例&#xff0c;对比预期输出和实际输出&#xff0c;记录并统计测试结果&#xff0c;减少重复的工作量。 单元测试 针对最小的单元测试&#xff0c;Java中就是一个一个的方法就是一个一个的单…

【深度学习新浪潮】新视角生成的研究进展调研报告(2025年4月)

新视角生成(Novel View Synthesis)是计算机视觉与图形学领域的核心技术,旨在从单张或稀疏图像中生成任意视角的高保真图像,突破传统多视角数据的限制,实现对三维场景的自由探索。作为计算机视觉与图形学的交叉领域,近新视角生成年来在算法创新、应用落地和工具生态上均取…

OpenHarmony OS 5.0与Android 13显示框架对比

1. 架构概述 1.1 OpenHarmony OS 5.0架构 OpenHarmony OS 5.0采用分层架构设计&#xff0c;图形显示系统从底层到顶层包括&#xff1a; 应用层&#xff1a;ArkUI应用和第三方应用框架层&#xff1a;ArkUI框架、窗口管理API系统服务层&#xff1a;图形合成服务、窗口管理服务…

[Java] 泛型

目录 1、初识泛型 1.1、泛型类的使用 1.2、泛型如何编译的 2、泛型的上界 3、通配符 4、通配符上界 5、通配符下界 1、初识泛型 泛型&#xff1a;就是将类型进行了传递。从代码上讲&#xff0c;就是对类型实现了参数化。 泛型的主要目的&#xff1a;就是指定当前的容器…

Spark–steaming

实验项目: 找出所有有效数据&#xff0c;要求电话号码为11位&#xff0c;但只要列中没有空值就算有效数据。 按地址分类&#xff0c;输出条数最多的前20个地址及其数据。 代码讲解&#xff1a; 导包和声明对象&#xff0c;设置Spark配置对象和SparkContext对象。 使用Spark S…

深度学习训练中的显存溢出问题分析与优化:以UNet图像去噪为例

最近在训练一个基于 Tiny-UNet 的图像去噪模型时&#xff0c;我遇到了经典但棘手的错误&#xff1a; RuntimeError: CUDA out of memory。本文记录了我如何从复现、分析&#xff0c;到逐步优化并成功解决该问题的全过程&#xff0c;希望对深度学习开发者有所借鉴。 训练数据&am…

如何修复WordPress中“您所关注的链接已过期”的错误

几乎每个管理WordPress网站的人都可能遇到过“您关注的链接已过期”的错误&#xff0c;尤其是在上传插件或者主题的时候。本文将详细解释该错误出现的原因以及如何修复&#xff0c;帮助您更好地管理WordPress网站。 为什么会出现“您关注的链接已过期”的错误 为了防止资源被滥…