Files
vue-ts-example/src/components/UseIntersectionObserverInfiniteLoading.vue
严浩 dcbdba3a55
All checks were successful
/ depcheck (push) Successful in 2m30s
/ lint-build-and-check (push) Successful in 2m39s
/ build-and-deploy-to-vercel (push) Successful in 3m4s
/ playwright (push) Successful in 4m48s
/ surge (push) Successful in 2m38s
docs: 添加 Vuetify 相关链接到 UseIntersectionObserverInfiniteLoading 组件
2025-03-27 11:24:16 +08:00

159 lines
3.9 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!--
* - https://github.com/vuetifyjs/vuetify/blob/05076ce61f6af069198dc4f676509f0a5e306c73/packages/vuetify/src/components/VInfiniteScroll/VInfiniteScroll.tsx
* - https://github.com/vuetifyjs/vuetify/blob/05076ce61f6af069198dc4f676509f0a5e306c73/packages/vuetifyjs/vuetify/packages/vuetify/src/composables/intersectionObserver.ts
-->
<script lang="ts">
/* https://github.com/youzan/vant/blob/be93d4990fd671338b5d1066cf8419519d668d65/packages/vant/src/list/List.tsx */
function checkIsVisible(el: Element, root: Element | null = null) {
if (!el) return false;
const elRect = el.getBoundingClientRect();
const rootRect = root
? root.getBoundingClientRect()
: { bottom: window.innerHeight, left: 0, right: window.innerWidth, top: 0 };
return (
elRect.bottom >= rootRect.top &&
elRect.top <= rootRect.bottom &&
elRect.right >= rootRect.left &&
elRect.left <= rootRect.right
);
}
</script>
<script setup lang="ts">
/**
* @example
*
* ```ts
* const list = ref<Record<string, never>[]>([]);
* const loadData = async (page: number) => { ... };
* ```
*
* ```vue
* <UseIntersectionObserverInfiniteLoading />
* ```
*/
const props = defineProps<{
complete: boolean;
error: boolean;
errorText: string;
loading: boolean;
}>();
const emit = defineEmits<{
clickError: [];
load: [];
}>();
defineSlots<{
// 加载完成(没有更多了)
complete(): unknown;
// 加载失败
error(): unknown;
// 加载完成(还有更多)
loaded(): unknown;
// 加载中
loading(): unknown;
}>();
const check = (reason?: string) => {
nextTick(() => {
if (
props.loading ||
props.complete ||
// props.disabled ||
props.error
) {
return;
}
if (checkIsVisible(target.value!)) {
emit('load');
}
});
};
const target = ref(null);
const { pause, resume } = useIntersectionObserver(
target,
([entry]) => {
if (entry?.isIntersecting) {
if (props.loading) return;
check('isIntersecting');
}
},
{
immediate: false,
root: undefined,
rootMargin: '0px',
// 数值形式单个值表示目标元素可见部分与整个目标元素的比例。例如threshold: 0.5
// 目标元素可见比例达到 50% 时触发回调。
// 数组形式多个值表示多个可见比例触发点。例如threshold: [0, 0.25, 0.5, 0.75, 1.0]。
// 在目标元素可见部分从 0% 增加到 100% 时,每达到一个阈值都会触发回调。
threshold: 0,
},
);
watchEffect(() => {
if (props.complete) {
pause();
} else {
resume();
}
});
watch(
() => [props.loading, props.complete, props.error],
() => check('watch'),
);
</script>
<template>
<div class="infinite-loading" ref="target">
<div v-if="complete" class="infinite-loading__complete">
<slot name="complete">
<span>没有更多了</span>
</slot>
</div>
<template v-else>
<template v-if="error">
<div class="infinite-loading__error" @click="emit('clickError')">
<slot name="error">
<span> {{ props.errorText || '加载失败,点击重试' }} </span>
</slot>
</div>
</template>
<template v-else>
<div v-show="loading" class="infinite-loading__loading">
<slot name="loading">
<span> 加载中... </span>
</slot>
</div>
<div v-show="!loading" class="infinite-loading__loaded" @click="check('click loaded')">
<slot name="loaded">
<span> 加载更多 </span>
</slot>
</div>
</template>
</template>
</div>
</template>
<style>
.infinite-loading__loading,
.infinite-loading__loaded,
.infinite-loading__complete,
.infinite-loading__error {
display: flex;
justify-content: center;
align-items: center;
min-height: 3rem;
color: #666;
}
.infinite-loading__loaded,
.infinite-loading__error {
cursor: pointer;
}
</style>