vue3 el-select懒加载以及自定义指令

要求:自定义搜索、自定义指令,滚动懒加载每次展示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>
Vue2的el-select懒加载组件是一种用于处理大选项的下拉选择框的解决方案。它可以在用户滚动到下拉列表底部时动态加载更多选项,以提高性能和用户体验。 要实现el-select懒加载组件,你需要使用Vue2的异步组件和自定义指令。以下是一个简单的示例代码: ```html <template> <div> <el-select v-model="selectedOption" v-lazy-load="loadOptions"> <el-option v-for="option in options" :key="option.value" :label="option.label" :value="option.value"></el-option> </el-select> </div> </template> <script> export default { data() { return { selectedOption: &#39;&#39;, options: [], isLoading: false, page: 1, pageSize: 10 }; }, directives: { &#39;lazy-load&#39;: { bind(el, binding, vnode) { const selectWrapper = el.querySelector(&#39;.el-select-dropdown .el-select-dropdown__wrap&#39;); selectWrapper.addEventListener(&#39;scroll&#39;, function() { const scrollHeight = selectWrapper.scrollHeight; const scrollTop = selectWrapper.scrollTop; const clientHeight = selectWrapper.clientHeight; if (scrollHeight - scrollTop - clientHeight <= 5 && !binding.value.isLoading) { binding.value.loadMore(); } }); } } }, methods: { loadOptions() { // 初始化加载选项 this.loadMore(); }, loadMore() { this.isLoading = true; // 模拟异步加载数据 setTimeout(() => { const newOptions = []; for (let i = 0; i < this.pageSize; i++) { const value = this.page * this.pageSize + i; newOptions.push({ value: value, label: &#39;Option &#39; + value }); } this.options = this.options.concat(newOptions); this.page++; this.isLoading = false; }, 1000); } } }; </script> ``` 在上面的示例中,我们使用了自定义指令`v-lazy-load`来监听下拉列表的滚动事件,并在滚动到底部时调用`loadMore`方法加载更多选项。`loadMore`方法模拟了异步加载数据的过程,并将新加载的选项添加到`options`数组中。 你可以根据实际需求修改代码,例如调整每次加载的选项数量、修改异步加载数据的方式等。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值