# 如何使用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进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。