🌈 An enterprise-class UI components based on Ant Design and Vue. 🐜
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

684 lines
21 KiB

import PropTypes from '../_util/vue-types';
import BaseMixin from '../_util/BaseMixin';
import { hasProp, getComponent, splitAttrs, isValidElement } from '../_util/props-util';
import Pager from './Pager';
import Options from './Options';
import LOCALE from './locale/zh_CN';
import KEYCODE from './KeyCode';
import classNames from '../_util/classNames';
import { defineComponent, withDirectives } from 'vue';
import antInput from '../_util/antInputDirective';
import { cloneElement } from '../_util/vnode';
import firstNotUndefined from '../_util/firstNotUndefined';
// 是否是正整数
function isInteger(value) {
return typeof value === 'number' && isFinite(value) && Math.floor(value) === value;
}
function defaultItemRender({ originalElement }) {
return originalElement;
}
function calculatePage(p, state, props) {
const pageSize = typeof p === 'undefined' ? state.statePageSize : p;
return Math.floor((props.total - 1) / pageSize) + 1;
}
export default defineComponent({
name: 'Pagination',
mixins: [BaseMixin],
inheritAttrs: false,
props: {
disabled: PropTypes.looseBool,
prefixCls: PropTypes.string.def('rc-pagination'),
selectPrefixCls: PropTypes.string.def('rc-select'),
current: PropTypes.number,
defaultCurrent: PropTypes.number.def(1),
total: PropTypes.number.def(0),
pageSize: PropTypes.number,
defaultPageSize: PropTypes.number.def(10),
hideOnSinglePage: PropTypes.looseBool.def(false),
showSizeChanger: PropTypes.looseBool,
showLessItems: PropTypes.looseBool.def(false),
// showSizeChange: PropTypes.func.def(noop),
selectComponentClass: PropTypes.any,
showPrevNextJumpers: PropTypes.looseBool.def(true),
showQuickJumper: PropTypes.oneOfType([PropTypes.looseBool, PropTypes.object]).def(false),
showTitle: PropTypes.looseBool.def(true),
pageSizeOptions: PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.number, PropTypes.string])),
buildOptionText: PropTypes.func,
showTotal: PropTypes.func,
simple: PropTypes.looseBool,
locale: PropTypes.object.def(LOCALE),
itemRender: PropTypes.func.def(defaultItemRender),
prevIcon: PropTypes.any,
nextIcon: PropTypes.any,
jumpPrevIcon: PropTypes.any,
jumpNextIcon: PropTypes.any,
totalBoundaryShowSizeChanger: PropTypes.number.def(50),
},
data() {
const props = this.$props;
let current = firstNotUndefined([this.current, this.defaultCurrent]);
const pageSize = firstNotUndefined([this.pageSize, this.defaultPageSize]);
current = Math.min(current, calculatePage(pageSize, undefined, props));
return {
stateCurrent: current,
stateCurrentInputValue: current,
statePageSize: pageSize,
};
},
watch: {
current(val) {
this.setState({
stateCurrent: val,
stateCurrentInputValue: val,
});
},
pageSize(val) {
const newState: any = {};
let current = this.stateCurrent;
const newCurrent = calculatePage(val, this.$data, this.$props);
current = current > newCurrent ? newCurrent : current;
if (!hasProp(this, 'current')) {
newState.stateCurrent = current;
newState.stateCurrentInputValue = current;
}
newState.statePageSize = val;
this.setState(newState);
},
stateCurrent(_val, oldValue) {
// When current page change, fix focused style of prev item
// A hacky solution of https://github.com/ant-design/ant-design/issues/8948
this.$nextTick(() => {
if (this.$refs.paginationNode) {
const lastCurrentNode = this.$refs.paginationNode.querySelector(
`.${this.prefixCls}-item-${oldValue}`,
);
if (lastCurrentNode && document.activeElement === lastCurrentNode) {
lastCurrentNode.blur();
}
}
});
},
total() {
const newState: any = {};
const newCurrent = calculatePage(this.pageSize, this.$data, this.$props);
if (hasProp(this, 'current')) {
const current = Math.min(this.current, newCurrent);
newState.stateCurrent = current;
newState.stateCurrentInputValue = current;
} else {
let current = this.stateCurrent;
if (current === 0 && newCurrent > 0) {
current = 1;
} else {
current = Math.min(this.stateCurrent, newCurrent);
}
newState.stateCurrent = current;
}
this.setState(newState);
},
},
methods: {
getJumpPrevPage() {
return Math.max(1, this.stateCurrent - (this.showLessItems ? 3 : 5));
},
getJumpNextPage() {
return Math.min(
calculatePage(undefined, this.$data, this.$props),
this.stateCurrent + (this.showLessItems ? 3 : 5),
);
},
getItemIcon(icon, label) {
const { prefixCls } = this.$props;
const iconNode = getComponent(this, icon, this.$props) || (
<button type="button" aria-label={label} class={`${prefixCls}-item-link`} />
);
return iconNode;
},
getValidValue(e) {
const inputValue = e.target.value;
const allPages = calculatePage(undefined, this.$data, this.$props);
const { stateCurrentInputValue } = this.$data;
let value;
if (inputValue === '') {
value = inputValue;
} else if (isNaN(Number(inputValue))) {
value = stateCurrentInputValue;
} else if (inputValue >= allPages) {
value = allPages;
} else {
value = Number(inputValue);
}
return value;
},
isValid(page) {
return isInteger(page) && page !== this.stateCurrent;
},
shouldDisplayQuickJumper() {
const { showQuickJumper, pageSize, total } = this.$props;
if (total <= pageSize) {
return false;
}
return showQuickJumper;
},
// calculatePage (p) {
// let pageSize = p
// if (typeof pageSize === 'undefined') {
// pageSize = this.statePageSize
// }
// return Math.floor((this.total - 1) / pageSize) + 1
// },
handleKeyDown(event) {
if (event.keyCode === KEYCODE.ARROW_UP || event.keyCode === KEYCODE.ARROW_DOWN) {
event.preventDefault();
}
},
handleKeyUp(e) {
if (e.isComposing || e.target.composing) return;
const value = this.getValidValue(e);
const stateCurrentInputValue = this.stateCurrentInputValue;
if (value !== stateCurrentInputValue) {
this.setState({
stateCurrentInputValue: value,
});
}
if (e.keyCode === KEYCODE.ENTER) {
this.handleChange(value);
} else if (e.keyCode === KEYCODE.ARROW_UP) {
this.handleChange(value - 1);
} else if (e.keyCode === KEYCODE.ARROW_DOWN) {
this.handleChange(value + 1);
}
},
changePageSize(size) {
let current = this.stateCurrent;
const preCurrent = current;
const newCurrent = calculatePage(size, this.$data, this.$props);
current = current > newCurrent ? newCurrent : current;
// fix the issue:
// Once 'total' is 0, 'current' in 'onShowSizeChange' is 0, which is not correct.
if (newCurrent === 0) {
current = this.stateCurrent;
}
if (typeof size === 'number') {
if (!hasProp(this, 'pageSize')) {
this.setState({
statePageSize: size,
});
}
if (!hasProp(this, 'current')) {
this.setState({
stateCurrent: current,
stateCurrentInputValue: current,
});
}
}
this.__emit('update:pageSize', size);
if (current !== preCurrent) {
this.__emit('update:current', current);
}
this.__emit('showSizeChange', current, size);
this.__emit('change', current, size);
},
handleChange(p) {
const { disabled } = this.$props;
let page = p;
if (this.isValid(page) && !disabled) {
const currentPage = calculatePage(undefined, this.$data, this.$props);
if (page > currentPage) {
page = currentPage;
} else if (page < 1) {
page = 1;
}
if (!hasProp(this, 'current')) {
this.setState({
stateCurrent: page,
stateCurrentInputValue: page,
});
}
// this.__emit('input', page)
this.__emit('update:current', page);
this.__emit('change', page, this.statePageSize);
return page;
}
return this.stateCurrent;
},
prev() {
if (this.hasPrev()) {
this.handleChange(this.stateCurrent - 1);
}
},
next() {
if (this.hasNext()) {
this.handleChange(this.stateCurrent + 1);
}
},
jumpPrev() {
this.handleChange(this.getJumpPrevPage());
},
jumpNext() {
this.handleChange(this.getJumpNextPage());
},
hasPrev() {
return this.stateCurrent > 1;
},
hasNext() {
return this.stateCurrent < calculatePage(undefined, this.$data, this.$props);
},
getShowSizeChanger() {
const { showSizeChanger, total, totalBoundaryShowSizeChanger } = this.$props;
if (typeof showSizeChanger !== 'undefined') {
return showSizeChanger;
}
return total > totalBoundaryShowSizeChanger;
},
runIfEnter(event, callback, ...restParams) {
if (event.key === 'Enter' || event.charCode === 13) {
callback(...restParams);
}
},
runIfEnterPrev(event) {
this.runIfEnter(event, this.prev);
},
runIfEnterNext(event) {
this.runIfEnter(event, this.next);
},
runIfEnterJumpPrev(event) {
this.runIfEnter(event, this.jumpPrev);
},
runIfEnterJumpNext(event) {
this.runIfEnter(event, this.jumpNext);
},
handleGoTO(event) {
if (event.keyCode === KEYCODE.ENTER || event.type === 'click') {
this.handleChange(this.stateCurrentInputValue);
}
},
renderPrev(prevPage) {
const { itemRender } = this.$props;
const prevButton = itemRender({
page: prevPage,
type: 'prev',
originalElement: this.getItemIcon('prevIcon', 'prev page'),
});
const disabled = !this.hasPrev();
return isValidElement(prevButton)
? cloneElement(prevButton, disabled ? { disabled } : {})
: prevButton;
},
renderNext(nextPage) {
const { itemRender } = this.$props;
const nextButton = itemRender({
page: nextPage,
type: 'next',
originalElement: this.getItemIcon('nextIcon', 'next page'),
});
const disabled = !this.hasNext();
return isValidElement(nextButton)
? cloneElement(nextButton, disabled ? { disabled } : {})
: nextButton;
},
},
render() {
const {
prefixCls,
disabled,
hideOnSinglePage,
total,
locale,
showQuickJumper,
showLessItems,
showTitle,
showTotal,
simple,
itemRender,
showPrevNextJumpers,
jumpPrevIcon,
jumpNextIcon,
selectComponentClass,
selectPrefixCls,
pageSizeOptions,
} = this.$props;
const { stateCurrent, statePageSize } = this;
const { class: className, ...restAttrs } = splitAttrs(this.$attrs).extraAttrs;
// When hideOnSinglePage is true and there is only 1 page, hide the pager
if (hideOnSinglePage === true && this.total <= statePageSize) {
return null;
}
const allPages = calculatePage(undefined, this.$data, this.$props);
const pagerList = [];
let jumpPrev = null;
let jumpNext = null;
let firstPager = null;
let lastPager = null;
let gotoButton = null;
const goButton = showQuickJumper && showQuickJumper.goButton;
const pageBufferSize = showLessItems ? 1 : 2;
const prevPage = stateCurrent - 1 > 0 ? stateCurrent - 1 : 0;
const nextPage = stateCurrent + 1 < allPages ? stateCurrent + 1 : allPages;
const hasPrev = this.hasPrev();
const hasNext = this.hasNext();
if (simple) {
if (goButton) {
if (typeof goButton === 'boolean') {
gotoButton = (
<button type="button" onClick={this.handleGoTO} onKeyup={this.handleGoTO}>
{locale.jump_to_confirm}
</button>
);
} else {
gotoButton = (
<span onClick={this.handleGoTO} onKeyup={this.handleGoTO}>
{goButton}
</span>
);
}
gotoButton = (
<li
title={showTitle ? `${locale.jump_to}${stateCurrent}/${allPages}` : null}
class={`${prefixCls}-simple-pager`}
>
{gotoButton}
</li>
);
}
return (
<ul
class={classNames(
`${prefixCls} ${prefixCls}-simple`,
{ [`${prefixCls}-disabled`]: disabled },
className,
)}
{...restAttrs}
>
<li
title={showTitle ? locale.prev_page : null}
onClick={this.prev}
tabindex={hasPrev ? 0 : null}
onKeypress={this.runIfEnterPrev}
class={classNames(`${prefixCls}-prev`, {
[`${prefixCls}-disabled`]: !hasPrev,
})}
aria-disabled={!hasPrev}
>
{this.renderPrev(prevPage)}
</li>
<li
title={showTitle ? `${stateCurrent}/${allPages}` : null}
class={`${prefixCls}-simple-pager`}
>
{withDirectives(
<input
type="text"
value={this.stateCurrentInputValue}
disabled={disabled}
onKeydown={this.handleKeyDown}
onKeyup={this.handleKeyUp}
onInput={this.handleKeyUp}
onChange={this.handleKeyUp}
size="3"
/>,
[[antInput]],
)}
<span class={`${prefixCls}-slash`}></span>
{allPages}
</li>
<li
title={showTitle ? locale.next_page : null}
onClick={this.next}
tabindex={hasNext ? 0 : null}
onKeypress={this.runIfEnterNext}
class={classNames(`${prefixCls}-next`, {
[`${prefixCls}-disabled`]: !hasNext,
})}
aria-disabled={!hasNext}
>
{this.renderNext(nextPage)}
</li>
{gotoButton}
</ul>
);
}
if (allPages <= 3 + pageBufferSize * 2) {
const pagerProps = {
locale,
rootPrefixCls: prefixCls,
showTitle,
itemRender,
onClick: this.handleChange,
onKeypress: this.runIfEnter,
};
if (!allPages) {
pagerList.push(
<Pager {...pagerProps} key="noPager" page={1} class={`${prefixCls}-item-disabled`} />,
);
}
for (let i = 1; i <= allPages; i += 1) {
const active = stateCurrent === i;
pagerList.push(<Pager {...pagerProps} key={i} page={i} active={active} />);
}
} else {
const prevItemTitle = showLessItems ? locale.prev_3 : locale.prev_5;
const nextItemTitle = showLessItems ? locale.next_3 : locale.next_5;
if (showPrevNextJumpers) {
jumpPrev = (
<li
title={this.showTitle ? prevItemTitle : null}
key="prev"
onClick={this.jumpPrev}
tabindex="0"
onKeypress={this.runIfEnterJumpPrev}
class={classNames(`${prefixCls}-jump-prev`, {
[`${prefixCls}-jump-prev-custom-icon`]: !!jumpPrevIcon,
})}
>
{itemRender({
page: this.getJumpPrevPage(),
type: 'jump-prev',
originalElement: this.getItemIcon('jumpPrevIcon', 'prev page'),
})}
</li>
);
jumpNext = (
<li
title={this.showTitle ? nextItemTitle : null}
key="next"
tabindex="0"
onClick={this.jumpNext}
onKeypress={this.runIfEnterJumpNext}
class={classNames(`${prefixCls}-jump-next`, {
[`${prefixCls}-jump-next-custom-icon`]: !!jumpNextIcon,
})}
>
{itemRender({
page: this.getJumpNextPage(),
type: 'jump-next',
originalElement: this.getItemIcon('jumpNextIcon', 'next page'),
})}
</li>
);
}
lastPager = (
<Pager
locale={locale}
last
rootPrefixCls={prefixCls}
onClick={this.handleChange}
onKeypress={this.runIfEnter}
key={allPages}
page={allPages}
active={false}
showTitle={showTitle}
itemRender={itemRender}
/>
);
firstPager = (
<Pager
locale={locale}
rootPrefixCls={prefixCls}
onClick={this.handleChange}
onKeypress={this.runIfEnter}
key={1}
page={1}
active={false}
showTitle={showTitle}
itemRender={itemRender}
/>
);
let left = Math.max(1, stateCurrent - pageBufferSize);
let right = Math.min(stateCurrent + pageBufferSize, allPages);
if (stateCurrent - 1 <= pageBufferSize) {
right = 1 + pageBufferSize * 2;
}
if (allPages - stateCurrent <= pageBufferSize) {
left = allPages - pageBufferSize * 2;
}
for (let i = left; i <= right; i += 1) {
const active = stateCurrent === i;
pagerList.push(
<Pager
locale={locale}
rootPrefixCls={prefixCls}
onClick={this.handleChange}
onKeypress={this.runIfEnter}
key={i}
page={i}
active={active}
showTitle={showTitle}
itemRender={itemRender}
/>,
);
}
if (stateCurrent - 1 >= pageBufferSize * 2 && stateCurrent !== 1 + 2) {
pagerList[0] = (
<Pager
locale={locale}
rootPrefixCls={prefixCls}
onClick={this.handleChange}
onKeypress={this.runIfEnter}
key={left}
page={left}
class={`${prefixCls}-item-after-jump-prev`}
active={false}
showTitle={this.showTitle}
itemRender={itemRender}
/>
);
pagerList.unshift(jumpPrev);
}
if (allPages - stateCurrent >= pageBufferSize * 2 && stateCurrent !== allPages - 2) {
pagerList[pagerList.length - 1] = (
<Pager
locale={locale}
rootPrefixCls={prefixCls}
onClick={this.handleChange}
onKeypress={this.runIfEnter}
key={right}
page={right}
class={`${prefixCls}-item-before-jump-next`}
active={false}
showTitle={this.showTitle}
itemRender={itemRender}
/>
);
pagerList.push(jumpNext);
}
if (left !== 1) {
pagerList.unshift(firstPager);
}
if (right !== allPages) {
pagerList.push(lastPager);
}
}
let totalText = null;
if (showTotal) {
totalText = (
<li class={`${prefixCls}-total-text`}>
{showTotal(total, [
total === 0 ? 0 : (stateCurrent - 1) * statePageSize + 1,
stateCurrent * statePageSize > total ? total : stateCurrent * statePageSize,
])}
</li>
);
}
const prevDisabled = !hasPrev || !allPages;
const nextDisabled = !hasNext || !allPages;
const buildOptionText = this.buildOptionText || this.$slots.buildOptionText;
return (
<ul
unselectable="on"
ref="paginationNode"
{...restAttrs}
class={classNames(
{ [`${prefixCls}`]: true, [`${prefixCls}-disabled`]: disabled },
className,
)}
>
{totalText}
<li
title={showTitle ? locale.prev_page : null}
onClick={this.prev}
tabindex={prevDisabled ? null : 0}
onKeypress={this.runIfEnterPrev}
class={classNames(`${prefixCls}-prev`, {
[`${prefixCls}-disabled`]: prevDisabled,
})}
aria-disabled={prevDisabled}
>
{this.renderPrev(prevPage)}
</li>
{pagerList}
<li
title={showTitle ? locale.next_page : null}
onClick={this.next}
tabindex={nextDisabled ? null : 0}
onKeypress={this.runIfEnterNext}
class={classNames(`${prefixCls}-next`, {
[`${prefixCls}-disabled`]: nextDisabled,
})}
aria-disabled={nextDisabled}
>
{this.renderNext(nextPage)}
</li>
<Options
disabled={disabled}
locale={locale}
rootPrefixCls={prefixCls}
selectComponentClass={selectComponentClass}
selectPrefixCls={selectPrefixCls}
changeSize={this.getShowSizeChanger() ? this.changePageSize : null}
current={stateCurrent}
pageSize={statePageSize}
pageSizeOptions={pageSizeOptions}
buildOptionText={buildOptionText || null}
quickGo={this.shouldDisplayQuickJumper() ? this.handleChange : null}
goButton={goButton}
/>
</ul>
);
},
});