当前位置: 首页 > news >正文

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

一、背景

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

二、效果图

在这里插入图片描述

三、功能特点

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

四、组件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 = valthis.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 = falsethis.$emit('change', this.getCheckedData())},// 处理全选是否选中或者半选handleCheckAllIndeterminate() {this.checkAll = this.checkedList.length === this.options.lengththis.isIndeterminate = this.checkedList.length > 0 && this.checkedList.length < this.options.length},// 滚动事件handleScroll(e) {this.throttledScroll(e)},handleScrollContent(e) {let scrollTop = e.target.scrollTopthis.offsetY = scrollTop - scrollTop % this.itemHthis.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.scrollBoxif (scrollBox) {scrollBox.scrollTop = 0  // 将 scrollTop 设置为 0,确保每次弹出时滚动条回到顶部this.offsetY = 0}},// 初始化数据initData() {this.keyword = ''this.checkAll = falsethis.isIndeterminate = falsethis.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 = datathis.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. 组件销毁时会自动清理节流和防抖函数

相关文章:

  • 如何给文件增加拓展名
  • 1️⃣5️⃣three.js_GUI辅助调试器
  • Qt进阶开发:鼠标及键盘事件
  • AOSP Android14 Launcher3——RectFSpringAnim窗口动画类详解
  • ComfyUI+Sonic实战,三步实现图片开口说话
  • 单个或批量实现-提取PDF文档中的合同号和姓名并按“合同号_姓名”格式重命名文件。
  • 【文献分享】Model-based evaluation提供了数据和代码
  • day48—双指针-通过删除字母匹配到字典最长单词(LeetCode-524)
  • rk3568main.cc解析
  • 多路转接select服务器
  • Node.js简介(nvm使用)
  • docker-compose搭建kafka
  • Git Flow分支模型
  • L2-2、示范教学与角色扮演:激发模型“模仿力“与“人格“
  • 从单模态到多模态:深度生成模型的演进历程
  • 【武汉理工大学第四届ACM校赛】copy
  • EAL4+与等保2.0:解读中国网络安全双标准
  • 用 Go 优雅地清理 HTML 并抵御 XSS——Bluemonday
  • 嵌入式---超声波测距模块
  • 时间模块 demo
  • 18米巨作绘写伏羲女娲,安徽展石虎最后十年重彩画
  • 浦江观察|3.6亿元消费券,为上海餐饮业带来了什么?
  • 4月语言学联合书单|法庭审判话语的态度表达及人际功能研究
  • 包邮到高原,跨越4083公里送妈妈一张按摩椅
  • 农业未来十年展望:预计粮食单产水平将提高7.8%,达到421千克/亩
  • 成功卫冕!孙颖莎4比0战胜蒯曼,获澳门世界杯女单冠军