Vue3+Ts笔记:基于element-UI 实现下拉框滚动翻页查询通用组件

element 提供了 el-select组件,并且支持远程搜索,但是对于数据量大需要翻页的场景并未提供相应配置,所以自己写了一个通用组件,作为记录

初始化控件,定义传入参数

将远程查询的接口封装为函数,作为参数传入组件,可以适应多种场景

复制代码
<script lang="ts" setup>
import { nextTick, onMounted, ref, watch } from 'vue';
import { message } from "@/utils/message";

interface FetchParams {//远程搜索使用的参数
    [key: string]: any;
}

const props = defineProps({
    fetchData: {//传入进行查询的接口
        type: Function,
        required: true
    },
    modelValue: {//select 组件绑定的值
        type: [String, Number],
        default: ''
    },
    name: {//下拉框无对应选项的时候用于填充到选项里的值
        type: String,
        default: ''
    },
    placeholder: String,
    disabled: Boolean,
    param: {//查询接口调用的参数
        type: Object,
        default: () => ({})
    }
});

调用传入函数获取远程数据

对传入函数的基本调用以及一些异常处理,loading的值是el-select的配置项,这里需要注意的是在配置了loading = true时,刷新下拉框会内容时会明显看到选项闪烁一下,如果不希望展示搜索刷新的效果,就不要配置loading

复制代码
const getData = async (params: FetchParams, isLoadMore = false) => {
    if (!hasMore.value && isLoadMore) return;
    
    loading.value = true;
    try {
        const res = await props.fetchData(params);
        if (!res.error) {
            const newItems = res.items?.map(item => ({
                label: item.name,
                value: item.id
            })) || [];
            
            if (isLoadMore) {
                // 加载更多时,追加数据
                itemList.value = [...itemList.value, ...newItems];
            } else {
                // 首次加载或搜索时,替换数据
                itemList.value = newItems;
            }
            
            // 如果有 name 值,确保它在列表中
            if (props.name && value.value) {
                const existingItem = itemList.value.find(item => item.value === value.value);
                if (!existingItem) {
                    itemList.value = [{
                        label: props.name,
                        value: value.value
                    }, ...itemList.value];
                }
            }
            
            // 判断是否还有更多数据
            hasMore.value = newItems.length === params.MaxResultCount;
            totalItems.value = itemList.value.length;
        } else {
            // 如果接口返回错误,清空数据
            itemList.value = [];
            hasMore.value = false;
            totalItems.value = 0;
        }
    } catch (error) {
        // 如果发生错误,清空数据
        itemList.value = [];
        hasMore.value = false;
        totalItems.value = 0;
        message(error, { customClass: 'el', type: 'error' });
    } finally {
        loading.value = false;
    }
};

远程搜索方法

复制代码
// 远程搜索方法
const remoteSearch = debounce(async (query: string) => {
    currentPage.value = 1; // 重置页码
    hasMore.value = true; // 重置加载更多状态
    if (query) {
        const params: FetchParams = {
            ...props.param,
            skipCount: 1,
            MaxResultCount: 10,
            keyword: query
        };
        await getData(params);
    } else {
        // 如果搜索词为空,则加载初始数据
        const params: FetchParams = {
            ...props.param,
            skipCount: 1,
            MaxResultCount: 10
        };
        await getData(params);
    }
}, 300);

下拉滚动事件监听

这里就是最坑的地方了,因为el-select组件被封装过,所以@scroll.native不会有任何效果。为了实现下拉框的滚动加载功能,只能用@visible-change 来监听下拉框,并在下拉框状态变化的时候添加下拉滚动事件

