要求:自定义搜索、自定义指令,滚动懒加载每次展示10条数据
在element-plus中,el-select的选项框是使用popper.js生成的,不在body中,无法直接获取,这时需要巧妙地使用popper-class属性来获取到dom并挂载到el-select进行监听来实现我们的需求。
下面是代码

<template>
    <el-select 
        v-model="value"
        :loading="loading"
        loading-text="数据加载中..." 
        filterable
        clearable
        popper-class="event-select-poper"
        v-el-select-loadmore="loadmore"
        :placeholder="placeholder"
        style="width: 100%"
        :filter-method="filterOptions"
        @change="handleEventsChange"
        @visible-change="handleVisibleChange">
        <el-option
            v-for="(item, index) in options"
            :key="index"
            :label="item.eventTheme"
            :value="item.logCode">
        </el-option>
    </el-select>
</template>

在vue3的setup语法糖中,以v开头的变量会被识别为指令,一般指令变量命名使用小驼峰语法

<script setup>
import { ref, reactive, watch, nextTick, onBeforeMount } from 'vue'

const props = defineProps({
    code: {
        type: [String, Number],
        default: ''
    },
    placeholder: {
        type: String,
        default: ''
    },
    disabled: {
        type: Boolean,
        default: false
    }
})

const emits = defineEmits(['change'])

let options = reactive([])
let optionCodes = ref([])
let loading = ref(false)
let value = ref('')
let allEvents = reactive([
    { eventTheme: '1', logCode: 1 },
    { eventTheme: '2', logCode: 2 },
    { eventTheme: '3', logCode: 3 },
    { eventTheme: '4', logCode: 4 },
    { eventTheme: '5', logCode: 5 },
    { eventTheme: '6', logCode: 6 },
    { eventTheme: '7', logCode: 7 },
    { eventTheme: '8', logCode: 8 },
    { eventTheme: '9', logCode: 9 },
    { eventTheme: '10', logCode: 10 },
    { eventTheme: '11', logCode: 11 },
    { eventTheme: '12', logCode: 12 },
    { eventTheme: '13', logCode: 13 },
    { eventTheme: '14', logCode: 14 },
    { eventTheme: '15', logCode: 15 },
    { eventTheme: '16', logCode: 16 },
])
let allFilterEvents = reactive([
{ eventTheme: '1', logCode: 1 },
    { eventTheme: '2', logCode: 2 },
    { eventTheme: '3', logCode: 3 },
    { eventTheme: '4', logCode: 4 },
    { eventTheme: '5', logCode: 5 },
    { eventTheme: '6', logCode: 6 },
    { eventTheme: '7', logCode: 7 },
    { eventTheme: '8', logCode: 8 },
    { eventTheme: '9', logCode: 9 },
    { eventTheme: '10', logCode: 10 },
    { eventTheme: '11', logCode: 11 },
    { eventTheme: '12', logCode: 12 },
    { eventTheme: '13', logCode: 13 },
    { eventTheme: '14', logCode: 14 },
    { eventTheme: '15', logCode: 15 },
    { eventTheme: '16', logCode: 16 },
])
let pageNum = ref(1)
let pageSize = ref(10)

// 自定义v-el-select-loadmore指令
const vElSelectLoadmore = {
    beforeMount(el, binding) {
        /** 
         * vue2时:
         * el.querySelector('.el-select-dropdown .el-select-dropdown__wrap');
         * vue3时:
         * 在el-select给一个参数popper-class="event-select-poper"           
         * element-plus中el-select的选项是使用的popper.js生成的,无法直接获取
        */
		const selectDom = document.querySelector('.event-select-poper .el-select-dropdown__wrap')
		let loadMores = function() {
            /**
            * scrollHeight 获取元素内容高度(只读)
            * scrollTop 获取或者设置元素的偏移值,常用于, 计算滚动条的位置, 当一个元素的容器没有产生垂直方向的滚动条, 那它的scrollTop的值默认为0.
            * clientHeight 读取元素的可见高度(只读)
            * 如果元素滚动到底, 下面等式返回true, 没有则返回false:
            * ele.scrollHeight - ele.scrollTop === ele.clientHeight;
            */
            // 判断是否到底
			const isBase = this.scrollHeight - this.scrollTop <= this.clientHeight + 20
			if (isBase) {
                // 增加防抖
				binding.value && binding.value()
			}
		}
        // 将获取到的dom和函数挂载到el-select上,实例销毁时好处理
		el.selectDomInfo = selectDom
		el.selectLoadMore = loadMores
        // 监听滚动事件
		selectDom?.addEventListener('scroll', loadMores.bind(selectDom))
	},
    // 实例销毁
	beforeUnmount(el) {
		if (el.selectLoadMore) {
			el.selectDomInfo.removeEventListener('scroll', el.selectLoadMore)
			delete el.selectDomInfo
			delete el.selectLoadMore
		}
	}
}

