vxe-table-demonic
Version:
一个基于 vue 的 PC 端表单/表格组件,支持增删改查、虚拟列表、虚拟树、懒加载、快捷菜单、数据校验、树形结构、打印导出、表单渲染、数据分页、弹窗、自定义模板、渲染器、JSON 配置式...
585 lines (537 loc) • 19.3 kB
text/typescript
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<VxePagerPropTypes.Size>, default: () => GlobalConfig.pager.size || GlobalConfig.size },
// 自定义布局
layouts: { type: Array as PropType<VxePagerPropTypes.Layouts>, default: () => GlobalConfig.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: () => GlobalConfig.pager.pageSize || 10 },
// 总条数
total: { type: Number as PropType<VxePagerPropTypes.Total>, default: 0 },
// 显示页码按钮的数量
pagerCount: { type: Number as PropType<VxePagerPropTypes.PagerCount>, default: () => GlobalConfig.pager.pagerCount || 7 },
// 每页大小选项列表
pageSizes: { type: Array as PropType<VxePagerPropTypes.PageSizes>, default: () => GlobalConfig.pager.pageSizes || [10, 15, 20, 50, 100] },
// 列对其方式
align: { type: String as PropType<VxePagerPropTypes.Align>, default: () => GlobalConfig.pager.align },
// 带边框
border: { type: Boolean as PropType<VxePagerPropTypes.Border>, default: () => GlobalConfig.pager.border },
// 带背景颜色
background: { type: Boolean as PropType<VxePagerPropTypes.Background>, default: () => GlobalConfig.pager.background },
// 配套的样式
perfect: { type: Boolean as PropType<VxePagerPropTypes.Perfect>, default: () => GlobalConfig.pager.perfect },
// 当只有一页时隐藏
autoHidden: { type: Boolean as PropType<VxePagerPropTypes.AutoHidden>, default: () => GlobalConfig.pager.autoHidden },
transfer: { type: Boolean as PropType<VxePagerPropTypes.Transfer>, default: () => GlobalConfig.pager.transfer },
className: [String, Function] as PropType<VxePagerPropTypes.ClassName>,
// 自定义图标
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.IconHome>,
iconEndPage: String as PropType<VxePagerPropTypes.IconEnd>
},
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<PagerReactData>({
inpCurrPage: props.currentPage
})
const refElem = ref() as Ref<HTMLDivElement>
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()
}
})