From e3c44faf422c5ba742d09d16b1b145f24489d584 Mon Sep 17 00:00:00 2001 From: oasis Date: Thu, 13 Feb 2025 13:59:11 +0800 Subject: [PATCH 1/2] refactor: data abstraction via hooks --- packages/nutui-taro-demo/config/index.js | 1 + packages/nutui-taro-demo/tsconfig.json | 3 + src/hooks/use-pagination.ts | 108 ++++++++++++++++++++ src/packages/pagination/pagination.taro.tsx | 100 ++++++++---------- src/packages/pagination/pagination.tsx | 96 +++++++---------- tsconfig.h5.json | 1 + 6 files changed, 191 insertions(+), 118 deletions(-) create mode 100644 src/hooks/use-pagination.ts diff --git a/packages/nutui-taro-demo/config/index.js b/packages/nutui-taro-demo/config/index.js index e0c8379093..27dca226ee 100644 --- a/packages/nutui-taro-demo/config/index.js +++ b/packages/nutui-taro-demo/config/index.js @@ -120,6 +120,7 @@ const config = { '@/sites': path.resolve(__dirname, '../../../src/sites'), '@/locales': path.resolve(__dirname, '../../../src/locales'), '@/utils': path.resolve(__dirname, '../../../src/utils'), + '@/hooks': path.resolve(__dirname, '../../../src/hooks'), '@nutui/nutui-react-taro': path.resolve( __dirname, '../../../src/packages/nutui.react.taro.ts' diff --git a/packages/nutui-taro-demo/tsconfig.json b/packages/nutui-taro-demo/tsconfig.json index 6fdd6ecc45..20a0d147ab 100644 --- a/packages/nutui-taro-demo/tsconfig.json +++ b/packages/nutui-taro-demo/tsconfig.json @@ -30,6 +30,9 @@ ], "@/utils": [ "src/utils/*" + ], + "@/hooks": [ + "src/hooks/*" ] }, "typeRoots": [ diff --git a/src/hooks/use-pagination.ts b/src/hooks/use-pagination.ts new file mode 100644 index 0000000000..7833ab2efa --- /dev/null +++ b/src/hooks/use-pagination.ts @@ -0,0 +1,108 @@ +interface PaginationOptions { + // 当前页码, 从 1 开始 + current: number + // 数据总条数 + total: number + // 每页显示的条目数 + itemsPerPage: number + // 指示器条目数量 + displayCount: number + // 省略符号 + ellipse: boolean +} + +export type ButtonItem = { number: number; text: string; selected?: boolean } + +type PaginationResult = [ + // 指示器 + buttons: Array, + // 指示器的总数量 + buttonsCount: number, +] + +const defaultPaginationOptions: Partial = { + current: 0, + itemsPerPage: 10, + displayCount: 5, + ellipse: false, +} + +function human2Machine(number: number) { + return --number +} + +function calculateButtons(options: PaginationOptions, buttonsCount: number) { + // 分页器内部的索引从 0 开始,用户使用的索引从 1 开始 + const halfIndex = Math.floor(options.displayCount / 2) + const buttonsCountIndex = human2Machine(buttonsCount) + 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, { + buttonsCount, + ellipse: options.ellipse, + displayCount: options.displayCount, + }) +} + +function addEllipses( + buttons: Array, + { + displayCount, + buttonsCount, + ellipse, + }: { displayCount: number; buttonsCount: number; ellipse: boolean } +) { + if (buttonsCount <= displayCount || !ellipse) return buttons + const start = buttons[0] + const end = buttons[buttons.length - 1] + + const leftEllipse = start.number > 1 + const rightEllipse = end.number < buttonsCount + if (leftEllipse) { + buttons.unshift({ number: start.number - 1, text: '...' }) + } + if (rightEllipse) { + buttons.push({ number: end.number + 1, text: '...' }) + } + return buttons +} + +export const usePagination = (options: PaginationOptions): PaginationResult => { + const mergedOptions = { + ...defaultPaginationOptions, + ...options, + } + const { total, itemsPerPage } = mergedOptions + const buttonsCount = Math.ceil((total || 0) / itemsPerPage) || 1 + + return [calculateButtons(mergedOptions, buttonsCount), buttonsCount] +} diff --git a/src/packages/pagination/pagination.taro.tsx b/src/packages/pagination/pagination.taro.tsx index dc83a70f3b..ac4fba2218 100644 --- a/src/packages/pagination/pagination.taro.tsx +++ b/src/packages/pagination/pagination.taro.tsx @@ -1,10 +1,11 @@ -import React, { FunctionComponent, ReactNode, useMemo } from 'react' +import React, { FunctionComponent, ReactNode } from 'react' import classNames from 'classnames' import { View } from '@tarojs/components' import { useConfig } from '@/packages/configprovider/index.taro' import { usePropsValue } from '@/utils/use-props-value' import { BasicComponent, ComponentDefaults } from '@/utils/typings' import addColorForHarmony from '@/utils/add-color-for-harmony' +import { ButtonItem, usePagination } from '@/hooks/use-pagination' export interface PaginationProps extends BasicComponent { defaultValue: number @@ -57,53 +58,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: ButtonItem) => { + 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 ( @@ -111,16 +92,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' && ( @@ -129,16 +110,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} ) })} @@ -147,27 +127,27 @@ export const Pagination: FunctionComponent< {mode === 'simple' && ( - {`${currentPage}/${pageCount}`} + {`${current}/${pageCount}`} )} = pageCount ? `${classPrefix}-next-disabled` : '' + current >= pageCount ? `${classPrefix}-next-disabled` : '' )} - onClick={(e) => handleSelectPage(currentPage + 1)} + 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 34c2224dd3..386f755927 100644 --- a/src/packages/pagination/pagination.tsx +++ b/src/packages/pagination/pagination.tsx @@ -1,8 +1,9 @@ -import React, { FunctionComponent, useMemo, ReactNode } from 'react' +import React, { FunctionComponent, ReactNode } from 'react' import classNames from 'classnames' import { useConfig } from '@/packages/configprovider' import { usePropsValue } from '@/utils/use-props-value' import { BasicComponent, ComponentDefaults } from '@/utils/typings' +import { ButtonItem, usePagination } from '@/hooks/use-pagination' export interface PaginationProps extends BasicComponent { defaultValue: number @@ -55,53 +56,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: ButtonItem) => { + 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 ( @@ -109,12 +90,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}
@@ -124,16 +105,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}
) })} @@ -142,16 +122,16 @@ export const Pagination: FunctionComponent< {mode === 'simple' && (
- {currentPage}/{pageCount} + {current}/{pageCount}
)}
= pageCount ? `${classPrefix}-next-disabled` : '' + current >= pageCount ? `${classPrefix}-next-disabled` : '' )} - onClick={(e) => handleSelectPage(currentPage + 1)} + onClick={() => nextPage()} > {next || locale.pagination.next}
@@ -159,7 +139,7 @@ export const Pagination: FunctionComponent< )} {mode === 'lite' && (
-
{currentPage}
+
{current}
/
{pageCount}
diff --git a/tsconfig.h5.json b/tsconfig.h5.json index 552af15f6a..f8a733b97d 100644 --- a/tsconfig.h5.json +++ b/tsconfig.h5.json @@ -3,6 +3,7 @@ "include": [ "./src/packages", "./src/utils", + "./src/hooks", "./src/locales", "./src/vite-env.d.ts" ], From 1ce75a2f424a305e7e5b427c2b935accecb6130a Mon Sep 17 00:00:00 2001 From: oasis Date: Wed, 12 Mar 2025 15:26:38 +0800 Subject: [PATCH 2/2] fix: review --- src/hooks/use-pagination.ts | 40 +++++++++++---------- src/packages/pagination/pagination.taro.tsx | 35 ++++++------------ src/packages/pagination/pagination.tsx | 35 ++++++------------ 3 files changed, 44 insertions(+), 66 deletions(-) diff --git a/src/hooks/use-pagination.ts b/src/hooks/use-pagination.ts index 7833ab2efa..465bf342d5 100644 --- a/src/hooks/use-pagination.ts +++ b/src/hooks/use-pagination.ts @@ -11,13 +11,17 @@ interface PaginationOptions { ellipse: boolean } -export type ButtonItem = { number: number; text: string; selected?: boolean } +export type PaginationNode = { + number: number + text: string + selected?: boolean +} type PaginationResult = [ // 指示器 - buttons: Array, + PaginationNodes: Array, // 指示器的总数量 - buttonsCount: number, + PaginationNodesCount: number, ] const defaultPaginationOptions: Partial = { @@ -31,10 +35,10 @@ function human2Machine(number: number) { return --number } -function calculateButtons(options: PaginationOptions, buttonsCount: number) { +function calculateNodes(options: PaginationOptions, nodesCount: number) { // 分页器内部的索引从 0 开始,用户使用的索引从 1 开始 const halfIndex = Math.floor(options.displayCount / 2) - const buttonsCountIndex = human2Machine(buttonsCount) + const buttonsCountIndex = human2Machine(nodesCount) const displayCountIndex = human2Machine(options.displayCount) const currentIndex = human2Machine(options.current) let start @@ -67,33 +71,33 @@ function calculateButtons(options: PaginationOptions, buttonsCount: number) { } return addEllipses(buttons, { - buttonsCount, + nodesCount, ellipse: options.ellipse, displayCount: options.displayCount, }) } function addEllipses( - buttons: Array, + nodes: Array, { displayCount, - buttonsCount, + nodesCount, ellipse, - }: { displayCount: number; buttonsCount: number; ellipse: boolean } + }: { displayCount: number; nodesCount: number; ellipse: boolean } ) { - if (buttonsCount <= displayCount || !ellipse) return buttons - const start = buttons[0] - const end = buttons[buttons.length - 1] + 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 < buttonsCount + const rightEllipse = end.number < nodesCount if (leftEllipse) { - buttons.unshift({ number: start.number - 1, text: '...' }) + nodes.unshift({ number: start.number - 1, text: '...' }) } if (rightEllipse) { - buttons.push({ number: end.number + 1, text: '...' }) + nodes.push({ number: end.number + 1, text: '...' }) } - return buttons + return nodes } export const usePagination = (options: PaginationOptions): PaginationResult => { @@ -102,7 +106,7 @@ export const usePagination = (options: PaginationOptions): PaginationResult => { ...options, } const { total, itemsPerPage } = mergedOptions - const buttonsCount = Math.ceil((total || 0) / itemsPerPage) || 1 + const nodesCount = Math.ceil((total || 0) / itemsPerPage) || 1 - return [calculateButtons(mergedOptions, buttonsCount), buttonsCount] + return [calculateNodes(mergedOptions, nodesCount), nodesCount] } diff --git a/src/packages/pagination/pagination.taro.tsx b/src/packages/pagination/pagination.taro.tsx index 8be296fab2..770497f0ae 100644 --- a/src/packages/pagination/pagination.taro.tsx +++ b/src/packages/pagination/pagination.taro.tsx @@ -1,25 +1,12 @@ -import React, { FunctionComponent, ReactNode } 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 { BasicComponent, ComponentDefaults } from '@/utils/typings' +import { ComponentDefaults } from '@/utils/typings' import addColorForHarmony from '@/utils/add-color-for-harmony' -import { ButtonItem, usePagination } from '@/hooks/use-pagination' - -export interface PaginationProps extends BasicComponent { - defaultValue: number - value: number - mode: 'multi' | 'simple' | 'lite' - prev: ReactNode - next: ReactNode - total: number - pageSize: number - itemSize: number - ellipse: boolean - itemRender: (page: any, index: number) => ReactNode - onChange: (currPage: number) => void -} +import { PaginationNode, usePagination } from '@/hooks/use-pagination' +import { TaroPaginationProps } from '@/types' const defaultProps = { ...ComponentDefaults, @@ -31,9 +18,9 @@ const defaultProps = { pageSize: 10, itemSize: 5, ellipse: false, -} as PaginationProps +} as TaroPaginationProps export const Pagination: FunctionComponent< - Partial & + Partial & Omit, 'onChange'> > = (props) => { const { locale } = useConfig() @@ -73,7 +60,7 @@ export const Pagination: FunctionComponent< itemsPerPage: pageSize, }) - const handleClick = (item: ButtonItem) => { + const handleClick = (item: PaginationNode) => { if (item.selected) return if (item.number > pageCount || item.number < 1) return setCurrent(item.number) @@ -132,10 +119,10 @@ export const Pagination: FunctionComponent< )} = pageCount ? `${classPrefix}-next-disabled` : '' - )} + className={classNames({ + [`${classPrefix}-next`]: true, + [`${classPrefix}-next-disabled`]: current >= pageCount, + })} onClick={() => nextPage()} > {addColorForHarmony( diff --git a/src/packages/pagination/pagination.tsx b/src/packages/pagination/pagination.tsx index 1d9866df0e..951770037a 100644 --- a/src/packages/pagination/pagination.tsx +++ b/src/packages/pagination/pagination.tsx @@ -1,23 +1,10 @@ -import React, { FunctionComponent, ReactNode } from 'react' +import React, { FunctionComponent } from 'react' import classNames from 'classnames' import { useConfig } from '@/packages/configprovider' import { usePropsValue } from '@/hooks/use-props-value' -import { BasicComponent, ComponentDefaults } from '@/utils/typings' -import { ButtonItem, usePagination } from '@/hooks/use-pagination' - -export interface PaginationProps extends BasicComponent { - defaultValue: number - value: number - mode: 'multi' | 'simple' | 'lite' - prev: ReactNode - next: ReactNode - total: number - pageSize: number - itemSize: number - ellipse: boolean - itemRender: (page: any, index: number) => ReactNode - onChange: (currPage: number) => void -} +import { ComponentDefaults } from '@/utils/typings' +import { PaginationNode, usePagination } from '@/hooks/use-pagination' +import { WebPaginationProps } from '@/types' const defaultProps = { ...ComponentDefaults, @@ -29,9 +16,9 @@ const defaultProps = { pageSize: 10, itemSize: 5, ellipse: false, -} as PaginationProps +} as WebPaginationProps export const Pagination: FunctionComponent< - Partial & + Partial & Omit, 'onChange'> > = (props) => { const { locale } = useConfig() @@ -71,7 +58,7 @@ export const Pagination: FunctionComponent< itemsPerPage: pageSize, }) - const handleClick = (item: ButtonItem) => { + const handleClick = (item: PaginationNode) => { if (item.selected) return if (item.number > pageCount || item.number < 1) return setCurrent(item.number) @@ -127,10 +114,10 @@ export const Pagination: FunctionComponent< )}
= pageCount ? `${classPrefix}-next-disabled` : '' - )} + className={classNames({ + [`${classPrefix}-next`]: true, + [`${classPrefix}-next-disabled`]: current >= pageCount, + })} onClick={() => nextPage()} > {next || locale.pagination.next}