perf: 优化博客文章列表页分页组件
This commit is contained in:
parent
f3feaf499a
commit
b764d2a10e
103
theme/src/client/components/Pagination.vue
Normal file
103
theme/src/client/components/Pagination.vue
Normal file
@ -0,0 +1,103 @@
|
||||
<script setup lang="ts">
|
||||
import type { PlumeThemeBlog } from '../../shared/index.js'
|
||||
|
||||
type NonFalseAndNullable<T> = T extends false | null | undefined ? never : T
|
||||
|
||||
defineProps<{
|
||||
pagination: NonFalseAndNullable<PlumeThemeBlog['pagination']>
|
||||
page: number
|
||||
totalPage: number
|
||||
isFirstPage: boolean
|
||||
isLastPage: boolean
|
||||
pageRange: { value: number | string, more?: true }[]
|
||||
}>()
|
||||
const emit = defineEmits<{ change: [value: number] }>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="pagination">
|
||||
<button
|
||||
type="button"
|
||||
class="btn prev"
|
||||
:disabled="isFirstPage"
|
||||
@click="() => emit('change', page - 1)"
|
||||
>
|
||||
{{ pagination?.prevPageText || 'Prev' }}
|
||||
</button>
|
||||
<div class="page-range">
|
||||
<button
|
||||
v-for="{ value, more } in pageRange"
|
||||
:key="value"
|
||||
class="btn"
|
||||
:disabled="more"
|
||||
:class="{ more, active: value === page }"
|
||||
type="button"
|
||||
@click="() => !more && emit('change', value as number)"
|
||||
>
|
||||
{{ more ? '...' : value }}
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
class="btn next"
|
||||
:disabled="isLastPage"
|
||||
@click="() => emit('change', page + 1)"
|
||||
>
|
||||
{{ pagination?.nextPageText || 'Next' }}
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.pagination {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 32px 20px 24px;
|
||||
margin-bottom: 64px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 2px 4px;
|
||||
margin: 0 2px;
|
||||
font-weight: 500;
|
||||
line-height: 1;
|
||||
color: var(--vp-c-text-2);
|
||||
border-radius: 4px;
|
||||
transition: all var(--t-color);
|
||||
}
|
||||
|
||||
.btn.active {
|
||||
color: var(--vp-c-bg);
|
||||
background-color: var(--vp-c-brand-2);
|
||||
border-color: var(--vp-c-brand-2);
|
||||
}
|
||||
|
||||
.btn[disabled],
|
||||
.btn[disabled]:hover {
|
||||
color: var(--vp-c-gray-1);
|
||||
cursor: not-allowed;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.btn.more {
|
||||
color: var(--vp-c-gray-1);
|
||||
cursor: not-allowed;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.pagination {
|
||||
padding: 20px;
|
||||
background-color: var(--vp-c-bg);
|
||||
border-radius: 6px;
|
||||
box-shadow: var(--vp-shadow-1);
|
||||
}
|
||||
|
||||
.page-range .btn {
|
||||
padding: 4px 12px;
|
||||
margin: 0 8px;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -1,12 +1,14 @@
|
||||
<script lang="ts" setup>
|
||||
import { usePostListControl } from '../composables/index.js'
|
||||
import PostItem from './PostItem.vue'
|
||||
import Pagination from './Pagination.vue'
|
||||
|
||||
const {
|
||||
pagination,
|
||||
postList,
|
||||
page,
|
||||
totalPage,
|
||||
pageRange,
|
||||
isLastPage,
|
||||
isFirstPage,
|
||||
isPaginationEnabled,
|
||||
@ -21,28 +23,16 @@ const {
|
||||
:key="post.path"
|
||||
:post="post"
|
||||
/>
|
||||
|
||||
<div v-if="isPaginationEnabled" class="pagination">
|
||||
<button
|
||||
type="button"
|
||||
class="btn prev"
|
||||
:disabled="isFirstPage"
|
||||
@click="() => changePage(-1)"
|
||||
>
|
||||
{{ pagination?.prevPageText || 'Prev' }}
|
||||
</button>
|
||||
<span class="page-info">
|
||||
{{ page }} / {{ totalPage }}
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
class="btn next"
|
||||
:disabled="isLastPage"
|
||||
@click="() => changePage(1)"
|
||||
>
|
||||
{{ pagination?.nextPageText || 'Next' }}
|
||||
</button>
|
||||
</div>
|
||||
<Pagination
|
||||
v-if="isPaginationEnabled"
|
||||
:pagination="pagination"
|
||||
:page="page"
|
||||
:total-page="totalPage"
|
||||
:page-range="pageRange"
|
||||
:is-last-page="isLastPage"
|
||||
:is-first-page="isFirstPage"
|
||||
@change="changePage"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -52,40 +42,4 @@ const {
|
||||
padding-top: 2rem;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.pagination {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 2rem 1.25rem 4rem;
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 0 4px;
|
||||
font-weight: 500;
|
||||
color: var(--vp-c-brand-1);
|
||||
background-color: var(--vp-c-bg);
|
||||
border: 1px solid var(--vp-c-brand-1);
|
||||
border-radius: 4px;
|
||||
transition: all var(--t-color);
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
color: var(--vp-c-bg);
|
||||
background-color: var(--vp-c-brand-2);
|
||||
border-color: var(--vp-c-brand-2);
|
||||
}
|
||||
|
||||
.btn[disabled],
|
||||
.btn[disabled]:hover {
|
||||
color: var(--vp-c-gray-1);
|
||||
cursor: not-allowed;
|
||||
background-color: transparent;
|
||||
border-color: var(--vp-c-divider);
|
||||
}
|
||||
|
||||
.page-info {
|
||||
font-weight: 500;
|
||||
color: var(--vp-c-text-3);
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { usePageLang } from 'vuepress/client'
|
||||
import { useExtraBlogData as _useExtraBlogData, useBlogPostData } from '@vuepress-plume/plugin-blog-data/client'
|
||||
import { type Ref, computed } from 'vue'
|
||||
import { useMediaQuery } from '@vueuse/core'
|
||||
import type { PlumeThemeBlogPostItem } from '../../shared/index.js'
|
||||
import { useLocaleLink, useRouteQuery, useThemeLocaleData } from '../composables/index.js'
|
||||
import { toArray } from '../utils/index.js'
|
||||
@ -24,6 +25,7 @@ export function usePostListControl() {
|
||||
const list = useLocalePostList()
|
||||
const blog = computed(() => themeData.value.blog || {})
|
||||
const pagination = computed(() => blog.value.pagination || {})
|
||||
const is960 = useMediaQuery('(max-width: 960px)')
|
||||
|
||||
const postList = computed(() => {
|
||||
const stickyList = list.value.filter(item =>
|
||||
@ -77,8 +79,51 @@ export function usePostListControl() {
|
||||
)
|
||||
})
|
||||
|
||||
const changePage = (offset: number) => {
|
||||
page.value += offset
|
||||
const pageRange = computed(() => {
|
||||
let range: { value: number | string, more?: true }[] = []
|
||||
const total = totalPage.value
|
||||
const _page = page.value
|
||||
const per = is960.value ? 4 : 5
|
||||
|
||||
if (total <= 0)
|
||||
return range
|
||||
if (total <= 10) {
|
||||
range = Array.from({ length: total }, (_, i) => ({ value: i + 1 }))
|
||||
}
|
||||
else {
|
||||
let i = 1
|
||||
let hasMore = false
|
||||
while (i <= total) {
|
||||
if ((_page <= per && i <= per) || (_page >= total - (per - 1) && i >= total - (per - 1))) {
|
||||
hasMore = false
|
||||
range.push({ value: i })
|
||||
}
|
||||
else if (i <= 2 || i >= total - 1) {
|
||||
hasMore = false
|
||||
range.push({ value: i })
|
||||
}
|
||||
else if (
|
||||
(_page > per + 1 || _page < total - (per + 1))
|
||||
&& _page - i < per - 2
|
||||
&& i - _page < per - 2
|
||||
) {
|
||||
hasMore = false
|
||||
range.push({ value: i })
|
||||
}
|
||||
else if (!hasMore) {
|
||||
hasMore = true
|
||||
range.push({ value: i, more: true })
|
||||
}
|
||||
i++
|
||||
}
|
||||
}
|
||||
return range
|
||||
})
|
||||
|
||||
const changePage = (current: number) => {
|
||||
if (page.value === current)
|
||||
return
|
||||
page.value = current
|
||||
window.scrollTo({ top: 0, left: 0, behavior: 'instant' })
|
||||
}
|
||||
|
||||
@ -87,6 +132,7 @@ export function usePostListControl() {
|
||||
postList: finalList,
|
||||
page,
|
||||
totalPage,
|
||||
pageRange,
|
||||
isLastPage,
|
||||
isFirstPage,
|
||||
isPaginationEnabled,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user