perf: 优化博客文章列表页分页组件

This commit is contained in:
pengzhanbo 2024-02-05 15:40:57 +08:00
parent f3feaf499a
commit b764d2a10e
3 changed files with 163 additions and 60 deletions

View 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>

View File

@ -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>

View File

@ -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,