Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(pagination): data abstraction via hooks #2988

Open
wants to merge 3 commits into
base: feat_v3.x
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 108 additions & 0 deletions src/hooks/use-pagination.ts
Original file line number Diff line number Diff line change
@@ -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<ButtonItem>,
// 指示器的总数量
buttonsCount: number,
]

const defaultPaginationOptions: Partial<PaginationOptions> = {
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)
}

Check warning on line 53 in src/hooks/use-pagination.ts

View check run for this annotation

Codecov / codecov/patch

src/hooks/use-pagination.ts#L52-L53

Added lines #L52 - L53 were not covered by tests
} else if (end - start > displayCountIndex) {
end = start + displayCountIndex
}

Check warning on line 56 in src/hooks/use-pagination.ts

View check run for this annotation

Codecov / codecov/patch

src/hooks/use-pagination.ts#L55-L56

Added lines #L55 - L56 were not covered by tests
}

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<ButtonItem>,
{
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]
}
100 changes: 40 additions & 60 deletions src/packages/pagination/pagination.taro.tsx
Original file line number Diff line number Diff line change
@@ -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 '@/hooks/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
Expand Down Expand Up @@ -57,70 +58,50 @@ export const Pagination: FunctionComponent<
}

const classPrefix = 'nut-pagination'
const [currentPage, setCurrentPage] = usePropsValue<number>({
const [current, setCurrent] = usePropsValue<number>({
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<any>
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 (
<View className={classNames(classPrefix, className)} style={style}>
{(mode === 'multi' || mode === 'simple') && (
<>
<View
className={classNames(
`${classPrefix}-prev`,
mode === 'multi' ? '' : `${classPrefix}-simple-border`,
currentPage === 1 ? `${classPrefix}-prev-disabled` : ''
)}
onClick={(e) => 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'
)}
</View>
Comment on lines 102 to 105
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

建议将颜色值移至主题配置!

当前硬编码的颜色值 #c2c4cc#ff0f23 违反了 DRY 原则。建议:

  1. 将颜色值移至主题配置中
  2. 使用语义化的变量名(如 disabled-text-color, active-text-color
  3. 通过 useConfig 钩子访问这些主题变量
-current === 1 ? '#c2c4cc' : '#ff0f23'
+current === 1 ? theme.disabledTextColor : theme.activeTextColor

Also applies to: 141-144

{mode === 'multi' && (
Expand All @@ -129,16 +110,15 @@ export const Pagination: FunctionComponent<
return (
<View
key={`${index}pagination`}
className={classNames(`${classPrefix}-item`, {
[`${classPrefix}-item-active`]:
item.number === currentPage,
className={classNames({
[`${classPrefix}-item`]: true,
[`${classPrefix}-item-active`]: item.selected,
})}
onClick={(e) => {
item.number !== currentPage &&
handleSelectPage(item.number)
onClick={() => {
handleClick(item)
}}
>
{itemRender ? itemRender(item, currentPage) : item.text}
{itemRender ? itemRender(item, current) : item.text}
</View>
)
})}
Expand All @@ -147,27 +127,27 @@ export const Pagination: FunctionComponent<
{mode === 'simple' && (
<View className={`${classPrefix}-contain`}>
<View className={`${classPrefix}-simple`}>
{`${currentPage}/${pageCount}`}
{`${current}/${pageCount}`}
</View>
</View>
)}
<View
className={classNames(
`${classPrefix}-next`,
currentPage >= 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'
)}
</View>
</>
)}
{mode === 'lite' && (
<View className={`${classPrefix}-lite`}>
<View className={`${classPrefix}-lite-active`}>{currentPage}</View>
<View className={`${classPrefix}-lite-active`}>{current}</View>
<View className={`${classPrefix}-lite-spliterator`}>/</View>
<View className={`${classPrefix}-lite-default`}>{pageCount}</View>
</View>
Expand Down
Loading
Loading