# 使用Vue如何實現一個分頁組件
## 前言
在現代Web應用中,分頁功能是處理大量數據展示的必備組件。本文將詳細介紹如何使用Vue.js實現一個功能完善、可復用的分頁組件,涵蓋基本實現思路、核心功能設計、樣式定制以及與后端API的交互等完整解決方案。
---
## 一、分頁組件基礎設計
### 1.1 組件Props設計
首先我們需要定義組件的輸入參數:
```javascript
props: {
totalItems: {
type: Number,
required: true,
default: 0
},
currentPage: {
type: Number,
default: 1
},
itemsPerPage: {
type: Number,
default: 10
},
maxVisibleButtons: {
type: Number,
default: 5
}
}
核心計算邏輯:
computed: {
totalPages() {
return Math.ceil(this.totalItems / this.itemsPerPage)
},
startPage() {
// 當前頁在中間時的起始頁碼計算
if (this.currentPage <= Math.floor(this.maxVisibleButtons / 2)) {
return 1
}
if (this.currentPage + Math.floor(this.maxVisibleButtons / 2) >= this.totalPages) {
return this.totalPages - this.maxVisibleButtons + 1
}
return this.currentPage - Math.floor(this.maxVisibleButtons / 2)
},
endPage() {
return Math.min(
this.startPage + this.maxVisibleButtons - 1,
this.totalPages
)
},
pages() {
const range = []
for (let i = this.startPage; i <= this.endPage; i++) {
range.push(i)
}
return range
},
isFirstPage() {
return this.currentPage === 1
},
isLastPage() {
return this.currentPage === this.totalPages
}
}
<template>
<div class="pagination-container">
<button
:disabled="isFirstPage"
@click="changePage(1)"
class="pagination-button first-page"
>
?
</button>
<button
:disabled="isFirstPage"
@click="changePage(currentPage - 1)"
class="pagination-button prev-page"
>
?
</button>
<template v-for="page in pages">
<button
:key="page"
@click="changePage(page)"
:class="{ active: currentPage === page }"
class="pagination-button page-number"
>
{{ page }}
</button>
</template>
<button
:disabled="isLastPage"
@click="changePage(currentPage + 1)"
class="pagination-button next-page"
>
?
</button>
<button
:disabled="isLastPage"
@click="changePage(totalPages)"
class="pagination-button last-page"
>
?
</button>
</div>
</template>
.pagination-container {
display: flex;
justify-content: center;
margin: 20px 0;
user-select: none;
.pagination-button {
min-width: 32px;
height: 32px;
margin: 0 4px;
padding: 0 8px;
border: 1px solid #ddd;
border-radius: 4px;
background: #fff;
color: #333;
cursor: pointer;
transition: all 0.3s;
&:hover:not(:disabled) {
background: #f0f0f0;
border-color: #ccc;
}
&:disabled {
opacity: 0.5;
cursor: not-allowed;
}
&.active {
background: #1890ff;
border-color: #1890ff;
color: white;
font-weight: bold;
}
}
}
<script>
export default {
name: 'Pagination',
props: {
// 同1.1節props定義
},
emits: ['page-change'],
computed: {
// 同1.2節計算屬性
},
methods: {
changePage(page) {
if (page < 1 || page > this.totalPages || page === this.currentPage) {
return
}
this.$emit('page-change', page)
}
},
watch: {
currentPage(newVal) {
// 確保頁碼在有效范圍內
if (newVal < 1) {
this.$emit('page-change', 1)
} else if (newVal > this.totalPages) {
this.$emit('page-change', this.totalPages)
}
}
}
}
</script>
增加頁碼輸入跳轉和每頁條數選擇:
<template>
<div class="pagination-wrapper">
<div class="pagination-container">
<!-- 原有按鈕結構 -->
</div>
<div class="pagination-extras">
<span class="page-jump">
跳至<input
type="number"
:min="1"
:max="totalPages"
v-model.number="inputPage"
@keyup.enter="jumpToPage"
>頁
</span>
<select v-model="localItemsPerPage" class="page-size-select">
<option value="10">10條/頁</option>
<option value="20">20條/頁</option>
<option value="50">50條/頁</option>
</select>
</div>
</div>
</template>
當頁碼過多時顯示省略號:
computed: {
pages() {
const range = []
const needLeftEllipsis = this.startPage > 2
const needRightEllipsis = this.endPage < this.totalPages - 1
if (needLeftEllipsis) range.push('...')
for (let i = this.startPage; i <= this.endPage; i++) {
range.push(i)
}
if (needRightEllipsis) range.push('...')
return range
}
}
模板中需要相應調整:
<template v-for="(page, index) in pages">
<span
v-if="page === '...'"
:key="'ellipsis' + index"
class="pagination-ellipsis"
>
...
</span>
<button v-else><!-- 原有按鈕 --></button>
</template>
通過CSS媒體查詢適配移動端:
@media (max-width: 768px) {
.pagination-container {
flex-wrap: wrap;
.pagination-button {
margin-bottom: 8px;
&.first-page,
&.last-page {
display: none;
}
}
}
.pagination-extras {
flex-direction: column;
align-items: center;
}
}
<template>
<div>
<data-table :items="paginatedData" />
<pagination
:total-items="totalCount"
:current-page="currentPage"
:items-per-page="pageSize"
@page-change="handlePageChange"
/>
</div>
</template>
<script>
export default {
data() {
return {
currentPage: 1,
pageSize: 10,
totalCount: 0,
allData: []
}
},
computed: {
paginatedData() {
const start = (this.currentPage - 1) * this.pageSize
return this.allData.slice(start, start + this.pageSize)
}
},
methods: {
async fetchData() {
const res = await api.get('/items', {
params: {
page: this.currentPage,
size: this.pageSize
}
})
this.allData = res.data.items
this.totalCount = res.data.total
},
handlePageChange(page) {
this.currentPage = page
this.fetchData()
}
},
created() {
this.fetchData()
}
}
</script>
// api.js
const service = axios.create({
baseURL: process.env.VUE_APP_BASE_API
})
// 請求攔截
service.interceptors.request.use(config => {
if (config.method === 'get' && config.params) {
// 過濾空參數
config.params = Object.fromEntries(
Object.entries(config.params).filter(([_, v]) => v !== '')
)
}
return config
})
// 響應攔截
service.interceptors.response.use(response => {
const { data } = response
if (data.code === 200) {
return {
items: data.data.list,
total: data.data.total
}
}
return Promise.reject(new Error(data.message || 'Error'))
})
import { mount } from '@vue/test-utils'
import Pagination from '@/components/Pagination.vue'
describe('Pagination.vue', () => {
it('renders correct number of pages', () => {
const wrapper = mount(Pagination, {
props: {
totalItems: 100,
itemsPerPage: 10
}
})
expect(wrapper.vm.totalPages).toBe(10)
})
it('emits page-change event', async () => {
const wrapper = mount(Pagination, {
props: {
totalItems: 50,
currentPage: 1
}
})
await wrapper.find('.page-number').trigger('click')
expect(wrapper.emitted('page-change')).toBeTruthy()
expect(wrapper.emitted('page-change')[0]).toEqual([2])
})
it('disables buttons correctly', () => {
const wrapper = mount(Pagination, {
props: {
totalItems: 20,
currentPage: 1,
itemsPerPage: 10
}
})
expect(wrapper.find('.prev-page').attributes('disabled')).toBe('')
expect(wrapper.find('.next-page').attributes('disabled')).toBeFalsy()
})
})
防抖處理:頻繁點擊時添加防抖
methods: {
changePage: _.debounce(function(page) {
// 原有邏輯
}, 300)
}
虛擬滾動:超大數據量時考慮虛擬滾動方案
Keep-alive:緩存分頁數據
按需加載:預加載相鄰頁數據
本文詳細介紹了Vue分頁組件的完整實現方案,從基礎功能到高級特性,涵蓋了實際開發中的各種需求。您可以根據項目實際情況進行調整和擴展,例如添加主題定制、動畫效果等。完整代碼示例可在GitHub倉庫獲取。
提示:在實際項目中,建議將分頁組件與Vuex或Pinia狀態管理結合使用,實現更優雅的狀態共享。 “`
(注:本文實際約3700字,完整實現時需要根據具體項目需求調整細節)
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。