增加翻页

This commit is contained in:
2026-01-06 15:36:23 +08:00
parent e146d862a2
commit 0d21af765c
3 changed files with 186 additions and 57 deletions
+41
View File
@@ -0,0 +1,41 @@
<script setup lang="ts">
defineProps<{
width?: string | number
height?: string | number
}>()
</script>
<template>
<svg
:width="width || 24"
:height="height || 24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
class="icon-arrow"
>
<path
d="M15 19l-7-7 7-7"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="arrow-path"
/>
</svg>
</template>
<style scoped>
.icon-arrow {
overflow: visible;
}
.arrow-path {
transition: transform 0.4s cubic-bezier(0.34, 1.56, 0.64, 1);
transform-origin: center;
}
.icon-arrow:hover .arrow-path {
transform: translateX(-4px) scale(1.15);
}
</style>
+41
View File
@@ -0,0 +1,41 @@
<script setup lang="ts">
defineProps<{
width?: string | number
height?: string | number
}>()
</script>
<template>
<svg
:width="width || 24"
:height="height || 24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
class="icon-arrow"
>
<path
d="M9 5l7 7-7 7"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="arrow-path"
/>
</svg>
</template>
<style scoped>
.icon-arrow {
overflow: visible;
}
.arrow-path {
transition: transform 0.4s cubic-bezier(0.34, 1.56, 0.64, 1);
transform-origin: center;
}
.icon-arrow:hover .arrow-path {
transform: translateX(4px) scale(1.15);
}
</style>
+104 -57
View File
@@ -1,7 +1,9 @@
<script setup lang="ts">
import { computed, ref, watch, onMounted, onUnmounted, nextTick } from 'vue';
import { computed, ref, watch } from 'vue';
import type { ExtendedPreset, PresetType } from '../../types';
import IconPresetType from '../icons/IconPresetType.vue';
import IconArrowLeft from '../icons/IconArrowLeft.vue';
import IconArrowRight from '../icons/IconArrowRight.vue';
const props = defineProps<{
presets: ExtendedPreset[];
@@ -17,72 +19,31 @@ const emit = defineEmits<{
(e: 'toggle-favorite', preset: ExtendedPreset): void;
}>();
// Lazy Loading Logic
const PAGE_SIZE = 20;
const displayLimit = ref(PAGE_SIZE);
// Pagination Logic
const PAGE_SIZE = 24;
const currentPage = ref(1);
const containerRef = ref<HTMLElement | null>(null);
const sentinelRef = ref<HTMLElement | null>(null);
let observer: IntersectionObserver | null = null;
const totalPages = computed(() => Math.ceil(props.presets.length / PAGE_SIZE));
const displayedPresets = computed(() => {
return props.presets.slice(0, displayLimit.value);
const start = (currentPage.value - 1) * PAGE_SIZE;
const end = start + PAGE_SIZE;
return props.presets.slice(start, end);
});
watch(() => props.presets, () => {
displayLimit.value = PAGE_SIZE;
currentPage.value = 1;
if (containerRef.value) {
containerRef.value.scrollTop = 0;
}
// Reset observer if needed, but the sentinel remains or is recreated
nextTick(() => {
checkIntersection();
});
});
function checkIntersection() {
if (observer && sentinelRef.value) {
// Re-observe just in case
observer.disconnect();
observer.observe(sentinelRef.value);
}
}
onMounted(() => {
observer = new IntersectionObserver((entries) => {
if (entries[0] && entries[0].isIntersecting) {
loadMore();
}
}, {
root: containerRef.value,
rootMargin: '200px',
threshold: 0.1
});
if (sentinelRef.value) {
observer.observe(sentinelRef.value);
}
});
onUnmounted(() => {
if (observer) {
observer.disconnect();
observer = null;
}
});
// Watch sentinel ref changes (e.g. when switching from empty to having presets)
watch(sentinelRef, (newEl) => {
if (observer) {
observer.disconnect();
if (newEl) {
observer.observe(newEl);
}
}
});
function loadMore() {
if (displayLimit.value < props.presets.length) {
displayLimit.value += PAGE_SIZE;
function changePage(page: number) {
if (page < 1 || page > totalPages.value) return;
currentPage.value = page;
if (containerRef.value) {
containerRef.value.scrollTop = 0;
}
}
@@ -190,7 +151,29 @@ function formatDate(dateStr: string) {
</div>
</div>
</div>
<div ref="sentinelRef" class="sentinel" style="height: 20px; width: 100%;"></div>
<div v-if="totalPages > 1" class="pagination-controls">
<button
:disabled="currentPage === 1"
@click="changePage(currentPage - 1)"
class="page-btn nav-btn prev-page"
>
<IconArrowLeft width="16" height="16" />
</button>
<div class="page-numbers">
<span class="page-info"> {{ currentPage }} / {{ totalPages }} </span>
<span class="total-count">({{ presets.length }} 个预设)</span>
</div>
<button
:disabled="currentPage === totalPages"
@click="changePage(currentPage + 1)"
class="page-btn nav-btn next-page"
>
<IconArrowRight width="16" height="16" />
</button>
</div>
</template>
</div>
</template>
@@ -201,9 +184,12 @@ function formatDate(dateStr: string) {
height: 100%;
overflow-y: auto;
scrollbar-gutter: stable;
display: flex;
flex-direction: column;
}
.empty-state {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
@@ -451,4 +437,65 @@ function formatDate(dateStr: string) {
padding: 0.125rem 0.375rem;
border-radius: var(--radius-sm);
}
.pagination-controls {
display: flex;
align-items: center;
justify-content: center;
padding: 1.5rem 0 0.5rem 0;
gap: 1rem;
margin-top: auto;
}
.page-btn {
display: flex;
align-items: center;
justify-content: center;
width: 2.25rem;
height: 2.25rem;
border: 1px solid var(--color-border);
background-color: var(--color-bg-secondary);
color: var(--color-text-secondary);
border-radius: var(--radius-md);
cursor: pointer;
transition: all 0.2s ease;
}
.page-btn:hover:not(:disabled) {
background-color: var(--color-bg-tertiary);
color: var(--color-text-primary);
border-color: var(--color-border-hover);
}
.page-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
background-color: var(--color-bg-primary);
}
.page-numbers {
display: flex;
flex-direction: column;
align-items: center;
gap: 0.125rem;
}
.page-info {
font-size: 0.875rem;
font-weight: 500;
color: var(--color-text-primary);
}
.total-count {
font-size: 0.75rem;
color: var(--color-text-tertiary);
}
.prev-page:hover:not(:disabled) :deep(.arrow-path) {
transform: translateX(-4px) scale(1.15);
}
.next-page:hover:not(:disabled) :deep(.arrow-path) {
transform: translateX(4px) scale(1.15);
}
</style>