vxe-pc-ui
Version:
A vue based PC component library
734 lines (684 loc) • 23.9 kB
text/typescript
import { defineComponent, h, PropType, computed, inject, ref, Ref, reactive, nextTick, watch } from 'vue'
import XEUtils from 'xe-utils'
import { getIcon, getConfig, getI18n, globalEvents, GLOBAL_EVENT_KEYS, createEvent, useSize } from '../../ui'
import { errLog } from '../../ui/src/log'
import VxeSelectComponent from '../../select/src/select'
import VxeInputComponent from '../../input/src/input'
import type { VxePagerPropTypes, VxePagerConstructor, VxePagerEmits, VxeSelectEvents, ValueOf, PagerPrivateRef, PagerMethods, PagerPrivateMethods, VxePagerPrivateMethods, PagerReactData, VxeInputEvents } from '../../../types'
import type { VxeGridConstructor, VxeGridPrivateMethods } from '../../../types/components/grid'
export default defineComponent({
name: 'VxePager',
props: {
size: {
type: String as PropType<VxePagerPropTypes.Size>,
default: () => getConfig().pager.size || getConfig().size
},
// 自定义布局
layouts: {
type: Array as PropType<VxePagerPropTypes.Layouts>,
default: () => getConfig().pager.layouts || ['PrevJump', 'PrevPage', 'Jump', 'PageCount', 'NextPage', 'NextJump', 'Sizes', 'Total']
},
// 当前页
currentPage: {
type: Number as PropType<VxePagerPropTypes.CurrentPage>,
default: 1
},
// 加载中
loading: Boolean as PropType<VxePagerPropTypes.Loading>,
// 每页大小
pageSize: {
type: Number as PropType<VxePagerPropTypes.PageSize>,
default: () => getConfig().pager.pageSize || 10
},
// 总条数
total: { type: Number as PropType<VxePagerPropTypes.Total>, default: 0 },
// 显示页码按钮的数量
pagerCount: {
type: Number as PropType<VxePagerPropTypes.PagerCount>,
default: () => getConfig().pager.pagerCount || 7
},
// 每页大小选项列表
pageSizes: {
type: Array as PropType<VxePagerPropTypes.PageSizes>,
default: () => getConfig().pager.pageSizes || [10, 15, 20, 50, 100]
},
// 列对其方式
align: {
type: String as PropType<VxePagerPropTypes.Align>,
default: () => getConfig().pager.align
},
// 带边框
border: {
type: Boolean as PropType<VxePagerPropTypes.Border>,
default: () => getConfig().pager.border
},
// 带背景颜色
background: {
type: Boolean as PropType<VxePagerPropTypes.Background>,
default: () => getConfig().pager.background
},
// 配套的样式
perfect: {
type: Boolean as PropType<VxePagerPropTypes.Perfect>,
default: () => getConfig().pager.perfect
},
// 当只有一页时隐藏
autoHidden: {
type: Boolean as PropType<VxePagerPropTypes.AutoHidden>,
default: () => getConfig().pager.autoHidden
},
transfer: {
type: Boolean as PropType<VxePagerPropTypes.Transfer>,
default: () => getConfig().pager.transfer
},
className: [String, Function] as PropType<VxePagerPropTypes.ClassName>,
pageSizePlacement: {
type: String as PropType<VxePagerPropTypes.PageSizePlacement>,
default: () => getConfig().pager.pageSizePlacement
},
// 自定义图标
iconPrevPage: String as PropType<VxePagerPropTypes.IconPrevPage>,
iconJumpPrev: String as PropType<VxePagerPropTypes.IconJumpPrev>,
iconJumpNext: String as PropType<VxePagerPropTypes.IconJumpNext>,
iconNextPage: String as PropType<VxePagerPropTypes.IconNextPage>,
iconJumpMore: String as PropType<VxePagerPropTypes.IconJumpMore>,
iconHomePage: String as PropType<VxePagerPropTypes.IconHomePage>,
iconEndPage: String as PropType<VxePagerPropTypes.IconEndPage>
},
emits: [
'update:pageSize',
'update:currentPage',
'page-change'
] as VxePagerEmits,
setup (props, context) {
const { slots, emit } = context
const xID = XEUtils.uniqueId()
const { computeSize } = useSize(props)
const $xeGrid = inject<(VxeGridConstructor & VxeGridPrivateMethods) | null>('$xeGrid', null)
const reactData = reactive<PagerReactData>({
inpCurrPage: props.currentPage
})
const refElem = ref() as Ref<HTMLDivElement>
const refMaps: PagerPrivateRef = {
refElem
}
const computePageCount = computed(() => {
return getPageCount(props.total, props.pageSize)
})
const computeNumList = computed(() => {
const { pagerCount } = props
const pageCount = computePageCount.value
const len = pageCount > pagerCount ? pagerCount - 2 : pagerCount
const rest = []
for (let index = 0; index < len; index++) {
rest.push(index)
}
return rest
})
const computeOffsetNumber = computed(() => {
return Math.floor((props.pagerCount - 2) / 2)
})
const computeSizeList = computed(() => {
return props.pageSizes.map((item) => {
if (XEUtils.isNumber(item)) {
return {
value: item,
label: `${getI18n('vxe.pager.pagesize', [item])}`
}
}
return { value: '', label: '', ...item }
})
})
const $xePager = {
xID,
props,
context,
getRefMaps: () => refMaps
} as unknown as VxePagerConstructor & VxePagerPrivateMethods
let pagerMethods = {} as PagerMethods
let pagerPrivateMethods = {} as PagerPrivateMethods
const getPageCount = (total: number, size: number) => {
return Math.max(Math.ceil(total / size), 1)
}
const jumpPageEvent = (evnt: Event, currentPage: number) => {
emit('update:currentPage', currentPage)
if (evnt && currentPage !== props.currentPage) {
pagerMethods.dispatchEvent('page-change', { type: 'current', pageSize: props.pageSize, currentPage }, evnt)
}
}
const changeCurrentPage = (currentPage: number, evnt?: Event) => {
emit('update:currentPage', currentPage)
if (evnt && currentPage !== props.currentPage) {
pagerMethods.dispatchEvent('page-change', { type: 'current', pageSize: props.pageSize, currentPage }, evnt)
}
}
const triggerJumpEvent: VxeInputEvents.Blur = (params) => {
const { $event } = params
const inputElem: HTMLInputElement = $event.target as HTMLInputElement
const inpValue = XEUtils.toInteger(inputElem.value)
const pageCount = computePageCount.value
const current = inpValue <= 0 ? 1 : inpValue >= pageCount ? pageCount : inpValue
const currPage = XEUtils.toValueString(current)
inputElem.value = currPage
reactData.inpCurrPage = currPage
changeCurrentPage(current, $event)
}
const handleHomePage = (evnt?: Event) => {
const { currentPage } = props
if (currentPage > 1) {
changeCurrentPage(1, evnt)
}
}
const handleEndPage = (evnt?: Event) => {
const { currentPage } = props
const pageCount = computePageCount.value
if (currentPage < pageCount) {
changeCurrentPage(pageCount, evnt)
}
}
const handlePrevPage = (evnt?: Event) => {
const { currentPage } = props
const pageCount = computePageCount.value
if (currentPage > 1) {
changeCurrentPage(Math.min(pageCount, Math.max(currentPage - 1, 1)), evnt)
}
}
const handleNextPage = (evnt?: Event) => {
const { currentPage } = props
const pageCount = computePageCount.value
if (currentPage < pageCount) {
changeCurrentPage(Math.min(pageCount, currentPage + 1), evnt)
}
}
const handlePrevJump = (evnt?: Event) => {
const numList = computeNumList.value
changeCurrentPage(Math.max(props.currentPage - numList.length, 1), evnt)
}
const handleNextJump = (evnt?: Event) => {
const pageCount = computePageCount.value
const numList = computeNumList.value
changeCurrentPage(Math.min(props.currentPage + numList.length, pageCount), evnt)
}
const pageSizeEvent: VxeSelectEvents.Change = (params) => {
const { value } = params
const pageSize = XEUtils.toNumber(value)
const pageCount = getPageCount(props.total, pageSize)
let currentPage = props.currentPage
if (currentPage > pageCount) {
currentPage = pageCount
emit('update:currentPage', pageCount)
}
emit('update:pageSize', pageSize)
pagerMethods.dispatchEvent('page-change', { type: 'size', pageSize, currentPage }, params.$event)
}
const jumpKeydownEvent: VxeInputEvents.Keydown = (params) => {
const { $event } = params
if (globalEvents.hasKey($event, GLOBAL_EVENT_KEYS.ENTER)) {
triggerJumpEvent(params)
} else if (globalEvents.hasKey($event, GLOBAL_EVENT_KEYS.ARROW_UP)) {
$event.preventDefault()
handleNextPage($event)
} else if (globalEvents.hasKey($event, GLOBAL_EVENT_KEYS.ARROW_DOWN)) {
$event.preventDefault()
handlePrevPage($event)
}
}
// 第一页
const renderHomePage = () => {
const { currentPage, total } = props
const homeSlot = slots.home
const pageCount = computePageCount.value
if (homeSlot) {
return h('span', {
class: 'vxe-pager--custom-home-btn'
}, homeSlot({ $pager: $xePager, total, currentPage, pageCount }))
}
return h('button', {
class: ['vxe-pager--home-btn', {
'is--disabled': currentPage <= 1
}],
type: 'button',
title: getI18n('vxe.pager.homePageTitle'),
onClick: handleHomePage
}, [
h('i', {
class: ['vxe-pager--btn-icon', props.iconHomePage || getIcon().PAGER_HOME]
})
])
}
// 上一页
const renderPrevPage = () => {
const { currentPage, total } = props
const prevPageSlot = slots.prevPage || slots['prev-page']
const pageCount = computePageCount.value
if (prevPageSlot) {
return h('span', {
class: 'vxe-pager--custom-prev-btn'
}, prevPageSlot({ $pager: $xePager, total, currentPage, pageCount }))
}
return h('button', {
class: ['vxe-pager--prev-btn', {
'is--disabled': currentPage <= 1
}],
type: 'button',
title: getI18n('vxe.pager.prevPageTitle'),
onClick: handlePrevPage
}, [
h('i', {
class: ['vxe-pager--btn-icon', props.iconPrevPage || getIcon().PAGER_PREV_PAGE]
})
])
}
// 向上翻页
const renderPrevJump = (tagName?: string) => {
const { currentPage, total } = props
const prevJumpSlot = slots.prevJump || slots['prev-jump']
const pageCount = computePageCount.value
if (prevJumpSlot) {
return h('span', {
class: 'vxe-pager--custom-jump-prev'
}, prevJumpSlot({ $pager: $xePager, total, currentPage, pageCount }))
}
return h(tagName || 'button', {
class: ['vxe-pager--jump-prev', {
'is--fixed': !tagName,
'is--disabled': currentPage <= 1
}],
type: 'button',
title: getI18n('vxe.pager.prevJumpTitle'),
onClick: handlePrevJump
}, [
tagName
? h('i', {
class: ['vxe-pager--jump-more-icon', props.iconJumpMore || getIcon().PAGER_JUMP_MORE]
})
: null,
h('i', {
class: ['vxe-pager--jump-icon', props.iconJumpPrev || getIcon().PAGER_JUMP_PREV]
})
])
}
// 向下翻页
const renderNextJump = (tagName?: string) => {
const { currentPage, total } = props
const nextJumpSlot = slots.nextJump || slots['next-jump']
const pageCount = computePageCount.value
if (nextJumpSlot) {
return h('span', {
class: 'vxe-pager--custom-jump-next'
}, nextJumpSlot({ $pager: $xePager, total, currentPage, pageCount }))
}
return h(tagName || 'button', {
class: ['vxe-pager--jump-next', {
'is--fixed': !tagName,
'is--disabled': currentPage >= pageCount
}],
type: 'button',
title: getI18n('vxe.pager.nextJumpTitle'),
onClick: handleNextJump
}, [
tagName
? h('i', {
class: ['vxe-pager--jump-more-icon', props.iconJumpMore || getIcon().PAGER_JUMP_MORE]
})
: null,
h('i', {
class: ['vxe-pager--jump-icon', props.iconJumpNext || getIcon().PAGER_JUMP_NEXT]
})
])
}
// 下一页
const renderNextPage = () => {
const { currentPage, total } = props
const nextPageSlot = slots.nextPage || slots['next-page']
const pageCount = computePageCount.value
if (nextPageSlot) {
return h('span', {
class: 'vxe-pager--custom-next-btn'
}, nextPageSlot({ $pager: $xePager, total, currentPage, pageCount }))
}
return h('button', {
class: ['vxe-pager--next-btn', {
'is--disabled': currentPage >= pageCount
}],
type: 'button',
title: getI18n('vxe.pager.nextPageTitle'),
onClick: handleNextPage
}, [
h('i', {
class: ['vxe-pager--btn-icon', props.iconNextPage || getIcon().PAGER_NEXT_PAGE]
})
])
}
// 最后一页
const renderEndPage = () => {
const { currentPage, total } = props
const endSlot = slots.end
const pageCount = computePageCount.value
if (endSlot) {
return h('span', {
class: 'vxe-pager--custom-end-btn'
}, endSlot({ $pager: $xePager, total, currentPage, pageCount }))
}
return h('button', {
class: ['vxe-pager--end-btn', {
'is--disabled': currentPage >= pageCount
}],
type: 'button',
title: getI18n('vxe.pager.endPageTitle'),
onClick: handleEndPage
}, [
h('i', {
class: ['vxe-pager--btn-icon', props.iconEndPage || getIcon().PAGER_END]
})
])
}
// 页数
const renderNumber = (showJump?: boolean) => {
const { currentPage, total, pagerCount } = props
const numberSlot = showJump ? (slots.numberJump || slots['number-jump']) : slots.number
const nums = []
const pageCount = computePageCount.value
const numList = computeNumList.value
const offsetNumber = computeOffsetNumber.value
const isOv = pageCount > pagerCount
const isLt = isOv && currentPage > offsetNumber + 1
const isGt = isOv && currentPage < pageCount - offsetNumber
const restList: number[] = []
let startNumber = 1
if (isOv) {
if (currentPage >= pageCount - offsetNumber) {
startNumber = Math.max(pageCount - numList.length + 1, 1)
} else {
startNumber = Math.max(currentPage - offsetNumber, 1)
}
}
if (showJump && isLt) {
restList.push(1)
nums.push(
h('button', {
class: 'vxe-pager--num-btn',
type: 'button',
onClick: (evnt: Event) => jumpPageEvent(evnt, 1)
}, '1'),
renderPrevJump('span')
)
}
numList.forEach((item, index) => {
const number = startNumber + index
if (number <= pageCount) {
restList.push(number)
nums.push(
h('button', {
key: number,
class: ['vxe-pager--num-btn', {
'is--active': currentPage === number
}],
type: 'button',
onClick: (evnt: Event) => jumpPageEvent(evnt, number)
}, `${number}`)
)
}
})
if (showJump && isGt) {
restList.push(pageCount)
nums.push(
renderNextJump('button'),
h('button', {
class: 'vxe-pager--num-btn',
type: 'button',
onClick: (evnt: Event) => jumpPageEvent(evnt, pageCount)
}, pageCount)
)
}
if (numberSlot) {
return h('span', {
class: 'vxe-pager--custom-btn-wrapper'
}, numberSlot({ $pager: $xePager, total, numList: restList, currentPage, pageCount }))
}
return h('span', {
class: 'vxe-pager--btn-wrapper'
}, nums)
}
// jumpNumber
const renderJumpNumber = () => {
return renderNumber(true)
}
// sizes
const renderSizes = () => {
const { total, currentPage, pageSize, pageSizePlacement, transfer } = props
const sizesSlot = slots.sizes
const sizeList = computeSizeList.value
const pageCount = computePageCount.value
if (sizesSlot) {
return h('span', {
class: 'vxe-pager--custom-sizes'
}, sizesSlot({ $pager: $xePager, total, currentPage, pageCount, pageSize, options: sizeList }))
}
return h(VxeSelectComponent, {
class: 'vxe-pager--sizes',
modelValue: pageSize,
placement: pageSizePlacement,
transfer: transfer,
options: sizeList,
onChange: pageSizeEvent
})
}
// Jump
const renderJump = (isFull?: boolean) => {
const { total } = props
const { inpCurrPage } = reactData
const jumpSlot = isFull ? (slots.fullJump || slots['full-jump']) : slots.jump
const pageCount = computePageCount.value
if (jumpSlot) {
return h('span', {
class: 'vxe-pager--custom-jump'
}, jumpSlot({ $pager: $xePager, total, currentPage: inpCurrPage, pageCount }))
}
return h('span', {
class: 'vxe-pager--jump'
}, [
isFull
? h('span', {
class: 'vxe-pager--goto-text'
}, getI18n('vxe.pager.goto'))
: null,
h(VxeInputComponent, {
class: 'vxe-pager--goto',
modelValue: reactData.inpCurrPage,
placeholder: getI18n('vxe.pager.gotoTitle'),
align: 'center',
type: 'integer',
max: pageCount,
min: 1,
controls: false,
onKeydown: jumpKeydownEvent,
onBlur: triggerJumpEvent,
'onUpdate:modelValue' (val) {
reactData.inpCurrPage = val
}
}),
isFull
? h('span', {
class: 'vxe-pager--classifier-text'
}, getI18n('vxe.pager.pageClassifier'))
: null
])
}
// FullJump
const renderFullJump = () => {
return renderJump(true)
}
// PageCount
const renderPageCount = () => {
const { currentPage, total } = props
const pageCountSlot = slots.pageCount || slots['page-count']
const pageCount = computePageCount.value
if (pageCountSlot) {
return h('span', {
class: 'vxe-pager--custom-count'
}, pageCountSlot({ $pager: $xePager, total, currentPage, pageCount }))
}
return h('span', {
class: 'vxe-pager--count'
}, [
h('span', {
class: 'vxe-pager--separator'
}),
h('span', pageCount)
])
}
// total
const renderTotal = () => {
const { currentPage, total } = props
const totalSlot = slots.total
const pageCount = computePageCount.value
if (totalSlot) {
return h('span', {
class: 'vxe-pager--custom-total'
}, totalSlot({ $pager: $xePager, total, currentPage, pageCount }))
}
return h('span', {
class: 'vxe-pager--total'
}, getI18n('vxe.pager.total', [total]))
}
const dispatchEvent = (type: ValueOf<VxePagerEmits>, params: Record<string, any>, evnt: Event | null) => {
emit(type, createEvent(evnt, { $pager: $xePager }, params))
}
pagerMethods = {
dispatchEvent,
homePage () {
handleHomePage()
return nextTick()
},
endPage () {
handleEndPage()
return nextTick()
},
prevPage () {
handlePrevPage()
return nextTick()
},
nextPage () {
handleNextPage()
return nextTick()
},
prevJump () {
handlePrevJump()
return nextTick()
},
nextJump () {
handleNextJump()
return nextTick()
},
jumpPage (currentPage) {
const current = XEUtils.toNumber(currentPage) || 1
reactData.inpCurrPage = current
changeCurrentPage(current)
return nextTick()
}
}
pagerPrivateMethods = {
handlePrevPage,
handleNextPage,
handlePrevJump,
handleNextJump
}
Object.assign($xePager, pagerMethods, pagerPrivateMethods)
watch(() => props.currentPage, (value) => {
reactData.inpCurrPage = value
})
const renderVN = () => {
const { align, layouts, className } = props
const childNodes = []
const vSize = computeSize.value
const pageCount = computePageCount.value
if (slots.left) {
childNodes.push(
h('span', {
class: 'vxe-pager--left-wrapper'
}, slots.left({ $grid: $xeGrid }))
)
}
layouts.forEach((name) => {
let renderFn
switch (name) {
case 'Home':
renderFn = renderHomePage
break
case 'PrevJump':
renderFn = renderPrevJump
break
case 'PrevPage':
renderFn = renderPrevPage
break
case 'Number':
renderFn = renderNumber
break
case 'JumpNumber':
renderFn = renderJumpNumber
break
case 'NextPage':
renderFn = renderNextPage
break
case 'NextJump':
renderFn = renderNextJump
break
case 'End':
renderFn = renderEndPage
break
case 'Sizes':
renderFn = renderSizes
break
case 'FullJump':
renderFn = renderFullJump
break
case 'Jump':
renderFn = renderJump
break
case 'PageCount':
renderFn = renderPageCount
break
case 'Total':
renderFn = renderTotal
break
}
if (renderFn) {
childNodes.push(renderFn())
} else {
if (process.env.VUE_APP_VXE_ENV === 'development') {
errLog('vxe.error.notProp', [`layouts -> ${name}`])
}
}
})
if (slots.right) {
childNodes.push(
h('span', {
class: 'vxe-pager--right-wrapper'
}, slots.right({ $grid: $xeGrid }))
)
}
return h('div', {
ref: refElem,
class: ['vxe-pager', className ? (XEUtils.isFunction(className) ? className({ $pager: $xePager }) : className) : '', {
[`size--${vSize}`]: vSize,
[`align--${align}`]: align,
'is--border': props.border,
'is--background': props.background,
'is--perfect': props.perfect,
'is--hidden': props.autoHidden && pageCount === 1,
'is--loading': props.loading
}]
}, [
h('div', {
class: 'vxe-pager--wrapper'
}, childNodes)
])
}
$xePager.renderVN = renderVN
return $xePager
},
render () {
return this.renderVN()
}
})