el-select下拉菜单虚拟列表化(含搜索功能)

需求简介

vue2+element-ui项目中,当el-select中数据量较大时,会导致页面加载和渲染卡顿。在现在的el-select的基础上使用分页或者虚拟列表的形式去处理大量的下拉菜单,保证页面的正常渲染及el-select的正常回显。

需求分析

主要涉及几个点:

  1. 下拉菜单主体实现虚拟展示,保证渲染效率
  2. 展开和关闭时要保证已选中的选项在虚拟列表内,保证回显的是 label,而不是 value
  3. select 清空时,虚拟列表回归到顶部
  4. 下拉菜单发生改变时,重新计算滚动条长度,并回归到顶部
  5. 多选场景暂不支持,因为无论如何都不可避免更多的js计算逻辑,两个数组去比较在极限场景下是无法避免的,所以我就砍掉了。
  6. 本地搜索时更新组件传入的下拉菜单。

完整代码

使用:

<template><div style="width: 100%;display: flex;align-items: center"><el-selectv-model="deviceValue"filterableclearable:filter-method="filterOption"@visible-change="changeVisible($event)"><virtual-optionsref="virtualRef":virtual-data="secList":select-value="deviceValue"/></el-select></div>
</template><script>
import { getXXX } from '@/api/xxx'
import VirtualOptions from './VirtualOptions.vue'export default {components: { VirtualOptions },props: {defaultValue: {type: String,default: ''},deviceType: {type: String,default: ''}},data() {return {deviceValue: '',secList: [],cloneSelList: [],queryType: 0,selectedObj: {}}},watch: {// 监听父组件传入的数据,更新到本地defaultValue(newVal) {this.deviceValue = newVal},// 监听本地数据的变化,通知父组件更新deviceValue(newVal) {for (const i of this.secList) {if (newVal === i.deviceId) {this.selectedObj = ibreak}}this.emitChange()},queryType() {this.emitChange()}},created() {this.getDeviceList()},methods: {emitChange() {const queryParamData = {};if (this.queryType === 0) {queryParamData['deviceId'] = this.selectedObj['deviceId'];} else if (this.queryType === 1) {queryParamData['sectionId'] = this.selectedObj['sectionId'];queryParamData['sectionName'] = this.selectedObj['sectionName'];}this.$emit('change', queryParamData)},filterOption(val) {if (val) {val = val.toUpperCase();this.secList = this.cloneSelList.filter((item) => {if (item.label.toUpperCase().indexOf(val) >= 0) {return true}})} else {this.secList = this.cloneSelList}},changeVisible(cb) {this.secList = this.cloneSelListthis.$nextTick(() => cb && this.$refs["virtualRef"].resetVirtual()); // 解决打开白屏问题,必须使用 $nextTick 延时处理},getDeviceList() {this.deviceValue = this.defaultValuegetXXX ({ deviceType: this.deviceType }).then(response => {const data = response.datathis.cloneSelList = this.secList = data})}}
}
</script>

VirtualOptions组件

<template><div class="option-wrap" :id="randomId"><!-- 真实dom --><div class="virtual-dom"><!-- 使用虚拟列表渲染el-option组件 --><el-option v-for="item in virtualOptions" :key="item.deviceId" :label="item.label" :value="item.deviceId" /><!-- 增加一个空的,解决第一次展示为空的问题 --><el-option v-if="virtualData && virtualData.length" disabled style="display: none" :value="''" :label="''" /></div></div>
</template><script>
import { getParents } from '@/utils/utils'export default {props: {virtualData: {type: Array,default: () => []},selectValue: {type: [String, Number],default: ''},isDevice: {type: Boolean,default: true}},data() {return {randomId: '', // 随机生成的ID值virtualOptions: [], // 虚拟列表的选项数据leafNumber: 0, // 选项的叶子节点数量optionHeight: 34, // 选项的高度};},watch: {// 监听selectValue的变化,当其发生变化时调用resetVirtual方法selectValue() {this.resetVirtual();},// 监听virtualData的变化virtualData() {this.resetVirtual()this.initWrapHeight()}},mounted() {// 初始化随机ID值,并在下一次DOM更新时调用initVirtual方法this.initId()this.$nextTick(() => this.initVirtual())},methods: {// 初始化虚拟列表容器的高度initWrapHeight() {const $wrap = document.getElementById(this.randomId);$wrap.style.height = this.virtualData.length * this.optionHeight + 100 + 'px'; // 设定虚拟列表的总高度this.initVirtual()},/*** 重置虚拟列表,使用场景有两个* 1- select选中时,保证选中数据正常为label值* 2- 下拉框展开保证下拉菜单回显正常*/resetVirtual() {const $wrap = document.getElementById(this.randomId);const $virtualDom = $wrap.querySelector('.virtual-dom');const $scroll = getParents($wrap, 'el-select-dropdown__wrap');const _scrollHeight = $scroll.offsetHeightlet vIndex = 0;if (this.selectValue !== '') {// 查找选中值在虚拟数据中的索引vIndex = this.virtualData.findIndex((item) => item.deviceId === this.selectValue);}// 计算可视区域内需要显示的选项数量const showNumber = parseInt(_scrollHeight / this.optionHeight) + this.leafNumber;// 更新虚拟列表的选项数据this.virtualOptions = this.virtualData.slice(vIndex, vIndex + showNumber);this.$nextTick(() => {// 更新虚拟列表的位置和滚动条的位置,实现选项的正确显示$virtualDom.style.transform = `translate(0, ${vIndex * this.optionHeight}px)`;$scroll.scrollTop = vIndex * this.optionHeight;});},// 初始化虚拟列表initVirtual() {const $wrap = document.getElementById(this.randomId);const $virtualDom = $wrap.querySelector('.virtual-dom');$wrap.style.height = this.virtualData.length * this.optionHeight + 100 + 'px'; // 设定虚拟列表的总高度const $scroll = getParents($wrap, 'el-select-dropdown__wrap');const scrollFunc = () => {const _scrollTop = $scroll.scrollTop;const showNumber = 8$virtualDom.style.transform = `translate(0, ${_scrollTop}px)`;// 保证选中项超出可视范围后导致回显选择项异常let vIndex = parseInt(_scrollTop / this.optionHeight);// 保证最后一屏显示正常  1 为 leafif (vIndex > this.virtualData.length - showNumber) {vIndex = this.virtualData.length - showNumber + 1;}if (this.selectValue !== '' &&this.virtualOptions.findIndex((item) => item.value === this.selectValue) > -1) {this.virtualOptions = [...this.virtualData.slice(vIndex, vIndex + showNumber),Object.assign({},this.virtualData.find((item) => item.deviceId === this.selectValue),{ hide: true },),];} else {if (this.virtualData.length < 8) {this.virtualOptions = this.virtualData} else {this.virtualOptions = this.virtualData.slice(vIndex, vIndex + showNumber);}}}// 重置virtualData时,清理滚动方法并初始化滚动容器高度this.$nextTick(() => $scroll.addEventListener('scroll', scrollFunc));this.$nextTick(scrollFunc())},// 生成一个随机的id值initId() {this.randomId = 'virtual_' + parseInt(Math.random() * 10 * 1024 * 1024 + '');}}
};
</script>

utils.js


/*** 根据class名称递归查询父节点* @param {HTMLElement} element 当前节点element* @param {string} className 需要查询的class值*/
export function getParents(element, className) {var returnParentElement = nullfunction getpNode(element, className) {// 创建父级节点的类数组const pClassList = element.parentNode.getAttribute('class').split(' ')const pNode = element.parentNodeif (!pClassList || !pClassList.length) {// 如果未找到类名数组,表示父类无类名,则再次递归getpNode(pNode, className)} else if (pClassList && !pClassList.includes(className)) {// 如果父类的类名中没有预期类名,则再次递归getpNode(pNode, className)} else if (pClassList && pClassList.includes(className)) {returnParentElement = pNode}}getpNode(element, className)return returnParentElement
}

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

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

相关文章

DPPE-N3中叠氮基团使得DPPE-N3能够与含有炔基的材料在铜离子的催化下发生点击化学反应,生成稳定的1,2,3-三唑环结构,2252461-33-7

一、基本信息 英文名称&#xff1a;DPPE-N3&#xff0c;DPPE-Azide 中文名称&#xff1a;DPPE-叠氮 CAS号&#xff1a;2252461-33-7 分子式&#xff1a;C43H83N4O9P 分子量&#xff1a;831.13 供应商&#xff1a;陕西新研博美生物科技 结构式&#xff1a; 二、结构特点…

算法学习第一弹——C++基础

早上好啊&#xff0c;大佬们。来看看咱们这回学点啥&#xff0c;在前不久刚出完C语言写的PTA中L1的题目&#xff0c;想必大家都不过瘾&#xff0c;感觉那些题都不过如此&#xff0c;所以&#xff0c;为了我们能更好的去处理更难的题目&#xff0c;小白兔决定奋发图强&#xff0…

[AcWing算法基础课]动态规划之01背包

题目链接&#xff1a;01背包 有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次。第 i 件物品的体积是 vi&#xff0c;价值是 wi。求解将哪些物品装入背包&#xff0c;可使这些物品的总体积不超过背包容量&#xff0c;且总价值最大。输出最大价值。 首先&#xff0c;我们…

【机器学习】机器学习中用到的高等数学知识

机器学习是一个跨学科领域&#xff0c;涉及多种高等数学知识。以下是一些在机器学习中常用的数学概念和技术&#xff1a; 1. 线性代数 (Linear Algebra) 向量和矩阵&#xff1a;用于表示数据集和特征。矩阵运算&#xff1a;加法、乘法和逆矩阵等&#xff0c;用于计算模型参数…

FreeRTOS 24:事件组EventGroup等待、清零、获取操作

等待事件标志位xEventGroupWaitBits() 既然标记了事件的发生&#xff0c;那么我怎么知道他到底有没有发生&#xff0c;这也是需要一个函数来获 取 事 件 是 否 已 经 发 生 &#xff0c; FreeRTOS 提 供 了 一 个 等 待 指 定 事 件 的 函 数 — — xEventGroupWaitBits()&…

世界坐标和Local坐标的区分

TargetPos.position(-TargetPos.forward*-4)TargetPos.up*7 这是相对于TargetPos的位置进行偏移&#xff0c; 动态的与Target的本地坐标改变 new Vector3(TargetPos.position.x, TargetPos.position.y 7, TargetPos.position.z - 5) 这个是直接new了一个世界坐标的Vector3 &…

Ubuntu 22.04 的Python3.11.8 安装

背景 新版本的Python需要更高版本的OpenSSL 依赖。使用操作系统的SSL不然会提示缺少SSL的报错。 部署 ## Openssl部署 wget https://github.com/openssl/openssl/releases/download/openssl-3.4.0/openssl-3.4.0.tar.gz## ./config --prefix/usr/local/openssl make &&…

在Ubuntu下安装RabbitMQ、添加一个新的登录用户并设置密码

在Ubuntu下安装RabbitMQ、添加一个新的登录用户并设置密码 在Ubuntu下安装RabbitMQ可以按照以下步骤进行&#xff1a;步骤 1: 更新系统步骤 2: 安装Erlang步骤 3: 添加RabbitMQ仓库步骤 4: 更新APT索引并安装RabbitMQ步骤 5: 启动RabbitMQ服务步骤 6: 检查RabbitMQ状态步骤 7: …

nacos单机源码解析-服务发现和心跳检测

目录 1 服务发现 1.1 客户端 1.1.1 入口 1.1.2 定时拉取 1.1.3 总结 1.2 服务端 2 心跳检测 2.1 客户端 2.2 服务端 2.2.1 处理心跳请求 2.2.2 开启定时任务进行心跳检测 2.2.3 总结 1 服务发现 服务列表&#xff1a;Nacos 维护一个服务列表&#xff0c;记录所有已注…

在线绘制带community的蛋白质-蛋白质相互作用(PPI)网络图

导读&#xff1a;分子相互作用网络图揭示了细胞内部分子间的复杂相互作用。通过识别网络中密集连接的节点所形成的社区&#xff08;community&#xff09;&#xff0c;可以揭示它们之间以前未知的功能联系。这些社区可能代表了具有共同功能的功能模块&#xff0c;对于理解细胞生…

【商城系统搭建流程】

商城系统的搭建流程可以分为以下几个步骤&#xff1a; 1.需求分析&#xff1a;确定商城系统的功能和特性&#xff0c;例如商品展示、购物车、订单管理、支付等。 2.系统设计&#xff1a;根据需求分析结果设计商城系统的架构&#xff0c;包括前端页面设计和后端数据库设计。 …

qt QTableView详解

1、概述 QTableView 是 Qt 框架中的一个高级视图类&#xff0c;用于以表格形式展示二维数据。它基于 QAbstractItemView&#xff0c;并与模型&#xff08;通常是 QAbstractTableModel 或 QStandardItemModel&#xff09;结合使用&#xff0c;以实现数据的展示和交互。QTableVi…

Orleans集群及Placement设置

服务端界面使用相同的clusterid和serviceid&#xff0c;相同ip地址&#xff0c;不同网关端口号和服务端口号&#xff0c;启动两个silo服务&#xff0c;并使用MySql数据库做Silo间信息同步&#xff0c;实现集群。 silo服务启动代码如下&#xff08;从nuget下载Microsoft.Orleans…

OceanBase 4.3.3 功能解析:列存副本

OceanBase 从4.3.0 版本开始&#xff0c;引入了列式存储的支持。用户可以根据业务的具体需求&#xff0c;选择创建列存表、行存表或是行列混存表。无论选择哪种表类型&#xff0c;在不同的Zone内&#xff0c;租户使用的副本模式都是一致的。详见官网文档&#xff1a; https://w…

【Linux】 IPC 进程间通信(三)(消息队列 信号量)

&#x1f4c3;个人主页&#xff1a;island1314 &#x1f525;个人专栏&#xff1a;Linux—登神长阶 ⛺️ 欢迎关注&#xff1a;&#x1f44d;点赞 &#x1f442;&#x1f3fd;留言 &#x1f60d;收藏 &#x1f49e; &#x1f49e; &#x1f49e; 一、消息队列 &#x1f48c;…

如何管理PHP API版本

管理PHP API版本是确保API稳定性和兼容性的关键步骤。以下是一些有效的PHP API版本管理方法&#xff1a; 一、使用命名空间和类库 在PHP中&#xff0c;可以通过命名空间和类库来实现API版本的管理。通过为不同版本的API创建不同的命名空间&#xff0c;可以将它们隔离开来&…

Docker:镜像构建 DockerFile

Docker&#xff1a;镜像构建 DockerFile 镜像构建docker build DockerfileFROMCOPYENVWORKDIRADDRUNCMDENTRYPOINTUSERARGVOLUME 镜像构建 在Docker官方提供的镜像中&#xff0c;大部分都是基础镜像&#xff0c;他们只提供某个简单的功能&#xff0c;如果想要一个功能更加丰富…

遥控器数图控链路系统核心技术+算法详解

一、核心技术 无线通信技术 遥控器数图控链路系统主要基于无线通信技术进行数据传输。通过特定的调制、编码和信号处理技术&#xff0c;将遥控器的操作指令转化为无线电信号&#xff0c;并传输给被控制设备。被控制设备接收到信号后&#xff0c;再将其解码为可识别的指令&…

Kafka 源码 KRaft 模式本地运行

KRaft&#xff08;Kafka Raft Metadata mode&#xff09;&#xff0c;从版本 2.8.0 开始作为测试特性引入&#xff0c;并在后续版本中持续得到改进和增强。 KRaft 模式是指 Kafka 使用 Raft 协议来管理集群元数据的一种运行模式&#xff0c;这标志着 Kafka 向去除对 ZooKeeper …

Java:使用Jackson的jsonPtrExp获取节点值的问题说明

使用Jackson时解析json时&#xff0c;经常会使用jsonPath直接获取某一节点下的值&#xff0c;这种方式非常直观 &#xff0c;例如&#xff1a; { “data”: { "test1": "value1", "test2": null, "test3": 10 } } 以Jackson2.13.5&…