记录一下vue2项目优化,虚拟列表vue-virtual-scroll-list处理10万条数据

文章目录

      • 封装BrandPickerVirtual.vue组件
      • 页面使用
      • 组件属性

在这里插入图片描述

select下拉接口一次性返回10万条数据,页面卡死,如何优化??这里使用 分页 + 虚拟列表(vue-virtual-scroll-list),去模拟一个下拉的内容显示区域。支持单选 + 多选 + 模糊查询 + 滚动触底自动分页请求


粗略实现,满足需求即可哈哈哈哈哈哈哈:
单选:
在这里插入图片描述


多选:
在这里插入图片描述


封装BrandPickerVirtual.vue组件

<template><div class="brand-picker-virtual"><el-popoverv-model="visible"placement="bottom-start"trigger="click"popper-class="brand-picker-popper":append-to-body="false":width="300"><div class="brand-picker-popover"><div class="search-box"><el-inputv-model="searchKeyword"placeholder="搜索品牌"prefix-icon="el-icon-search"clearable /></div><div class="brand-list" ref="brandList"><virtual-listref="virtualList"class="scroller":data-key="'brand_id'":data-sources="filteredBrands":data-component="itemComponent":estimate-size="40":keeps="20":item-class="'brand-item'":extra-props="{multiple,isSelected: isSelected,handleSelect: handleSelect,disabled}":buffer="10":bottom-threshold="30"@tobottom="handleScrollToBottom"/><div v-if="loading" class="loading-more"><i class="el-icon-loading"></i> 加载中...</div><div ref="observer" class="observer-target"></div></div><div v-if="multiple" class="footer"><el-button size="small" @click="handleClear">清空</el-button><el-button type="primary" size="small" @click="handleConfirm">确定</el-button></div></div><div slot="reference" class="el-input el-input--suffix select-trigger":class="{ 'is-focus': visible }"><div class="el-input__inner select-inner"><div class="select-tags" v-if="multiple && selectedBrands.length"><el-tagv-for="brand in selectedBrands":key="brand.brand_id"closable:disable-transitions="false"@close="handleRemoveTag(brand)"size="small"class="brand-tag">{{ brand.name }}</el-tag></div><div v-else-if="!multiple && selectedBrands.length" class="selected-single"><span class="selected-label">{{ selectedBrands[0].name }}</span></div><inputtype="text"readonly:placeholder="getPlaceholder"class="select-input"><i v-if="selectedBrands.length" class="el-icon-circle-close clear-icon" @click.stop="handleClear"></i></div></div></el-popover></div>
</template><script>
import VirtualList from 'vue-virtual-scroll-list'
import request from '@/utils/request'const BrandItem = {name: 'BrandItem',props: {source: {type: Object,required: true},multiple: Boolean,isSelected: Function,handleSelect: Function,disabled: Boolean},render(h) {const isItemSelected = this.isSelected(this.source)return h('div', {class: {'item-content': true,'is-selected': isItemSelected && !this.multiple},on: {click: (e) => {if (!this.disabled) {this.handleSelect(this.source)}}}}, [this.multiple && h('el-checkbox', {props: {value: isItemSelected,disabled: this.disabled}}),h('span', { class: 'brand-name' }, this.source.name)])}
}export default {name: 'BrandPickerVirtual',components: {VirtualList},props: {multiple: {type: Boolean,default: false},defaultBrandId: {type: [Array, String, Number],default: () => []},api: {type: String,default: 'admin/goods/brands'},disabled: {type: Boolean,default: false}},data() {return {visible: false, // 弹窗是否可见searchKeyword: '', // 搜索关键字brandList: [], // 品牌列表数据selectedBrands: [], // 已选中的品牌列表tempSelectedBrands: [], // 多选时的临时选中列表loading: false, // 是否正在加载数据itemComponent: BrandItem, // 品牌项组件pageNo: 1, // 当前页码pageSize: 20, // 每页数量hasMore: true, // 是否还有更多数据searchTimer: null, // 搜索防抖定时器searchLoading: false, // 搜索加载状态lastScrollTop: 0, // 上次滚动位置isFirstPageLoaded: false, // 是否已加载第一页数据observer: null // 交叉观察器实例}},computed: {/*** 根据搜索关键字过滤品牌列表* @returns {Array} 过滤后的品牌列表*/filteredBrands() {if (!this.searchKeyword) return this.brandListconst keyword = this.searchKeyword.toLowerCase()return this.brandList.filter(item =>item.name.toLowerCase().includes(keyword))},/*** 选中品牌的显示文本* @returns {string} 显示文本*/selectedText() {if (this.multiple) {return this.selectedBrands.length? `已选择 ${this.selectedBrands.length} 个品牌`: ''}return (this.selectedBrands[0] && this.selectedBrands[0].name) || ''},/*** 获取占位符文本*/getPlaceholder() {if (this.multiple) {return this.selectedBrands.length ? '' : '请选择品牌(可多选)'}return this.selectedBrands.length ? '' : '请选择品牌'}},watch: {/*** 监听默认品牌ID变化,同步选中状态*/defaultBrandId: {immediate: true,handler(val) {if (!val || !this.brandList.length) returnif (this.multiple) {this.selectedBrands = this.brandList.filter(item =>val.includes(item.brand_id))} else {const brand = this.brandList.find(item =>item.brand_id === val)this.selectedBrands = brand ? [brand] : []}this.tempSelectedBrands = [...this.selectedBrands]}},/*** 监听弹窗显示状态,首次打开时加载数据*/visible(val) {if (val) {if (this.multiple) {this.tempSelectedBrands = [...this.selectedBrands]}this.resetData()this.getBrandList()// 确保虚拟列表在显示时重新初始化this.$nextTick(() => {if (this.$refs.virtualList) {this.$refs.virtualList.reset()}})}},/*** 监听搜索关键字变化,带防抖的搜索处理*/searchKeyword(val) {if (this.searchTimer) {clearTimeout(this.searchTimer)}this.searchTimer = setTimeout(() => {this.resetData()this.getBrandList()}, 300)}},beforeDestroy() {if (this.observer) {this.observer.disconnect()}},methods: {/*** 初始化交叉观察器,用于监听滚动到底部*/initObserver() {this.observer = new IntersectionObserver((entries) => {const target = entries[0]if (target.isIntersecting && !this.loading && this.hasMore) {this.getBrandList(true)}},{root: this.$el.querySelector('.scroller'),threshold: 0.1})if (this.$refs.observer) {this.observer.observe(this.$refs.observer)}},/*** 获取品牌列表数据* @param {boolean} isLoadMore - 是否是加载更多*/async getBrandList(isLoadMore = false) {if (this.loading || (!isLoadMore && this.searchLoading)) returnif (isLoadMore && !this.hasMore) returnconst loading = isLoadMore ? 'loading' : 'searchLoading'this[loading] = truetry {if (isLoadMore) {this.pageNo++} else {this.pageNo = 1}const response = await request({url: this.api,method: 'get',params: {page_no: this.pageNo,page_size: this.pageSize,keyword: this.searchKeyword},loading: false})const { data, data_total } = response        if (!isLoadMore) {this.brandList = datathis.isFirstPageLoaded = true} else {this.brandList = [...this.brandList, ...data]}this.hasMore = this.brandList.length < data_totalif (this.defaultBrandId && !isLoadMore) {this.initializeSelection()}} catch (error) {console.error('获取品牌列表失败:', error)} finally {this[loading] = false}},/*** 滚动到底部的处理函数*/handleScrollToBottom() {if (!this.loading && this.hasMore) {this.getBrandList(true)}},/*** 初始化选中状态*/initializeSelection() {if (this.multiple) {this.selectedBrands = this.brandList.filter(item =>this.defaultBrandId.includes(item.brand_id))} else {const brand = this.brandList.find(item =>item.brand_id === this.defaultBrandId)this.selectedBrands = brand ? [brand] : []}this.tempSelectedBrands = [...this.selectedBrands]},/*** 判断品牌是否被选中* @param {Object} item - 品牌项* @returns {boolean} 是否选中*/isSelected(item) {return this.multiple? this.tempSelectedBrands.some(brand => brand.brand_id === item.brand_id): this.selectedBrands.some(brand => brand.brand_id === item.brand_id)},/*** 处理品牌选择* @param {Object} item - 选中的品牌项*/handleSelect(item) {if (this.multiple) {const index = this.tempSelectedBrands.findIndex(brand => brand.brand_id === item.brand_id)if (index > -1) {this.tempSelectedBrands.splice(index, 1)} else {this.tempSelectedBrands.push(item)}} else {this.selectedBrands = [item]this.visible = falsethis.emitChange()}},/*** 清空选中的品牌*/handleClear(e) {// 阻止事件冒泡,防止触发下拉框if (e) {e.stopPropagation()}this.selectedBrands = []this.tempSelectedBrands = []this.emitChange()},/*** 确认多选结果*/handleConfirm() {this.selectedBrands = [...this.tempSelectedBrands]this.visible = falsethis.emitChange()},/*** 触发选中值变化事件*/emitChange() {const value = this.multiple? this.selectedBrands.map(item => item.brand_id): (this.selectedBrands[0] && this.selectedBrands[0].brand_id) || nullthis.$emit('changed', value)},handleRemoveTag(brand) {const index = this.selectedBrands.findIndex(item => item.brand_id === brand.brand_id)if (index > -1) {this.selectedBrands.splice(index, 1)}this.tempSelectedBrands = [...this.selectedBrands]this.emitChange()},/*** 重置列表相关数据*/resetData() {this.brandList = []this.pageNo = 1this.hasMore = truethis.loading = falsethis.searchLoading = false}}
}
</script><style lang="scss">
.brand-picker-popper {max-height: calc(100vh - 100px);overflow: visible !important;left: 0 !important;top: 26px !important;.el-popover__title {margin: 0;padding: 0;}
}
</style><style lang="scss" scoped>
.brand-picker-virtual {display: inline-block;width: 100%;position: relative;.select-trigger {width: 100%;&.is-focus .el-input__inner {border-color: #409EFF;}}.select-inner {padding: 3px 8px;min-height: 32px;height: auto;cursor: pointer;position: relative;background-color: #fff;border: 1px solid #dcdfe6;border-radius: 4px;display: flex;align-items: center;flex-wrap: wrap;}.select-tags {display: flex;flex-wrap: wrap;gap: 4px;flex: 1;min-height: 24px;padding: 2px 0;.brand-tag {max-width: 100%;margin: 2px 0;&:last-child {margin-right: 4px;}}}.select-input {width: 0;min-width: 60px;margin: 2px 0;padding: 0;background: none;border: none;outline: none;height: 24px;line-height: 24px;font-size: 14px;color: #606266;flex: 1;&::placeholder {color: #c0c4cc;}}.clear-icon {position: absolute;right: 8px;color: #c0c4cc;font-size: 14px;cursor: pointer;transition: color .2s;&:hover {color: #909399;}}.selected-single {display: flex;align-items: center;flex: 1;padding-right: 24px;.selected-label {flex: 1;font-size: 14px;color: #606266;overflow: hidden;text-overflow: ellipsis;white-space: nowrap;}}.el-input__suffix,.el-icon-arrow-down {display: none;}.brand-picker-popover {margin-top: 4px !important;.search-box {padding: 0 0 12px;.el-input {font-size: 14px;}}.brand-list {position: relative;height: 320px;border: 1px solid #EBEEF5;border-radius: 4px;overflow: hidden;.scroller {height: 100%;overflow-y: auto !important;overflow-x: hidden;padding: 4px 0;/deep/ .virtual-list-container {position: relative !important;}/deep/ .virtual-list-phantom {position: relative !important;}/deep/ .brand-item {.item-content {padding-left: 8px;height: 40px;line-height: 40px;cursor: pointer;transition: all 0.3s;box-sizing: border-box;position: relative;font-size: 14px;color: #606266;border-bottom: 1px solid #f0f0f0;display: flex;align-items: center;user-select: none;.el-checkbox {margin-right: 8px;}.brand-name {flex: 1;overflow: hidden;text-overflow: ellipsis;white-space: nowrap;}&:hover {background-color: #F5F7FA;}&.is-selected {background-color: #F5F7FA;color: #409EFF;font-weight: 500;&::after {content: '';position: absolute;right: 15px;width: 14px;height: 14px;background: url(data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgMTAyNCAxMDI0IiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxwYXRoIGQ9Ik00MDYuNjU2IDcwNi45NDRsLTE2MC0xNjBjLTEyLjQ4LTEyLjQ4LTMyLjc2OC0xMi40OC00NS4yNDggMHMtMTIuNDggMzIuNzY4IDAgNDUuMjQ4bDE4Mi42MjQgMTgyLjYyNGMxMi40OCAxMi40OCAzMi43NjggMTIuNDggNDUuMjQ4IDBsNDAwLTQwMGMxMi40OC0xMi40OCAxMi40OC0zMi43NjggMC00NS4yNDhzLTMyLjc2OC0xMi40OC00NS4yNDggMEw0MDYuNjU2IDcwNi45NDR6IiBmaWxsPSIjNDA5RUZGIi8+PC9zdmc+) no-repeat center center;background-size: contain;}}}}}.loading-more {position: absolute;bottom: 0;left: 0;right: 0;padding: 8px;text-align: center;background: rgba(255, 255, 255, 0.95);color: #909399;font-size: 13px;z-index: 1;border-top: 1px solid #f0f0f0;}.observer-target {height: 2px;width: 100%;position: absolute;bottom: 0;left: 0;}}.footer {margin-top: 12px;text-align: right;padding: 0 2px;}}.selected-label {flex: 1;font-size: 14px;color: #606266;overflow: hidden;text-overflow: ellipsis;white-space: nowrap;}.selected-single {display: flex;align-items: center;flex: 1;padding: 0 4px;.selected-label {flex: 1;font-size: 14px;height: 24px;line-height: 24px;color: #606266;overflow: hidden;text-overflow: ellipsis;white-space: nowrap;}.el-icon-circle-close {margin-left: 8px;color: #c0c4cc;font-size: 14px;cursor: pointer;transition: color .2s;&:hover {color: #909399;}}}
}
</style> 

页面使用

<template><!-- 单选模式 --><brand-picker-virtual:default-brand-id="singleBrandId"@changed="handleBrandChange"/><!-- 多选模式 --><brand-picker-virtualmultiple:default-brand-id="multipleBrandIds"@changed="handleMultipleBrandChange"/>
</template><script>
// 注册组件别忘了,我这里省略了,我是个全局注册的
export default {data() {return {singleBrandId: null,  // 单选模式:存储单个品牌IDmultipleBrandIds: []  // 多选模式:存储品牌ID数组}},methods: {// 单选回调handleBrandChange(brandId) {this.singleBrandId = brandId},// 多选回调handleMultipleBrandChange(brandIds) {this.multipleBrandIds = brandIds}}
}
</script>

组件属性


props: {// 是否多选模式multiple: {type: Boolean,default: false},// 默认选中的品牌ID(单选时为number/string,多选时为array)defaultBrandId: {type: [Array, String, Number],default: () => []},// 自定义接口地址api: {type: String,default: 'admin/goods/brands'},// 是否禁用disabled: {type: Boolean,default: false}
}

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【芯片封测学习专栏 -- D2D 和 C2C 之间的区别】

请阅读【嵌入式开发学习必备专栏 Cache | MMU | AMBA BUS | CoreSight | Trace32 | CoreLink | ARM GCC | CSH】 文章目录 OverviewD2D&#xff08;Die-to-Die&#xff09;互联D2D 定义D2D 特点D2D 使用场景 C2C&#xff08;Chip-to-Chip&#xff09;互联C2C 定义C2C 特点C2C 使…

使用网页版Jupyter Notebook和VScode打开.ipynb文件

目录 正文 1、网页版Jupyter Notebook查看 2、VScode查看 因为总是忘记查看文件的网址&#xff0c;收藏了但分类众多每次都找不到……当个记录吧&#xff08;/捂脸哭&#xff09;&#xff01; 正文 此处以gitub中的某个仓库为例&#xff1a; https://github.com/INM-6/mu…

Qt监控系统远程网络登录/请求设备列表/服务器查看实时流/回放视频/验证码请求

一、前言说明 这几个功能是近期定制的功能&#xff0c;也非常具有代表性&#xff0c;核心就是之前登录和设备信息都是在本地&#xff0c;存放在数据库中&#xff0c;数据库可以是本地或者远程的&#xff0c;现在需要改成通过网络API请求的方式&#xff0c;现在很多的服务器很强…

企业级PHP异步RabbitMQ协程版客户端 2.0 正式发布

概述 workerman/rabbitmq 是一个异步RabbitMQ客户端&#xff0c;使用AMQP协议。 RabbitMQ是一个基于AMQP&#xff08;高级消息队列协议&#xff09;实现的开源消息组件&#xff0c;它主要用于在分布式系统中存储和转发消息。RabbitMQ由高性能、高可用以及高扩展性出名的Erlan…

百度视频搜索架构演进

导读 随着信息技术的迅猛发展&#xff0c;搜索引擎作为人们获取信息的主要途径&#xff0c;其背后的技术架构也在不断演进。本文详细阐述了近年来视频搜索排序框架的重大变革&#xff0c;特别是在大模型技术需求驱动下&#xff0c;如何从传统的多阶段级联框架逐步演变为更加高…

31_搭建Redis分片集群

Redis的主从复制模式和哨兵模式可以解决高可用、高并发读的问题。但是依然有两个问题没有解决:海量数据存储问题、高并发写的问题。由于数据量过大,单个master复制集难以承担,因此需要对多个复制集进行集群,形成水平扩展每个复制集只负责存储整个数据集的一部分,这就是Red…

如何用 ESP32-CAM 做一个实时视频流服务器

文章目录 ESP32-CAM 概述ESP32-S 处理器内存Camera 模块MicroSD 卡槽天线板载 LED 和闪光灯其他数据手册和原理图ESP32-CAM 功耗 ESP32-CAM 引脚参考引脚排列GPIO 引脚哪些 GPIO 可以安全使用&#xff1f;GPIO 0 引脚MicroSD 卡引脚 ESP32-CAM 的烧录方式使用 ESP32-CAM-MB 编程…

LS1046+XILINX XDMA PCIE调通

欢迎点赞收藏&#xff0c;欢迎私下讨论技术&#xff0c;分享技术 硬件平台 &#xff1a;NXP LS1046 XILINX FPGA 软件平台&#xff1a;LINUX 4.19.68 buildroot LS1046 PEX3 接 XILINX FPGA&#xff0c;linux使用designware的PCI主控制器。下载XILINX DMA驱动&#xff0c;解…

golang常用标准库

输入与输出-fmt包时间与日期-time包命令行参数解析-flag包日志-log包IO操作-os包IO操作-bufio包与ioutil包strconv包模板-template包http包contextjson/xmlreflect反射官方标准库 输入与输出-fmt包 输入与输出 常用输出函数 Print、Printf、Println&#xff1a;直接输出内容 Sp…

【物联网原理与运用】知识点总结(上)

目录 名词解释汇总 第一章 物联网概述 1.1物联网的基本概念及演进 1.2 物联网的内涵 1.3 物联网的特性——泛在性 1.4 物联网的基本特征与属性&#xff08;五大功能域&#xff09; 1.5 物联网的体系结构 1.6 物联网的关键技术 1.7 物联网的应用领域 第二章 感知与识别技术 2.1 …

macOS 安装tomcat9

macOS 安装tomcat9 URL&#xff1a;https://tomcat.apache.org/download-90.cgi 解压之后放到指定目录 /Users/lanren/install/tomcat-9 自己取个名字就行 给权限&#xff1a; ① 先进行权限修改&#xff1a;终端输入sudo chmod 755 /Users/lanren/install/tomcat-9/bin/…

基于STM32的智能家居蓝牙系统(论文+源码)

1总体方案设计 本次基于STM32的智能家居蓝牙系统&#xff0c;其系统总体架构如图2.1所示&#xff0c;采用STM32f103单片机作为控制器&#xff0c;通过DHT11传感器实现温湿度检测&#xff0c;MQ-2烟雾传感器实现烟雾检测&#xff0c;光敏电阻实现光照检测&#xff0c;同时将数据…

Ubuntu 磁盘修复

Ubuntu 磁盘修复 在 ubuntu 文件系统变成只读模式&#xff0c;该处理呢&#xff1f; 文件系统内部的错误&#xff0c;如索引错误、元数据损坏等&#xff0c;也可能导致系统进入只读状态。磁盘坏道或硬件故障也可能引发文件系统只读的问题。/etc/fstab配置错误&#xff0c;可能…

Android系统定制APP开发_如何对应用进行系统签名

前言 当项目开发需要使用系统级别权限或frame层某些api时&#xff0c;普通应用是无法使用的&#xff0c;需要在AndroidManifest中配置sharedUserId&#xff1a; AndroidManifest.xml中的android:sharedUserId“android.uid.system”&#xff0c;代表的意思是和系统相同的uid&a…

【MySQL】DATEDIFF()函数使用

DATEDIFF 函数用于计算两个日期之间的差值&#xff0c;以天为单位 DATEDIFF 函数返回一个整数&#xff0c;表示 date1 和 date2 之间的天数。如果 date1 在 date2 之前&#xff0c;结果为负数&#xff1b;如果在 date2 之后&#xff0c;结果为正数&#xff1b;如果相等&#xf…

yum系统报错:SyntaxError: multiple exception types must be parenthesized

执行yum相关步骤报错如下&#xff1a; File "/usr/bin/yum", line 30except KeyboardInterrupt, e:^^^^^^^^^^^^^^^^^^^^ SyntaxError: multiple exception types must be parenthesized原因&#xff1a;python解释器版本错误&#xff0c;yum运行版本为python 2.7&am…

MapReduce完整工作流程

1、mapreduce工作流程(终极版) 0. 任务提交 1. 拆-split逻辑切片--任务切分。 FileInputFormat--split切片计算工具 FileSplit--单个计算任务的数据范围。 2. 获得split信息和个数。 MapTask阶段 1. 读取split范围内的数据。k(偏移量)-v(行数据) 关键API&#xff1a;TextI…

毕业项目推荐:基于yolov8/yolov5/yolo11的动物检测识别系统(python+卷积神经网络)

文章目录 概要一、整体资源介绍技术要点功能展示&#xff1a;功能1 支持单张图片识别功能2 支持遍历文件夹识别功能3 支持识别视频文件功能4 支持摄像头识别功能5 支持结果文件导出&#xff08;xls格式&#xff09;功能6 支持切换检测到的目标查看 二、数据集三、算法介绍1. YO…

了解Node.js

Node.js是一个基于V8引擎的JavaScript运行时环境&#xff0c;它允许JavaScript代码在服务器端运行&#xff0c;从而实现后端开发。Node.js的出现&#xff0c;使得前端开发人员可以利用他们已经掌握的JavaScript技能&#xff0c;扩展技能树并成为全栈开发人员。本文将深入浅出地…

MAC上安装Octave

1. 当前最新版Octave是9.3版本&#xff0c;需要把mac os系统升级到14版本&#xff08;本人之前的版本是10版本&#xff09; https://wiki.octave.org/Octave_for_macOS octave的历史版本参考此文档&#xff1a;Octave for macOS (outdated) - Octavehttps://wiki.octave.org/Oc…