refactor(pager): [pager] pager component refactor (#1198)

* refactor(pager): [pager] pager component refactor

* refactor(pager): [pager] pager component refactor

* refactor(pager): [pager] pager component refactor

* refactor(pager): [pager] pager component refactor
This commit is contained in:
gimmyhehe 2023-12-29 10:01:52 +08:00 committed by GitHub
parent 2ce68c0edb
commit 9de15b4536
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 999 additions and 862 deletions

View File

@ -18,7 +18,7 @@ const currentPage = ref(5)
function onBeforePageChange(param) {
const { callback, rollback } = param
Modal.confirm('您确定要放弃当前页的修改吗?').then((res) => {
Modal.confirm('您确定要继续变更操作吗?').then((res) => {
if (res === 'confirm') {
callback && callback()
} else {

View File

@ -25,7 +25,7 @@ export default {
methods: {
onBeforePageChange(param) {
const { callback, rollback } = param
Modal.confirm('您确定要放弃当前页的修改吗?').then((res) => {
Modal.confirm('您确定要继续变更操作吗?').then((res) => {
if (res === 'confirm') {
callback && callback()
} else {

View File

@ -1,10 +1,12 @@
<template>
<tiny-pager layout="total, sizes, prev, pager, next, slot, jumper" :total="1000">
<template #default>
<span>默认插槽</span>
</template>
</tiny-pager>
<tiny-pager layout="sizes, prev, current, next, total" :total="1000"></tiny-pager>
<div>
<tiny-pager layout="total, sizes, prev, pager, next, slot, jumper" :total="1000">
<template #default>
<span>默认插槽</span>
</template>
</tiny-pager>
<tiny-pager layout="sizes, prev, current, next, total" :total="1000"></tiny-pager>
</div>
</template>
<script setup>

View File

@ -1,10 +1,12 @@
<template>
<tiny-pager layout="total, sizes, prev, pager, next, slot, jumper" :total="1000">
<template #default>
<span>默认插槽</span>
</template>
</tiny-pager>
<tiny-pager layout="sizes, prev, current, next, total" :total="1000"></tiny-pager>
<div>
<tiny-pager layout="total, sizes, prev, pager, next, slot, jumper" :total="1000">
<template #default>
<span>默认插槽</span>
</template>
</tiny-pager>
<tiny-pager layout="sizes, prev, current, next, total" :total="1000"></tiny-pager>
</div>
</template>
<script>

View File

@ -1,22 +1,24 @@
<template>
<tiny-pager
:current-page="currentPage"
@update:current-page="currentPage = $event"
:page-size="100"
layout="total, sizes, prev, pager, next"
:total="5000000"
:custom-total="true"
>
</tiny-pager>
<tiny-pager
:current-page="currentPage"
@update:current-page="currentPage = $event"
:page-size="100"
layout="total, sizes, prev, pager, next"
:total="5000000"
:custom-total="'条数超出百万'"
>
</tiny-pager>
<div>
<tiny-pager
:current-page="currentPage"
@update:current-page="currentPage = $event"
:page-size="100"
layout="total, sizes, prev, pager, next"
:total="5000000"
:custom-total="true"
>
</tiny-pager>
<tiny-pager
:current-page="currentPage"
@update:current-page="currentPage = $event"
:page-size="100"
layout="total, sizes, prev, pager, next"
:total="5000000"
custom-total="条数超出百万"
>
</tiny-pager>
</div>
</template>
<script setup>

View File

@ -1,22 +1,24 @@
<template>
<tiny-pager
:current-page="currentPage"
@update:current-page="currentPage = $event"
:page-size="100"
layout="total, sizes, prev, pager, next"
:total="5000000"
:custom-total="true"
>
</tiny-pager>
<tiny-pager
:current-page="currentPage"
@update:current-page="currentPage = $event"
:page-size="100"
layout="total, sizes, prev, pager, next"
:total="5000000"
:custom-total="'条数超出百万'"
>
</tiny-pager>
<div>
<tiny-pager
:current-page="currentPage"
@update:current-page="currentPage = $event"
:page-size="100"
layout="total, sizes, prev, pager, next"
:total="5000000"
:custom-total="true"
>
</tiny-pager>
<tiny-pager
:current-page="currentPage"
@update:current-page="currentPage = $event"
:page-size="100"
layout="total, sizes, prev, pager, next"
:total="5000000"
custom-total="条数超出百万"
>
</tiny-pager>
</div>
</template>
<script>

View File

@ -1,7 +1,7 @@
<template>
<div>
是否隐藏<tiny-switch v-model="isHide"></tiny-switch>
<tiny-pager :hide-on-single-page="isHide" layout="prev, pager, next" :total="1"></tiny-pager>
<tiny-pager :hide-on-single-page="isHide" :total="1"></tiny-pager>
</div>
</template>

View File

@ -1,6 +1,6 @@
import { test, expect } from '@playwright/test'
test('只有一页时隐藏分页', async ({ page }) => {
test('单页时隐藏', async ({ page }) => {
page.on('pageerror', (exception) => expect(exception).toBeNull())
await page.goto('pager#hide-on-single-page')

View File

@ -1,7 +1,7 @@
<template>
<div>
是否隐藏<tiny-switch v-model="isHide"></tiny-switch>
<tiny-pager :hide-on-single-page="isHide" layout="prev, pager, next" :total="1"></tiny-pager>
<tiny-pager :hide-on-single-page="isHide" :total="1"></tiny-pager>
</div>
</template>

View File

@ -29,30 +29,35 @@ function debounce(fn, delay = 50) {
function handleCurrentChange(val) {
Modal.message({
message: `current-change 事件,当前页: ${val}`
message: `current-change 事件,当前页: ${val}`,
status: 'info'
})
}
function handleSizeChange(val) {
Modal.message({
message: `size-change 事件,每页条目数: ${val}`
message: `size-change 事件,每页条目数: ${val}`,
status: 'info'
})
}
function prevClick(val) {
Modal.message({
message: `prev-click 事件,当前页: ${val}`
message: `prev-click 事件,当前页: ${val}`,
status: 'info'
})
}
function nextClick(val) {
Modal.message({
message: `next-click 事件,当前页: ${val}`
message: `next-click 事件,当前页: ${val}`,
status: 'info'
})
}
const fetchData = debounce(() => {
Modal.message({
message: '模拟后台拉取数据'
message: '模拟后台拉取数据',
status: 'info'
})
})
</script>

View File

@ -34,27 +34,32 @@ export default {
methods: {
handleCurrentChange(val) {
Modal.message({
message: `current-change 事件,当前页: ${val}`
message: `current-change 事件,当前页: ${val}`,
status: 'info'
})
},
handleSizeChange(val) {
Modal.message({
message: `size-change 事件,每页条目数: ${val}`
message: `size-change 事件,每页条目数: ${val}`,
status: 'info'
})
},
prevClick(val) {
Modal.message({
message: `prev-click 事件,当前页: ${val}`
message: `prev-click 事件,当前页: ${val}`,
status: 'info'
})
},
nextClick(val) {
Modal.message({
message: `next-click 事件,当前页: ${val}`
message: `next-click 事件,当前页: ${val}`,
status: 'info'
})
},
fetchData: debounce(() => {
Modal.message({
message: '模拟后台拉取数据'
message: '模拟后台拉取数据',
status: 'info'
})
})
}

View File

@ -131,9 +131,9 @@ export default {
},
{
'demoId': 'hide-on-single-page',
'name': { 'zh-CN': '只有一页时隐藏分页', 'en-US': 'Grid Table Pagination' },
'name': { 'zh-CN': '单页时隐藏', 'en-US': 'Grid Table Pagination' },
'desc': {
'zh-CN': '<p>当 <code>hide-on-single-page</code> 为 <code>true</code> 时,只有一页时会隐藏分页。</p>\n',
'zh-CN': '<p>通过 <code>hide-on-single-page</code> 设置当仅有一页时是否隐藏分页组件。</p>\n',
'en-US': '<p>When there is only one page, the pagination will be hidden.</p>\n'
},
'codeFiles': ['hide-on-single-page.vue']
@ -164,7 +164,7 @@ export default {
'name': { 'zh-CN': '分页变更前置处理', 'en-US': 'Pre processing of pagination changes' },
'desc': {
'zh-CN':
'<p>通过 <code>is-before-page-change</code> 开启前置处理特性,翻页或者改变页大小时会触发 <code>before-page-change</code> 事件。事件函数类型为 <a href="#IBeforeChangeEvent">IBeforeChangeEvent</a> 调用传参中的 <code>callback</code> 继续变更,调用 <code>rollback</code> 中止变更。</p>\n',
'<p>通过 <code>is-before-page-change</code> 开启前置处理特性,翻页或者改变页大小时会触发 <code>before-page-change</code> 事件。调用传参中的 <code>callback</code> 继续变更,调用 <code>rollback</code> 中止变更。</p>\n',
'en-US':
'<p>By enabling the pre processing feature through <code>is before page change</code> , the <code>before page change</code> event is triggered when flipping or changing page size. The event function type is <a="#IBeforeChangeEvent">IBeforeChangeEvent</a> , call <code>callback</code> in the passed parameters to continue the change, and call <code>rollback</code> to abort the change.</p>\n'
},

View File

@ -0,0 +1,453 @@
import type { IPagerRenderlessParams } from '@/types'
import { emitEvent } from '../common/event'
export const computedShowPager =
({ props, state }: Pick<IPagerRenderlessParams, 'props' | 'state'>) =>
(): boolean => {
const hidePager = props.hideOnSinglePage && (!state.internalPageCount || state.internalPageCount === 1)
return state.internalLayout.length > 0 && !hidePager
}
export const computedInternalLayout =
({ props }: Pick<IPagerRenderlessParams, 'props'>) =>
(): string[] => {
let layout = ''
if (props.mode && !props.layout) {
props.mode === 'number' && (layout = 'total, sizes, prev, pager, next, jumper')
props.mode === 'simple' && (layout = 'sizes, total, prev, current, next')
props.mode === 'complete' && (layout = 'sizes, total, prev, pager, next, jumper')
props.mode === 'fixed' && (layout = 'prev,pager,next')
} else if ((!props.mode && props.layout) || (props.mode && props.layout)) {
layout = props.layout
} else {
layout = 'total, prev, pager, next, jumper'
}
if (!layout) {
return []
} else {
const components = layout.split(',').map((item) => item.trim())
return components
}
}
export const computedTotalText =
({ props, t }: Pick<IPagerRenderlessParams, 'props' | 't'>) =>
(): string => {
if (typeof props.customTotal === 'string') return props.customTotal
const totals = Number(props.total)
if (isNaN(totals)) return '0'
const HUNDRED_THOUSAND = 100000
const MILLION = 1000000
const TEN_MILLION = 10000000
if (totals <= HUNDRED_THOUSAND) {
return String(totals)
} else if (totals <= MILLION) {
return t('ui.page.hundredThousand')
} else if (totals <= TEN_MILLION) {
return t('ui.page.million')
} else {
return t('ui.page.tenMillion')
}
}
export const computedInternalPageCount =
({ props, state }: Pick<IPagerRenderlessParams, 'props' | 'state'>) =>
(): number | null => {
if (typeof props.total === 'number') {
return Math.max(1, Math.ceil(props.total / state.internalPageSize))
} else if (typeof props.pageCount === 'number') {
return Math.max(1, props.pageCount)
}
return null
}
export const handleJumperFocus =
({ state }: Pick<IPagerRenderlessParams, 'state'>) =>
(e: Event): void => {
state.jumperBackup = (e.target as HTMLInputElement)?.value
}
export const watchInternalCurrentPage =
({ state, emit }: Pick<IPagerRenderlessParams, 'state' | 'emit'>) =>
(currentPage: number): void => {
const value = String(currentPage)
if (state.jumperValue !== value) {
state.jumperValue = value
}
emit('update:currentPage', currentPage)
emit('current-change', currentPage)
state.lastEmittedPage = -1
}
export const watchPageSizes =
({ state, props }: Pick<IPagerRenderlessParams, 'props' | 'state'>) =>
(newVal: number[]): void => {
if (Array.isArray(newVal)) {
state.internalPageSize = newVal.includes(props.pageSize) ? props.pageSize : newVal[0]
}
}
export const watchCurrentPage =
({ state, api }: Pick<IPagerRenderlessParams, 'api' | 'state'>) =>
(curPage: number): void => {
state.internalCurrentPage = api.getValidCurrentPage(curPage)
}
export const watchInternalPageCount =
({ state, api }: Pick<IPagerRenderlessParams, 'api' | 'state'>) =>
(pageCount: number | null): void => {
const oldCurPage = state.internalCurrentPage
if (pageCount && pageCount > 0 && oldCurPage === 0) {
state.internalCurrentPage = 1
} else if (oldCurPage > Number(pageCount)) {
state.internalCurrentPage = pageCount || 1
state.userChangePageSize && api.emitChange()
}
state.userChangePageSize = false
}
export const watchPageSize =
({ state }: Pick<IPagerRenderlessParams, 'state'>) =>
(pageSize: number): void => {
state.internalPageSize = isNaN(pageSize) ? 10 : pageSize
}
export const watchTotal =
({ state }: Pick<IPagerRenderlessParams, 'state'>) =>
(total: number | undefined): void => {
state.internalTotal = total
}
export const handleSizeChange =
({ props, state, api, emit, vm }: Pick<IPagerRenderlessParams, 'props' | 'state' | 'api' | 'emit' | 'vm'>) =>
(val: number): void => {
// 防止用户pagerSizes传入字符串数组导致bug
val = Number(val)
if (val !== state.internalPageSize) {
const callback = () => {
if (!api.beforeChangeHandler()) {
return
}
state.internalPageSize = val
state.userChangePageSize = true
state.showSizes = false
emit('update:pageSize', val)
emit('size-change', val)
emit('page-change', {
currentPage: state.internalCurrentPage,
pageSize: val,
total: state.internalTotal
})
vm.$refs.sizesList[0].state.showPopper = false
}
if (props.isBeforePageChange) {
let newPageSize = val
let currentPageSize = state.internalPageSize
let params = { newPageSize, currentPageSize, callback }
api.beforeSizeChangeHandler(params)
} else {
callback()
}
}
}
export const handleJumperInput =
({ state }: Pick<IPagerRenderlessParams, 'state'>) =>
(e: Event): void => {
const target = e.target as HTMLInputElement
if (!target.value) {
state.jumperValue = ''
} else if (/^\d+$/.test(target.value)) {
state.jumperValue = target.value || '1'
}
target.value = state.jumperValue
}
export const handleJumperChange =
({ props, state, api }: Pick<IPagerRenderlessParams, 'props' | 'state' | 'api'>) =>
(): void => {
api.parseValueNumber()
const callback = () => {
api.handleJumperClick()
}
const rollback = () => {
state.jumperValue = String(state.jumperBackup)
}
const newPage = state.jumperValue
const currentPage = state.jumperBackup
if (props.isBeforePageChange && newPage !== currentPage) {
const params = { newPage, currentPage, callback, rollback }
api.beforePagerChangeHandler(params)
} else {
callback()
}
}
export const handleJumperClick =
({ props, state, api }: Pick<IPagerRenderlessParams, 'props' | 'state' | 'api'>) =>
(): void => {
if (!api.canJumperGo() || props.disabled) return
state.internalCurrentPage = api.getValidCurrentPage(state.jumperValue)
api.emitChange()
}
export const isValueNumber =
({ state }: Pick<IPagerRenderlessParams, 'state'>) =>
(): boolean => {
return !isNaN(Number(state.jumperValue))
}
export const parseValueNumber =
({ state }: Pick<IPagerRenderlessParams, 'state'>) =>
(): void => {
let value = Number(
String(state.jumperValue)
.split(/[^0-9-+.]/)
.join('')
)
if (isNaN(value)) {
value = 1
}
value = Number(value.toFixed(0))
const min = 1
const max = state.internalPageCount || 1
if (value >= max) {
state.jumperValue = String(max)
} else if (value <= min) {
state.jumperValue = String(min)
} else {
state.jumperValue = String(value)
}
}
export const handleSizeShowPopover =
({ state, props }: Pick<IPagerRenderlessParams, 'props' | 'state'>) =>
(): void => {
if (props.disabled) {
state.showSizes = false
return
}
state.showSizes = true
}
export const handleSizeHidePopover =
({ state }: Pick<IPagerRenderlessParams, 'state'>) =>
(): void => {
state.showSizes = false
}
export const canJumperGo =
({ props, state, vm }: Pick<IPagerRenderlessParams, 'props' | 'state' | 'vm'>) =>
(): boolean => {
const inputValue = Number(vm.$refs.jumperInput[0].value || 0)
const currentPage = Number(state.internalCurrentPage || 0)
return props.accurateJumper ? inputValue !== currentPage : true
}
export const beforeSizeChangeHandler =
({ state, emit }: Pick<IPagerRenderlessParams, 'emit' | 'state'>) =>
(params): void => {
const { newPageSize, currentPageSize, callback } = params
const newPage = 1
const currentPage = state.internalCurrentPage
const temp = {
newPage,
newPageSize,
currentPage,
currentPageSize,
callback
}
emit('before-page-change', temp)
}
export const beforePagerChangeHandler =
({ state, emit }: Pick<IPagerRenderlessParams, 'emit' | 'state'>) =>
(params): void => {
const { newPage, currentPage, callback, rollback } = params
const newPageSize = state.internalPageSize
const currentPageSize = state.internalPageSize
const temp = {
newPage,
newPageSize,
currentPage,
currentPageSize,
callback,
rollback
}
emit('before-page-change', temp)
}
export const beforeJumperChangeHandler =
({ state, emit }: Pick<IPagerRenderlessParams, 'emit' | 'state'>) =>
(params): void => {
const { newPage, currentPage, callback, rollback } = params
const newPageSize = state.internalPageSize
const currentPageSize = state.internalPageSize
const temp = {
newPage,
newPageSize,
currentPage,
currentPageSize,
callback,
rollback
}
emit('before-page-change', temp)
}
export const copyEmit =
({ emit }: Pick<IPagerRenderlessParams, 'emit'>) =>
(...args): void => {
emit(args[0], ...args.slice(1))
}
export const beforeChangeHandler =
({ state, api }: Pick<IPagerRenderlessParams, 'api' | 'state'>) =>
(val: number = -1) => {
return emitEvent(api.copyEmit, 'before-change', state.internalCurrentPage, this, val)
}
export const handleCurrentChange =
({ state, api }: Pick<IPagerRenderlessParams, 'api' | 'state'>) =>
(val: number): void => {
if (!api.beforeChangeHandler(val)) {
return
}
state.internalCurrentPage = api.getValidCurrentPage(val)
state.userChangePageSize = true
api.emitChange()
}
export const prev =
({ state, props, api, emit }: Pick<IPagerRenderlessParams, 'props' | 'state' | 'api' | 'emit'>) =>
(): void => {
const callback = () => {
if (props.disabled || !api.beforeChangeHandler(state.internalCurrentPage - 1)) {
return
}
const newVal = state.internalCurrentPage - 1
state.internalCurrentPage = api.getValidCurrentPage(newVal)
emit('prev-click', state.internalCurrentPage)
api.emitChange()
}
if (props.isBeforePageChange) {
const newPage = state.internalCurrentPage - 1
const temp = api.buildBeforePageChangeParam({ newPage, callback })
emit('before-page-change', temp)
} else {
callback()
}
}
export const next =
({ props, state, api, emit }: Pick<IPagerRenderlessParams, 'props' | 'state' | 'api' | 'emit'>) =>
(): void => {
const callback = () => {
if (props.disabled || !api.beforeChangeHandler(state.internalCurrentPage + 1)) {
return
}
const newVal = state.internalCurrentPage + 1
state.internalCurrentPage = api.getValidCurrentPage(newVal)
emit('next-click', state.internalCurrentPage)
api.emitChange()
}
if (props.isBeforePageChange) {
const newPage = state.internalCurrentPage + 1
const temp = api.buildBeforePageChangeParam({ newPage, callback })
emit('before-page-change', temp)
} else {
callback()
}
}
export const buildBeforePageChangeParam =
({ state }: Pick<IPagerRenderlessParams, 'state'>) =>
(param) => {
const currentPage = state.internalCurrentPage
const newPageSize = state.internalPageSize
const currentPageSize = state.internalPageSize
return { currentPage, newPageSize, currentPageSize, ...param }
}
export const getValidCurrentPage =
({ state }: Pick<IPagerRenderlessParams, 'state'>) =>
(val: string | number) => {
const parseVal = Number(val)
const hasPageCount = typeof state.internalPageCount === 'number'
let resetVal
if (hasPageCount) {
if (parseVal < 1) {
resetVal = 1
} else if (parseVal > (state.internalPageCount || 0)) {
resetVal = state.internalPageCount
}
} else {
if (isNaN(parseVal) || parseVal < 1) {
resetVal = 1
}
}
if (resetVal === undefined && isNaN(parseVal)) {
resetVal = 1
} else if (resetVal === 0) {
resetVal = 1
}
return resetVal === undefined ? parseVal : resetVal
}
export const emitChange =
({ state, nextTick, emit }: Pick<IPagerRenderlessParams, 'emit' | 'state' | 'nextTick'>) =>
(): void => {
nextTick(() => {
if (state.internalCurrentPage !== state.lastEmittedPage || state.userChangePageSize) {
emit('update:current-page', state.internalCurrentPage)
emit('page-change', {
currentPage: state.internalCurrentPage,
pageSize: state.internalPageSize,
total: state.internalTotal
})
state.lastEmittedPage = state.internalCurrentPage
state.userChangePageSize = false
}
})
}
export const setTotal =
({ state }: Pick<IPagerRenderlessParams, 'state'>) =>
(val: number): void => {
state.internalTotal = val
}

View File

@ -1,7 +1,135 @@
export const api = []
import type {
IPagerApi,
IPagerProps,
IPagerState,
ISharedRenderlessParamHooks,
IPagerRenderlessParamUtils
} from '@/types'
import {
computedShowPager,
computedInternalLayout,
computedTotalText,
computedInternalPageCount,
handleJumperFocus,
handleSizeChange,
handleJumperInput,
handleJumperChange,
handleJumperClick,
isValueNumber,
parseValueNumber,
handleSizeShowPopover,
handleSizeHidePopover,
canJumperGo,
beforeSizeChangeHandler,
beforePagerChangeHandler,
copyEmit,
beforeChangeHandler,
handleCurrentChange,
prev,
next,
buildBeforePageChangeParam,
getValidCurrentPage,
emitChange,
setTotal,
watchInternalCurrentPage,
watchPageSizes,
watchCurrentPage,
watchInternalPageCount,
watchPageSize,
watchTotal
} from './index'
export const renderless = () => {
const api = {}
export const api = [
'state',
'handleJumperFocus',
'handleSizeChange',
'handleJumperInput',
'handleJumperChange',
'handleJumperClick',
'isValueNumber',
'parseValueNumber',
'handleSizeShowPopover',
'handleSizeHidePopover',
'canJumperGo',
'beforeSizeChangeHandler',
'beforePagerChangeHandler',
'beforeJumperChangeHandler',
'beforeChangeHandler',
'handleCurrentChange',
'prev',
'next',
'buildBeforePageChangeParam',
'getValidCurrentPage',
'emitChange',
'setTotal'
]
export const renderless = (
props: IPagerProps,
{ reactive, computed, watch }: ISharedRenderlessParamHooks,
{ emit, vm, nextTick, t }: IPagerRenderlessParamUtils
): IPagerApi => {
const api = {} as IPagerApi
const state: IPagerState = reactive({
showSizes: false,
internalCurrentPage: 1,
internalPageSize: props.pageSize,
lastEmittedPage: -1,
userChangePageSize: false,
internalTotal: props.total,
jumperValue: '1',
jumperBackup: '1',
showPager: computed(() => api.computedShowPager()),
internalLayout: computed(() => api.computedInternalLayout()),
totalText: computed(() => api.computedTotalText()),
internalPageCount: computed(() => api.computedInternalPageCount())
})
Object.assign(api, {
state,
computedShowPager: computedShowPager({ props, state }),
computedInternalLayout: computedInternalLayout({ props }),
computedTotalText: computedTotalText({ props, t }),
computedInternalPageCount: computedInternalPageCount({ props, state }),
getValidCurrentPage: getValidCurrentPage({ state }),
handleJumperFocus: handleJumperFocus({ state }),
handleSizeChange: handleSizeChange({ props, state, api, emit, vm }),
handleJumperInput: handleJumperInput({ state }),
handleJumperChange: handleJumperChange({ props, state, api }),
handleJumperClick: handleJumperClick({ props, state, api }),
isValueNumber: isValueNumber({ state }),
parseValueNumber: parseValueNumber({ state }),
handleSizeShowPopover: handleSizeShowPopover({ state, props }),
handleSizeHidePopover: handleSizeHidePopover({ state }),
canJumperGo: canJumperGo({ props, state, vm }),
beforeSizeChangeHandler: beforeSizeChangeHandler({ state, emit }),
beforePagerChangeHandler: beforePagerChangeHandler({ state, emit }),
copyEmit: copyEmit({ emit }),
beforeChangeHandler: beforeChangeHandler({ state, api }),
handleCurrentChange: handleCurrentChange({ state, api }),
prev: prev({ state, props, api, emit }),
next: next({ props, state, api, emit }),
buildBeforePageChangeParam: buildBeforePageChangeParam({ state }),
emitChange: emitChange({ state, nextTick, emit }),
setTotal: setTotal({ state }),
// watch
watchInternalCurrentPage: watchInternalCurrentPage({ state, emit }),
watchPageSizes: watchPageSizes({ state, props }),
watchCurrentPage: watchCurrentPage({ state, api }),
watchInternalPageCount: watchInternalPageCount({ state, api }),
watchPageSize: watchPageSize({ state }),
watchTotal: watchTotal({ state })
})
state.internalCurrentPage = api.getValidCurrentPage(props.currentPage)
watch(() => state.internalCurrentPage, api.watchInternalCurrentPage)
watch(() => props.pageSizes, api.watchPageSizes, { immediate: true })
watch(() => props.currentPage, api.watchCurrentPage)
watch(() => state.internalPageCount, api.watchInternalPageCount)
watch(() => props.pageSize, api.watchPageSize, { immediate: true })
watch(() => props.total, api.watchTotal)
return api
}

View File

@ -0,0 +1,96 @@
import type { ExtractPropTypes } from 'vue'
import type { pagerProps } from '@/pager/src'
import type { ISharedRenderlessFunctionParams, ISharedRenderlessParamUtils } from './shared.type'
import type {
computedShowPager,
computedInternalLayout,
computedTotalText,
computedInternalPageCount,
handleJumperFocus,
handleSizeChange,
handleJumperInput,
handleJumperChange,
handleJumperClick,
isValueNumber,
parseValueNumber,
handleSizeShowPopover,
handleSizeHidePopover,
canJumperGo,
beforeSizeChangeHandler,
beforePagerChangeHandler,
copyEmit,
beforeChangeHandler,
handleCurrentChange,
prev,
next,
buildBeforePageChangeParam,
getValidCurrentPage,
emitChange,
setTotal,
watchInternalCurrentPage,
watchPageSizes,
watchCurrentPage,
watchInternalPageCount,
watchPageSize,
watchTotal
} from '../src/pager'
export type IPagerProps = ExtractPropTypes<typeof pagerProps>
export interface IPagerState {
showPager: boolean
showSizes: boolean
internalCurrentPage: number
internalPageSize: number
lastEmittedPage: number
userChangePageSize: boolean
internalTotal: number | undefined
jumperValue: string
jumperBackup: string
internalLayout: string[]
totalText: string
internalPageCount: number | null
}
export interface IPagerApi {
state: IPagerState
t: IPagerRenderlessParamUtils['t']
computedShowPager: ReturnType<typeof computedShowPager>
computedInternalLayout: ReturnType<typeof computedInternalLayout>
computedTotalText: ReturnType<typeof computedTotalText>
computedInternalPageCount: ReturnType<typeof computedInternalPageCount>
handleJumperFocus: ReturnType<typeof handleJumperFocus>
handleSizeChange: ReturnType<typeof handleSizeChange>
handleJumperInput: ReturnType<typeof handleJumperInput>
handleJumperChange: ReturnType<typeof handleJumperChange>
handleJumperClick: ReturnType<typeof handleJumperClick>
isValueNumber: ReturnType<typeof isValueNumber>
parseValueNumber: ReturnType<typeof parseValueNumber>
handleSizeShowPopover: ReturnType<typeof handleSizeShowPopover>
handleSizeHidePopover: ReturnType<typeof handleSizeHidePopover>
canJumperGo: ReturnType<typeof canJumperGo>
beforeSizeChangeHandler: ReturnType<typeof beforeSizeChangeHandler>
beforePagerChangeHandler: ReturnType<typeof beforePagerChangeHandler>
copyEmit: ReturnType<typeof copyEmit>
beforeChangeHandler: ReturnType<typeof beforeChangeHandler>
handleCurrentChange: ReturnType<typeof handleCurrentChange>
prev: ReturnType<typeof prev>
next: ReturnType<typeof next>
buildBeforePageChangeParam: ReturnType<typeof buildBeforePageChangeParam>
getValidCurrentPage: ReturnType<typeof getValidCurrentPage>
emitChange: ReturnType<typeof emitChange>
setTotal: ReturnType<typeof setTotal>
watchInternalCurrentPage: ReturnType<typeof watchInternalCurrentPage>
watchPageSizes: ReturnType<typeof watchPageSizes>
watchCurrentPage: ReturnType<typeof watchCurrentPage>
watchInternalPageCount: ReturnType<typeof watchInternalPageCount>
watchPageSize: ReturnType<typeof watchPageSize>
watchTotal: ReturnType<typeof watchTotal>
}
export type IPagerRenderlessParams = ISharedRenderlessFunctionParams<never> & {
api: IPagerApi
state: IPagerState
props: IPagerProps
}
export type IPagerRenderlessParamUtils = ISharedRenderlessParamUtils<never>

View File

@ -1,70 +1,73 @@
import type { PropType } from 'vue'
import { $props, $prefix, $setup, defineComponent } from '@opentiny/vue-common'
import template from 'virtual-template?pc|mobile-first'
export const pagerProps = {
...$props,
accurateJumper: {
type: Boolean,
default: () => true
},
appendToBody: {
type: Boolean,
default: () => true
},
currentPage: {
type: Number,
default: () => 1
},
disabled: {
type: Boolean,
default: () => false
},
hideOnSinglePage: Boolean,
isBeforePageChange: Boolean,
layout: String,
mode: String,
nextText: String,
pageCount: Number,
pageSize: {
type: Number,
default: () => 10
},
pageSizes: {
type: Array as PropType<number[]>,
default: () => [10, 20, 30, 40, 50, 100]
},
pagerCount: {
type: Number,
validator: (value) => (value | 0) === value && value > 2 && value < 22 && value % 2 === 1,
default: () => 7
},
popperAppendToBody: {
type: Boolean,
default: () => true
},
showTotalLoading: {
type: Boolean,
default: () => false
},
customTotal: {
type: [Boolean, String],
default: () => false
},
popperClass: String,
prevText: String,
total: Number,
size: {
type: String,
default: ''
},
align: {
// 分页对齐方式 【leftcenterright】
type: String,
validator: (value) => ['left', 'center', 'right'].includes(value)
}
}
export default defineComponent({
name: $prefix + 'Pager',
props: {
...$props,
accurateJumper: {
type: Boolean,
default: () => true
},
appendToBody: {
type: Boolean,
default: () => true
},
currentPage: {
type: Number,
default: () => 1
},
disabled: {
type: Boolean,
default: () => false
},
hideOnSinglePage: Boolean,
isBeforePageChange: Boolean,
layout: String,
mode: String,
nextText: String,
pageCount: Number,
pageSize: {
type: Number,
default: () => 10
},
pageSizes: {
type: Array,
default: () => [10, 20, 30, 40, 50, 100]
},
pagerCount: {
type: Number,
validator: (value) => (value | 0) === value && value > 2 && value < 22 && value % 2 === 1,
default: () => 7
},
popperAppendToBody: {
type: Boolean,
default: () => true
},
showTotalLoading: {
type: Boolean,
default: () => false
},
customTotal: {
type: [Boolean, String],
default: () => false
},
popperClass: String,
prevText: String,
total: Number,
size: {
type: String,
default: ''
},
align: {
// 分页对齐方式 【leftcenterright】
type: String,
validator: (value) => ['left', 'center', 'right'].includes(value)
}
},
props: pagerProps,
setup(props, context) {
return $setup({ props, context, template })
}

View File

@ -9,752 +9,191 @@
* A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS.
*
-->
<template>
<div
v-if="state.showPager"
:class="['tiny-pager tiny-pager__number', size ? 'tiny-pager--' + size : '', disabled ? 'is-disabled' : '']"
:style="{ textAlign: align }"
>
<template v-for="(item, index) in state.internalLayout">
<!-- prev -->
<button
v-if="item === 'prev'"
:key="'prev' + index"
type="button"
class="tiny-pager__btn-prev"
:disabled="disabled || state.internalCurrentPage <= 1"
@click="prev"
>
<span v-if="prevText">{{ prevText }}</span>
<chevron-left v-else class="tiny-svg-size" />
</button>
<!-- jumper -->
<div v-else-if="item === 'jumper'" :key="'jumper' + index" class="tiny-pager__group">
<div class="tiny-pager__goto">
<input
type="text"
ref="jumperInput"
:value="state.jumperValue"
:disabled="disabled"
@focus="handleJumperFocus"
@input="handleJumperInput"
@change="handleJumperChange"
/>
<button :class="['tiny-btn', disabled ? 'is-disabled' : '']" type="button" @click="handleJumperClick">
{{ t('ui.page.goto') }}
</button>
</div>
</div>
<!-- current -->
<div v-else-if="item === 'current'" :key="'current' + index" class="tiny-pager__group tiny-unselect">
<ul class="tiny-pager__pages">
<li class="is-active">{{ state.internalCurrentPage }}</li>
</ul>
</div>
<!-- pager-item -->
<pager
v-else-if="item === 'pager'"
:key="'pager' + index"
:is-before-page-change="isBeforePageChange"
:current-page="state.internalCurrentPage"
:page-count="state.internalPageCount"
:pager-count="pagerCount"
:disabled="disabled"
@change="handleCurrentChange"
@before-page-change="beforePagerChangeHandler"
></pager>
<!-- next -->
<button
v-else-if="item === 'next'"
:key="'next' + index"
type="button"
class="tiny-pager__btn-next"
:disabled="disabled || state.internalCurrentPage === state.internalPageCount || state.internalPageCount === 0"
@click="next"
>
<span v-if="nextText">{{ nextText }}</span>
<chevron-right v-else class="tiny-svg-size" />
</button>
<!-- sizes -->
<div v-else-if="item === 'sizes'" :key="'sizes' + index" class="tiny-pager__group tiny-pager__sizes">
<tiny-popover
ref="sizesList"
placement="bottom-start"
:append-to-body="popperAppendToBody === false ? false : appendToBody"
trigger="click"
:popper-class="'tiny-pager__selector ' + (popperClass ? '' + popperClass : '')"
:visible-arrow="false"
:disabled="disabled"
@show="handleSizeShowPopover"
@hide="handleSizeHidePopover"
>
<template #reference>
<div class="tiny-pager__popover">
<div class="tiny-pager__page-size">
<span class="sizes">{{ state.internalPageSize }}</span>
<div class="tiny-pager__page-size-btn">
<triangle-down :class="['tiny-svg-size', state.showSizes ? 'tiny-svg-size__reverse-180' : '']" />
</div>
</div>
</div>
</template>
<div class="tiny-pager tiny-pager__selector-body">
<ul class="tiny-pager__selector-poplist">
<li
v-for="(sizeItem, sizeIndex) in pageSizes"
:key="sizeIndex"
:class="['list-item', sizeItem === state.internalPageSize ? 'is-selected select-pre' : '']"
:title="String(sizeItem)"
@click="handleSizeChange(sizeItem)"
>
{{ sizeItem }}
</li>
</ul>
</div>
</tiny-popover>
</div>
<slot v-else-if="item === 'slot'"></slot>
<!-- total -->
<div
v-else-if="item === 'total' && typeof state.internalTotal === 'number'"
:key="'total' + index"
class="tiny-pager__group tiny-pager__pull-left"
:class="{
'is-disabled': disabled,
'tiny-pager__loading': showTotalLoading
}"
>
<div :class="['tiny-pager__total', size ? 'tiny-pager--' + size : '']">
<template v-if="showTotalLoading">
<div v-loading="showTotalLoading" class="tiny-pager__total-loading"></div>
<span class="tiny-pager__loading-text">{{ t('ui.page.loadingTotals') }}</span>
</template>
<template v-else>
<span>{{ t('ui.page.total') }}</span>
<span class="tiny-pager__total-allpage"> {{ customTotal ? state.totalText : state.internalTotal }} </span>
</template>
</div>
</div>
</template>
</div>
</template>
<script lang="tsx">
import Pager from '@opentiny/vue-pager-item'
import Popover from '@opentiny/vue-popover'
import Loading from '@opentiny/vue-loading'
import { t } from '@opentiny/vue-locale'
import { $prefix, h, setup, defineComponent, $props } from '@opentiny/vue-common'
import { $prefix, setup, defineComponent, props } from '@opentiny/vue-common'
import { renderless, api } from '@opentiny/vue-renderless/pager/vue'
import { iconTriangleDown, iconChevronLeft, iconChevronRight } from '@opentiny/vue-icon'
import { emitEvent } from '@opentiny/vue-renderless/common/event'
import type { IPagerApi } from '@opentiny/vue-renderless/types/pager.type'
import '@opentiny/vue-theme/pager/index.less'
export default defineComponent({
name: $prefix + 'Pager',
props: {
...$props,
accurateJumper: {
type: Boolean,
default: () => true
},
appendToBody: {
type: Boolean,
default: () => true
},
currentPage: {
type: Number,
default: () => 1
},
disabled: {
type: Boolean,
default: () => false
},
hideOnSinglePage: Boolean,
isBeforePageChange: Boolean,
layout: String,
mode: String,
nextText: String,
pageCount: Number,
pageSize: {
type: Number,
default: () => 10
},
pageSizes: {
type: Array,
default: () => [10, 20, 30, 40, 50, 100]
},
pagerCount: {
type: Number,
validator: (value) => (value | 0) === value && value > 2 && value < 22 && value % 2 === 1,
default: () => 7
},
popperAppendToBody: {
type: Boolean,
default: () => true
},
showTotalLoading: {
type: Boolean,
default: () => false
},
customTotal: {
type: [Boolean, String],
default: () => false
},
popperClass: String,
prevText: String,
total: Number,
size: {
type: String,
default: ''
},
align: String
},
data() {
return {
internalCurrentPage: 1,
internalPageSize: 0,
lastEmittedPage: -1,
userChangePageSize: false,
internalTotal: this.total
}
props: [
...props,
'accurateJumper',
'appendToBody',
'currentPage',
'disabled',
'hideOnSinglePage',
'isBeforePageChange',
'layout',
'mode',
'nextText',
'pageCount',
'pageSize',
'pageSizes',
'pagerCount',
'popperAppendToBody',
'showTotalLoading',
'customTotal',
'popperClass',
'prevText',
'total',
'size',
'align'
],
directives: {
loading: Loading.directive
},
setup(props, context) {
return setup({ props, context, renderless, api })
},
render() {
const layout = this.internalLayout
if (!layout) {
return null
}
if (this.hideOnSinglePage && (!this.internalPageCount || this.internalPageCount === 1)) {
return null
}
const TEMPLATE_MAP = {
prev: <prev></prev>,
jumper: (
<jumper
ref="jumper"
isBeforePageChange={this.isBeforePageChange}
onBeforePageChange={this.beforeJumperChangeHandler}
disabled={this.disabled}
max={this.internalPageCount}></jumper>
),
current: <current></current>,
pager: (
<pager
isBeforePageChange={this.isBeforePageChange}
onBeforePageChange={this.beforePagerChangeHandler}
currentPage={this.internalCurrentPage}
pageCount={this.internalPageCount}
pagerCount={this.pagerCount}
onChange={this.handleCurrentChange}
disabled={this.disabled}></pager>
),
next: <next></next>,
sizes: (
<sizes
ref="sizes"
isBeforePageChange={this.isBeforePageChange}
onBeforePageChange={this.beforeSizeChangeHandler}
popperAppendToBody={this.popperAppendToBody === false ? false : this.appendToBody}
popperClass={this.popperClass}
pageSizes={this.pageSizes}></sizes>
),
slot: typeof this.slots.default === 'function' ? this.slots.default() : this.slots.default,
total: <total></total>
}
const components = layout.split(',').map((item) => item.trim())
const templateChildren = components.map((compo) => {
return TEMPLATE_MAP[compo]
})
return (
<div
class={[
'tiny-pager tiny-pager__number',
this.size ? 'tiny-pager--' + this.size : '',
this.disabled ? 'is-disabled' : ''
]}
style={{ textAlign: this.align }}>
{templateChildren}
</div>
)
return setup({ props, context, renderless, api }) as unknown as IPagerApi
},
components: {
Prev: {
render() {
const ChevronLeft = iconChevronLeft()
return (
<button
type="button"
class="tiny-pager__btn-prev"
disabled={this.$parent.disabled || this.$parent.internalCurrentPage <= 1}
onClick={this.$parent.prev}>
{this.$parent.prevText ? <span>{this.$parent.prevText}</span> : <ChevronLeft class="tiny-svg-size" />}
</button>
)
}
},
Next: {
render() {
const ChevronRight = iconChevronRight()
return (
<button
type="button"
class="tiny-pager__btn-next"
disabled={
this.$parent.disabled ||
this.$parent.internalCurrentPage === this.$parent.internalPageCount ||
this.$parent.internalPageCount === 0
}
onClick={this.$parent.next}>
{this.$parent.nextText ? <span>{this.$parent.nextText}</span> : <ChevronRight class="tiny-svg-size" />}
</button>
)
}
},
Current: {
render() {
const { internalCurrentPage } = this.$parent
return (
<div class="tiny-pager__group tiny-unselect">
<ul class="tiny-pager__pages">
<li class="is-active" v-text={internalCurrentPage}></li>
</ul>
</div>
)
}
},
Sizes: {
props: {
pageSizes: Array,
appendToBody: Boolean,
isBeforePageChange: Boolean,
popperClass: String,
popperAppendToBody: {
type: Boolean,
default: true
}
},
data() {
return {
showSizes: false
}
},
watch: {
pageSizes: {
immediate: true,
handler(newVal) {
if (Array.isArray(newVal)) {
this.$parent.internalPageSize = newVal.includes(this.$parent.pageSize)
? this.$parent.pageSize
: this.pageSizes[0]
}
}
}
},
render() {
const TriangleDown = iconTriangleDown()
const scopedSlots = {
reference: () => (
<div slot="reference" class="tiny-pager__popover">
<div class="tiny-pager__page-size" ref="pageSize">
<span class="sizes">{this.$parent.internalPageSize}</span>
<div class="tiny-pager__page-size-btn">
<TriangleDown class={['tiny-svg-size', this.showSizes ? 'tiny-svg-size__reverse-180' : '']} />
</div>
</div>
</div>
),
default: () => (
<div class="tiny-pager tiny-pager__selector-body">
<ul class="tiny-pager__selector-poplist">
{this.pageSizes.map((item) => (
<li
class={['list-item', item === this.$parent.internalPageSize ? 'is-selected select-pre' : '']}
val={item}
title={item}
onClick={() => this.handleChange(item)}>
{item}
</li>
))}
</ul>
</div>
)
}
return (
<div class="tiny-pager__group tiny-pager__sizes">
{h(Popover, {
props: {
placement: 'bottom-start',
appendToBody: this.popperAppendToBody,
trigger: 'click',
popperClass: 'tiny-pager__selector' + (this.popperClass ? ' ' + this.popperClass : ''),
visibleArrow: false,
disabled: this.$parent.disabled
},
on: {
show: this.handleShowPopover,
hide: this.handleHidePopover
},
scopedSlots,
ref: 'sizesList'
})}
</div>
)
},
methods: {
handleChange(val) {
if (val !== this.$parent.internalPageSize) {
const callback = () => {
if (!this.$parent.beforeChangeHandler()) {
return false
}
this.$parent.internalPageSize = val = parseInt(val, 10)
this.$parent.userChangePageSize = true
this.showSizes = false
this.$parent.$emit('update:pageSize', val)
this.$parent.$emit('size-change', val)
this.$parent.$emit('page-change', {
currentPage: this.$parent.internalCurrentPage,
pageSize: val,
total: this.$parent.internalTotal
})
this.$refs.sizesList.state.showPopper = false
}
if (this.isBeforePageChange) {
let newPageSize = val
let currentPageSize = this.$parent.internalPageSize
let params = { newPageSize, currentPageSize, callback }
this.$parent.beforeSizeChangeHandler(params)
} else {
callback()
}
}
},
handleShowPopover() {
if (this.$parent.disabled) return (this.showSizes = false)
this.showSizes = true
},
handleHidePopover() {
this.showSizes = false
}
}
},
Jumper: {
props: {
isBeforePageChange: Boolean,
disabled: Boolean,
min: {
type: Number,
default: 1
},
max: {
type: Number,
default: 10
},
initValue: {
type: Number,
default: 1
}
},
data() {
return {
backupValue: String(this.initValue),
value: String(this.initValue)
}
},
watch: {
'$parent.internalCurrentPage': {
handler(currentPage) {
const value = String(currentPage)
if (this.value !== value) {
this.value = value
}
},
immediate: true
}
},
methods: {
handleFocus(e) {
this.backupValue = e.target.value
},
handleInput(e) {
if (!e.target.value) {
this.value = ''
} else if (/^\d+$/.test(e.target.value)) {
this.value = Number(e.target.value) || 1
}
e.target.value = this.value
},
handleChange() {
this.parseValueNumber()
const callback = () => {
this.handleClick()
}
const rollback = () => {
this.value = String(this.backupValue)
}
const newPage = this.value
const currentPage = this.backupValue
if (this.isBeforePageChange && newPage !== currentPage) {
const params = { newPage, currentPage, callback, rollback }
this.$parent.beforePagerChangeHandler(params)
} else {
callback()
}
},
handleClick() {
if (!this.$parent.canJumperGo() || this.disabled) return
this.$parent.internalCurrentPage = this.$parent.getValidCurrentPage(this.value)
this.$parent.emitChange()
},
isValueNumber() {
return !isNaN(Number(this.value))
},
parseValueNumber() {
let value = Number(
String(this.value)
.split(/[^0-9-+.]/)
.join('')
)
if (isNaN(value)) {
value = this.min
}
value = value.toFixed(0)
const min = this.min
const max = this.max
if (value >= max) {
this.value = String(max)
} else if (value <= min) {
this.value = String(min)
} else {
this.value = String(value)
}
}
},
render() {
return h(
'div',
{
class: ['tiny-pager__group']
},
[
h('div', { class: ['tiny-pager__goto'] }, [
h('input', {
domProps: {
value: this.value
},
attrs: {
type: 'text',
disabled: this.disabled
},
on: {
focus: this.handleFocus,
input: this.handleInput,
change: this.handleChange
},
ref: 'input'
}),
h(
'button',
{
class: ['tiny-btn', this.disabled ? 'is-disabled' : ''],
attrs: { type: 'button' },
on: { click: this.handleClick }
},
[t('ui.page.goto')]
)
])
]
)
}
},
Total: {
watch: {
'$parent.showTotalLoading': function () {
this.$nextTick(() => {
this.serviceLoading()
})
}
},
methods: {
serviceLoading() {
if (document.querySelector('.tiny-pager__total-loading')) {
Loading.service({
target: document.querySelector('.tiny-pager__total-loading')
})
}
}
},
mounted() {
this.serviceLoading()
},
render() {
let tempalte = ''
if (typeof this.$parent.internalTotal === 'number') {
const loadingTotalTemplate = (
<div class="tiny-pager__group tiny-pager__pull-left tiny-pager__loading">
<div class="tiny-pager__total">
<div class="tiny-pager__total-loading"></div>
<span class="tiny-pager__loading-text">{t('ui.page.loadingTotals')}</span>
</div>
</div>
)
const totalTemplate = (
<div class={['tiny-pager__group tiny-pager__pull-left', this.$parent.disabled ? 'is-disabled' : '']}>
{' '}
<div class={['tiny-pager__total', this.$parent.size ? 'tiny-pager--' + this.$parent.size : '']}>
<span>{t('ui.page.total')}</span>
<span class="tiny-pager__total-allpage">
{this.$parent.customTotal ? this.$parent.totalText : this.$parent.internalTotal}
</span>
</div>
</div>
)
tempalte = this.$parent.showTotalLoading ? loadingTotalTemplate : totalTemplate
}
return tempalte
}
},
TinyPopover: Popover,
ChevronLeft: iconChevronLeft(),
ChevronRight: iconChevronRight(),
TriangleDown: iconTriangleDown(),
Pager
},
methods: {
canJumperGo() {
const inputValue = Number(this.$refs.jumper.$refs.input.value || 0)
const currentPage = Number(this.internalCurrentPage || 0)
return this.accurateJumper ? inputValue !== currentPage : true
},
beforeSizeChangeHandler(params) {
const { newPageSize, currentPageSize, callback } = params
const newPage = 1
const currentPage = this.internalCurrentPage
const temp = {
newPage,
newPageSize,
currentPage,
currentPageSize,
callback
}
this.$emit('before-page-change', temp)
},
beforePagerChangeHandler(params) {
const { newPage, currentPage, callback, rollback } = params
const newPageSize = this.internalPageSize
const currentPageSize = this.internalPageSize
const temp = {
newPage,
newPageSize,
currentPage,
currentPageSize,
callback,
rollback
}
this.$emit('before-page-change', temp)
},
beforeJumperChangeHandler(params) {
const { newPage, currentPage, callback, rollback } = params
const newPageSize = this.internalPageSize
const currentPageSize = this.internalPageSize
const temp = {
newPage,
newPageSize,
currentPage,
currentPageSize,
callback,
rollback
}
this.$emit('before-page-change', temp)
},
copyEmit(...args) {
this.$emit.apply(this, args)
},
beforeChangeHandler(val = -1) {
return emitEvent(this.copyEmit, 'before-change', this.internalCurrentPage, this, val)
},
handleCurrentChange(val) {
if (!this.beforeChangeHandler(val)) {
return false
}
this.internalCurrentPage = this.getValidCurrentPage(val)
this.userChangePageSize = true
this.emitChange()
},
prev() {
const callback = () => {
if (this.disabled || !this.beforeChangeHandler(this.internalCurrentPage - 1)) {
return false
}
const newVal = this.internalCurrentPage - 1
this.internalCurrentPage = this.getValidCurrentPage(newVal)
this.$emit('prev-click', this.internalCurrentPage)
this.emitChange()
}
if (this.isBeforePageChange) {
const newPage = this.internalCurrentPage - 1
const temp = this.buildBeforePageChangeParam({ newPage, callback })
this.$emit('before-page-change', temp)
} else {
callback()
}
},
next() {
const callback = () => {
if (this.disabled || !this.beforeChangeHandler(this.internalCurrentPage + 1)) {
return false
}
const newVal = this.internalCurrentPage + 1
this.internalCurrentPage = this.getValidCurrentPage(newVal)
this.$emit('next-click', this.internalCurrentPage)
this.emitChange()
}
if (this.isBeforePageChange) {
const newPage = this.internalCurrentPage + 1
const temp = this.buildBeforePageChangeParam({ newPage, callback })
this.$emit('before-page-change', temp)
} else {
callback()
}
},
buildBeforePageChangeParam(param) {
const currentPage = this.internalCurrentPage
const newPageSize = this.internalPageSize
const currentPageSize = this.internalPageSize
return { currentPage, newPageSize, currentPageSize, ...param }
},
getValidCurrentPage(val) {
val = parseInt(val, 10)
const hasPageCount = typeof this.internalPageCount === 'number'
let resetVal
if (hasPageCount) {
if (val < 1) {
resetVal = 1
} else if (val > this.internalPageCount) {
resetVal = this.internalPageCount
}
} else {
if (isNaN(val) || val < 1) {
resetVal = 1
}
}
if (resetVal === undefined && isNaN(val)) {
resetVal = 1
} else if (resetVal === 0) {
resetVal = 1
}
return resetVal === undefined ? val : resetVal
},
emitChange() {
this.$nextTick(() => {
if (this.internalCurrentPage !== this.lastEmittedPage || this.userChangePageSize) {
this.$emit('update:current-page', this.internalCurrentPage)
this.$emit('page-change', {
currentPage: this.internalCurrentPage,
pageSize: this.internalPageSize,
total: this.internalTotal
})
this.lastEmittedPage = this.internalCurrentPage
this.userChangePageSize = false
}
})
},
setTotal(val) {
this.internalTotal = val
}
},
computed: {
totalText() {
if (typeof this.customTotal === 'string') return this.customTotal
const totals = parseInt(this.total)
if (isNaN(totals)) return 0
const HUNDRED_THOUSAND = 100000
const MILLION = 1000000
const TEN_MILLION = 10000000
if (totals <= HUNDRED_THOUSAND) {
return totals
} else if (totals <= MILLION) {
return t('ui.page.hundredThousand')
} else if (totals <= TEN_MILLION) {
return t('ui.page.million')
} else {
return t('ui.page.tenMillion')
}
},
internalPageCount() {
if (typeof this.total === 'number') {
return Math.max(1, Math.ceil(this.total / this.internalPageSize))
} else if (typeof this.pageCount === 'number') {
return Math.max(1, this.pageCount)
}
return null
},
internalLayout() {
let layout = ''
if (this.mode && !this.layout) {
this.mode === 'number' && (layout = 'total, sizes, prev, pager, next, jumper')
this.mode === 'simple' && (layout = 'sizes, total, prev, current, next')
this.mode === 'complete' && (layout = 'sizes, total, prev, pager, next, jumper')
this.mode === 'fixed' && (layout = 'prev,pager,next')
} else if ((!this.mode && this.layout) || (this.mode && this.layout)) {
layout = this.layout
} else {
layout = 'total, prev, pager, next, jumper'
}
return layout
}
},
watch: {
currentPage: {
handler(curPage) {
this.internalCurrentPage = this.getValidCurrentPage(curPage)
},
immediate: true
},
internalPageCount(pageCount) {
/* istanbul ignore if */
const oldCurPage = this.internalCurrentPage
if (pageCount > 0 && oldCurPage === 0) {
this.internalCurrentPage = 1
} else if (oldCurPage > pageCount) {
this.internalCurrentPage = pageCount || 1
this.userChangePageSize && this.emitChange()
}
this.userChangePageSize = false
},
internalCurrentPage: {
handler(newVal) {
this.$emit('update:currentPage', newVal)
this.$emit('current-change', newVal)
this.lastEmittedPage = -1
}
},
pageSize: {
handler(pageSize) {
this.internalPageSize = isNaN(pageSize) ? 10 : pageSize
},
immediate: true
},
total(total) {
this.internalTotal = total
}
}
})
</script>