Element-plus的Select组件实现滚动加载

508 阅读1分钟

待解决的问题:如何恢复下拉框滚动条的位置到初始高度

<template>
  <el-select
    v-model="selectedValue"
    placeholder="请选择"
    ref="selectRef"
    @visible-change="handleVisibleChange"
    style="width: 150px"
  >
    <el-option
      v-for="item in visibleOptions"
      :key="item.value"
      :value="item.value"
      :label="item.label"
    ></el-option>
    <!-- 如果还有更多数据,显示加载更多的提示 -->
    <div v-if="hasMore && !isLoading" class="loading-prompt">加载更多...</div>
    <!-- 如果正在加载,显示加载中的提示 -->
    <div v-if="isLoading" class="loading-prompt loading-active">
      <el-icon class="el-icon-loading"></el-icon> 加载中...
    </div>
  </el-select>
</template>

<script setup>
import { ref, onMounted, onUnmounted, nextTick } from 'vue';
// import { ElSelect, ElOption, ElIcon } from 'element-plus';

const selectedValue = ref(null);
const visibleOptions = ref([]);
const options = ref([]);
const isLoading = ref(false);
const hasMore = ref(true);
const selectRef = ref(null);
const currentPage = ref(1);
const pageSize = ref(10);

let scrollListenerAdded = false;

// 模拟从后端获取数据的异步函数
async function fetchData() {
  await new Promise((resolve) => setTimeout(resolve, 1500));
  return Array.from({ length: pageSize.value }, (v, k) => ({
    value: `value${(currentPage.value - 1) * pageSize.value + k + 1}`,
    label: `选项${(currentPage.value - 1) * pageSize.value + k + 1}`,
  }));
}

// 加载数据的函数
async function loadOptions() {
  if (isLoading.value) return;
  isLoading.value = true;
  const newOptions = await fetchData();
  visibleOptions.value = visibleOptions.value.concat(newOptions);
  isLoading.value = false;
  if (newOptions.length < pageSize.value) hasMore.value = false;
}

// 处理下拉框显示和隐藏的函数
const handleVisibleChange = async (visible) => {
    if (visible) {

    const selectDropdown = document.querySelector(
            ".el-select-dropdown .el-select-dropdown__wrap"
      )
    if (!scrollListenerAdded) {
      await nextTick();
      selectDropdown.addEventListener('scroll', handleScroll);
      scrollListenerAdded = true;
    }

    } else {

    if (scrollListenerAdded) {
      const selectDropdown = document.querySelector(
            ".el-select-dropdown .el-select-dropdown__wrap"
        )
      selectDropdown.removeEventListener('scroll', handleScroll);
      scrollListenerAdded = false;
    }
    // 当下拉框隐藏时,重置分页信息
    currentPage.value = 1;
    hasMore.value = true;
        visibleOptions.value = visibleOptions.value.slice(0, pageSize.value)
  }
};

// 滚动加载更多数据的函数
const handleScroll = () => {
  const selectDropdown = document.querySelector(
            ".el-select-dropdown .el-select-dropdown__wrap"
        )
  const { scrollTop, scrollHeight, clientHeight } = selectDropdown;
  if (scrollTop + clientHeight >= scrollHeight  && hasMore.value && !isLoading.value) {
    loadMore();
  }
};

// 加载更多数据的函数
const loadMore = async () => {
  if (!hasMore.value || isLoading.value) return;
  isLoading.value = true;
  currentPage.value++;
  const newOptions = await fetchData();
  if (newOptions.length > 0) {
    visibleOptions.value = visibleOptions.value.concat(newOptions);
  } else {
    hasMore.value = false;
  }
  isLoading.value = false;
};

onMounted(async () => {
  await nextTick();
  await loadOptions();
});

onUnmounted(() => {
  if (scrollListenerAdded) {
    const selectDropdown = document.querySelector(
            ".el-select-dropdown .el-select-dropdown__wrap"
        )
    selectDropdown.removeEventListener('scroll', handleScroll);
    scrollListenerAdded = false;
  }
});
</script>

<style scoped>
.loading-prompt {
  text-align: center;
  padding: 8px;
  color: #606266;
}

.loading-active .el-icon-loading {
  animation: spin 1s infinite linear;
}

@keyframes spin {
  0% { transform: rotate(0deg); }
  100% { transform: rotate(360deg); }
}
</style>