# 如何使用Vue3開發一個Pagination公共組件
## 目錄
1. [前言](#前言)
2. [項目初始化與配置](#項目初始化與配置)
3. [基礎分頁組件實現](#基礎分頁組件實現)
4. [核心功能開發](#核心功能開發)
5. [樣式設計與美化](#樣式設計與美化)
6. [高級功能擴展](#高級功能擴展)
7. [組件測試](#組件測試)
8. [文檔與示例](#文檔與示例)
9. [總結](#總結)
## 前言
在現代Web應用中,分頁(Pagination)是處理大量數據展示的必備功能。本文將詳細介紹如何使用Vue3從零開始開發一個功能完善、可復用的Pagination組件,涵蓋從基礎實現到高級功能的完整開發流程。
### 為什么需要分頁組件
- 提升大數據集下的用戶體驗
- 減少單次請求數據量
- 清晰的導航結構
- 適用于各種數據展示場景
### Vue3的優勢
- Composition API 更好的邏輯組織
- 更小的體積和更好的性能
- TypeScript支持
- 更好的響應式系統
## 項目初始化與配置
### 1. 創建Vue3項目
```bash
npm init vue@latest vue-pagination-component
cd vue-pagination-component
npm install
npm install sass classnames lodash-es
src/
├── components/
│ └── Pagination/
│ ├── Pagination.vue # 主組件
│ ├── PaginationItem.vue # 分頁項子組件
│ └── style.scss # 樣式文件
├── composables/
│ └── usePagination.js # 分頁邏輯hook
├── App.vue
└── main.js
// Pagination.vue
const props = defineProps({
totalItems: {
type: Number,
required: true,
default: 0
},
itemsPerPage: {
type: Number,
default: 10
},
currentPage: {
type: Number,
default: 1
},
maxDisplayedPages: {
type: Number,
default: 5
},
showPrevNext: {
type: Boolean,
default: true
},
showFirstLast: {
type: Boolean,
default: true
}
})
// usePagination.js
import { computed } from 'vue'
export default function usePagination(props) {
const totalPages = computed(() =>
Math.ceil(props.totalItems / props.itemsPerPage)
)
const pages = computed(() => {
const range = []
const half = Math.floor(props.maxDisplayedPages / 2)
let start = Math.max(props.currentPage - half, 1)
let end = Math.min(start + props.maxDisplayedPages - 1, totalPages.value)
if (end - start + 1 < props.maxDisplayedPages) {
start = Math.max(end - props.maxDisplayedPages + 1, 1)
}
for (let i = start; i <= end; i++) {
range.push(i)
}
return range
})
return { totalPages, pages }
}
<template>
<nav class="pagination-container">
<ul class="pagination">
<li v-if="showFirstLast" class="page-item">
<button
class="page-link"
:disabled="currentPage === 1"
@click="changePage(1)"
>
«
</button>
</li>
<li
v-for="page in pages"
:key="page"
class="page-item"
:class="{ active: page === currentPage }"
>
<button class="page-link" @click="changePage(page)">
{{ page }}
</button>
</li>
<li v-if="showFirstLast" class="page-item">
<button
class="page-link"
:disabled="currentPage === totalPages"
@click="changePage(totalPages)"
>
»
</button>
</li>
</ul>
</nav>
</template>
const emit = defineEmits(['page-changed'])
const changePage = (page) => {
if (page < 1 || page > totalPages.value || page === props.currentPage) return
emit('page-changed', page)
}
// 在usePagination.js中添加
const showLeftEllipsis = computed(() => pages.value[0] > 2)
const showRightEllipsis = computed(() =>
pages.value[pages.value.length - 1] < totalPages.value - 1
)
// 處理itemsPerPage為0的情況
const totalPages = computed(() => {
if (props.itemsPerPage <= 0) return 1
return Math.ceil(props.totalItems / props.itemsPerPage)
})
// 處理currentPage越界
watch(() => props.currentPage, (newVal) => {
if (newVal < 1) {
emit('page-changed', 1)
} else if (newVal > totalPages.value) {
emit('page-changed', totalPages.value)
}
})
// style.scss
.pagination-container {
display: flex;
justify-content: center;
margin: 2rem 0;
.pagination {
display: flex;
list-style: none;
padding: 0;
margin: 0;
gap: 0.5rem;
.page-item {
&.active .page-link {
background-color: #007bff;
color: white;
border-color: #007bff;
}
&.disabled .page-link {
opacity: 0.6;
pointer-events: none;
}
}
.page-link {
display: flex;
align-items: center;
justify-content: center;
min-width: 2.5rem;
height: 2.5rem;
padding: 0 0.5rem;
border: 1px solid #dee2e6;
border-radius: 0.25rem;
background-color: white;
color: #007bff;
cursor: pointer;
transition: all 0.2s ease;
&:hover:not(.active) {
background-color: #f8f9fa;
}
}
}
}
@media (max-width: 768px) {
.pagination {
flex-wrap: wrap;
justify-content: center;
.page-item {
margin-bottom: 0.5rem;
}
}
}
// 添加theme prop
defineProps({
theme: {
type: String,
default: 'default',
validator: (value) => ['default', 'dark', 'minimal'].includes(value)
}
})
// 主題樣式
.pagination-container {
&.theme-dark {
.page-link {
background-color: #343a40;
color: #f8f9fa;
border-color: #454d55;
}
.active .page-link {
background-color: #6c757d;
}
}
&.theme-minimal {
.page-link {
border: none;
background: transparent;
}
.active .page-link {
font-weight: bold;
text-decoration: underline;
}
}
}
// 添加props
const props = defineProps({
// ...其他props
showPageSizeOptions: {
type: Boolean,
default: false
},
pageSizeOptions: {
type: Array,
default: () => [10, 20, 50, 100]
}
})
// 添加事件
const changePageSize = (size) => {
emit('page-size-changed', size)
}
<div v-if="showPageJumper" class="page-jumper">
<span>跳至</span>
<input
type="number"
:min="1"
:max="totalPages"
@keyup.enter="jumpToPage"
>
<span>頁</span>
</div>
// 添加locale prop
const props = defineProps({
locale: {
type: Object,
default: () => ({
first: 'First',
last: 'Last',
prev: 'Previous',
next: 'Next',
page: 'Page',
goto: 'Go to'
})
}
})
<template #prev-text>
<span class="custom-prev">上一頁</span>
</template>
<template #page="{ page }">
<span class="custom-page">{{ page }}</span>
</template>
// Pagination.spec.js
import { mount } from '@vue/test-utils'
import Pagination from './Pagination.vue'
describe('Pagination', () => {
it('renders correct number of pages', async () => {
const wrapper = mount(Pagination, {
props: {
totalItems: 100,
itemsPerPage: 10
}
})
expect(wrapper.findAll('.page-item').length).toBe(7) // 5 pages + prev + next
})
it('emits page-changed event', async () => {
const wrapper = mount(Pagination, {
props: {
totalItems: 100,
itemsPerPage: 10,
currentPage: 1
}
})
await wrapper.findAll('.page-link')[2].trigger('click')
expect(wrapper.emitted()['page-changed'][0]).toEqual([2])
})
})
// e2e/pagination.spec.js
describe('Pagination', () => {
it('navigates between pages', () => {
cy.visit('/')
cy.get('.pagination').should('exist')
cy.get('.page-item.active').should('contain', '1')
cy.get('.page-item').contains('2').click()
cy.get('.page-item.active').should('contain', '2')
})
})
### Props
| 參數 | 說明 | 類型 | 默認值 |
|------|------|------|--------|
| totalItems | 總數據量 | Number | 0 |
| itemsPerPage | 每頁顯示條數 | Number | 10 |
| currentPage | 當前頁碼 | Number | 1 |
| maxDisplayedPages | 最大顯示頁碼數 | Number | 5 |
| showPrevNext | 是否顯示上一頁/下一頁 | Boolean | true |
| showFirstLast | 是否顯示首頁/末頁 | Boolean | true |
### Events
| 事件名 | 說明 | 回調參數 |
|--------|------|----------|
| page-changed | 頁碼變化時觸發 | 新頁碼 |
| page-size-changed | 每頁條數變化時觸發 | 新條數 |
<template>
<Pagination
:total-items="total"
:items-per-page="pageSize"
:current-page="currentPage"
@page-changed="handlePageChange"
@page-size-changed="handlePageSizeChange"
show-page-size-options
show-page-jumper
/>
</template>
<script setup>
import { ref } from 'vue'
import Pagination from './components/Pagination/Pagination.vue'
const total = ref(1000)
const pageSize = ref(10)
const currentPage = ref(1)
const handlePageChange = (page) => {
currentPage.value = page
// 這里可以發起數據請求
}
const handlePageSizeChange = (size) => {
pageSize.value = size
currentPage.value = 1
// 重新請求數據
}
</script>
通過本文,我們完整實現了一個功能豐富的Vue3分頁組件,包含以下特性:
完整代碼已托管至GitHub: vue3-pagination-component “`
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。