import { defineComponent, h, PropType, computed, inject, resolveComponent, ComponentOptions, ref, Ref, reactive, nextTick, watch } from 'vue' import XEUtils from 'xe-utils' import GlobalConfig from '../../v-x-e-table/src/conf' import { hasEventKey, EVENT_KEYS } from '../../tools/event' import { useSize } from '../../hooks/size' import { errLog } from '../../tools/log' import { VxePagerPropTypes, VxePagerConstructor, VxePagerEmits, VxeSelectEvents, PagerPrivateRef, VxeGridConstructor, PagerMethods, PagerPrivateMethods, VxePagerPrivateMethods, PagerReactData } from '../../../types/all' export default defineComponent({ name: 'VxePager', props: { size: { type: String as PropType, default: () => GlobalConfig.pager.size || GlobalConfig.size }, // 自定义布局 layouts: { type: Array as PropType, default: () => GlobalConfig.pager.layouts || ['PrevJump', 'PrevPage', 'Jump', 'PageCount', 'NextPage', 'NextJump', 'Sizes', 'Total'] }, // 当前页 currentPage: { type: Number as PropType, default: 1 }, // 加载中 loading: Boolean as PropType, // 每页大小 pageSize: { type: Number as PropType, default: () => GlobalConfig.pager.pageSize || 10 }, // 总条数 total: { type: Number as PropType, default: 0 }, // 显示页码按钮的数量 pagerCount: { type: Number as PropType, default: () => GlobalConfig.pager.pagerCount || 7 }, // 每页大小选项列表 pageSizes: { type: Array as PropType, default: () => GlobalConfig.pager.pageSizes || [10, 15, 20, 50, 100] }, // 列对其方式 align: { type: String as PropType, default: () => GlobalConfig.pager.align }, // 带边框 border: { type: Boolean as PropType, default: () => GlobalConfig.pager.border }, // 带背景颜色 background: { type: Boolean as PropType, default: () => GlobalConfig.pager.background }, // 配套的样式 perfect: { type: Boolean as PropType, default: () => GlobalConfig.pager.perfect }, // 当只有一页时隐藏 autoHidden: { type: Boolean as PropType, default: () => GlobalConfig.pager.autoHidden }, transfer: { type: Boolean as PropType, default: () => GlobalConfig.pager.transfer }, className: [String, Function] as PropType, // 自定义图标 iconPrevPage: String as PropType, iconJumpPrev: String as PropType, iconJumpNext: String as PropType, iconNextPage: String as PropType, iconJumpMore: String as PropType, iconHomePage: String as PropType, iconEndPage: String as PropType }, 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('$xegrid', null as VxeGridConstructor | null) const reactData = reactive({ inpCurrPage: props.currentPage }) const refElem = ref() as Ref const refMaps: PagerPrivateRef = { refElem } 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 computePageCount = computed(() => { return getPageCount(props.total, props.pageSize) }) 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 = (evnt: Event) => { const inputElem: HTMLInputElement = evnt.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, evnt) } 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: `${GlobalConfig.i18n('vxe.pager.pagesize', [item])}` } } return { value: '', label: '', ...item } }) }) 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 }) } const jumpInputEvent = (evnt: KeyboardEvent) => { const inputElem: HTMLInputElement = evnt.target as HTMLInputElement reactData.inpCurrPage = inputElem.value } const jumpKeydownEvent = (evnt: KeyboardEvent) => { if (hasEventKey(evnt, EVENT_KEYS.ENTER)) { triggerJumpEvent(evnt) } else if (hasEventKey(evnt, EVENT_KEYS.ARROW_UP)) { evnt.preventDefault() handleNextPage(evnt) } else if (hasEventKey(evnt, EVENT_KEYS.ARROW_DOWN)) { evnt.preventDefault() handlePrevPage(evnt) } } // 第一页 const renderHomePage = () => { return h('button', { class: ['vxe-pager--prev-btn', { 'is--disabled': props.currentPage <= 1 }], type: 'button', title: GlobalConfig.i18n('vxe.pager.homePageTitle'), onClick: handleHomePage }, [ h('i', { class: ['vxe-pager--btn-icon', props.iconHomePage || GlobalConfig.icon.PAGER_HOME] }) ]) } // 上一页 const renderPrevPage = () => { return h('button', { class: ['vxe-pager--prev-btn', { 'is--disabled': props.currentPage <= 1 }], type: 'button', title: GlobalConfig.i18n('vxe.pager.prevPageTitle'), onClick: handlePrevPage }, [ h('i', { class: ['vxe-pager--btn-icon', props.iconPrevPage || GlobalConfig.icon.PAGER_PREV_PAGE] }) ]) } // 向上翻页 const renderPrevJump = (tagName?: string) => { return h(tagName || 'button', { class: ['vxe-pager--jump-prev', { 'is--fixed': !tagName, 'is--disabled': props.currentPage <= 1 }], type: 'button', title: GlobalConfig.i18n('vxe.pager.prevJumpTitle'), onClick: handlePrevJump }, [ tagName ? h('i', { class: ['vxe-pager--jump-more-icon', props.iconJumpMore || GlobalConfig.icon.PAGER_JUMP_MORE] }) : null, h('i', { class: ['vxe-pager--jump-icon', props.iconJumpPrev || GlobalConfig.icon.PAGER_JUMP_PREV] }) ]) } // 向下翻页 const renderNextJump = (tagName?: string) => { const pageCount = computePageCount.value return h(tagName || 'button', { class: ['vxe-pager--jump-next', { 'is--fixed': !tagName, 'is--disabled': props.currentPage >= pageCount }], type: 'button', title: GlobalConfig.i18n('vxe.pager.nextJumpTitle'), onClick: handleNextJump }, [ tagName ? h('i', { class: ['vxe-pager--jump-more-icon', props.iconJumpMore || GlobalConfig.icon.PAGER_JUMP_MORE] }) : null, h('i', { class: ['vxe-pager--jump-icon', props.iconJumpNext || GlobalConfig.icon.PAGER_JUMP_NEXT] }) ]) } // 下一页 const renderNextPage = () => { const pageCount = computePageCount.value return h('button', { class: ['vxe-pager--next-btn', { 'is--disabled': props.currentPage >= pageCount }], type: 'button', title: GlobalConfig.i18n('vxe.pager.nextPageTitle'), onClick: handleNextPage }, [ h('i', { class: ['vxe-pager--btn-icon', props.iconNextPage || GlobalConfig.icon.PAGER_NEXT_PAGE] }) ]) } // 最后一页 const renderEndPage = () => { const pageCount = computePageCount.value return h('button', { class: ['vxe-pager--prev-btn', { 'is--disabled': props.currentPage >= pageCount }], type: 'button', title: GlobalConfig.i18n('vxe.pager.endPageTitle'), onClick: handleEndPage }, [ h('i', { class: ['vxe-pager--btn-icon', props.iconEndPage || GlobalConfig.icon.PAGER_END] }) ]) } // 页数 const renderNumber = (showJump?: boolean) => { const { currentPage, pagerCount } = props 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 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) { 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) { 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) { nums.push( renderNextJump('button'), h('button', { class: 'vxe-pager--num-btn', type: 'button', onClick: (evnt: Event) => jumpPageEvent(evnt, pageCount) }, pageCount) ) } return h('span', { class: 'vxe-pager--btn-wrapper' }, nums) } // jumpNumber const renderJumpNumber = () => { return renderNumber(true) } // sizes const renderSizes = () => { const sizeList = computeSizeList.value return h(resolveComponent('vxe-select') as ComponentOptions, { class: 'vxe-pager--sizes', modelValue: props.pageSize, placement: 'top', transfer: props.transfer, options: sizeList, onChange: pageSizeEvent }) } // Jump const renderJump = (isFull?: boolean) => { return h('span', { class: 'vxe-pager--jump' }, [ isFull ? h('span', { class: 'vxe-pager--goto-text' }, GlobalConfig.i18n('vxe.pager.goto')) : null, h('input', { class: 'vxe-pager--goto', value: reactData.inpCurrPage, type: 'text', autocomplete: 'off', onInput: jumpInputEvent, onKeydown: jumpKeydownEvent, onBlur: triggerJumpEvent }), isFull ? h('span', { class: 'vxe-pager--classifier-text' }, GlobalConfig.i18n('vxe.pager.pageClassifier')) : null ]) } // FullJump const renderFullJump = () => { return renderJump(true) } // PageCount const renderPageCount = () => { const pageCount = computePageCount.value return h('span', { class: 'vxe-pager--count' }, [ h('span', { class: 'vxe-pager--separator' }), h('span', pageCount) ]) } // total const renderTotal = () => { return h('span', { class: 'vxe-pager--total' }, GlobalConfig.i18n('vxe.pager.total', [props.total])) } pagerMethods = { dispatchEvent (type, params, evnt) { emit(type, Object.assign({ $pager: $xepager, $event: evnt }, params)) }, homePage () { handleHomePage() return nextTick() }, endPage () { handleEndPage() return nextTick() }, prevPage () { handlePrevPage() return nextTick() }, nextPage () { handleNextPage() return nextTick() }, prevJump () { handlePrevJump() return nextTick() }, nextJump () { handleNextJump() 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_TABLE_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() } })