| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup generic="T"> |
| | | import { computed, onBeforeUnmount, onMounted, ref, useTemplateRef, watch } from 'vue' |
| | | |
| | | defineOptions({ name: 'ImPagedScroller' }) |
| | | |
| | | const props = withDefaults( |
| | | defineProps<{ |
| | | itemKey?: string // ä¸å¡ id åæ®µåï¼å¦ 'userId' / 'id'ï¼ï¼ä¸ä¼ / åæ®µå¼é string|number æ¶åé idx |
| | | items: T[] // å
¨éæ°æ® |
| | | pageSize?: number // æ¯é¡µæ¸²ææ¡æ° |
| | | threshold?: number // è·åºå¤å° px 触åä¸ä¸é¡µ |
| | | }>(), |
| | | { |
| | | itemKey: undefined, |
| | | pageSize: 30, |
| | | threshold: 30 |
| | | } |
| | | ) |
| | | |
| | | /** è§£ææ¯æ¡ item ç :keyï¼caller ä¼ itemKey åæå段åï¼æ æ / 缺失åéç´¢å¼ï¼é¿å
ä¼ éåæ®µæ¶å
¨è¡¨ undefined key */ |
| | | function resolveItemKey(item: T, idx: number): number | string { |
| | | if (!props.itemKey || item == null || typeof item !== 'object') { |
| | | return idx |
| | | } |
| | | const value = (item as Record<string, unknown>)[props.itemKey] |
| | | return typeof value === 'string' || typeof value === 'number' ? value : idx |
| | | } |
| | | |
| | | const scrollbarRef = useTemplateRef<HTMLDivElement>('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) |
| | | |
| | | let wrapEl: HTMLElement | null = null |
| | | |
| | | onMounted(() => { |
| | | wrapEl = scrollbarRef.value |
| | | 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> |
| | | |
| | | <template> |
| | | <!-- |
| | | å页å¢éæ»å¨å®¹å¨ |
| | | - æ»å°åºé¨èªå¨ page++ï¼ç´å°å
¨é¨æ¸²æå® |
| | | - éè¿ slot æ´é²æ¯ä¸é¡¹ï¼è®©è°ç¨æ¹èªå·±å³å®æ¸²æ |
| | | --> |
| | | <div ref="scrollbarRef" class="w-full h-full overflow-y-auto"> |
| | | <slot |
| | | v-for="(item, idx) in displayItems" |
| | | :item="item" |
| | | :index="idx" |
| | | :key="resolveItemKey(item, idx)" |
| | | ></slot> |
| | | <div |
| | | v-if="showFooter" |
| | | class="py-3 text-xs text-center text-[var(--ant-color-text-secondary)]" |
| | | > |
| | | å·²å°åºé¨ |
| | | </div> |
| | | </div> |
| | | </template> |