Files
geo/apps/desktop-client/src/renderer/components/PaginationBar.vue
T

143 lines
3.1 KiB
Vue
Raw Normal View History

<script setup lang="ts">
import { computed } from "vue";
const props = withDefaults(defineProps<{
current: number;
pageSize: number;
total: number;
maxVisiblePages?: number;
}>(), {
maxVisiblePages: 5,
});
const emit = defineEmits<{
(event: "change", page: number): void;
}>();
type PageToken = number | "ellipsis";
const totalPages = computed(() => Math.max(1, Math.ceil(props.total / props.pageSize)));
const tokens = computed<PageToken[]>(() => {
if (totalPages.value <= props.maxVisiblePages) {
return Array.from({ length: totalPages.value }, (_value, index) => index + 1);
}
const pages: PageToken[] = [1];
let start = Math.max(2, props.current - 1);
let end = Math.min(totalPages.value - 1, props.current + 1);
while (end - start < 2 && start > 2) {
start -= 1;
}
while (end - start < 2 && end < totalPages.value - 1) {
end += 1;
}
if (start > 2) {
pages.push("ellipsis");
}
for (let page = start; page <= end; page += 1) {
pages.push(page);
}
if (end < totalPages.value - 1) {
pages.push("ellipsis");
}
pages.push(totalPages.value);
return pages;
});
function goTo(page: number) {
if (page === props.current || page < 1 || page > totalPages.value) {
return;
}
emit("change", page);
}
</script>
<template>
<nav v-if="totalPages > 1" class="pagination" aria-label="Pagination">
<button class="pagination__nav" :disabled="props.current <= 1" @click="goTo(props.current - 1)">
上一页
</button>
<div class="pagination__pages">
<template v-for="token in tokens" :key="`${token}-${typeof token === 'number' ? token : Math.random()}`">
<span v-if="token === 'ellipsis'" class="pagination__ellipsis"></span>
<button
v-else
class="pagination__page"
:class="{ active: token === props.current }"
@click="goTo(token)"
>
{{ token }}
</button>
</template>
</div>
<button class="pagination__nav" :disabled="props.current >= totalPages" @click="goTo(props.current + 1)">
下一页
</button>
</nav>
</template>
<style scoped>
.pagination {
display: flex;
align-items: center;
justify-content: flex-end;
gap: 10px;
flex-wrap: wrap;
}
.pagination__pages {
display: inline-flex;
align-items: center;
gap: 6px;
}
.pagination__nav,
.pagination__page {
appearance: none;
min-width: 34px;
height: 34px;
padding: 0 12px;
border-radius: 10px;
border: 1px solid #dbe5f0;
background: #ffffff;
color: #334155;
cursor: pointer;
font-size: 12px;
font-weight: 600;
transition: background-color 0.2s ease, border-color 0.2s ease, color 0.2s ease, opacity 0.2s ease;
}
.pagination__nav:hover:not(:disabled),
.pagination__page:hover:not(:disabled) {
background: #f8fafc;
border-color: #bfdbfe;
}
.pagination__nav:disabled,
.pagination__page:disabled {
cursor: not-allowed;
opacity: 0.5;
}
.pagination__page.active {
background: #1677ff;
border-color: #1677ff;
color: #ffffff;
}
.pagination__ellipsis {
width: 18px;
text-align: center;
color: #94a3b8;
font-size: 14px;
}
</style>