vue3+elementUI-plus实现select下拉框的虚拟滚动

网上查了几个方案,要不就是不兼容,要不就是不支持vue3,

最终找到一个合适的,并且已上线使用,需要修改一下样式:

代码如下:

main.js里引用

bash 复制代码
import 'vue3-virtual-scroller/dist/vue3-virtual-scroller.css';
import { RecycleScroller } from 'vue3-virtual-scroller';
app.component('RecycleScroller', RecycleScroller);

vue文件:

bash 复制代码
<el-form-item label="用户" prop="seriesId">
                    <div ref="selectWrapper" @click="toggleDropdown($event)" class="select-wrapper">
                        <el-input style="width: 240px"
                        placeholder="请输入搜索内容"
                        v-model="selectedOption"
                        :suffix-icon="selectedOption ? 'el-icon-circle-close' : null"
                        @clear="clearSearch"
                        ></el-input>
                    </div>
                    <el-icon class="clear-btn" v-if="selectedOption" @click.stop="clearSearch"><CircleClose /></el-icon>
                    <Teleport to="body">
                        <div v-show="isOpen"  ref="dropdown" class="virtual-dropdown" :style="dropdownStyles" @click="closeDropdown">
                            <RecycleScroller
                                class="virtual-list"
                                :buffer="1000"
                                :prerender="200"
                                style="height: 270px"
                                :item-size="24"
                                key-field="id"
                                :items="filteredSeriesList"
                                >
                                <template v-slot="{ item, index }">
                                    <div class="list-item" :key="index" @click.stop="handleItemClick(item)">
                                    <span>{{ item.id }}</span>&nbsp;-&nbsp;
                                    <span>{{ item.name }}</span>
                                    </div>
                                </template>
                                </RecycleScroller>
                        </div>
                    </Teleport>
                </el-form-item>

js代码:

bash 复制代码
<script setup name="LeadsList">
const rowCount = ref(0);
    const rowCount2 = ref(0);
    const leadsList = ref([]);
    const loading = ref(false);
    const activeButton = ref(0);
    const seriesList = ref([]);
    const filteredSeriesList = ref([]); // 初始状态下,筛选后的列表与原始列表相同
    const indexLayer = ref(false);
    const open = ref(false);
    const dropdown = ref(null);
    const repeatLoading = ref(false);
    const repeatList = ref([]);
    const dropdownStyles = ref({});
    const selectWrapper = ref({});
    const isOpen = ref(false);
    const selectedOption = ref('');
    const data = reactive({
        queryParams: {
            pageIndex: 1,
            pageSize: 10,
            phone: "",
            seriesId: null,
        },
        queryParamsRepeat: {
            pageIndex: 1,
            pageSize: 10,
            companyId: 1,
            userId: 1
        }
    });

    const { queryParams,queryParamsRepeat } = toRefs(data);


    watch(selectedOption, (newValue) => {
        search(newValue);
    });
    function search (keyword) {
        if (keyword.trim() === "") {
      filteredSeriesList.value = [...seriesList.value];
        } else {
        filteredSeriesList.value = seriesList.value.filter((item) =>
            item.name.toLowerCase().includes(keyword.toLowerCase())
        );
        }
    }
    function handleItemClick(item) {
      queryParams.value.seriesId = item.id;
      selectedOption.value = item.name;
      closeDropdown()
    }
    function closeDropdown(event = null) {
        isOpen.value = false;
        if (
            event &&
            (selectWrapper.value.contains(event.target) ||
            dropdown.value.contains(event.target))
        ) {
            return;
        }
        isOpen.value = false;
    }
    function toggleDropdown($event) {
        $event.stopPropagation(); // 阻止事件冒泡
        isOpen.value = !isOpen.value;
        if (isOpen.value) {
            const rect = selectWrapper.value.getBoundingClientRect();
            const { x, y, width, height } = rect;
            dropdownStyles.value = {
            position: 'fixed',
            top: `${y + height}px`,
            left: `${x}px`,
            width: `${width}px`,
            };
        }
    }
    function clearSearch () {
        queryParams.value.seriesId = '';
        selectedOption.value = '';
        filteredSeriesList.value = [...seriesList.value];
    }

css代码:

bash 复制代码
.virtual-dropdown {
  position: absolute;
  inset: 100% auto auto 0;
  z-index: 2000;
  width: 100%;
  overflow-y: auto;
  overflow-x: hidden;
  border: 1px solid #ebeef5;
  border-radius: 4px;
  background-color: #fff;
  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
}

.list-item {
  display: flex;
  padding: 0 10px;
  align-items: center;
  height: 24px;
  cursor: pointer;
  white-space: nowrap;
  text-overflow: ellipsis;
  overflow: hidden;
}

.list-item:hover {
  background-color: #f5f7fa;
}
.clear-btn {
    border: none;
    background: transparent;
    cursor: pointer;
    position: absolute;
    right: 14px;
  }
相关推荐
却尘5 分钟前
Next.js 请求最佳实践 - vercel 2026一月发布指南
前端·react.js·next.js
ccnocare6 分钟前
浅浅看一下设计模式
前端
Lee川9 分钟前
🎬 从标签到屏幕:揭秘现代网页构建与适配之道
前端·面试
Ticnix36 分钟前
ECharts初始化、销毁、resize 适配组件封装(含完整封装代码)
前端·echarts
纯爱掌门人39 分钟前
终焉轮回里,藏着 AI 与人类的答案
前端·人工智能·aigc
twl43 分钟前
OpenClaw 深度技术解析
前端
崔庆才丨静觅1 小时前
比官方便宜一半以上!Grok API 申请及使用
前端
星光不问赶路人1 小时前
vue3使用jsx语法详解
前端·vue.js
天蓝色的鱼鱼1 小时前
shadcn/ui,给你一个真正可控的UI组件库
前端
布列瑟农的星空1 小时前
前端都能看懂的Rust入门教程(三)——控制流语句
前端·后端·rust