feat: 添加无限加载组件和示例页面
All checks were successful
/ playwright (push) Successful in 3m16s
/ depcheck (push) Successful in 1m25s
/ build-and-deploy-to-vercel (push) Successful in 1m14s

This commit is contained in:
严浩
2024-12-07 23:11:08 +08:00
parent 12a02eb193
commit 2574e38d9a
4 changed files with 184 additions and 17 deletions

View File

@ -0,0 +1,86 @@
<script lang="ts">
function checkIsVisible(el: Element, root: Element | null = null) {
if (!el) return false;
const elRect = el.getBoundingClientRect();
const rootRect = root
? root.getBoundingClientRect()
: { top: 0, left: 0, bottom: window.innerHeight, right: window.innerWidth };
return (
elRect.bottom >= rootRect.top &&
elRect.top <= rootRect.bottom &&
elRect.right >= rootRect.left &&
elRect.left <= rootRect.right
);
}
</script>
<script setup lang="ts">
const target = ref(null);
// defineSlots
const state = ref<'' | 'loading' | 'loaded' | 'complete' | 'error'>(''); // TODO: use ts-enum-util
const props = defineProps<{
asyncLoad: () => Promise<{ hasMore: boolean }>;
}>();
const load = async () => {
if (state.value === 'loading') return;
state.value = 'loading';
try {
const { hasMore } = await props.asyncLoad();
state.value = hasMore ? 'loaded' : 'complete';
if (hasMore) {
await nextTick();
if (checkIsVisible(target.value!)) {
load();
}
}
if (!hasMore) {
pause(); // TODO: 下拉刷新后,怎么恢复? maybe @register
}
} catch (error) {
state.value = 'error';
}
};
const { pause /* , resume, isSupported, isActive */ } = useIntersectionObserver(
target,
([entry]) => {
if (entry?.isIntersecting) {
load();
}
},
{
// 数值形式单个值表示目标元素可见部分与整个目标元素的比例。例如threshold: 0.5
// 目标元素可见比例达到 50% 时触发回调。
// 数组形式多个值表示多个可见比例触发点。例如threshold: [0, 0.25, 0.5, 0.75, 1.0]。
// 在目标元素可见部分从 0% 增加到 100% 时,每达到一个阈值都会触发回调。
threshold: 0,
},
);
</script>
<template>
<div class="infinite-loading" ref="target">
<slot v-if="state === 'complete'" name="complete">
<span>没有更多了</span>
</slot>
<template v-else>
<div v-show="state == 'loading'">
<slot name="loading">
<span> 加载中... </span>
</slot>
</div>
<slot v-if="state === 'error'" name="error">
<div>
<span> 加载失败 </span>
<button @click="load">重试</button>
</div>
</slot>
</template>
</div>
</template>