mirror of
https://github.com/element-plus/element-plus.git
synced 2025-01-30 11:16:12 +08:00
feat: add pagination (#195)
* feat(component): add type 'pagination' for form usage #120 * feat(pagination): optimize pagination code * fix(pagination): optimize code * feat(component): add type 'pagination' for form usage #120 * feat(pagination): optimize pagination code * fix(pagination): optimize code * feat(pagination): update component Co-authored-by: pannana <pannana@ucloud.cn> Co-authored-by: zouhang <zouhang@didiglobal.com>
This commit is contained in:
parent
37a5a9156d
commit
cc2ab5c417
@ -46,6 +46,7 @@ import ElTree from '@element-plus/tree'
|
||||
import ElColorPicker from '@element-plus/color-picker'
|
||||
import ElSelect from '@element-plus/select'
|
||||
import ElTimeSelect from '@element-plus/time-select'
|
||||
import ElPagination from '@element-plus/pagination'
|
||||
|
||||
export {
|
||||
ElAlert,
|
||||
@ -94,6 +95,7 @@ export {
|
||||
ElColorPicker,
|
||||
ElSelect,
|
||||
ElTimeSelect,
|
||||
ElPagination,
|
||||
}
|
||||
|
||||
const install = (app: App): void => {
|
||||
@ -144,6 +146,7 @@ const install = (app: App): void => {
|
||||
ElColorPicker(app)
|
||||
ElSelect(app)
|
||||
ElTimeSelect(app)
|
||||
ElPagination(app)
|
||||
}
|
||||
|
||||
const elementUI = {
|
||||
|
@ -42,6 +42,7 @@
|
||||
"@element-plus/tabs": "^0.0.0",
|
||||
"@element-plus/form": "^0.0.0",
|
||||
"@element-plus/tree": "^0.0.0",
|
||||
"@element-plus/select": "^0.0.0"
|
||||
"@element-plus/select": "^0.0.0",
|
||||
"@element-plus/pagination": "^0.0.0"
|
||||
}
|
||||
}
|
||||
|
194
packages/pagination/__tests__/pagination.spec.ts
Normal file
194
packages/pagination/__tests__/pagination.spec.ts
Normal file
@ -0,0 +1,194 @@
|
||||
import { mount } from '@vue/test-utils'
|
||||
import { sleep } from '@element-plus/test-utils'
|
||||
import Pagination from '../src/index'
|
||||
|
||||
const TIME_OUT = 100
|
||||
|
||||
describe('Pagination.vue', () => {
|
||||
test('layout', () => {
|
||||
const wrapper = mount(Pagination, {
|
||||
props: {
|
||||
layout: 'prev, pager, next',
|
||||
},
|
||||
})
|
||||
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('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,
|
||||
},
|
||||
})
|
||||
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: 4,
|
||||
},
|
||||
})
|
||||
expect(wrapper.findAll('li.number').length).toBe(4)
|
||||
})
|
||||
|
||||
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)
|
||||
})
|
||||
})
|
||||
|
5
packages/pagination/index.ts
Normal file
5
packages/pagination/index.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import { App } from 'vue'
|
||||
import Pagination from './src/index'
|
||||
export default (app: App): void => {
|
||||
app.component(Pagination.name, Pagination)
|
||||
}
|
12
packages/pagination/package.json
Normal file
12
packages/pagination/package.json
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"name": "@element-plus/pagination",
|
||||
"version": "0.0.0",
|
||||
"main": "dist/index.js",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"vue": "^3.0.0-rc.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vue/test-utils": "^2.0.0-beta.0"
|
||||
}
|
||||
}
|
291
packages/pagination/src/index.ts
Normal file
291
packages/pagination/src/index.ts
Normal file
@ -0,0 +1,291 @@
|
||||
import {
|
||||
defineComponent,
|
||||
h,
|
||||
ref,
|
||||
computed,
|
||||
watchEffect,
|
||||
watch,
|
||||
nextTick,
|
||||
provide,
|
||||
} from 'vue'
|
||||
import { IPagination } from './pagination'
|
||||
|
||||
import Prev from './prev.vue'
|
||||
import Next from './next.vue'
|
||||
import Sizes from './sizes.vue'
|
||||
import Jumper from './jumper.vue'
|
||||
import Total from './total.vue'
|
||||
import Pager from './pager.vue'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ElPagination',
|
||||
|
||||
components: {
|
||||
Prev,
|
||||
Next,
|
||||
Sizes,
|
||||
Jumper,
|
||||
Total,
|
||||
Pager,
|
||||
},
|
||||
props: {
|
||||
pageSize: {
|
||||
type: Number,
|
||||
default: 10,
|
||||
},
|
||||
|
||||
small: Boolean,
|
||||
|
||||
total: {
|
||||
type: Number,
|
||||
default: 1000,
|
||||
},
|
||||
|
||||
pageCount: {
|
||||
type: Number,
|
||||
default: 50,
|
||||
},
|
||||
|
||||
pagerCount: {
|
||||
type: Number,
|
||||
validator: (value: number) => {
|
||||
return (
|
||||
(value | 0) === value && value > 4 && value < 22 && value % 2 === 1
|
||||
)
|
||||
},
|
||||
default: 7,
|
||||
},
|
||||
|
||||
currentPage: {
|
||||
type: Number,
|
||||
default: 1,
|
||||
},
|
||||
|
||||
layout: {
|
||||
type: String,
|
||||
default: 'prev, pager, next, jumper, ->, total',
|
||||
},
|
||||
|
||||
pageSizes: {
|
||||
type: Array,
|
||||
default: () => {
|
||||
return [10, 20, 30, 40, 50, 100]
|
||||
},
|
||||
},
|
||||
|
||||
popperClass: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
|
||||
prevText: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
|
||||
nextText: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
|
||||
background: Boolean,
|
||||
|
||||
disabled: Boolean,
|
||||
|
||||
hideOnSinglePage: Boolean,
|
||||
},
|
||||
|
||||
emits: [
|
||||
'size-change',
|
||||
'current-change',
|
||||
'prev-click',
|
||||
'next-click',
|
||||
'update:currentPage',
|
||||
],
|
||||
setup(props, { emit }) {
|
||||
const internalCurrentPage = ref(1)
|
||||
const lastEmittedPage = ref(-1)
|
||||
const userChangePageSize = ref(false)
|
||||
|
||||
const internalPageSize = ref(0)
|
||||
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)
|
||||
}
|
||||
return null
|
||||
})
|
||||
|
||||
watchEffect(() => {
|
||||
internalCurrentPage.value = getValidCurrentPage(props.currentPage)
|
||||
})
|
||||
watchEffect(() => {
|
||||
internalPageSize.value = isNaN(props.pageSize) ? 10 : props.pageSize
|
||||
})
|
||||
watchEffect(() => {
|
||||
emit('update:currentPage', internalCurrentPage.value)
|
||||
lastEmittedPage.value = -1
|
||||
})
|
||||
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
|
||||
userChangePageSize.value && emitChange()
|
||||
}
|
||||
userChangePageSize.value = false
|
||||
})
|
||||
|
||||
function emitChange() {
|
||||
nextTick(() => {
|
||||
if (internalCurrentPage.value !== lastEmittedPage.value || userChangePageSize) {
|
||||
emit('current-change', internalCurrentPage.value)
|
||||
lastEmittedPage.value = internalCurrentPage.value
|
||||
userChangePageSize.value = false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function handleCurrentChange(val: number) {
|
||||
internalCurrentPage.value = getValidCurrentPage(val)
|
||||
userChangePageSize.value = true
|
||||
emitChange()
|
||||
}
|
||||
|
||||
function handleSizesChange(val: number) {
|
||||
userChangePageSize.value = true
|
||||
internalPageSize.value = val
|
||||
}
|
||||
|
||||
function prev() {
|
||||
if (props.disabled) return
|
||||
const newVal = internalCurrentPage.value - 1
|
||||
internalCurrentPage.value = getValidCurrentPage(newVal)
|
||||
emit('prev-click', internalCurrentPage)
|
||||
emitChange()
|
||||
}
|
||||
|
||||
function next() {
|
||||
if (props.disabled) return
|
||||
const newVal = internalCurrentPage.value + 1
|
||||
internalCurrentPage.value = getValidCurrentPage(newVal)
|
||||
emit('next-click', internalCurrentPage.value)
|
||||
emitChange()
|
||||
}
|
||||
|
||||
function getValidCurrentPage(value: number | string) {
|
||||
if (typeof value === 'string') {
|
||||
value = parseInt(value, 10)
|
||||
}
|
||||
let resetValue: number | undefined
|
||||
const havePageCount = typeof internalPageCount.value === 'number'
|
||||
|
||||
if (!havePageCount) {
|
||||
if (isNaN(value) || value < 1) resetValue = 1
|
||||
} else {
|
||||
if (value < 1) {
|
||||
resetValue = 1
|
||||
} else if (value > internalPageCount.value) {
|
||||
resetValue = internalPageCount.value
|
||||
}
|
||||
}
|
||||
|
||||
if (resetValue === undefined && isNaN(value)) {
|
||||
resetValue = 1
|
||||
} else if (resetValue === 0) {
|
||||
resetValue = 1
|
||||
}
|
||||
|
||||
return resetValue === undefined ? value : resetValue
|
||||
}
|
||||
|
||||
provide<IPagination>('pagination', {
|
||||
pageCount: computed(() => props.pageCount),
|
||||
disabled: computed(() => props.disabled),
|
||||
currentPage: computed(() => internalCurrentPage.value),
|
||||
changeEvent: handleCurrentChange,
|
||||
handleSizesChange,
|
||||
})
|
||||
|
||||
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 || this.internalPageCount === 1)) return null
|
||||
|
||||
const rootNode = h('div', { class: ['el-pagination', { 'is-background': this.background, 'el-pagination--small': this.small }] })
|
||||
const rootChilds = []
|
||||
const rightWrapperRoot = h('div', { class: 'el-pagination__rightwrapper' })
|
||||
const rightWrapperChilds = []
|
||||
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(compo => {
|
||||
if (compo === '->') {
|
||||
haveRightWrapper = true
|
||||
return
|
||||
}
|
||||
if (!haveRightWrapper) {
|
||||
rootChilds.push(TEMPLATE_MAP[compo])
|
||||
} else {
|
||||
rightWrapperChilds.push(TEMPLATE_MAP[compo])
|
||||
}
|
||||
})
|
||||
|
||||
if (haveRightWrapper) {
|
||||
rootChilds.unshift(rightWrapperRoot)
|
||||
}
|
||||
|
||||
return h(rootNode, {}, rootChilds)
|
||||
},
|
||||
|
||||
})
|
57
packages/pagination/src/jumper.vue
Normal file
57
packages/pagination/src/jumper.vue
Normal file
@ -0,0 +1,57 @@
|
||||
<template>
|
||||
<span class="el-pagination__jump">
|
||||
{{ t('el.pagination.goto') }}
|
||||
<el-input
|
||||
class="el-pagination__editor is-in-pagination"
|
||||
:min="1"
|
||||
:max="pageCount"
|
||||
:disabled="disabled"
|
||||
:model-value="innerValue"
|
||||
type="number"
|
||||
@update:modelValue="handleInput"
|
||||
@change="handleChange"
|
||||
/>
|
||||
{{ t('el.pagination.pageClassifier') }}
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import {
|
||||
computed,
|
||||
defineComponent,
|
||||
ref,
|
||||
} from 'vue'
|
||||
import { t } from '@element-plus/locale'
|
||||
import ElInput from '@element-plus/input/src/index.vue'
|
||||
import { usePagination } from './usePagination'
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
ElInput,
|
||||
},
|
||||
setup() {
|
||||
const { pagination, pageCount, disabled, currentPage } = usePagination()
|
||||
const userInput = ref<Nullable<number>>(null)
|
||||
const innerValue = computed(() => userInput.value ?? currentPage.value)
|
||||
|
||||
function handleInput(val: number | string) {
|
||||
userInput.value = Number(val)
|
||||
}
|
||||
|
||||
function handleChange(val: number | string) {
|
||||
pagination?.changeEvent(Number(val))
|
||||
userInput.value = null
|
||||
}
|
||||
|
||||
return {
|
||||
t,
|
||||
userInput,
|
||||
pageCount,
|
||||
disabled,
|
||||
handleInput,
|
||||
handleChange,
|
||||
innerValue,
|
||||
}
|
||||
},
|
||||
})
|
||||
</script>
|
43
packages/pagination/src/next.vue
Normal file
43
packages/pagination/src/next.vue
Normal file
@ -0,0 +1,43 @@
|
||||
<template>
|
||||
<button
|
||||
type="button"
|
||||
class="btn-next"
|
||||
:disabled="internalDisabled"
|
||||
@click.self.prevent
|
||||
>
|
||||
<span v-if="nextText">{{ nextText }}</span>
|
||||
<i v-else class="el-icon el-icon-arrow-right"></i>
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, computed } from 'vue'
|
||||
export default defineComponent({
|
||||
name: 'Next',
|
||||
props: {
|
||||
disabled: Boolean,
|
||||
currentPage: {
|
||||
type: Number,
|
||||
default: 1,
|
||||
},
|
||||
pageCount: {
|
||||
type: Number,
|
||||
default: 50,
|
||||
},
|
||||
nextText: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const internalDisabled = computed(() => props.disabled
|
||||
|| props.currentPage === props.pageCount
|
||||
|| props.pageCount === 0,
|
||||
)
|
||||
|
||||
return {
|
||||
internalDisabled,
|
||||
}
|
||||
},
|
||||
})
|
||||
</script>
|
187
packages/pagination/src/pager.vue
Normal file
187
packages/pagination/src/pager.vue
Normal file
@ -0,0 +1,187 @@
|
||||
<template>
|
||||
<ul class="el-pager" @click="onPagerClick">
|
||||
<li
|
||||
v-if="pageCount > 0"
|
||||
:class="{ active: currentPage === 1, disabled }"
|
||||
class="number"
|
||||
>
|
||||
1
|
||||
</li>
|
||||
<li
|
||||
v-if="showPrevMore"
|
||||
class="el-icon more btn-quickprev"
|
||||
:class="[quickprevIconClass, { disabled }]"
|
||||
@mouseenter="onMouseenter('left')"
|
||||
@mouseleave="quickprevIconClass = 'el-icon-more'"
|
||||
>
|
||||
</li>
|
||||
<li
|
||||
v-for="pager in pagers"
|
||||
:key="pager"
|
||||
:class="{ active: currentPage === pager, disabled }"
|
||||
class="number"
|
||||
>
|
||||
{{ pager }}
|
||||
</li>
|
||||
<li
|
||||
v-if="showNextMore"
|
||||
class="el-icon more btn-quicknext"
|
||||
:class="[quicknextIconClass, { disabled }]"
|
||||
@mouseenter="onMouseenter('right')"
|
||||
@mouseleave="quicknextIconClass = 'el-icon-more'"
|
||||
>
|
||||
</li>
|
||||
<li
|
||||
v-if="pageCount > 1"
|
||||
:class="{ active: currentPage === pageCount, disabled }"
|
||||
class="number"
|
||||
>
|
||||
{{ pageCount }}
|
||||
</li>
|
||||
</ul>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import {
|
||||
defineComponent,
|
||||
ref,
|
||||
computed,
|
||||
watchEffect,
|
||||
} from 'vue'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ElPager',
|
||||
props: {
|
||||
currentPage: {
|
||||
type: Number,
|
||||
default: 1,
|
||||
},
|
||||
pageCount: {
|
||||
type: Number,
|
||||
default: 50,
|
||||
},
|
||||
pagerCount: {
|
||||
type: Number,
|
||||
default: 7,
|
||||
},
|
||||
disabled: Boolean,
|
||||
},
|
||||
emits: ['change'],
|
||||
setup(props, { emit }) {
|
||||
const showPrevMore = ref(false)
|
||||
const showNextMore = ref(false)
|
||||
const quicknextIconClass = ref('el-icon-more')
|
||||
const quickprevIconClass = ref('el-icon-more')
|
||||
const pagers = computed(() => {
|
||||
const pagerCount = props.pagerCount
|
||||
const halfPagerCount = (pagerCount - 1) / 2
|
||||
const currentPage = Number(props.currentPage)
|
||||
const pageCount = Number(props.pageCount)
|
||||
|
||||
let showPrevMore = false
|
||||
let showNextMore = false
|
||||
if (pageCount > pagerCount) {
|
||||
if (currentPage > pagerCount - halfPagerCount) {
|
||||
showPrevMore = true
|
||||
}
|
||||
if (currentPage < pageCount - halfPagerCount) {
|
||||
showNextMore = true
|
||||
}
|
||||
}
|
||||
const array = []
|
||||
if (showPrevMore && !showNextMore) {
|
||||
const startPage = pageCount - (pagerCount - 2)
|
||||
for (let i = startPage; i < pageCount; i++) {
|
||||
array.push(i)
|
||||
}
|
||||
} else if (!showPrevMore && showNextMore) {
|
||||
for (let i = 2; i < pagerCount; i++) {
|
||||
array.push(i)
|
||||
}
|
||||
} else if (showPrevMore && showNextMore) {
|
||||
const offset = Math.floor(pagerCount / 2) - 1
|
||||
for (let i = currentPage - offset; i <= currentPage + offset; i++) {
|
||||
array.push(i)
|
||||
}
|
||||
} else {
|
||||
for (let i = 2; i < pageCount; i++) {
|
||||
array.push(i)
|
||||
}
|
||||
}
|
||||
|
||||
return array
|
||||
})
|
||||
|
||||
watchEffect(() => {
|
||||
const halfPagerCount = (props.pagerCount - 1) / 2
|
||||
|
||||
showPrevMore.value = false
|
||||
showNextMore.value = false
|
||||
|
||||
if (props.pageCount > props.pagerCount) {
|
||||
if (props.currentPage > props.pagerCount - halfPagerCount) {
|
||||
showPrevMore.value = true
|
||||
}
|
||||
if (props.currentPage < props.pageCount - halfPagerCount) {
|
||||
showNextMore.value = true
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
watchEffect(() => {
|
||||
if(!showPrevMore.value) quickprevIconClass.value = 'el-icon-more'
|
||||
})
|
||||
watchEffect(() => {
|
||||
if(!showNextMore.value) quicknextIconClass.value = 'el-icon-more'
|
||||
})
|
||||
|
||||
function onMouseenter(direction: 'left' | 'right') {
|
||||
if (props.disabled) return
|
||||
if (direction === 'left') {
|
||||
quickprevIconClass.value = 'el-icon-d-arrow-left'
|
||||
} else {
|
||||
quicknextIconClass.value = 'el-icon-d-arrow-right'
|
||||
}
|
||||
}
|
||||
|
||||
function onPagerClick(event: UIEvent) {
|
||||
const target = event.target as HTMLElement
|
||||
if (target.tagName.toLowerCase() === 'ul' || props.disabled) {
|
||||
return
|
||||
}
|
||||
|
||||
let newPage = Number(target.textContent)
|
||||
const pageCount = props.pageCount
|
||||
const currentPage = props.currentPage
|
||||
const pagerCountOffset = props.pagerCount - 2
|
||||
if (target.className.includes('more')) {
|
||||
if (target.className.includes('quickprev')) {
|
||||
newPage = currentPage - pagerCountOffset
|
||||
} else if (target.className.includes('quicknext')) {
|
||||
newPage = currentPage + pagerCountOffset
|
||||
}
|
||||
}
|
||||
if (!isNaN(newPage)) {
|
||||
if (newPage < 1) {
|
||||
newPage = 1
|
||||
}
|
||||
if (newPage > pageCount) {
|
||||
newPage = pageCount
|
||||
}
|
||||
}
|
||||
if (newPage !== currentPage) {
|
||||
emit('change', newPage)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
showPrevMore,
|
||||
showNextMore,
|
||||
quicknextIconClass,
|
||||
quickprevIconClass,
|
||||
pagers,
|
||||
onMouseenter,
|
||||
onPagerClick,
|
||||
}
|
||||
},
|
||||
})
|
||||
</script>
|
35
packages/pagination/src/pagination.ts
Normal file
35
packages/pagination/src/pagination.ts
Normal file
@ -0,0 +1,35 @@
|
||||
import { ComputedRef } from 'vue'
|
||||
import { AnyFunction } from '@element-plus/utils/types'
|
||||
|
||||
export interface IPagination {
|
||||
currentPage?: ComputedRef<number>
|
||||
pageCount?: ComputedRef<number>
|
||||
disabled?: ComputedRef<boolean>
|
||||
changeEvent?: AnyFunction<any>
|
||||
handleSizesChange?: AnyFunction<any>
|
||||
}
|
||||
|
||||
export interface IPaginationProps {
|
||||
pageSize: number
|
||||
small: boolean
|
||||
total: number
|
||||
pageCount: number
|
||||
pagerCount: number
|
||||
currentPage: number
|
||||
layout: Record<string, string | undefined>
|
||||
pageSizes: Array<number>
|
||||
popperClass: string
|
||||
prevText: string
|
||||
nextText: string
|
||||
background: boolean
|
||||
disabled: boolean
|
||||
hideOnSinglePage: boolean
|
||||
}
|
||||
|
||||
export interface IPaginationSetups {
|
||||
currentPage: number
|
||||
pageCount: number
|
||||
pagerCount: number
|
||||
disabled: boolean
|
||||
pageSizes: Array<number>
|
||||
}
|
36
packages/pagination/src/prev.vue
Normal file
36
packages/pagination/src/prev.vue
Normal file
@ -0,0 +1,36 @@
|
||||
<template>
|
||||
<button
|
||||
type="button"
|
||||
class="btn-prev"
|
||||
:disabled="internalDisabled"
|
||||
@click.self.prevent
|
||||
>
|
||||
<span v-if="prevText ">{{ prevText }}</span>
|
||||
<i v-else class="el-icon el-icon-arrow-left"></i>
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, computed } from 'vue'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'Prev',
|
||||
props: {
|
||||
disabled: Boolean,
|
||||
currentPage: {
|
||||
type: Number,
|
||||
default: 1,
|
||||
},
|
||||
prevText: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const internalDisabled = computed(() => props.disabled || props.currentPage <= 1)
|
||||
return {
|
||||
internalDisabled,
|
||||
}
|
||||
},
|
||||
})
|
||||
</script>
|
80
packages/pagination/src/sizes.vue
Normal file
80
packages/pagination/src/sizes.vue
Normal file
@ -0,0 +1,80 @@
|
||||
<template>
|
||||
<span class="el-pagination__sizes">
|
||||
<el-select
|
||||
:model-value="innerPageSize"
|
||||
:disabled="disabled"
|
||||
:popper-class="popperClass"
|
||||
size="mini"
|
||||
@change="handleChange"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in innerPagesizes"
|
||||
:key="item"
|
||||
:value="item"
|
||||
:label="item + t('el.pagination.pagesize')"
|
||||
/>
|
||||
</el-select>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, watch, computed, ref } from 'vue'
|
||||
import ElSelect from '@element-plus/select/src/select.vue'
|
||||
import ElOption from '@element-plus/select/src/option.vue'
|
||||
import { t } from '@element-plus/locale'
|
||||
import { isEqual } from 'lodash'
|
||||
import { usePagination } from './usePagination'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'Sizes',
|
||||
components: {
|
||||
ElSelect,
|
||||
ElOption,
|
||||
},
|
||||
props: {
|
||||
pageSize: Number,
|
||||
pageSizes: {
|
||||
type: Array,
|
||||
default: () => {
|
||||
return [10, 20, 30, 40, 50, 100]
|
||||
},
|
||||
},
|
||||
popperClass: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
disabled: Boolean,
|
||||
},
|
||||
emits: ['page-size-change'],
|
||||
setup(props, { emit }) {
|
||||
const { pagination } = usePagination()
|
||||
const innerPageSize = ref<Nullable<number>>(props.pageSize)
|
||||
|
||||
watch(() => props.pageSizes, (newVal, oldVal) => {
|
||||
if (isEqual(newVal, oldVal)) return
|
||||
if (Array.isArray(newVal)) {
|
||||
const pageSize = newVal.indexOf(props.pageSize) > -1
|
||||
? props.pageSize
|
||||
: props.pageSizes[0]
|
||||
emit('page-size-change', pageSize)
|
||||
}
|
||||
})
|
||||
|
||||
const innerPagesizes = computed(() => props.pageSizes)
|
||||
|
||||
function handleChange(val: number) {
|
||||
if (val !== innerPageSize.value) {
|
||||
innerPageSize.value = val
|
||||
pagination?.handleSizesChange(Number(val))
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
t,
|
||||
innerPagesizes,
|
||||
innerPageSize,
|
||||
handleChange,
|
||||
}
|
||||
},
|
||||
})
|
||||
</script>
|
30
packages/pagination/src/total.vue
Normal file
30
packages/pagination/src/total.vue
Normal file
@ -0,0 +1,30 @@
|
||||
<template>
|
||||
<span class="el-pagination__total">
|
||||
{{
|
||||
t('el.pagination.total', {
|
||||
total,
|
||||
})
|
||||
}}
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
import { t } from '@element-plus/locale'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'Total',
|
||||
props: {
|
||||
total: {
|
||||
type: Number,
|
||||
default: 1000,
|
||||
},
|
||||
},
|
||||
setup() {
|
||||
return {
|
||||
t,
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
</script>
|
13
packages/pagination/src/usePagination.ts
Normal file
13
packages/pagination/src/usePagination.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import { inject } from 'vue'
|
||||
import { IPagination } from './pagination'
|
||||
|
||||
export const usePagination = () => {
|
||||
const pagination = inject<IPagination>('pagination', {})
|
||||
|
||||
return {
|
||||
pagination,
|
||||
pageCount: pagination.pageCount,
|
||||
disabled: pagination.disabled,
|
||||
currentPage: pagination.currentPage,
|
||||
}
|
||||
}
|
@ -209,7 +209,7 @@ export default defineComponent({
|
||||
name: String,
|
||||
id: String,
|
||||
modelValue: {
|
||||
type: [Array, String],
|
||||
type: [Array, String, Number],
|
||||
},
|
||||
autocomplete: {
|
||||
type: String,
|
||||
|
Loading…
Reference in New Issue
Block a user