复制代码
const handleVisibleChange = async (visible) => {
    if (visible) {
        // 先清空数据
        itemList.value = [];
        totalItems.value = 0;
        hasMore.value = true;
        currentPage.value = 1;
        
        // 加载初始数据
        const params: FetchParams = {
            ...props.param,
            skipCount: 1,
            MaxResultCount: 10
        };
        await getData(params);
        
        // 添加滚动事件监听
        const dropdown = document.querySelector('.myselect-loadmore .el-select-dropdown__wrap');
        if (dropdown) {
            dropdown.addEventListener('scroll', handleScroll);
            // 重置滚动位置
            dropdown.scrollTop = 0;
        }
    } else {
        // 移除滚动事件监听
        const dropdown = document.querySelector('.el-select-dropdown');
        if (dropdown) {
            dropdown.removeEventListener('scroll', handleScroll);
        }
    }
};

//防抖函数
function debounce<T extends (...args: any[]) => any>(func: T, delay: number) {
    let timeout: NodeJS.Timeout;
    return function (this: any, ...args: Parameters<T>) {
        const context = this;
        clearTimeout(timeout);
        timeout = setTimeout(() => func.apply(context, args), delay);
    };
}

const handleScroll = debounce(async (event) => {
    // 判断是否滚动到底部
    const bottom = event.target.scrollHeight === event.target.scrollTop + event.target.clientHeight;
    if (bottom && !loading.value && hasMore.value) {
        currentPage.value++;
        // 调用父组件传递的函数,并传入子组件的参数
        const params: FetchParams = {
            ...props.param,
            skipCount: currentPage.value,
            MaxResultCount: 10
        };
        await getData(params, true);
    }
}, 100);

el-select 组件配置

popper-class的设置是为了让事件能够准确绑定到下拉框,不添加该属性可能导致事件被绑定到父组件上面

复制代码
<template>
    <el-select 
        v-model="value" 
        class="myselect" 
        :placeholder="placeholder || '请选择!'" 
        popper-class="myselect-loadmore"
        filterable 
        remote
        remote-show-suffix
        :remote-method="remoteSearch"
        clearable 
        :disabled="disabled" 
        @visible-change="handleVisibleChange"
        @update:modelValue="(val) => emit('update:modelValue', val)">
        <el-option v-for="(item, index) in itemList" :key="item.value" :label="item.label" :value="item.value" />
    </el-select>
</template>

父组件调用示例

复制代码
              <SelectCommon v-model="Id" :fetch-data="getList" :param="selectParams"
                placeholder="请选择" />

适用于Vue3+ts+element的场景,组件完整代码已经上传至github,文件添加到项目可以直接调用。

地址:https://github.com/LearnerPing/SelectCommon.git

如果你觉得还算好用,请在github上面给我点个star

相关推荐
南城以南溫暖如初14710 小时前
社交裂变商城系统设计与实战开发指南
java·spring boot·mysql·vue·mybatis
南城以南溫暖如初14710 小时前
分享返现商城系统开发实战:技术架构与运营经验指南
java·spring boot·mysql·架构·vue·mybatis
阿图灵11 小时前
MakerHub 开发报告:v1.0.0 → v1.1.0(单日 26 提交,图片渲染、目录跟随与数据真实化)
前端·vue·个人网站·deepseek·开发报告
南城以南溫暖如初14712 小时前
社群电商实战指南:从零搭建高转化社区的技术架构
java·spring boot·mysql·系统架构·vue·mybatis
南城以南溫暖如初14712 小时前
会员返利商城系统开发实战:架构设计与实现指南
java·spring boot·mysql·vue·mybatis
jay神3 天前
【计算机毕业设计】基于Spring Boot的宠物领养管理系统
java·spring boot·后端·vue·毕业设计·宠物
要开心吖ZSH3 天前
腾讯 IM 前端对接小白教程:医患一对一聊天(含自定义报告卡片)
vue·健康医疗·即时通讯·im
深念Y4 天前
07-SSR水合问题实战排查与修复记录
前端·vue·vite·nuxt·ssr·csr·水合
深念Y4 天前
NativeScript 移动端开发踩坑记录
前端·ui·vue·安卓·移动端·native·原生
avi91114 天前
【】js不同颜色(Vue 框架)今时今日2026年学编程入门(10月1日)
前端·vue.js·vue·vue框架·前端入门·vue入门·html上传