虚拟列表
使用vue-virtual-scroller,本次的使用场景是用于element-plus中的select下拉选择组件。封装组件如下:(此处用到的是recycle-scroller)
<template>
<div style="width: 100%">
<el-select
style="width: 100%"
v-model="selectedIds"
clearable
filterable
:filter-method="dataFilter"
:placeholder="placeholderText"
v-loading="isLoading"
multiple
collapse-tags
:max-collapse-tags="props.elSelectShowNum"
:disabled="props.isDisabledSelect"
@visible-change="handleVisibleChange"
@change="handleSelectChange"
>
<recycle-scroller
v-show="visible"
:items="filteredOptions"
:item-size="34"
:buffer="50"
:prerender="10"
style="height: 200px"
key-field="id">
<template #default="{ item }">
<el-option
:label="item.name"
:value="item.id"
:key="item.id"
:disabled="disabledChannelIds.includes(item.id) || tableData.some((row) => row.id === item.id)"></el-option>
</template>
</recycle-scroller>
</el-select>
</div>
</template>
<script setup>
const props = defineProps({
// 下拉选项数据
optionData: {
type: Array,
default: () => []
},
// 是否显示加载中
isLoading: {
type: Boolean,
default: false
},
// 需要显示的 Tag 的最大数量
elSelectShowNum: {
type: Number,
default: 1
},
// 是否需要禁用select下拉框(禁用后无法点击并出现下拉框),默认不禁用
isDisabledSelect: {
type: Boolean,
default: false
},
// 所有的禁用项
disabledChannelIds: {
type: Array,
default: () => []
},
placeholderText: {
type: String,
default: '请选择'
},
// 表格数据(根据表格数据判断是否禁用某些选项)
tableData: {
type: Array,
default: () => []
}
})
let selectedIds = ref([])
const filteredOptions = ref([]);
const visible = ref(false);
// 为filteredOptions赋初始值,否则一开始就无数据
watchEffect(() => {
filteredOptions.value = props.optionData;
});
const dataFilter = (val) => {
if (!val) {
// 没有输入搜索字时,即显示全部下拉选项
filteredOptions.value = props.optionData;
return;
}
// 有输入搜索字时,过滤下拉选项
filteredOptions.value = props.optionData.filter((option) => option.name.toLowerCase().includes(val.toLowerCase()));
};
const handleVisibleChange = (boolean) => {
// 控制 RecycleScroller 的可见性
visible.value = boolean;
};
const emit = defineEmits(["selectChange"]);
const handleSelectChange = (val) => {
// 选中值发生变化时,通知父组件
selectedIds.value = val;
emit("selectChange", val);
};
// 将resetData暴露出去,供父组件调用
// 通过defineExpose
const resetData = () => {
// 重置下拉选项
selectedIds.value = [];
filteredOptions.value = props.optionData;
};
defineExpose({resetData})
</script>
<style scoped>
/* 整个滚动条 */
::-webkit-scrollbar {
width: 3px;
height: 3px;
}
/* 滚动条有滑块的轨道部分 */
::-webkit-scrollbar-track-piece {
background-color: transparent;
border-radius: 5px;
}
/* 滚动条滑块(竖向:vertical 横向:horizontal) */
::-webkit-scrollbar-thumb {
cursor: pointer;
background-color:#999999;
border-radius: 5px;
}
/* 滚动条滑块hover */
::-webkit-scrollbar-thumb:hover {
background-color: #999999;
}
/* 同时有垂直和水平滚动条时交汇的部分 */
::-webkit-scrollbar-corner {
display: block; /* 修复交汇时出现的白块 */
}
</style>
滚动加载
使用element-plus的Infinite Scroll无限滚动,核心思想是:数据全量加载,页面分段渲染。第一次加载前100条,滚动到页面底部后,从全量数据中拿到第100~200这100条,push到用于渲染的renderData中,此时renderData应有200条数据。以此类推。
代码如下:
<template>
<ul class="monitor-list" v-infinite-scroll="loadMore" style="overflow: auto" >
<li
v-for="item in monitorDataRender"
:key="item.id"
@click="toClickMonitor(item)"
:class="{'li-active': currentMonitor.id === item.id,'li-out-line': item.status == '0'}"
>
{{ item.name }}
</li>
</ul>
</template>
<script setup>
// 从接口获取到的全量的数据
const monitorList = ref([]);
// 用于渲染的数据
const monitorDataRender = ref([])
const getMonitorList = () => {
// 从接口拿到数据
DeviceChannelList(params).then((res) => {
monitorList.value = res.data;
monitorDataRender.value = res.data.slice(0, 100)
})
}
const loadMore = () => {
if (monitorDataRender.value.length < monitorList.value.length) {
monitorDataRender.value.push(...monitorList.value.slice(monitorDataRender.value.length, monitorDataRender.value.length + 100))
}
}
</script>