mirror of
https://github.com/element-plus/element-plus.git
synced 2026-03-13 07:51:17 +08:00
refactor(pagination): pagination internal logic refactored (#2491)
* refactor(pagination): pagination internal logic refactored * improvement(pagination): improve pr by better naming and misc
This commit is contained in:
@@ -76,6 +76,7 @@ export default {
|
||||
pagesize: '/page',
|
||||
total: 'Total {total}',
|
||||
pageClassifier: '',
|
||||
deprecationWarning: 'Deprecated usages detected, please refer to the el-pagination documentation for more details',
|
||||
},
|
||||
messagebox: {
|
||||
title: 'Message',
|
||||
|
||||
@@ -76,6 +76,7 @@ export default {
|
||||
pagesize: '条/页',
|
||||
total: '共 {total} 条',
|
||||
pageClassifier: '页',
|
||||
deprecationWarning: '你使用了一些已被废弃的用法,请参考 el-pagination 的官方文档',
|
||||
},
|
||||
messagebox: {
|
||||
title: '提示',
|
||||
|
||||
@@ -1,407 +1,371 @@
|
||||
import { mount } from '@vue/test-utils'
|
||||
import { sleep } from '@element-plus/test-utils'
|
||||
import { mount, VueWrapper } from '@vue/test-utils'
|
||||
import Pagination from '../src/index'
|
||||
import { nextTick, ref, h } from 'vue'
|
||||
|
||||
const TIME_OUT = 100
|
||||
const assertElementsExistence = (wrapper: VueWrapper<any>, selectors: string[], existence: boolean) => {
|
||||
selectors.forEach(selector => {
|
||||
expect(wrapper.find(selector).exists()).toBe(existence)
|
||||
})
|
||||
}
|
||||
|
||||
describe('Pagination.vue', () => {
|
||||
test('layout', () => {
|
||||
const wrapper = mount(Pagination, {
|
||||
props: {
|
||||
layout: 'prev, pager, next',
|
||||
},
|
||||
const assertCurrent = (wrapper, page) => {
|
||||
expect(wrapper.find('.el-pager li.active.number').text()).toBe(String(page))
|
||||
}
|
||||
const assertPages = (wrapper, total) => {
|
||||
expect(wrapper.find('.el-pagination .el-pager li:last-child').text()).toBe(String(total))
|
||||
}
|
||||
|
||||
describe('Pagination', () => {
|
||||
describe('test invalid usages', () => {
|
||||
const cacheWarn = console.warn
|
||||
beforeEach(() => {
|
||||
console.warn = jest.fn()
|
||||
})
|
||||
afterEach(() => {
|
||||
console.warn = cacheWarn
|
||||
})
|
||||
test('both absence of total & pageCount is invalid', async () => {
|
||||
expect(console.warn).not.toHaveBeenCalled()
|
||||
const total = ref(undefined)
|
||||
const wrapper = mount({
|
||||
setup() {
|
||||
return () => {
|
||||
return h(Pagination, { total: total.value })
|
||||
}
|
||||
},
|
||||
})
|
||||
expect(wrapper.find('.el-pagination').exists()).toBe(false)
|
||||
expect(console.warn).toHaveBeenCalled()
|
||||
total.value = 100
|
||||
await nextTick()
|
||||
expect(wrapper.find('.el-pagination').exists()).toBe(true)
|
||||
})
|
||||
test('current-page defined while absence of current-page listener is invalid', () => {
|
||||
expect(console.warn).not.toHaveBeenCalled()
|
||||
const wrapper = mount({
|
||||
setup() {
|
||||
return () => {
|
||||
return h(Pagination, {
|
||||
total: 100,
|
||||
currentPage: 1,
|
||||
})
|
||||
}
|
||||
},
|
||||
})
|
||||
expect(wrapper.find('.el-pagination').exists()).toBe(false)
|
||||
expect(console.warn).toHaveBeenCalled()
|
||||
})
|
||||
test('layout with `sizes` restrictions(page-count)', () => {
|
||||
expect(console.warn).not.toHaveBeenCalled()
|
||||
const wrapper = mount({
|
||||
setup() {
|
||||
return () => {
|
||||
return h(Pagination, {
|
||||
layout: 'sizes, pager',
|
||||
pageCount: 10,
|
||||
})
|
||||
}
|
||||
},
|
||||
})
|
||||
expect(wrapper.find('.el-pagination').exists()).toBe(false)
|
||||
expect(console.warn).toHaveBeenCalled()
|
||||
})
|
||||
test('layout with `sizes` restrictions(page-size)', () => {
|
||||
expect(console.warn).not.toHaveBeenCalled()
|
||||
const wrapper = mount({
|
||||
setup() {
|
||||
return () => {
|
||||
return h(Pagination, {
|
||||
layout: 'sizes, pager',
|
||||
pageSize: 10,
|
||||
})
|
||||
}
|
||||
},
|
||||
})
|
||||
expect(wrapper.find('.el-pagination').exists()).toBe(false)
|
||||
expect(console.warn).toHaveBeenCalled()
|
||||
})
|
||||
expect(wrapper.find('button.btn-prev').exists()).toBe(true)
|
||||
expect(wrapper.find('ul.el-pager').exists()).toBe(true)
|
||||
expect(wrapper.find('button.btn-next').exists()).toBe(true)
|
||||
expect(wrapper.find('.el-pagination__jump').exists()).toBe(false)
|
||||
expect(wrapper.find('.el-pagination__rightwrapper').exists()).toBe(false)
|
||||
expect(wrapper.find('.el-pagination__total').exists()).toBe(false)
|
||||
})
|
||||
|
||||
test('change layout value', async () => {
|
||||
const layout = ref('prev, pager, next')
|
||||
|
||||
const Comp = {
|
||||
describe('test layout & layout reactive change', () => {
|
||||
const layoutRef = ref('')
|
||||
const wrapper = mount({
|
||||
setup() {
|
||||
return () => {
|
||||
return h(Pagination, {
|
||||
class: 'pagination-wrapper',
|
||||
layout: layout.value,
|
||||
total: 100,
|
||||
layout: layoutRef.value,
|
||||
})
|
||||
}
|
||||
},
|
||||
}
|
||||
const wrapper = mount(Comp)
|
||||
|
||||
expect(wrapper.find('button.btn-prev').exists()).toBe(true)
|
||||
expect(wrapper.find('ul.el-pager').exists()).toBe(true)
|
||||
expect(wrapper.find('button.btn-next').exists()).toBe(true)
|
||||
expect(wrapper.find('.el-pagination__rightwrapper').exists()).toBe(false)
|
||||
|
||||
layout.value = 'prev, pager, next, ->'
|
||||
await nextTick()
|
||||
expect(wrapper.find('button.btn-prev').exists()).toBe(true)
|
||||
expect(wrapper.find('ul.el-pager').exists()).toBe(true)
|
||||
expect(wrapper.find('button.btn-next').exists()).toBe(true)
|
||||
expect(wrapper.find('.el-pagination__rightwrapper').exists()).toBe(false)
|
||||
|
||||
layout.value = 'prev, pager, next, ->, jumper'
|
||||
await nextTick()
|
||||
expect(wrapper.find('button.btn-prev').exists()).toBe(true)
|
||||
expect(wrapper.find('ul.el-pager').exists()).toBe(true)
|
||||
expect(wrapper.find('button.btn-next').exists()).toBe(true)
|
||||
expect(wrapper.find('.el-pagination__rightwrapper').exists()).toBe(true)
|
||||
expect(wrapper.find('.el-pagination__rightwrapper .el-pagination__jump').exists()).toBe(true)
|
||||
|
||||
layout.value = 'prev, pager, next, ->, jumper, sizes'
|
||||
await nextTick()
|
||||
expect(wrapper.find('button.btn-prev').exists()).toBe(true)
|
||||
expect(wrapper.find('ul.el-pager').exists()).toBe(true)
|
||||
expect(wrapper.find('button.btn-next').exists()).toBe(true)
|
||||
expect(wrapper.find('.el-pagination__rightwrapper').exists()).toBe(true)
|
||||
expect(wrapper.find('.el-pagination__rightwrapper .el-pagination__jump').exists()).toBe(true)
|
||||
expect(wrapper.find('.el-pagination__rightwrapper .el-pagination__sizes').exists()).toBe(true)
|
||||
|
||||
layout.value = 'prev, pager, next, ->, jumper, sizes, total'
|
||||
await nextTick()
|
||||
expect(wrapper.find('button.btn-prev').exists()).toBe(true)
|
||||
expect(wrapper.find('ul.el-pager').exists()).toBe(true)
|
||||
expect(wrapper.find('button.btn-next').exists()).toBe(true)
|
||||
expect(wrapper.find('.el-pagination__rightwrapper').exists()).toBe(true)
|
||||
expect(wrapper.find('.el-pagination__rightwrapper .el-pagination__jump').exists()).toBe(true)
|
||||
expect(wrapper.find('.el-pagination__rightwrapper .el-pagination__sizes').exists()).toBe(true)
|
||||
expect(wrapper.find('.el-pagination__rightwrapper .el-pagination__total').exists()).toBe(true)
|
||||
})
|
||||
|
||||
test('slot', () => {
|
||||
const TestComponent = {
|
||||
template: `
|
||||
<el-pagination
|
||||
layout="slot, prev, pager, next"
|
||||
:page-size="25"
|
||||
:total="100">
|
||||
<span class="slot-test">slot test</span>
|
||||
</el-pagination>
|
||||
`,
|
||||
components: {
|
||||
'el-pagination': Pagination,
|
||||
},
|
||||
}
|
||||
const wrapper = mount(TestComponent)
|
||||
expect(wrapper.find('.slot-test').exists()).toBe(true)
|
||||
})
|
||||
|
||||
test('small', () => {
|
||||
const wrapper = mount(Pagination, {
|
||||
props: {
|
||||
small: true,
|
||||
},
|
||||
})
|
||||
expect(wrapper.vm.$el.classList.contains('el-pagination--small')).toBe(true)
|
||||
})
|
||||
|
||||
test('pageSize', () => {
|
||||
const wrapper = mount(Pagination, {
|
||||
props: {
|
||||
pageSize: 25,
|
||||
total: 100,
|
||||
},
|
||||
test('layout empty', async () => {
|
||||
await nextTick()
|
||||
expect(wrapper.find('.el-pagination').exists()).toBe(false)
|
||||
})
|
||||
expect(wrapper.findAll('li.number').length).toBe(4)
|
||||
})
|
||||
|
||||
test('pageSize: NaN', () => {
|
||||
const wrapper = mount(Pagination, {
|
||||
props: {
|
||||
pageSize: NaN,
|
||||
total: 100,
|
||||
},
|
||||
})
|
||||
expect(wrapper.findAll('li.number').length).toBe(7)
|
||||
})
|
||||
|
||||
test('pageCount', () => {
|
||||
const wrapper = mount(Pagination, {
|
||||
props: {
|
||||
pageSize: 25,
|
||||
pagerCount: 5,
|
||||
pageCount: 50,
|
||||
},
|
||||
})
|
||||
expect(wrapper.findAll('li.number').length).toBe(5)
|
||||
})
|
||||
|
||||
test('pagerCount', () => {
|
||||
const wrapper = mount(Pagination, {
|
||||
props: {
|
||||
pageSize: 25,
|
||||
total: 1000,
|
||||
pagerCount: 21,
|
||||
},
|
||||
})
|
||||
expect(wrapper.findAll('li.number').length).toBe(21)
|
||||
})
|
||||
|
||||
test('will work without total & page-count', async () => {
|
||||
const wrapper = mount(Pagination, {
|
||||
props: {
|
||||
pageSize: 25,
|
||||
currentPage: 2,
|
||||
},
|
||||
})
|
||||
wrapper.find('.btn-prev').trigger('click')
|
||||
await sleep(TIME_OUT)
|
||||
expect(wrapper.vm.internalCurrentPage).toEqual(1)
|
||||
wrapper.find('.btn-prev').trigger('click')
|
||||
expect(wrapper.vm.internalCurrentPage).toEqual(1)
|
||||
})
|
||||
|
||||
test('currentPage', () => {
|
||||
const wrapper = mount(Pagination, {
|
||||
props: {
|
||||
pageSize: 20,
|
||||
total: 200,
|
||||
currentPage: 3,
|
||||
},
|
||||
})
|
||||
expect(wrapper.find('li.number.active').text()).toEqual('3')
|
||||
})
|
||||
|
||||
test('currentPage: NaN', () => {
|
||||
const wrapper = mount(Pagination, {
|
||||
props: {
|
||||
pageSize: 20,
|
||||
total: 200,
|
||||
currentPage: NaN,
|
||||
},
|
||||
})
|
||||
expect(wrapper.find('li.number.active').text()).toEqual('1')
|
||||
expect(wrapper.vm.$el.querySelectorAll('li.number').length).toBe(7)
|
||||
})
|
||||
|
||||
test('layout is empty', () => {
|
||||
const wrapper = mount(Pagination, {
|
||||
props: {
|
||||
layout: '',
|
||||
},
|
||||
})
|
||||
expect(wrapper.vm.$el.textContent).toEqual('')
|
||||
})
|
||||
})
|
||||
|
||||
describe('click pager', () => {
|
||||
test('click ul', () => {
|
||||
const wrapper = mount(Pagination, {
|
||||
props: {
|
||||
total: 1000,
|
||||
},
|
||||
})
|
||||
wrapper.find('.el-pager').trigger('click')
|
||||
expect(wrapper.vm.internalCurrentPage).toEqual(1)
|
||||
})
|
||||
|
||||
test('click li', () => {
|
||||
const wrapper = mount(Pagination, {
|
||||
props: {
|
||||
total: 1000,
|
||||
},
|
||||
})
|
||||
wrapper.findAll('.el-pager li.number')[1].trigger('click')
|
||||
expect(wrapper.vm.internalCurrentPage).toEqual(2)
|
||||
})
|
||||
|
||||
test('click next icon-more', () => {
|
||||
const wrapper = mount(Pagination, {
|
||||
props: {
|
||||
total: 1000,
|
||||
},
|
||||
})
|
||||
wrapper.find('.btn-quicknext.more').trigger('click')
|
||||
expect(wrapper.vm.internalCurrentPage).toEqual(6)
|
||||
})
|
||||
|
||||
test('click prev icon-more', async () => {
|
||||
const wrapper = mount(Pagination, {
|
||||
props: {
|
||||
total: 1000,
|
||||
},
|
||||
})
|
||||
wrapper.find('.btn-quicknext.more').trigger('click')
|
||||
await sleep(TIME_OUT)
|
||||
expect(wrapper.find('.btn-quickprev.more').exists()).toBe(true)
|
||||
wrapper.find('.btn-quickprev.more').trigger('click')
|
||||
expect(wrapper.vm.internalCurrentPage).toEqual(1)
|
||||
})
|
||||
|
||||
test('click last page', async () => {
|
||||
const wrapper = mount(Pagination, {
|
||||
props: {
|
||||
total: 1000,
|
||||
},
|
||||
})
|
||||
const nodes = wrapper.findAll('li.number')
|
||||
nodes[nodes.length - 1].trigger('click')
|
||||
await sleep(TIME_OUT)
|
||||
expect(wrapper.find('.btn-quickprev.more').exists()).toBe(true)
|
||||
expect(wrapper.find('.btn-quicknext.more').exists()).toBe(false)
|
||||
})
|
||||
|
||||
test('should emit change size evt and update pageSize', async () => {
|
||||
const onSizeChange = jest.fn()
|
||||
const wrapper = mount({
|
||||
components: {
|
||||
'el-pagination': Pagination,
|
||||
},
|
||||
template: `
|
||||
<el-pagination
|
||||
popper-class="select-dropdown-klass"
|
||||
@size-change="onSizeChange"
|
||||
v-model:page-size="pageSize"
|
||||
:total="1000"
|
||||
:page-sizes="[100, 200, 300]"
|
||||
layout="sizes, pager"
|
||||
/>
|
||||
`,
|
||||
methods: {
|
||||
onSizeChange,
|
||||
},
|
||||
data(){
|
||||
return {
|
||||
pageSize: 200,
|
||||
const layoutSelectorPairs = [
|
||||
['sizes', '.el-pagination__sizes'],
|
||||
['prev', 'button.btn-prev'],
|
||||
['pager', 'ul.el-pager'],
|
||||
['next', 'button.btn-next'],
|
||||
['jumper', '.el-pagination__jump'],
|
||||
['total', '.el-pagination__total'],
|
||||
]
|
||||
layoutSelectorPairs.forEach(([layout], idx) => {
|
||||
test(`layout with only '${layout}'`, async () => {
|
||||
layoutRef.value = layout
|
||||
await nextTick()
|
||||
for(let i = 0; i < layoutSelectorPairs.length; i++) {
|
||||
expect(wrapper.find(layoutSelectorPairs[i][1]).exists()).toBe(i === idx)
|
||||
}
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
const items = document.querySelector('.select-dropdown-klass').querySelectorAll('.el-select-dropdown__item:not(.selected)');
|
||||
(items[0] as HTMLOptionElement)?.click()
|
||||
expect(onSizeChange).toHaveBeenCalled()
|
||||
expect(wrapper.vm.pageSize).toBe(100)
|
||||
expect(wrapper.findComponent(Pagination).emitted()).toHaveProperty('size-change')
|
||||
})
|
||||
|
||||
|
||||
test('should handle total size change', async () => {
|
||||
const onCurrentChange = jest.fn()
|
||||
const wrapper = mount({
|
||||
components: {
|
||||
[Pagination.name]: Pagination,
|
||||
},
|
||||
template: `
|
||||
<el-pagination
|
||||
:total="total"
|
||||
:page-size="pageSize"
|
||||
@current-change="onCurrentChange"
|
||||
v-model:currentPage="currentPage"
|
||||
/>
|
||||
},
|
||||
`,
|
||||
methods: {
|
||||
onCurrentChange,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
currentPage: 3,
|
||||
total: 1000,
|
||||
pageSize: 100,
|
||||
}
|
||||
},
|
||||
test(`layout with '->, total'`, async () => {
|
||||
layoutRef.value = '->, total'
|
||||
await nextTick()
|
||||
assertElementsExistence(wrapper, ['.el-pagination__total', '.el-pagination__rightwrapper'], true)
|
||||
})
|
||||
|
||||
await nextTick()
|
||||
test('layout with default layout prop', () => {
|
||||
const wrapper = mount({
|
||||
setup() {
|
||||
return () => {
|
||||
return h(Pagination, {
|
||||
total: 100,
|
||||
})
|
||||
}
|
||||
},
|
||||
})
|
||||
assertElementsExistence(wrapper, [
|
||||
'.el-pagination__rightwrapper',
|
||||
'button.btn-prev',
|
||||
'ul.el-pager',
|
||||
'button.btn-next',
|
||||
'.el-pagination__jump',
|
||||
], true)
|
||||
})
|
||||
|
||||
expect(wrapper.vm.currentPage).toBe(3)
|
||||
|
||||
wrapper.vm.total = 100
|
||||
await nextTick()
|
||||
expect(wrapper.vm.currentPage).toBe(1)
|
||||
expect(onCurrentChange).toHaveBeenCalledWith(1)
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
test('repeat click next & change current page', async () => {
|
||||
const onCurrentChange = jest.fn()
|
||||
const wrapper = mount({
|
||||
components: {
|
||||
[Pagination.name]: Pagination,
|
||||
},
|
||||
template: `
|
||||
<el-pagination
|
||||
:total="total"
|
||||
:page-size="pageSize"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
@current-change="onCurrentChange"
|
||||
v-model:currentPage="currentPage"
|
||||
/>
|
||||
},
|
||||
`,
|
||||
methods: {
|
||||
onCurrentChange,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
currentPage: 1,
|
||||
total: 400,
|
||||
pageSize: 100,
|
||||
test('test layout with slot', () => {
|
||||
const TestComponent = {
|
||||
template: `
|
||||
<el-pagination
|
||||
layout="slot, prev, pager, next"
|
||||
:page-size="25"
|
||||
:total="100">
|
||||
<span class="slot-test">slot test</span>
|
||||
</el-pagination>
|
||||
`,
|
||||
components: {
|
||||
'el-pagination': Pagination,
|
||||
},
|
||||
}
|
||||
},
|
||||
const wrapper = mount(TestComponent)
|
||||
expect(wrapper.find('.slot-test').exists()).toBe(true)
|
||||
})
|
||||
|
||||
test('test small layout', () => {
|
||||
const wrapper = mount({
|
||||
setup() {
|
||||
return () => {
|
||||
return h(Pagination, {
|
||||
total: 100,
|
||||
small: true,
|
||||
})
|
||||
}
|
||||
},
|
||||
})
|
||||
expect(wrapper.vm.$el.classList.contains('el-pagination--small')).toBe(true)
|
||||
})
|
||||
|
||||
test('test with background', async () => {
|
||||
const withBackground = ref(true)
|
||||
const wrapper = mount({
|
||||
setup() {
|
||||
return () => {
|
||||
return h(Pagination, {
|
||||
total: 100,
|
||||
background: withBackground.value,
|
||||
})
|
||||
}
|
||||
},
|
||||
})
|
||||
expect(wrapper.find('.is-background').exists()).toBe(true)
|
||||
withBackground.value = false
|
||||
await nextTick()
|
||||
expect(wrapper.find('.is-background').exists()).toBe(false)
|
||||
})
|
||||
|
||||
test('test hide-on-single-page prop', async () => {
|
||||
const hideOnSinglePage = ref(false)
|
||||
const wrapper = mount({
|
||||
setup() {
|
||||
return () => {
|
||||
return h(Pagination, {
|
||||
total: 10, // deivded by default page-size(10), there will be only one page
|
||||
hideOnSinglePage: hideOnSinglePage.value,
|
||||
})
|
||||
}
|
||||
},
|
||||
})
|
||||
expect(wrapper.find('.el-pagination').exists()).toBe(true)
|
||||
hideOnSinglePage.value = true
|
||||
await nextTick()
|
||||
expect(wrapper.find('.el-pagination').exists()).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
await nextTick()
|
||||
describe('test pageSize & currentPage reactive change', () => {
|
||||
test(`test pageSize change`, async () => {
|
||||
const pageSize = ref(10)
|
||||
const wrapper = mount({
|
||||
setup() {
|
||||
return () => {
|
||||
return h(Pagination, {
|
||||
layout: 'pager',
|
||||
total: 100,
|
||||
pageSize: pageSize.value,
|
||||
})
|
||||
}
|
||||
},
|
||||
})
|
||||
// total pages = Math.ceil(total / pageSize)
|
||||
assertPages(wrapper, 10)
|
||||
pageSize.value = 20
|
||||
await nextTick()
|
||||
assertPages(wrapper, 5)
|
||||
pageSize.value = 55
|
||||
await nextTick()
|
||||
assertPages(wrapper, 2)
|
||||
})
|
||||
test('test currentPage change', async () => {
|
||||
const pageSize = ref(10)
|
||||
const defaultCurrentPage = ref(2)
|
||||
const wrapper = mount({
|
||||
setup() {
|
||||
return () => {
|
||||
return h(Pagination, {
|
||||
layout: 'prev, pager, next',
|
||||
total: 100,
|
||||
pageSize: pageSize.value,
|
||||
defaultCurrentPage: defaultCurrentPage.value,
|
||||
})
|
||||
}
|
||||
},
|
||||
})
|
||||
assertCurrent(wrapper, 2)
|
||||
defaultCurrentPage.value = 1
|
||||
assertCurrent(wrapper, 2) // still 2
|
||||
await wrapper.find('.el-pager li:last-child').trigger('click')
|
||||
assertCurrent(wrapper, 10)
|
||||
await wrapper.find('button.btn-prev').trigger('click')
|
||||
assertCurrent(wrapper, 9)
|
||||
await wrapper.find('button.btn-next').trigger('click')
|
||||
assertCurrent(wrapper, 10)
|
||||
pageSize.value = 50
|
||||
await nextTick()
|
||||
assertCurrent(wrapper, 2)
|
||||
})
|
||||
|
||||
expect(wrapper.vm.currentPage).toBe(1)
|
||||
wrapper.find('.btn-next').trigger('click')
|
||||
await nextTick()
|
||||
expect(wrapper.vm.currentPage).toBe(2)
|
||||
wrapper.vm.currentPage = 1
|
||||
await nextTick()
|
||||
expect(wrapper.vm.currentPage).toBe(1)
|
||||
wrapper.find('.btn-next').trigger('click')
|
||||
await nextTick()
|
||||
expect(wrapper.vm.currentPage).toBe(2)
|
||||
})
|
||||
test('test pageCount change and side effect', async () => {
|
||||
const pageCount = ref(10)
|
||||
const wrapper = mount({
|
||||
setup() {
|
||||
return () => {
|
||||
return h(Pagination, {
|
||||
layout: 'prev, pager, next',
|
||||
pageCount: pageCount.value,
|
||||
})
|
||||
}
|
||||
},
|
||||
})
|
||||
assertPages(wrapper, 10)
|
||||
pageCount.value = 20
|
||||
await nextTick()
|
||||
assertPages(wrapper, 20)
|
||||
await wrapper.find('.el-pager li:last-child').trigger('click')
|
||||
assertCurrent(wrapper, 20)
|
||||
pageCount.value = 5
|
||||
await nextTick()
|
||||
// side effect, if currentPage is greater than pageCount
|
||||
// currentPage should change accordingly
|
||||
assertPages(wrapper, 5)
|
||||
assertCurrent(wrapper, 5)
|
||||
})
|
||||
|
||||
test('repeat click prev & change current page', async () => {
|
||||
const onCurrentChange = jest.fn()
|
||||
const wrapper = mount({
|
||||
components: {
|
||||
[Pagination.name]: Pagination,
|
||||
},
|
||||
template: `
|
||||
<el-pagination
|
||||
:total="total"
|
||||
:page-size="pageSize"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
@current-change="onCurrentChange"
|
||||
v-model:currentPage="currentPage"
|
||||
/>
|
||||
},
|
||||
`,
|
||||
methods: {
|
||||
onCurrentChange,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
currentPage: 2,
|
||||
total: 400,
|
||||
pageSize: 100,
|
||||
}
|
||||
},
|
||||
test('test listener work', async () => {
|
||||
const pageSizeWatcher = jest.fn()
|
||||
const currentPageWatcher = jest.fn()
|
||||
const wrapper = mount({
|
||||
setup() {
|
||||
return () => {
|
||||
return h(Pagination, {
|
||||
total: 100,
|
||||
layout: 'prev, pager, next, sizes',
|
||||
['onUpdate:currentPage']: currentPageWatcher,
|
||||
['onUpdate:pageSize']: pageSizeWatcher,
|
||||
})
|
||||
}
|
||||
},
|
||||
})
|
||||
await wrapper.find('.el-pager li:last-child').trigger('click')
|
||||
assertCurrent(wrapper, 10 /* Math.ceil(100/10) */)
|
||||
expect(currentPageWatcher).toHaveBeenCalled()
|
||||
await wrapper.find('.el-select').trigger('click')
|
||||
await wrapper.getComponent('.el-select-dropdown').find('li:nth-child(2)').trigger('click')
|
||||
expect(pageSizeWatcher).toHaveBeenCalled()
|
||||
assertCurrent(wrapper, 5/* Math.ceil(100/20) */)
|
||||
})
|
||||
})
|
||||
|
||||
await nextTick()
|
||||
describe('test a11y supports', () => {
|
||||
test('test a11y attributes', async () => {
|
||||
const wrapper = mount({
|
||||
setup() {
|
||||
return () => {
|
||||
return h(Pagination, {
|
||||
total: 100,
|
||||
})
|
||||
}
|
||||
},
|
||||
})
|
||||
expect(wrapper.find('.el-pagination').attributes('aria-label')).toBe('pagination')
|
||||
expect(wrapper.find('.el-pagination').attributes('role')).toBe('pagination')
|
||||
expect(wrapper.find('.el-pagination .btn-prev').attributes('aria-disabled')).toBe('true')
|
||||
expect(wrapper.find('.el-pagination .btn-next').attributes('aria-disabled')).toBe('false')
|
||||
expect(wrapper.find('.el-pager li:first-child').attributes('aria-current')).toBe('true')
|
||||
expect(wrapper.find('.el-pager li:last-child').attributes('aria-current')).toBe('false')
|
||||
await wrapper.find('.el-pager li:last-child').trigger('click')
|
||||
expect(wrapper.find('.el-pagination .btn-prev').attributes('aria-disabled')).toBe('false')
|
||||
expect(wrapper.find('.el-pagination .btn-next').attributes('aria-disabled')).toBe('true')
|
||||
expect(wrapper.find('.el-pager li:first-child').attributes('aria-current')).toBe('false')
|
||||
expect(wrapper.find('.el-pager li:last-child').attributes('aria-current')).toBe('true')
|
||||
})
|
||||
|
||||
expect(wrapper.vm.currentPage).toBe(2)
|
||||
wrapper.find('.btn-prev').trigger('click')
|
||||
await nextTick()
|
||||
expect(wrapper.vm.currentPage).toBe(1)
|
||||
wrapper.vm.currentPage = 2
|
||||
await nextTick()
|
||||
expect(wrapper.vm.currentPage).toBe(2)
|
||||
wrapper.find('.btn-prev').trigger('click')
|
||||
await nextTick()
|
||||
expect(wrapper.vm.currentPage).toBe(1)
|
||||
test('test tabindex interactive', async () => {
|
||||
const wrapper = mount({
|
||||
setup() {
|
||||
return () => {
|
||||
return h(Pagination, {
|
||||
total: 100,
|
||||
})
|
||||
}
|
||||
},
|
||||
})
|
||||
await wrapper.find('.el-pager li:nth-child(2)').trigger('click')
|
||||
assertCurrent(wrapper, 2)
|
||||
await wrapper.find('.el-pager li:nth-child(3)').trigger('click', {
|
||||
key: 'Enter',
|
||||
})
|
||||
assertCurrent(wrapper, 3)
|
||||
// TODO getComputedStyle is not implemented in jsdom, so I duno how to assert style of psuedo-class
|
||||
/*
|
||||
* await wrapper.find('.el-pager li:nth-child(3)').trigger('keyup', {
|
||||
* key: 'Tab',
|
||||
* })
|
||||
* const style = window.getComputedStyle(wrapper.find('.el-pager li:nth-child(4)').element, ':focus-visible')
|
||||
* expect(style.outline).toBeTruthy()
|
||||
*/
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import {
|
||||
defineComponent,
|
||||
h,
|
||||
ref,
|
||||
computed,
|
||||
watch,
|
||||
provide,
|
||||
computed,
|
||||
defineComponent,
|
||||
getCurrentInstance,
|
||||
} from 'vue'
|
||||
import { warn } from '@element-plus/utils/error'
|
||||
import { t } from '@element-plus/locale'
|
||||
import { IPagination } from './pagination'
|
||||
|
||||
import Prev from './prev.vue'
|
||||
@@ -15,10 +17,17 @@ import Jumper from './jumper.vue'
|
||||
import Total from './total.vue'
|
||||
import Pager from './pager.vue'
|
||||
|
||||
const getValidPageSize = (val: number) => Number.isNaN(val) ? 10 : val
|
||||
/**
|
||||
* It it user's responsibility to guarantee that the value of props.total... is number
|
||||
* (same as pageSize, defaultPageSize, currentPage, defaultCurrentPage, pageCount)
|
||||
* Otherwise we can reasonable infer that the corresponding field is absent
|
||||
*/
|
||||
const isAbsent = v => typeof v !== 'number'
|
||||
|
||||
const componentName = 'ElPagination'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ElPagination',
|
||||
name: componentName,
|
||||
|
||||
components: {
|
||||
Prev,
|
||||
@@ -29,14 +38,23 @@ export default defineComponent({
|
||||
Pager,
|
||||
},
|
||||
props: {
|
||||
pageSize: {
|
||||
total: {
|
||||
type: Number,
|
||||
default: 10,
|
||||
},
|
||||
|
||||
small: Boolean,
|
||||
pageSize: {
|
||||
type: Number,
|
||||
},
|
||||
|
||||
total: {
|
||||
defaultPageSize: {
|
||||
type: Number,
|
||||
},
|
||||
|
||||
currentPage: {
|
||||
type: Number,
|
||||
},
|
||||
|
||||
defaultCurrentPage: {
|
||||
type: Number,
|
||||
},
|
||||
|
||||
@@ -54,11 +72,6 @@ export default defineComponent({
|
||||
default: 7,
|
||||
},
|
||||
|
||||
currentPage: {
|
||||
type: Number,
|
||||
default: 1,
|
||||
},
|
||||
|
||||
layout: {
|
||||
type: String,
|
||||
default: 'prev, pager, next, jumper, ->, total',
|
||||
@@ -86,6 +99,8 @@ export default defineComponent({
|
||||
default: '',
|
||||
},
|
||||
|
||||
small: Boolean,
|
||||
|
||||
background: Boolean,
|
||||
|
||||
disabled: Boolean,
|
||||
@@ -94,201 +109,213 @@ export default defineComponent({
|
||||
},
|
||||
|
||||
emits: [
|
||||
'update:current-page',
|
||||
'update:page-size',
|
||||
// events below are depracated
|
||||
// v-model:current-page and v-model:page-size are better choices
|
||||
'size-change',
|
||||
'current-change',
|
||||
'prev-click',
|
||||
'next-click',
|
||||
'update:currentPage',
|
||||
'update:pageSize',
|
||||
],
|
||||
setup(props, { emit }) {
|
||||
const lastEmittedPage = ref(-1)
|
||||
const userChangePageSize = ref(false)
|
||||
const internalPageSize = ref(getValidPageSize(props.pageSize))
|
||||
|
||||
const internalPageCount = computed<Nullable<number>>(() => {
|
||||
if (typeof props.total === 'number') {
|
||||
return Math.max(1, Math.ceil(props.total / internalPageSize.value))
|
||||
} else if (typeof props.pageCount === 'number') {
|
||||
return Math.max(1, props.pageCount)
|
||||
setup(props, { emit, slots }) {
|
||||
const vnodeProps = getCurrentInstance().vnode.props || {}
|
||||
// we can find @xxx="xxx" props on `vnodeProps` to check if user bind corresponding events
|
||||
const hasCurrentPageListener = 'onUpdate:currentPage' in vnodeProps || 'onCurrentChange' in vnodeProps
|
||||
const hasPageSizeListener = 'onUpdate:pageSize' in vnodeProps || 'onSizeChange' in vnodeProps
|
||||
const assertValidUsage = computed(() => {
|
||||
// Users have to set either one, otherwise count of pages cannot be determined
|
||||
if (isAbsent(props.total) && isAbsent(props.pageCount)) return false
|
||||
// <el-pagination ...otherProps :current-page="xxx" /> without corresponding listener is forbidden now
|
||||
// Users have to use two way binding of `currentPage`
|
||||
// If users just want to provide a default value, `defaultCurrentPage` is here for you
|
||||
if (!isAbsent(props.currentPage) && !hasCurrentPageListener) return false
|
||||
// When you want to change sizes, things get more complex, detailed below
|
||||
// Basically the most important value we need is page count
|
||||
// either directly from props.pageCount
|
||||
// or calculated from props.total
|
||||
// we will take props.pageCount precedence over props.total
|
||||
if (props.layout.includes('sizes')) {
|
||||
if (!isAbsent(props.pageCount)) {
|
||||
// if props.pageCount is assign by user, then user have to watch pageSize change
|
||||
// and recalculate pageCount
|
||||
if (!hasPageSizeListener) return false
|
||||
} else if (!isAbsent(props.total)) {
|
||||
// Otherwise, we will see if user have props.pageSize defined
|
||||
// If so, meaning user want to have pageSize controlled himself/herself from component
|
||||
// Thus page size listener is required
|
||||
// users are account for page size change
|
||||
if (!isAbsent(props.pageSize)) {
|
||||
if (!hasPageSizeListener) {
|
||||
return false
|
||||
}
|
||||
} else {
|
||||
// (else block just for explaination)
|
||||
// else page size is controlled by el-pagination internally
|
||||
}
|
||||
}
|
||||
}
|
||||
return null
|
||||
return true
|
||||
})
|
||||
|
||||
const internalCurrentPage = ref(getValidCurrentPage(props.currentPage))
|
||||
const innerPageSize = ref(isAbsent(props.defaultPageSize) ? 10 : props.defaultPageSize)
|
||||
const innerCurrentPage = ref(isAbsent(props.defaultCurrentPage) ? 1 : props.defaultCurrentPage)
|
||||
|
||||
function emitChange() {
|
||||
if (
|
||||
internalCurrentPage.value !== lastEmittedPage.value ||
|
||||
userChangePageSize.value
|
||||
) {
|
||||
lastEmittedPage.value = internalCurrentPage.value
|
||||
userChangePageSize.value = false
|
||||
emit('update:currentPage', internalCurrentPage.value)
|
||||
emit('current-change', internalCurrentPage.value)
|
||||
const pageSizeBridge = computed({
|
||||
get() {
|
||||
return isAbsent(props.pageSize) ? innerPageSize.value : props.pageSize
|
||||
},
|
||||
set(v: number) {
|
||||
if (isAbsent(props.pageSize)) {
|
||||
innerPageSize.value = v
|
||||
}
|
||||
if (hasPageSizeListener) {
|
||||
emit('update:page-size', v)
|
||||
emit('size-change', v)
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
const pageCountBridge = computed<number>(() => {
|
||||
let pageCount = 0
|
||||
if (!isAbsent(props.pageCount)) {
|
||||
pageCount = props.pageCount
|
||||
} else if (!isAbsent(props.total)) {
|
||||
pageCount = Math.max(1, Math.ceil(props.total / pageSizeBridge.value))
|
||||
}
|
||||
}
|
||||
// side effect
|
||||
if (currentPageBridge.value > pageCount) {
|
||||
currentPageBridge.value = pageCount
|
||||
}
|
||||
return pageCount
|
||||
})
|
||||
|
||||
const currentPageBridge = computed<number>({
|
||||
get() {
|
||||
return isAbsent(props.currentPage) ? innerCurrentPage.value : props.currentPage
|
||||
},
|
||||
set(v) {
|
||||
let newCurrentPage = v
|
||||
if (v < 1) {
|
||||
newCurrentPage = 1
|
||||
} else if (v > pageCountBridge.value) {
|
||||
newCurrentPage = pageCountBridge.value
|
||||
}
|
||||
if (isAbsent(props.currentPage)) {
|
||||
innerCurrentPage.value = newCurrentPage
|
||||
}
|
||||
if (hasCurrentPageListener) {
|
||||
emit('update:current-page', newCurrentPage)
|
||||
emit('current-change', newCurrentPage)
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
function handleCurrentChange(val: number) {
|
||||
internalCurrentPage.value = getValidCurrentPage(val)
|
||||
userChangePageSize.value = true
|
||||
emitChange()
|
||||
currentPageBridge.value = val
|
||||
}
|
||||
|
||||
function handleSizesChange(val: number) {
|
||||
userChangePageSize.value = true
|
||||
internalPageSize.value = val
|
||||
emit('update:pageSize', val)
|
||||
emit('size-change', val)
|
||||
function handleSizeChange(val: number) {
|
||||
pageSizeBridge.value = val
|
||||
const newPageCount = pageCountBridge.value
|
||||
if (currentPageBridge.value > newPageCount) {
|
||||
currentPageBridge.value = newPageCount
|
||||
}
|
||||
}
|
||||
|
||||
function prev() {
|
||||
if (props.disabled) return
|
||||
const newVal = internalCurrentPage.value - 1
|
||||
internalCurrentPage.value = getValidCurrentPage(newVal)
|
||||
emit('prev-click', internalCurrentPage.value)
|
||||
emitChange()
|
||||
currentPageBridge.value -= 1
|
||||
emit('prev-click', currentPageBridge.value)
|
||||
}
|
||||
|
||||
function next() {
|
||||
if (props.disabled) return
|
||||
const newVal = internalCurrentPage.value + 1
|
||||
internalCurrentPage.value = getValidCurrentPage(newVal)
|
||||
emit('next-click', internalCurrentPage.value)
|
||||
emitChange()
|
||||
currentPageBridge.value += 1
|
||||
emit('next-click', currentPageBridge.value)
|
||||
}
|
||||
|
||||
function getValidCurrentPage(value: number | string) {
|
||||
if (typeof value === 'string') {
|
||||
value = parseInt(value, 10)
|
||||
}
|
||||
|
||||
let resetValue: number | undefined
|
||||
|
||||
if (isNaN(value) || value < 1) {
|
||||
resetValue = 1
|
||||
} else if (internalPageCount.value < value){
|
||||
resetValue = internalPageCount.value
|
||||
}
|
||||
|
||||
return resetValue ?? value
|
||||
}
|
||||
|
||||
watch(() => props.currentPage, val => {
|
||||
internalCurrentPage.value = getValidCurrentPage(val)
|
||||
lastEmittedPage.value = internalCurrentPage.value
|
||||
})
|
||||
|
||||
watch(() => props.pageSize, val => {
|
||||
internalPageSize.value = getValidPageSize(val)
|
||||
})
|
||||
|
||||
watch(
|
||||
() => internalPageCount.value,
|
||||
val => {
|
||||
const oldPage = internalCurrentPage.value
|
||||
if (val > 0 && oldPage === 0) {
|
||||
internalCurrentPage.value = 1
|
||||
} else if (oldPage > val) {
|
||||
internalCurrentPage.value = val === 0 ? 1 : val
|
||||
emitChange()
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
provide<IPagination>('pagination', {
|
||||
pageCount: computed(() => props.pageCount),
|
||||
pageCount: pageCountBridge,
|
||||
disabled: computed(() => props.disabled),
|
||||
currentPage: computed(() => internalCurrentPage.value),
|
||||
currentPage: currentPageBridge,
|
||||
changeEvent: handleCurrentChange,
|
||||
handleSizesChange,
|
||||
handleSizeChange,
|
||||
})
|
||||
|
||||
return {
|
||||
internalCurrentPage,
|
||||
internalPageSize,
|
||||
lastEmittedPage,
|
||||
userChangePageSize,
|
||||
internalPageCount,
|
||||
|
||||
getValidCurrentPage,
|
||||
emitChange,
|
||||
handleCurrentChange,
|
||||
|
||||
prev,
|
||||
next,
|
||||
}
|
||||
},
|
||||
render() {
|
||||
const layout = this.layout
|
||||
|
||||
if (!layout) return null
|
||||
if (this.hideOnSinglePage && this.internalPageCount <= 1) return null
|
||||
|
||||
const rootNode = h('div', {
|
||||
class: [
|
||||
'el-pagination',
|
||||
{
|
||||
'is-background': this.background,
|
||||
'el-pagination--small': this.small,
|
||||
},
|
||||
],
|
||||
})
|
||||
const rootChildren = []
|
||||
const rightWrapperChildren = []
|
||||
const rightWrapperRoot = h('div', { class: 'el-pagination__rightwrapper' }, rightWrapperChildren)
|
||||
const TEMPLATE_MAP = {
|
||||
prev: h(Prev, {
|
||||
disabled: this.disabled,
|
||||
currentPage: this.internalCurrentPage,
|
||||
prevText: this.prevText,
|
||||
onClick: this.prev,
|
||||
}),
|
||||
jumper: h(Jumper),
|
||||
pager: h(Pager, {
|
||||
currentPage: this.internalCurrentPage,
|
||||
pageCount: this.internalPageCount,
|
||||
pagerCount: this.pagerCount,
|
||||
onChange: this.handleCurrentChange,
|
||||
disabled: this.disabled,
|
||||
}),
|
||||
next: h(Next, {
|
||||
disabled: this.disabled,
|
||||
currentPage: this.internalCurrentPage,
|
||||
pageCount: this.internalPageCount,
|
||||
nextText: this.nextText,
|
||||
onClick: this.next,
|
||||
}),
|
||||
sizes: h(Sizes, {
|
||||
pageSize: this.pageSize,
|
||||
pageSizes: this.pageSizes,
|
||||
popperClass: this.popperClass,
|
||||
disabled: this.disabled,
|
||||
}),
|
||||
slot: this.$slots?.default?.() ?? null,
|
||||
total: h(Total, { total: this.total }),
|
||||
}
|
||||
|
||||
const components = layout.split(',').map((item: string) => item.trim())
|
||||
|
||||
let haveRightWrapper = false
|
||||
|
||||
components.forEach((c: keyof typeof TEMPLATE_MAP | '->') => {
|
||||
if (c === '->') {
|
||||
haveRightWrapper = true
|
||||
return
|
||||
return () => {
|
||||
if (!assertValidUsage.value) {
|
||||
warn(componentName, t('el.pagination.docRefer'))
|
||||
return null
|
||||
}
|
||||
if (!haveRightWrapper) {
|
||||
rootChildren.push(TEMPLATE_MAP[c])
|
||||
} else {
|
||||
rightWrapperChildren.push(TEMPLATE_MAP[c])
|
||||
if (!props.layout) return null
|
||||
if (props.hideOnSinglePage && pageCountBridge.value <= 1) return null
|
||||
const rootChildren = []
|
||||
const rightWrapperChildren = []
|
||||
const rightWrapperRoot = h('div', { class: 'el-pagination__rightwrapper' }, rightWrapperChildren)
|
||||
const TEMPLATE_MAP = {
|
||||
prev: h(Prev, {
|
||||
disabled: props.disabled,
|
||||
currentPage: currentPageBridge.value,
|
||||
prevText: props.prevText,
|
||||
onClick: prev,
|
||||
}),
|
||||
jumper: h(Jumper),
|
||||
pager: h(Pager, {
|
||||
currentPage: currentPageBridge.value,
|
||||
pageCount: pageCountBridge.value,
|
||||
pagerCount: props.pagerCount,
|
||||
onChange: handleCurrentChange,
|
||||
disabled: props.disabled,
|
||||
}),
|
||||
next: h(Next, {
|
||||
disabled: props.disabled,
|
||||
currentPage: currentPageBridge.value,
|
||||
pageCount: pageCountBridge.value,
|
||||
nextText: props.nextText,
|
||||
onClick: next,
|
||||
}),
|
||||
sizes: h(Sizes, {
|
||||
pageSize: pageSizeBridge.value,
|
||||
pageSizes: props.pageSizes,
|
||||
popperClass: props.popperClass,
|
||||
disabled: props.disabled,
|
||||
}),
|
||||
slot: slots?.default?.() ?? null,
|
||||
total: h(Total, { total: isAbsent(props.total) ? 0 : props.total }),
|
||||
}
|
||||
})
|
||||
|
||||
if (haveRightWrapper && rightWrapperChildren.length > 0) {
|
||||
rootChildren.unshift(rightWrapperRoot)
|
||||
const components = props.layout.split(',').map((item: string) => item.trim())
|
||||
|
||||
let haveRightWrapper = false
|
||||
|
||||
components.forEach((c: keyof typeof TEMPLATE_MAP | '->') => {
|
||||
if (c === '->') {
|
||||
haveRightWrapper = true
|
||||
return
|
||||
}
|
||||
if (!haveRightWrapper) {
|
||||
rootChildren.push(TEMPLATE_MAP[c])
|
||||
} else {
|
||||
rightWrapperChildren.push(TEMPLATE_MAP[c])
|
||||
}
|
||||
})
|
||||
|
||||
if (haveRightWrapper && rightWrapperChildren.length > 0) {
|
||||
rootChildren.unshift(rightWrapperRoot)
|
||||
}
|
||||
|
||||
return h('div', {
|
||||
role: 'pagination',
|
||||
'aria-label': 'pagination',
|
||||
class: [
|
||||
'el-pagination',
|
||||
{
|
||||
'is-background': props.background,
|
||||
'el-pagination--small': props.small,
|
||||
},
|
||||
],
|
||||
}, rootChildren)
|
||||
}
|
||||
|
||||
return h(rootNode, {}, rootChildren)
|
||||
},
|
||||
})
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
type="button"
|
||||
class="btn-next"
|
||||
:disabled="internalDisabled"
|
||||
:aria-disabled="internalDisabled"
|
||||
@click.self.prevent
|
||||
>
|
||||
<span v-if="nextText">{{ nextText }}</span>
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
<template>
|
||||
<ul class="el-pager" @click="onPagerClick">
|
||||
<ul class="el-pager" @click="onPagerClick" @keyup.enter="onEnter">
|
||||
<li
|
||||
v-if="pageCount > 0"
|
||||
:class="{ active: currentPage === 1, disabled }"
|
||||
class="number"
|
||||
:aria-current="currentPage === 1"
|
||||
tabindex="0"
|
||||
>
|
||||
1
|
||||
</li>
|
||||
@@ -20,6 +22,8 @@
|
||||
:key="pager"
|
||||
:class="{ active: currentPage === pager, disabled }"
|
||||
class="number"
|
||||
:aria-current="currentPage === pager"
|
||||
tabindex="0"
|
||||
>
|
||||
{{ pager }}
|
||||
</li>
|
||||
@@ -35,6 +39,8 @@
|
||||
v-if="pageCount > 1"
|
||||
:class="{ active: currentPage === pageCount, disabled }"
|
||||
class="number"
|
||||
:aria-current="currentPage === pageCount"
|
||||
tabindex="0"
|
||||
>
|
||||
{{ pageCount }}
|
||||
</li>
|
||||
@@ -142,6 +148,16 @@ export default defineComponent({
|
||||
}
|
||||
}
|
||||
|
||||
function onEnter(e: UIEvent) {
|
||||
const target = e.target as HTMLElement
|
||||
if (target.tagName.toLowerCase() === 'li' && Array.from(target.classList).includes('number')) {
|
||||
const newPage = Number(target.textContent)
|
||||
if (newPage !== props.currentPage) {
|
||||
emit('change', newPage)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function onPagerClick(event: UIEvent) {
|
||||
const target = event.target as HTMLElement
|
||||
if (target.tagName.toLowerCase() === 'ul' || props.disabled) {
|
||||
@@ -180,6 +196,7 @@ export default defineComponent({
|
||||
pagers,
|
||||
onMouseenter,
|
||||
onPagerClick,
|
||||
onEnter,
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
@@ -6,7 +6,7 @@ export interface IPagination {
|
||||
pageCount?: ComputedRef<number>
|
||||
disabled?: ComputedRef<boolean>
|
||||
changeEvent?: AnyFunction<any>
|
||||
handleSizesChange?: AnyFunction<any>
|
||||
handleSizeChange?: AnyFunction<any>
|
||||
}
|
||||
|
||||
export interface IPaginationProps {
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
type="button"
|
||||
class="btn-prev"
|
||||
:disabled="internalDisabled"
|
||||
:aria-disabled="internalDisabled"
|
||||
@click.self.prevent
|
||||
>
|
||||
<span v-if="prevText ">{{ prevText }}</span>
|
||||
|
||||
@@ -69,7 +69,7 @@ export default defineComponent({
|
||||
function handleChange(val: number) {
|
||||
if (val !== innerPageSize.value) {
|
||||
innerPageSize.value = val
|
||||
pagination?.handleSizesChange(Number(val))
|
||||
pagination?.handleSizeChange(Number(val))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -278,7 +278,7 @@ $--pagination-line-height-extra-small: $--pagination-height-extra-small !default
|
||||
cursor: pointer;
|
||||
box-sizing: border-box;
|
||||
text-align: center;
|
||||
margin: 0;
|
||||
margin: 1px;
|
||||
|
||||
&.btn-quicknext,
|
||||
&.btn-quickprev {
|
||||
@@ -302,6 +302,10 @@ $--pagination-line-height-extra-small: $--pagination-height-extra-small !default
|
||||
border-left: 0;
|
||||
}
|
||||
|
||||
&:focus-visible {
|
||||
outline: 1px solid $--pagination-hover-color;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: $--pagination-hover-color;
|
||||
}
|
||||
|
||||
@@ -144,6 +144,10 @@ button, input, select, textarea {
|
||||
border-left: #FE6C6F 5px solid;
|
||||
margin: 20px 0;
|
||||
|
||||
ul {
|
||||
color: #5e6d82;
|
||||
}
|
||||
|
||||
code {
|
||||
background-color: rgba(255, 255, 255, .7);
|
||||
color: #445368;
|
||||
|
||||
@@ -216,10 +216,12 @@ When there is only one page, hide the pagination by setting the `hide-on-single-
|
||||
| small | whether to use small pagination | boolean | — | false |
|
||||
| background | whether the buttons have a background color | boolean | — | false |
|
||||
| page-size | item count of each page, supports the v-model bidirectional binding | number | — | 10 |
|
||||
| default-page-size | default initial value of page size | number | - | - |
|
||||
| total | total item count | number | — | — |
|
||||
| page-count | total page count. Set either `total` or `page-count` and pages will be displayed; if you need `page-sizes`, `total` is required | number | — | — |
|
||||
| pager-count | number of pagers. Pagination collapses when the total page count exceeds this value | number | odd number between 5 and 21 | 7 |
|
||||
| current-page | current page number, supports the v-model bidirectional binding | number | — | 1 |
|
||||
| default-current-page | default initial value of current-page | number | - | - |
|
||||
| layout | layout of Pagination, elements separated with a comma | string | `sizes` / `prev` / `pager` / `next` / `jumper` / `->` / `total` / `slot` | 'prev, pager, next, jumper, ->, total' |
|
||||
| page-sizes | options of item count per page | number[] | — | [10, 20, 30, 40, 50, 100] |
|
||||
| popper-class | custom class name for the page size Select's dropdown | string | — | — |
|
||||
@@ -228,6 +230,13 @@ When there is only one page, hide the pagination by setting the `hide-on-single-
|
||||
| disabled | whether Pagination is disabled | boolean | — | false |
|
||||
| hide-on-single-page | whether to hide when there's only one page | boolean | — | - |
|
||||
|
||||
:::warning
|
||||
We'll detect some deprecated usages, if your pagination don't appeared or worked as expected, please check rules below:
|
||||
- You have to define one of `total` and `page-count`, otherwise we can't determine count of total pages.When both defined, `page-count` taken as priority.
|
||||
- If `current-page` is defined, you have to listen `current-page` change, by also define `@update:current-page`, otherwise pagination didn't work.
|
||||
- If `page-size` is defined while page size selector displayed(`sizes` included in `layout`), you have to listen `page-size` change as well, by define `@update:page-size`, otherwise change of page size didn't work.
|
||||
:::
|
||||
|
||||
### Events
|
||||
| Event Name | Description | Parameters |
|
||||
|---------|--------|---------|
|
||||
@@ -236,6 +245,10 @@ When there is only one page, hide the pagination by setting the `hide-on-single-
|
||||
| prev-click | triggers when the prev button is clicked and current page changes | the new current page |
|
||||
| next-click | triggers when the next button is clicked and current page changes | the new current page |
|
||||
|
||||
:::warning
|
||||
Events above are not recommended(but are still supported for compatible reason), better chioce is to use the two-way data binding by `v-model`.
|
||||
:::
|
||||
|
||||
### Slots
|
||||
| Name | Description |
|
||||
| --- | --- |
|
||||
|
||||
@@ -216,10 +216,12 @@
|
||||
| small | 是否使用小型分页样式 | boolean | — | false |
|
||||
| background | 是否为分页按钮添加背景色 | boolean | — | false |
|
||||
| page-size | 每页显示条目个数,支持 v-model 双向绑定 | number | — | 10 |
|
||||
| default-page-size | 每页显示条目数的初始值;| number | - | - |
|
||||
| total | 总条目数 | number | — | — |
|
||||
| page-count | 总页数,total 和 page-count 设置任意一个就可以达到显示页码的功能;如果要支持 page-sizes 的更改,则需要使用 total 属性 | Number | — | — |
|
||||
| pager-count | 页码按钮的数量,当总页数超过该值时会折叠 | number | 大于等于 5 且小于等于 21 的奇数 | 7 |
|
||||
| current-page | 当前页数,支持 v-model 双向绑定 | number | — | 1 |
|
||||
| default-current-page | 当前页数的初始值 | number | - | - |
|
||||
| layout | 组件布局,子组件名用逗号分隔| String | `sizes`, `prev`, `pager`, `next`, `jumper`, `->`, `total`, `slot` | 'prev, pager, next, jumper, ->, total' |
|
||||
| page-sizes | 每页显示个数选择器的选项设置 | number[] | — | [10, 20, 30, 40, 50, 100] |
|
||||
| popper-class | 每页显示个数选择器的下拉框类名 | string | — | — |
|
||||
@@ -228,6 +230,13 @@
|
||||
| disabled | 是否禁用 | boolean | — | false |
|
||||
| hide-on-single-page | 只有一页时是否隐藏 | boolean | — | - |
|
||||
|
||||
:::warning
|
||||
我们现在会检查一些不合理的用法,如果发现分页器未显示,可以核对是否违反以下情形:
|
||||
- `total` 和 `page-count` 必须传一个,不然组件无法判断总页数;优先使用 `page-count`;
|
||||
- 如果传入了 `current-page` 必须监听 `current-page` 变更的事件(`onUpdate:currentPage`);否则分页切换不起作用;
|
||||
- 如果传入了 `page-size`,且布局包含 `page-size` 选择器(即 `layout` 包含 `sizes`),必须监听 `page-size` 变更的事件(`onUpdate:pageSize`),否则 `page-size` 切换不起作用;
|
||||
:::
|
||||
|
||||
### Events
|
||||
| 事件名称 | 说明 | 回调参数 |
|
||||
|---------|--------|---------|
|
||||
@@ -236,6 +245,10 @@
|
||||
| prev-click | 用户点击上一页按钮改变当前页后触发 | 当前页 |
|
||||
| next-click | 用户点击下一页按钮改变当前页后触发 | 当前页 |
|
||||
|
||||
:::warning
|
||||
以上事件不推荐使用;如果要监听 current-page 和 page-size 的改变,使用 v-model 双向绑定是个更好的选择。
|
||||
:::
|
||||
|
||||
### Slot
|
||||
| name | 说明 |
|
||||
|------|--------|
|
||||
|
||||
Reference in New Issue
Block a user