BlogPagination.vue 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147
  1. <script setup>
  2. import { computed, ref } from 'vue'
  3. const props = defineProps({
  4. posts: {
  5. type: Array,
  6. required: true
  7. },
  8. postsPerPage: {
  9. type: Number,
  10. default: 10
  11. }
  12. })
  13. const currentPage = ref(1)
  14. const paginatedPosts = computed(() => {
  15. const start = (currentPage.value - 1) * props.postsPerPage
  16. const end = start + props.postsPerPage
  17. return props.posts.slice(start, end)
  18. })
  19. const totalPages = computed(() => Math.ceil(props.posts.length / props.postsPerPage))
  20. const goToPage = (page) => {
  21. if (page >= 1 && page <= totalPages.value) {
  22. currentPage.value = page
  23. window.scrollTo({ top: 0, behavior: 'smooth' })
  24. }
  25. }
  26. const goToPrevPage = () => goToPage(currentPage.value - 1)
  27. const goToNextPage = () => goToPage(currentPage.value + 1)
  28. </script>
  29. <template>
  30. <div>
  31. <div v-if="posts.length === 0" class="py-12 text-center text-gray-500">
  32. No posts found.
  33. </div>
  34. <div v-else class="divide-y divide-gray-200 dark:divide-slate-200/5">
  35. <ul class="divide-y divide-gray-200 dark:divide-slate-200/5">
  36. <li class="py-12" v-for="{ title, url, date, excerpt, author } of paginatedPosts">
  37. <article class="space-y-2 xl:grid xl:grid-cols-4 xl:space-y-0 xl:items-baseline">
  38. <div class="space-y-5 xl:col-span-3">
  39. <div class="space-y-6">
  40. <h2 class="text-2xl leading-8 font-bold tracking-tight">
  41. <a class="text-gray-900 dark:text-white hover:text-gray-600 dark:hover:text-gray-300" :href="url">{{ title }}</a>
  42. </h2>
  43. <div class="meta-row flex justify-end items-center gap-4 text-sm text-gray-500 dark:text-gray-400 mb-2">
  44. <span v-if="author" class="author">by <span class="font-medium">{{ author }}</span></span>
  45. <span class="date"><time :datetime="new Date(date.time).toISOString()">{{ date.string }}</time></span>
  46. </div>
  47. <div
  48. v-if="excerpt"
  49. class="prose dark:prose-invert max-w-none text-gray-500 dark:text-gray-300"
  50. v-html="excerpt"
  51. ></div>
  52. </div>
  53. <div class="text-base leading-6 font-medium">
  54. <a class="text-blue-600 dark:text-blue-400 hover:text-gray-600 dark:hover:text-gray-300" aria-label="read more" :href="url">Read more →</a>
  55. </div>
  56. </div>
  57. </article>
  58. </li>
  59. </ul>
  60. <div v-if="totalPages > 1" class="pagination-nav">
  61. <button
  62. @click="goToPrevPage"
  63. :disabled="currentPage === 1"
  64. class="pagination-btn"
  65. >
  66. Previous
  67. </button>
  68. <span class="pagination-info">
  69. Page {{ currentPage }} of {{ totalPages }}
  70. </span>
  71. <button
  72. @click="goToNextPage"
  73. :disabled="currentPage === totalPages"
  74. class="pagination-btn"
  75. >
  76. Next
  77. </button>
  78. </div>
  79. </div>
  80. </div>
  81. </template>
  82. <style scoped>
  83. .pagination-nav {
  84. display: flex;
  85. justify-content: center;
  86. align-items: center;
  87. gap: 2.5rem;
  88. margin-top: 2.5rem;
  89. }
  90. .pagination-btn {
  91. padding: 0.5rem 1.5rem;
  92. font-size: 1rem;
  93. font-weight: 500;
  94. color: var(--vp-c-brand);
  95. background: var(--vp-c-bg);
  96. border: 1px solid var(--vp-c-divider);
  97. border-radius: 6px;
  98. cursor: pointer;
  99. transition: background 0.2s, color 0.2s;
  100. }
  101. .pagination-btn:disabled {
  102. color: var(--vp-c-text-3);
  103. background: var(--vp-c-bg-soft);
  104. cursor: not-allowed;
  105. opacity: 0.6;
  106. }
  107. .pagination-info {
  108. font-size: 1rem;
  109. color: var(--vp-c-text-2);
  110. min-width: 140px;
  111. text-align: center;
  112. }
  113. .xl\\:col-span-4.flex.items-center.justify-between {
  114. margin-bottom: 0.5rem;
  115. }
  116. .xl\\:col-span-4 .ml-auto {
  117. margin-left: auto;
  118. text-align: right;
  119. min-width: 120px;
  120. }
  121. .meta-row {
  122. display: flex;
  123. justify-content: space-between;
  124. align-items: center;
  125. margin-bottom: 0.5rem;
  126. }
  127. .meta-row .author {
  128. margin-right: 1.5rem;
  129. }
  130. .meta-row .date {
  131. min-width: 100px;
  132. text-align: right;
  133. }
  134. </style>