feat(im): 优化 PagedScroller.vue 组件

im
YunaiV 2026-04-26 21:46:13 +08:00
parent 802a10cf85
commit d37af6d959
1 changed files with 95 additions and 0 deletions

View File

@ -0,0 +1,95 @@
<template>
<!--
分页增量滚动容器
- 滚到底部自动 page++直到全部渲染完
- 通过 slot 暴露每一项让调用方自己决定渲染
-->
<el-scrollbar ref="scrollbarRef" class="w-full h-full">
<slot v-for="(item, idx) in displayItems" :item="item" :index="idx" :key="idx"></slot>
<div
v-if="showFooter"
class="py-3 text-xs text-center text-[var(--el-text-color-secondary)]"
>
已到底部
</div>
</el-scrollbar>
</template>
<script lang="ts" setup generic="T">
import { computed, onMounted, onBeforeUnmount, ref, useTemplateRef, watch } from 'vue'
import { ElScrollbar } from 'element-plus'
defineOptions({ name: 'ImPagedScroller' })
const props = withDefaults(
defineProps<{
items: T[] //
pageSize?: number //
threshold?: number // px
}>(),
{
pageSize: 30,
threshold: 30
}
)
const scrollbarRef = useTemplateRef<InstanceType<typeof ElScrollbar>>('scrollbarRef')
const page = ref(1)
const displayItems = computed(() => {
const limit = Math.min(page.value * props.pageSize, props.items.length)
return props.items.slice(0, limit)
})
const allLoaded = computed(() => displayItems.value.length >= props.items.length)
/** 仅当超过一页时才显示「已到底部」,避免短列表也出现这条提示 */
const showFooter = computed(() => allLoaded.value && props.items.length > props.pageSize)
// el-scrollbar overflow:hidden .el-scrollbar__wrap
let wrapEl: HTMLElement | null = null
onMounted(() => {
wrapEl = scrollbarRef.value?.$el?.querySelector('.el-scrollbar__wrap') ?? null
wrapEl?.addEventListener('scroll', onScroll)
})
onBeforeUnmount(() => {
wrapEl?.removeEventListener('scroll', onScroll)
})
/** 切换数据源(如切会话)时重置分页:避免新列表沿用旧 page首屏出现空段 */
watch(
() => props.items,
() => {
page.value = 1
}
)
/** 滚到距底 threshold 内时自增 page扩出下一段切片 */
function onScroll(e: Event) {
const el = e.target as HTMLElement
if (el.scrollTop + el.clientHeight < el.scrollHeight - props.threshold) {
return
}
if (allLoaded.value) {
return
}
page.value++
}
defineExpose({
/** 手动滚到顶部 */
scrollTop: () => {
if (wrapEl) {
wrapEl.scrollTop = 0
}
},
/** 手动滚到底部 */
scrollBottom: () => {
if (wrapEl) {
wrapEl.scrollTop = wrapEl.scrollHeight
}
}
})
</script>