您好,登录后才能下订单哦!
密码登录
登录注册
点击 登录注册 即表示同意《亿速云用户服务条款》
# 如何使用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进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。