admin-vue3/src/views/im/home/components/PagedScroller.vue

96 lines
2.5 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<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>