Pagination 分页
用于在分页结果之间导航。
用法
在页码之间导航
组件结构
加粗的是必需部件。
data-scope="pagination":root · summary · jumper · prev-trigger · next-trigger · item · ellipsis-trigger · page-size-select · positioner · content
示例
尺寸
适配不同的界面密度
简洁模式
只显示上一页、当前页与下一页
快速跳页
输入页码后按 Enter 跳转
每页条数
调整每页展示数量
展开省略位
查看被折叠的页码
设计指引
何时使用
- 结果总数已知,需要跳转到指定页。
- 用户需要确认当前位置与剩余页数。
何时不用
- 连续加载的内容流,使用无限滚动。
- 数据量较少,无需分页。
特性
count表示总条数,pageSize表示每页条数。siblingCount控制当前页两侧展示的页码数量。- 支持上一页、下一页、跳页、每页数量与可展开省略位。
- 更改
pageSize后自动重算总页数并校正当前页。
组合
summary显示当前结果范围。jumper用于输入页码并按 Enter 跳转。page-size-select提供每页数量选择。
最佳实践
- 将当前页同步到地址,便于刷新和分享。
- 数据加载期间保留分页器,避免布局跳动。
反模式
- 不要将
count当作总页数。 - 不要在结果很少时使用分页。
API 参考
产物
| 层 | 值 |
|---|---|
| 自定义元素 | <xh-pagination> |
| Vue 组件 | XhPaginationContent XhPaginationEllipsisTrigger XhPaginationItem XhPaginationJumper XhPaginationNextTrigger XhPaginationPageSizeSelect XhPaginationPositioner XhPaginationPrevTrigger XhPaginationRoot XhPaginationSummary |
| 组合式函数 | usePagination |
| 状态机 | paginationMachine |
| 皮肤 | @xihan-ui/styles/pagination.css |
Props
| 属性 | 类型 | 必填 | 说明 |
|---|---|---|---|
count | number | 总条数(不是总页数)。总页数由它与 pageSize 计算。 | |
pageSize | number | 每页条数,默认 10;小于 1 的值一律按 1 处理。提供即受控,语义同 page。 | |
defaultPageSize | number | 非受控初始每页条数,默认 10。 | |
pageSizeOptions | number[] | 可选的每页条数档位,默认 [10, 20, 50, 100]。只做取值来源,不决定长相。 | |
page | number | 当前页。提供即受控:内部不再自行修改,只发 onPageChange。 | |
defaultPage | number | 非受控初始页,默认 1。 | |
siblingCount | number | 当前页两侧各显示的页数,默认 1。 | |
dir | Direction | 文字方向,只作用于排版;上一页 / 下一页的语义不随之翻转,上一页永远是 page - 1。 | |
translations | Partial<PaginationTranslations> | ||
placement | Placement | 省略位展开后的落点,默认 bottom-start(列表类浮层)。 | |
offset | number | 浮层与省略位之间的间距(px),默认 8。 | |
openDelay | number | 指针停在省略位多久后才展开(ms),默认 200;只收有限非负数。 | |
closeDelay | number | 指针离开后多久收起(ms),默认 300:留出斜向划入浮层的时间;只收有限非负数。 | |
tone | Tone | 语气:brand / neutral / success / warning / danger / info,决定使用哪族颜色。 | |
size | Size | 尺寸:sm / md / lg。 | |
onPageChange | (details: PaginationPageChangeDetails) => void | 页码变化意图回调;受控时是唯一出口,非受控时随内部写入一并通知。 | |
onPageSizeChange | (details: PaginationPageSizeChangeDetails) => void | 每页条数变化意图回调,语义同上;一并给出换算后的页码。 |
事件
自定义元素将载荷放在 detail;Vue 使用同名 emit。
| 事件 | 载荷 | 说明 |
|---|---|---|
page-change | PaginationPageChangeDetails | 页码变化;detail 为 { page: number, pageSize: number } |
page-size-change | `` | 每页条数变化;detail 为 { pageSize: number, page: number },页码是换算后的 |
插槽
仅列出带载荷的插槽。
| Vue 组件 | 插槽 | 载荷 | 说明 |
|---|---|---|---|
XhPaginationContent | default | { pages: number[] } | |
XhPaginationRoot | default | PaginationRootSlotProps | |
XhPaginationSummary | default | { summaryText: string, start: number, end: number, count: number } |
React 适配器 props
只列各组件自己声明的那些:继承自 ComponentPropsWithRef 的 DOM 属性不在其中,根组件上与上面 Props 表同名的也不重复列。Vue 的对应物是上面的插槽表。
| React 组件 | 属性 | 类型 | 必填 | 说明 |
|---|---|---|---|---|
XhPaginationContent | children | SlotChildren<PaginationContentSlotProps> | ||
XhPaginationEllipsisTrigger | side | PaginationEllipsisSide | 该省略位所在的一侧:首页与窗口之间是 start,窗口与末页之间是 end。 | |
XhPaginationItem | value | number | string | 是 | 该项对应的页码,兼收字符串。 |
XhPaginationPageSizeSelect | container | () => Element | null | 浮层挂载的容器;未提供时按全局配置,再未提供时挂载到 body。 | |
XhPaginationPositioner | container | () => Element | null | 浮层挂载的容器;未提供时按全局配置,再未提供时挂载到 body。 | |
XhPaginationRoot | children | SlotChildren<PaginationRootSlotProps> | ||
XhPaginationSummary | children | SlotChildren<PaginationSummarySlotProps> |
状态
公开状态写入 data-state。
| 部件 | 取值 |
|---|---|
ellipsis-trigger | 'open' | 'closed' |
positioner | 'open' | 'closed' |
content | 'open' | 'closed' |
以下名称仅用于内部状态机。
状态:closed · opening · visible · visible.open · visible.closing
事件:PAGE.SET · PAGE_SIZE.SET · PAGE.PREV · PAGE.NEXT · ELLIPSIS.ENTER · ELLIPSIS.LEAVE · ELLIPSIS.TOGGLE · ELLIPSIS.CLOSE · after.openDelay · after.closeDelay · PRESS.START · PRESS.END
判据:isSameEllipsis · canPress
connect API
getXxxProps() 返回对应部件的宿主属性。
| 成员 | 类型 | 说明 |
|---|---|---|
page | number | 当前页,恒在 [1, max(totalPages, 1)] 内。 |
pageSize | number | |
pageSizeOptions | number[] | 可选的每页条数档位,默认 [10, 20, 50, 100];已按升序去重并夹到至少 1。 |
count | number | |
totalPages | number | |
pages | PaginationPage[] | 页码序列,作者按它渲染 item 与 ellipsis-trigger。 |
pageItems | PaginationPageItem[] | 同一序列,但省略位附带被折叠的页码:展开省略号需要使用它。 |
openEllipsis | PaginationEllipsisSide | null | 当前展开的是哪一侧的省略位;未展开时为 null。 |
pageRange | PaginationEntryRange | 当前页对应的条目区间,1 基闭区间;无数据时是 { start: 0, end: 0 }。 |
summaryText | string | 信息区文本,由 translations.summary 与 pageRange / count 算出。 |
previousPage | number | null | 上一页页码;已在首页(或无数据)时为 null。 |
nextPage | number | null | |
setPage | (page: number) => void | 页码会被夹进合法区间,越界入参不会写出越界的页。 |
goToPrevPage | () => void | |
goToNextPage | () => void | |
setPageSize | (pageSize: number) => void | 更换每页条数:页码随之换算,使改档前的第一条仍留在页内。 |
slice | <V>(data: readonly V[]) => V[] | 按当前页从整份数据中切出该页。 |
getRootProps | () => T['element'] | |
getSummaryProps | () => T['element'] | 信息区容器;文本由作者放置,默认使用 api.summaryText。 |
getJumperProps | () => T['input'] | 跳页输入框:输入页码按回车即跳转,越界值由 setPage 夹回合法区间。 |
getPrevTriggerProps | () => T['button'] | |
getNextTriggerProps | () => T['button'] | |
getItemProps | (props: PaginationItemProps) => T['button'] | |
getEllipsisTriggerProps | (props: PaginationEllipsisTriggerProps) => T['button'] | 省略位:可展开的按钮,展开后列出被折叠的页码。 |
getPageSizeSelectProps | () => T['element'] | 每页条数控制器的挂载点:只负责排布的一格,控件本体是内嵌下拉的角色节点。 |
pageSizeSelect | SelectApi<T> | 每页条数的下拉,整份 select 的 api。档位由 collection 给出(文字取 translations.pageSizeOption),选中值即当前每页条数;作者按它渲染 select 的角色节点。 |
getPositionerProps | () => T['element'] | |
getContentProps | () => T['element'] | |
closeEllipsis | () => void | 收起展开的省略位。 |
无障碍
键盘
规格出处:W3C APG
| 按键 | 生效条件 | 行为 |
|---|---|---|
Enter / Space | focus in item | 跳到该页码(原生按钮激活,平台把按键翻成 click) |
Enter / Space | focus in prev-trigger, 非首页 | 回上一页;首页时按钮是原生 disabled,焦点根本落不上去 |
Enter / Space | focus in next-trigger, 非末页 | 进下一页;末页时按钮是原生 disabled |
Enter / Space | focus in ellipsis-trigger | 摊开被折叠的那几页;再按一次收起。纯悬停会把键盘用户挡在外面,而那几页除了这里没有别的入口 |
Enter / Space | held in prev-trigger / next-trigger / item / ellipsis-trigger, 该钮未禁用 | 按住期间该钮投影 data-pressed,与指针 :active 同一副按压面;抬起或失焦撤下,摊开面板收起时面板里被按住的页码也撤下。到边界的翻页钮是原生 disabled,不进按压面 |
Escape | ellipsis-trigger 已摊开 | 收起摊开的页码面板(走消解层,点面板外面同样收起) |
Tab / Shift+Tab | focus in root | 逐个经过每个可用按钮:分页不做 roving tabindex,用户要能 Tab 到某一页再确认;禁用的首尾按钮自动脱离序列 |
ARIA
以下属性由 connect 生成。
| 部件 | 属性 | 值 |
|---|---|---|
root | aria-label | label.root |
jumper | aria-label | label.jumper |
prev-trigger | aria-label | label.prevTrigger |
next-trigger | aria-label | label.nextTrigger |
item | aria-current | 'page' | undefined |
item | aria-label | label.item(item.page) |
ellipsis-trigger | aria-controls | content 部件的 id | undefined |
ellipsis-trigger | aria-expanded | 'true' | 'false' |
ellipsis-trigger | aria-haspopup | 'true' |
ellipsis-trigger | aria-label | label.ellipsis( (items.find(item => item.type === 'el… |
content | aria-hidden | !open || undefined |
content | aria-label | label.ellipsis(folded.length) |
content | role | 'group' |
样式参考
皮肤
@xihan-ui/styles/pagination.css 使用 [data-scope="pagination"][data-part="root"] 部件选择器,位于 xihan.components 层。覆盖样式使用 xihan.overrides。
数据属性
由 connect 生成;条件不成立时不输出无值属性。
| 部件 | 属性 | 值 |
|---|---|---|
root | data-empty | ''(条件成立时才出现) |
root | data-size | props.size |
root | data-tone | props.tone |
summary | data-empty | ''(条件成立时才出现) |
jumper | data-empty | ''(条件成立时才出现) |
prev-trigger | data-disabled | ''(条件成立时才出现) |
prev-trigger | data-pressed | ''(条件成立时才出现) |
prev-trigger | data-xh-action-control | '' |
prev-trigger | data-xh-action-display | 'always' |
prev-trigger | data-xh-action-profile | 'text' |
prev-trigger | data-xh-action-size | props.size |
prev-trigger | data-xh-action-variant | 'ghost' |
next-trigger | data-disabled | ''(条件成立时才出现) |
next-trigger | data-pressed | ''(条件成立时才出现) |
next-trigger | data-xh-action-control | '' |
next-trigger | data-xh-action-display | 'always' |
next-trigger | data-xh-action-profile | 'text' |
next-trigger | data-xh-action-size | props.size |
next-trigger | data-xh-action-variant | 'ghost' |
item | data-current | ''(条件成立时才出现) |
item | data-pressed | ''(条件成立时才出现) |
item | data-xh-action-control | '' |
item | data-xh-action-display | 'always' |
item | data-xh-action-profile | 'text' |
item | data-xh-action-size | props.size |
item | data-xh-action-variant | 'ghost' |
ellipsis-trigger | data-pressed | ''(条件成立时才出现) |
ellipsis-trigger | data-side | props.side |
ellipsis-trigger | data-state | 'open' | 'closed' |
ellipsis-trigger | data-xh-action-control | '' |
ellipsis-trigger | data-xh-action-display | 'always' |
ellipsis-trigger | data-xh-action-profile | 'text' |
ellipsis-trigger | data-xh-action-size | props.size |
ellipsis-trigger | data-xh-action-variant | 'ghost' |
page-size-select | data-empty | ''(条件成立时才出现) |
positioner | data-hidden | ''(条件成立时才出现) |
positioner | data-placement | 定位引擎算出的实际落位 |
positioner | data-positioned | ''(条件成立时才出现) |
positioner | data-size | props.size |
positioner | data-state | 'open' | 'closed' |
positioner | data-tone | props.tone |
content | data-placement | 定位引擎算出的实际落位 |
content | data-size | props.size |
content | data-state | 'open' | 'closed' |
CSS 变量
本组件公开覆盖槽由独立皮肤的实际消费位生成;默认来源、作用部件和状态均与 CSS 同源。
| 变量 | 部件 | CSS 属性 | 状态 | 默认来源 | 说明 |
|---|---|---|---|---|---|
--xh-pagination-content-bg | content | background | default | --xh-bg-surface | pagination 的 content 部件 background 覆盖槽。 |
--xh-pagination-content-border | content | border | default | --xh-border-default | pagination 的 content 部件 border 覆盖槽。 |
--xh-pagination-content-max-h | content | max-block-size | default | --xh-overlay-max-h | pagination 的 content 部件 max-block-size 覆盖槽。 |
--xh-pagination-content-max-w | content | max-inline-size | default | --xh-overlay-max-w | pagination 的 content 部件 max-inline-size 覆盖槽。 |
--xh-pagination-content-p | content | padding | default | --xh-space-1 | pagination 的 content 部件 padding 覆盖槽。 |
--xh-pagination-content-radius | content | border-radius | default | --xh-shape-overlay | pagination 的 content 部件 border-radius 覆盖槽。 |
--xh-pagination-content-shadow | content | box-shadow | default | --xh-elevation-floating | pagination 的 content 部件 box-shadow 覆盖槽。 |
--xh-pagination-ellipsis-trigger-fg | ellipsis-trigger | color | defaultdisabledhoveris(:active, [data-pressed])loadingnot([data-disabled])not([data-loading])pressed | --xh-fg-subtle | pagination 的 ellipsis-trigger 部件 color 覆盖槽。 |
--xh-pagination-font-size | ellipsis-triggeritemjumpernext-triggerprev-triggersummary | font-size | default | --xh-_pagination-font-size | pagination 的 ellipsis-trigger、item、jumper、next-trigger、prev-trigger、summary 部件 font-size 覆盖槽。 |
--xh-pagination-gap | contentroot | gap | default | --xh-space-1 | pagination 的 content、root 部件 gap 覆盖槽。 |
--xh-pagination-icon-size | ellipsis-triggeritemnext-triggerpositionerprev-triggerroot | --xh-icon-size | defaultis([data-part='root'], [data-part='positioner'])size=lgsize=sm | --xh-_action-profile-glyph-size--xh-glyph-size-lg--xh-glyph-size-md--xh-glyph-size-sm | pagination 的 ellipsis-trigger、item、next-trigger、positioner、prev-trigger、root 部件 --xh-icon-size 覆盖槽。 |
--xh-pagination-item-bg | ellipsis-triggeritemnext-triggerprev-trigger | --xh-ink-surfacebackground-color | defaultxh-ink-surface | --xh-_action-variant-bg-rest | pagination 的 ellipsis-trigger、item、next-trigger、prev-trigger 部件 --xh-ink-surface、background-color 覆盖槽。 |
--xh-pagination-item-bg-active | ellipsis-triggeritemnext-triggerprev-trigger | background-color | disabledis(:active, [data-pressed])loadingnot([data-disabled])not([data-loading])pressed | --xh-_action-variant-bg-pressed | pagination 的 ellipsis-trigger、item、next-trigger、prev-trigger 部件 background-color 覆盖槽。 |
--xh-pagination-item-bg-hover | ellipsis-triggeritemnext-triggerprev-trigger | background-color | disabledhoverloadingnot([data-disabled])not([data-loading]) | --xh-_action-variant-bg-hover | pagination 的 ellipsis-trigger、item、next-trigger、prev-trigger 部件 background-color 覆盖槽。 |
--xh-pagination-item-bg-selected | item | --xh-ink-surfacebackground-color | currentfocus-visiblexh-ink-surface | --xh-_pagination-selected-bg | pagination 的 item 部件 --xh-ink-surface、background-color 覆盖槽。 |
--xh-pagination-item-bg-selected-active | item | background-color | currentdisabledis(:active, [data-pressed])loadingnot([data-disabled])not([data-loading])pressed | --xh-_pagination-selected-bg-active | pagination 的 item 部件 background-color 覆盖槽。 |
--xh-pagination-item-bg-selected-hover | item | background-color | currentdisabledhoverloadingnot([data-disabled])not([data-loading]) | --xh-_pagination-selected-bg-hover | pagination 的 item 部件 background-color 覆盖槽。 |
--xh-pagination-item-border-selected | item | borderborder-color | currentfocus-visible | --xh-_pagination-selected-bg | pagination 的 item 部件 border、border-color 覆盖槽。 |
--xh-pagination-item-border-selected-active | item | border-color | currentdisabledis(:active, [data-pressed])loadingnot([data-disabled])not([data-loading])pressed | --xh-_pagination-selected-bg-active | pagination 的 item 部件 border-color 覆盖槽。 |
--xh-pagination-item-border-selected-hover | item | border-color | currentdisabledhoverloadingnot([data-disabled])not([data-loading]) | --xh-_pagination-selected-bg-hover | pagination 的 item 部件 border-color 覆盖槽。 |
--xh-pagination-item-fg | ellipsis-triggeritemjumpernext-triggerprev-trigger | color | defaultdisabledhoveris(:active, [data-pressed])loadingnot([data-disabled])not([data-loading])pressed | --xh-_action-variant-fg-hover--xh-_action-variant-fg-pressed--xh-_action-variant-fg-rest--xh-fg-default | pagination 的 ellipsis-trigger、item、jumper、next-trigger、prev-trigger 部件 color 覆盖槽。 |
--xh-pagination-item-fg-selected | item | color | currentdisabledfocus-visiblehoveris(:active, [data-pressed])loadingnot([data-disabled])not([data-loading])pressed | --xh-_pagination-selected-fg | pagination 的 item 部件 color 覆盖槽。 |
--xh-pagination-item-font-weight | ellipsis-triggeritemnext-triggerprev-trigger | font-weight | default | --xh-text-label-weight | pagination 的 ellipsis-trigger、item、next-trigger、prev-trigger 部件 font-weight 覆盖槽。 |
--xh-pagination-item-h | ellipsis-triggeritemjumpernext-triggerprev-triggersummary | block-size | default | --xh-_pagination-item-size | pagination 的 ellipsis-trigger、item、jumper、next-trigger、prev-trigger、summary 部件 block-size 覆盖槽。 |
--xh-pagination-item-min-size | ellipsis-triggeritemnext-triggerprev-trigger | min-inline-size | default | --xh-_pagination-item-size | pagination 的 ellipsis-trigger、item、next-trigger、prev-trigger 部件 min-inline-size 覆盖槽。 |
--xh-pagination-item-px | ellipsis-triggeritemjumpernext-triggerprev-trigger | padding-inline | default | --xh-_pagination-item-px | pagination 的 ellipsis-trigger、item、jumper、next-trigger、prev-trigger 部件 padding-inline 覆盖槽。 |
--xh-pagination-item-radius | ellipsis-triggeritemjumpernext-triggerprev-trigger | border-radius | default | --xh-shape-control | pagination 的 ellipsis-trigger、item、jumper、next-trigger、prev-trigger 部件 border-radius 覆盖槽。 |
--xh-pagination-item-shadow | item | box-shadow | currentdisabledfocus-visiblehoveris(:active, [data-pressed])loadingnot([data-disabled])not([data-loading])pressed | none | pagination 的 item 部件 box-shadow 覆盖槽。 |
--xh-pagination-jumper-bg | jumper | background | default | --xh-bg-surface | pagination 的 jumper 部件 background 覆盖槽。 |
--xh-pagination-jumper-bg-hover | jumper | background | hovernot(:disabled) | --xh-bg-subtle-hover | pagination 的 jumper 部件 background 覆盖槽。 |
--xh-pagination-jumper-border | jumper | border | default | --xh-border-default | pagination 的 jumper 部件 border 覆盖槽。 |
--xh-pagination-jumper-border-hover | jumper | border-color | hovernot(:disabled) | --xh-border-strong | pagination 的 jumper 部件 border-color 覆盖槽。 |
--xh-pagination-jumper-w | jumper | inline-size | default | --xh-space-8 | pagination 的 jumper 部件 inline-size 覆盖槽。 |
--xh-pagination-layer | positioner | z-index | default | --xh-_layer | pagination 的 positioner 部件 z-index 覆盖槽。 |
--xh-pagination-summary-fg | summary | color | default | --xh-fg-muted | pagination 的 summary 部件 color 覆盖槽。 |
动效
动效角色:按压 · 状态 · 出现(锚定面板) · 出现(无锚定弹出)(见动效规范)。
共享关键帧 xh-pop-in · xh-pop-out 由 family/motion.css 提供,皮肤 @import 它,单独引入仍成立;background-color · border-color 走 transition 过渡。时长与缓动读动效令牌,改令牌即改全局节奏。
皮肤之外还有一段:退场由适配器的退场闸门把关,动画播完才真收起。
系统开启减弱动效时由令牌层统一收敛,皮肤不另作判断。
响应式
皮肤另按输入能力分档:pointer: coarse:同一份皮肤在触屏与带指针的设备上不一样,与视口宽度无关。
RTL
皮肤用逻辑属性排布(inline-start 一族),dir="rtl" 下自动镜像;另有按 dir 分支的规则。
