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

相关推荐
一只酸奶牛^_^7 小时前
使用nginx部署多个vue项目
阿里云·vue
知识分享小能手14 小时前
Vue3 学习教程,从入门到精通,Vue3 中使用 Axios 进行 Ajax 请求的语法知识点与案例代码(23)
前端·javascript·vue.js·学习·ajax·vue·vue3
YGY Webgis糕手之路1 天前
Cesium 快速入门(三)Viewer:三维场景的“外壳”
前端·经验分享·笔记·vue·web
YGY Webgis糕手之路1 天前
Cesium 快速入门(二)底图更换
前端·经验分享·笔记·vue
YGY Webgis糕手之路1 天前
Cesium 快速入门(七)材质详解
前端·经验分享·笔记·vue·web
YGY Webgis糕手之路2 天前
Cesium 快速入门(八)Primitive(图元)系统深度解析
前端·经验分享·笔记·vue·web
YGY Webgis糕手之路2 天前
Cesium 快速入门(四)相机控制完全指南
前端·经验分享·笔记·vue·web
YGY Webgis糕手之路2 天前
Cesium 快速入门(六)实体类型介绍
前端·经验分享·笔记·vue·web
YGY Webgis糕手之路2 天前
Cesium 快速入门(一)快速搭建项目
前端·经验分享·笔记·vue·web
sunbyte2 天前
50天50个小项目 (Vue3 + Tailwindcss V4) ✨ | 3dBackgroundBoxes(3D背景盒子组件)
前端·javascript·vue.js·3d·vue