forked from opentiny/tiny-vue
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:
parent
2ce68c0edb
commit
9de15b4536
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
<template>
|
||||
<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>
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
<template>
|
||||
<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>
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
<template>
|
||||
<div>
|
||||
<tiny-pager
|
||||
:current-page="currentPage"
|
||||
@update:current-page="currentPage = $event"
|
||||
|
@ -14,9 +15,10 @@
|
|||
:page-size="100"
|
||||
layout="total, sizes, prev, pager, next"
|
||||
:total="5000000"
|
||||
:custom-total="'条数超出百万'"
|
||||
custom-total="条数超出百万"
|
||||
>
|
||||
</tiny-pager>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
<template>
|
||||
<div>
|
||||
<tiny-pager
|
||||
:current-page="currentPage"
|
||||
@update:current-page="currentPage = $event"
|
||||
|
@ -14,9 +15,10 @@
|
|||
:page-size="100"
|
||||
layout="total, sizes, prev, pager, next"
|
||||
:total="5000000"
|
||||
:custom-total="'条数超出百万'"
|
||||
custom-total="条数超出百万"
|
||||
>
|
||||
</tiny-pager>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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')
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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'
|
||||
})
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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'
|
||||
},
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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>
|
|
@ -1,9 +1,8 @@
|
|||
import type { PropType } from 'vue'
|
||||
import { $props, $prefix, $setup, defineComponent } from '@opentiny/vue-common'
|
||||
import template from 'virtual-template?pc|mobile-first'
|
||||
|
||||
export default defineComponent({
|
||||
name: $prefix + 'Pager',
|
||||
props: {
|
||||
export const pagerProps = {
|
||||
...$props,
|
||||
accurateJumper: {
|
||||
type: Boolean,
|
||||
|
@ -32,7 +31,7 @@ export default defineComponent({
|
|||
default: () => 10
|
||||
},
|
||||
pageSizes: {
|
||||
type: Array,
|
||||
type: Array as PropType<number[]>,
|
||||
default: () => [10, 20, 30, 40, 50, 100]
|
||||
},
|
||||
pagerCount: {
|
||||
|
@ -64,7 +63,11 @@ export default defineComponent({
|
|||
type: String,
|
||||
validator: (value) => ['left', 'center', 'right'].includes(value)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
name: $prefix + 'Pager',
|
||||
props: pagerProps,
|
||||
setup(props, context) {
|
||||
return $setup({ props, context, template })
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Reference in New Issue