diff --git a/src/hooks/use-pagination.ts b/src/hooks/use-pagination.ts new file mode 100644 index 0000000000..465bf342d5 --- /dev/null +++ b/src/hooks/use-pagination.ts @@ -0,0 +1,112 @@ +interface PaginationOptions { + // 当前页码, 从 1 开始 + current: number + // 数据总条数 + total: number + // 每页显示的条目数 + itemsPerPage: number + // 指示器条目数量 + displayCount: number + // 省略符号 + ellipse: boolean +} + +export type PaginationNode = { + number: number + text: string + selected?: boolean +} + +type PaginationResult = [ + // 指示器 + PaginationNodes: Array, + // 指示器的总数量 + PaginationNodesCount: number, +] + +const defaultPaginationOptions: Partial = { + current: 0, + itemsPerPage: 10, + displayCount: 5, + ellipse: false, +} + +function human2Machine(number: number) { + return --number +} + +function calculateNodes(options: PaginationOptions, nodesCount: number) { + // 分页器内部的索引从 0 开始,用户使用的索引从 1 开始 + const halfIndex = Math.floor(options.displayCount / 2) + const buttonsCountIndex = human2Machine(nodesCount) + const displayCountIndex = human2Machine(options.displayCount) + const currentIndex = human2Machine(options.current) + let start + let end + if (buttonsCountIndex <= displayCountIndex) { + start = 0 + end = buttonsCountIndex + } else { + start = Math.max(0, currentIndex - halfIndex) + end = Math.min(buttonsCountIndex, currentIndex + halfIndex) + if (end - start < displayCountIndex) { + if (start === 0) { + end = Math.min(start + displayCountIndex, buttonsCountIndex) + } else if (end === buttonsCountIndex) { + start = Math.max(end - displayCountIndex, 1) + } + } else if (end - start > displayCountIndex) { + end = start + displayCountIndex + } + } + + const buttons = [] + for (let i = start; i <= end; i++) { + const humanPageNumber = i + 1 + buttons.push({ + number: humanPageNumber, + text: humanPageNumber.toString(), + selected: options.current === humanPageNumber, + }) + } + + return addEllipses(buttons, { + nodesCount, + ellipse: options.ellipse, + displayCount: options.displayCount, + }) +} + +function addEllipses( + nodes: Array, + { + displayCount, + nodesCount, + ellipse, + }: { displayCount: number; nodesCount: number; ellipse: boolean } +) { + if (nodesCount <= displayCount || !ellipse) return nodes + const start = nodes[0] + const end = nodes[nodes.length - 1] + + const leftEllipse = start.number > 1 + const rightEllipse = end.number < nodesCount + if (leftEllipse) { + nodes.unshift({ number: start.number - 1, text: '...' }) + } + if (rightEllipse) { + nodes.push({ number: end.number + 1, text: '...' }) + } + return nodes +} + +export const usePagination = (options: PaginationOptions): PaginationResult => { + const mergedOptions = { + ...defaultPaginationOptions, + ...options, + } + const { total, itemsPerPage } = mergedOptions + const nodesCount = Math.ceil((total || 0) / itemsPerPage) || 1 + + return [calculateNodes(mergedOptions, nodesCount), nodesCount] +} diff --git a/src/packages/pagination/pagination.taro.tsx b/src/packages/pagination/pagination.taro.tsx index f67c6844e4..770497f0ae 100644 --- a/src/packages/pagination/pagination.taro.tsx +++ b/src/packages/pagination/pagination.taro.tsx @@ -1,11 +1,12 @@ -import React, { FunctionComponent, useMemo } from 'react' +import React, { FunctionComponent } from 'react' import classNames from 'classnames' import { View } from '@tarojs/components' import { useConfig } from '@/packages/configprovider/index.taro' import { usePropsValue } from '@/hooks/use-props-value' import { ComponentDefaults } from '@/utils/typings' import addColorForHarmony from '@/utils/add-color-for-harmony' -import { WebPaginationProps } from '@/types' +import { PaginationNode, usePagination } from '@/hooks/use-pagination' +import { TaroPaginationProps } from '@/types' const defaultProps = { ...ComponentDefaults, @@ -17,9 +18,9 @@ const defaultProps = { pageSize: 10, itemSize: 5, ellipse: false, -} as WebPaginationProps +} as TaroPaginationProps export const Pagination: FunctionComponent< - Partial & + Partial & Omit, 'onChange'> > = (props) => { const { locale } = useConfig() @@ -44,53 +45,33 @@ export const Pagination: FunctionComponent< } const classPrefix = 'nut-pagination' - const [currentPage, setCurrentPage] = usePropsValue({ + const [current, setCurrent] = usePropsValue({ value, defaultValue, finalValue: 1, onChange, }) - // (total + pageSize) => pageCount 计算页面的数量 - const pageCount = useMemo(() => { - const num = Math.ceil(total / pageSize) - return Number.isNaN(num) ? 1 : Math.max(1, num) - }, [total, pageSize]) - - // (currentPage + itemSize + pageCount) => pages 显示的 item 列表 - const pages = useMemo(() => { - const items = [] as Array - let startPage = 1 - let endPage = pageCount - const partialShow = pageCount > itemSize - if (partialShow) { - // 选中的 page 放在中间位置 - startPage = Math.max(currentPage - Math.floor(itemSize / 2), 1) - endPage = startPage + itemSize - 1 - if (endPage > pageCount) { - endPage = pageCount - startPage = endPage - itemSize + 1 - } - } - // 遍历生成数组 - for (let i = startPage; i <= endPage; i++) { - items.push({ number: i, text: i }) - } - // 判断是否有折叠 - if (partialShow && itemSize > 0 && ellipse) { - if (startPage > 1) { - items.unshift({ number: startPage - 1, text: '...' }) - } - if (endPage < pageCount) { - items.push({ number: endPage + 1, text: '...' }) - } - } - return items - }, [currentPage, itemSize, pageCount]) + const [pages, pageCount] = usePagination({ + total, + ellipse, + current, + displayCount: itemSize, + itemsPerPage: pageSize, + }) - const handleSelectPage = (curPage: number) => { - if (curPage > pageCount || curPage < 1) return - setCurrentPage(curPage) + const handleClick = (item: PaginationNode) => { + if (item.selected) return + if (item.number > pageCount || item.number < 1) return + setCurrent(item.number) + } + const prevPage = () => { + const prev = current - 1 + prev >= 1 && setCurrent(prev) + } + const nextPage = () => { + const next = current + 1 + next <= pageCount && setCurrent(next) } return ( @@ -98,16 +79,16 @@ export const Pagination: FunctionComponent< {(mode === 'multi' || mode === 'simple') && ( <> handleSelectPage(currentPage - 1)} + className={classNames({ + [`${classPrefix}-prev`]: true, + [`${classPrefix}-simple-border`]: mode !== 'multi', + [`${classPrefix}-prev-disabled`]: current === 1, + })} + onClick={() => prevPage()} > {addColorForHarmony( prev || locale.pagination.prev, - currentPage === 1 ? '#c2c4cc' : '#ff0f23' + current === 1 ? '#c2c4cc' : '#ff0f23' )} {mode === 'multi' && ( @@ -116,16 +97,15 @@ export const Pagination: FunctionComponent< return ( { - item.number !== currentPage && - handleSelectPage(item.number) + onClick={() => { + handleClick(item) }} > - {itemRender ? itemRender(item, currentPage) : item.text} + {itemRender ? itemRender(item, current) : item.text} ) })} @@ -134,27 +114,27 @@ export const Pagination: FunctionComponent< {mode === 'simple' && ( - {`${currentPage}/${pageCount}`} + {`${current}/${pageCount}`} )} = pageCount ? `${classPrefix}-next-disabled` : '' - )} - onClick={(e) => handleSelectPage(currentPage + 1)} + className={classNames({ + [`${classPrefix}-next`]: true, + [`${classPrefix}-next-disabled`]: current >= pageCount, + })} + onClick={() => nextPage()} > {addColorForHarmony( next || locale.pagination.next, - currentPage >= pageCount ? '#c2c4cc' : '#ff0f23' + current >= pageCount ? '#c2c4cc' : '#ff0f23' )} )} {mode === 'lite' && ( - {currentPage} + {current} / {pageCount} diff --git a/src/packages/pagination/pagination.tsx b/src/packages/pagination/pagination.tsx index 2bd2615ac3..951770037a 100644 --- a/src/packages/pagination/pagination.tsx +++ b/src/packages/pagination/pagination.tsx @@ -1,9 +1,10 @@ -import React, { FunctionComponent, useMemo } from 'react' +import React, { FunctionComponent } from 'react' import classNames from 'classnames' import { useConfig } from '@/packages/configprovider' import { usePropsValue } from '@/hooks/use-props-value' import { ComponentDefaults } from '@/utils/typings' -import { TaroPaginationProps } from '@/types' +import { PaginationNode, usePagination } from '@/hooks/use-pagination' +import { WebPaginationProps } from '@/types' const defaultProps = { ...ComponentDefaults, @@ -15,9 +16,9 @@ const defaultProps = { pageSize: 10, itemSize: 5, ellipse: false, -} as TaroPaginationProps +} as WebPaginationProps export const Pagination: FunctionComponent< - Partial & + Partial & Omit, 'onChange'> > = (props) => { const { locale } = useConfig() @@ -42,53 +43,33 @@ export const Pagination: FunctionComponent< } const classPrefix = 'nut-pagination' - const [currentPage, setCurrentPage] = usePropsValue({ + const [current, setCurrent] = usePropsValue({ value, defaultValue, finalValue: 1, onChange, }) - // (total + pageSize) => pageCount 计算页面的数量 - const pageCount = useMemo(() => { - const num = Math.ceil(total / pageSize) - return Number.isNaN(num) ? 1 : Math.max(1, num) - }, [total, pageSize]) - - // (currentPage + itemSize + pageCount) => pages 显示的 item 列表 - const pages = useMemo(() => { - const items = [] as Array - let startPage = 1 - let endPage = pageCount - const partialShow = pageCount > itemSize - if (partialShow) { - // 选中的 page 放在中间位置 - startPage = Math.max(currentPage - Math.floor(itemSize / 2), 1) - endPage = startPage + itemSize - 1 - if (endPage > pageCount) { - endPage = pageCount - startPage = endPage - itemSize + 1 - } - } - // 遍历生成数组 - for (let i = startPage; i <= endPage; i++) { - items.push({ number: i, text: i }) - } - // 判断是否有折叠 - if (partialShow && itemSize > 0 && ellipse) { - if (startPage > 1) { - items.unshift({ number: startPage - 1, text: '...' }) - } - if (endPage < pageCount) { - items.push({ number: endPage + 1, text: '...' }) - } - } - return items - }, [currentPage, itemSize, pageCount]) + const [pages, pageCount] = usePagination({ + total, + ellipse, + current, + displayCount: itemSize, + itemsPerPage: pageSize, + }) - const handleSelectPage = (curPage: number) => { - if (curPage > pageCount || curPage < 1) return - setCurrentPage(curPage) + const handleClick = (item: PaginationNode) => { + if (item.selected) return + if (item.number > pageCount || item.number < 1) return + setCurrent(item.number) + } + const prevPage = () => { + const prev = current - 1 + prev >= 1 && setCurrent(prev) + } + const nextPage = () => { + const next = current + 1 + next <= pageCount && setCurrent(next) } return ( @@ -96,12 +77,12 @@ export const Pagination: FunctionComponent< {(mode === 'multi' || mode === 'simple') && ( <>
handleSelectPage(currentPage - 1)} + className={classNames({ + [`${classPrefix}-prev`]: true, + [`${classPrefix}-simple-border`]: mode !== 'multi', + [`${classPrefix}-prev-disabled`]: current === 1, + })} + onClick={() => prevPage()} > {prev || locale.pagination.prev}
@@ -111,16 +92,15 @@ export const Pagination: FunctionComponent< return (
{ - item.number !== currentPage && - handleSelectPage(item.number) + onClick={() => { + handleClick(item) }} > - {itemRender ? itemRender(item, currentPage) : item.text} + {itemRender ? itemRender(item, current) : item.text}
) })} @@ -129,16 +109,16 @@ export const Pagination: FunctionComponent< {mode === 'simple' && (
- {currentPage}/{pageCount} + {current}/{pageCount}
)}
= pageCount ? `${classPrefix}-next-disabled` : '' - )} - onClick={(e) => handleSelectPage(currentPage + 1)} + className={classNames({ + [`${classPrefix}-next`]: true, + [`${classPrefix}-next-disabled`]: current >= pageCount, + })} + onClick={() => nextPage()} > {next || locale.pagination.next}
@@ -146,7 +126,7 @@ export const Pagination: FunctionComponent< )} {mode === 'lite' && (
-
{currentPage}
+
{current}
/
{pageCount}