watch(() => allFilterEvents, () => {
    let startIndex = pageNum.value * pageSize.value - pageSize.value
    let endIndex = pageNum.value * pageSize.value
    options = allFilterEvents.slice(startIndex, endIndex)
    optionCodes.value = options.value.map(item => { return item.code })
}, {
    immediate: true,
    deep: true
})

watch(() => props.code, async (val) => {
    // 加载完毕后尝试设置默认值
    await setSelectedDefaultValue(val)
}, {
    immediate: true,
    deep: true
})

// 添加一个方法用于设置默认值
const setSelectedDefaultValue = async (defaultValue) => {
    if (defaultValue) {
        // 如果默认值不在当前options中,尝试加载更多直到找到
        while (!optionCodes.value.includes(defaultValue)) {
            await new Promise(resolve => setTimeout(resolve, 0)) // 异步等待,模拟加载延迟
            if (pageNum.value * pageSize.value <= allEvents.value.length) {
                await loadmore()
            } else {
                break // 防止无限循环
            }
        }
        eventCode.value = defaultValue
    }
}

onBeforeMount(async () => {
    await loadEvents()
    // 这里调用一次设置默认值
    await setSelectedDefaultValue(props.code)
})

// 模拟懒加载
const loadmore = () => {
    // 数据加载完成之后,不需要再执行懒加载
    if (allEvents.length <= options.length) { return }
    pageNum.value++
    nextTick(() => {
        loading.value = true
        let startIndex = pageNum.value * pageSize.value - pageSize.value
        let endIndex = pageNum.value * pageSize.value
        options = [
            ...options,
            ...allFilterEvents.slice(startIndex, endIndex)
        ]
        optionCodes.value = options.value.map(item => { return item.code })
        loading.value = false
    })
}

// 自定义过滤函数
const filterOptions = (query = '') => {
    pageNum.value = 1
    nextTick(() => {
        if(query === ''){
            // 搜索词为空时,显示全部
            allFilterEvents = JSON.parse(JSON.stringify(allEvents))
            await setSelectedDefaultValue(props.code)
        }else{
            // 搜索词不为空时,从所有关键字列表中筛选匹配项
            let arr = allEvents.filter((item) => {
                return item.includes(query)
            })
            $set(this, 'allFilterEvents', arr)
        }
    })
}

// 下拉框隐藏时,重置分页已经过滤得到的列表
const handleVisibleChange = (visible) => {
    if(!visible){
        pageNum.value = 1
        nextTick(() => {
            allFilterEvents = JSON.parse(JSON.stringify(allEvents))
        })
    }
}

// 获取事件列表
const loadEvents = () => {
    loading.value = true
    allFilterEvents = allEvents
    // 调用接口
    loading.value = false
}

const handleEventsChange = (val) => {
    emits('change', val)
}
</script>
GitHub 加速计划 / vu / vue
101
18
下载
vuejs/vue: 是一个用于构建用户界面的 JavaScript 框架,具有简洁的语法和丰富的组件库,可以用于开发单页面应用程序和多页面应用程序。
最近提交(Master分支:1 个月前 )
9e887079 [skip ci] 11 个月前
73486cb5 * chore: fix link broken Signed-off-by: snoppy <michaleli@foxmail.com> * Update packages/template-compiler/README.md [skip ci] --------- Signed-off-by: snoppy <michaleli@foxmail.com> Co-authored-by: Eduardo San Martin Morote <posva@users.noreply.github.com> 1 年前
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