From 314366aa8d2e1d9ffa4aff31ae2fab7299efafb7 Mon Sep 17 00:00:00 2001 From: HIsheR Date: Mon, 26 Oct 2026 11:53:49 +0800 Subject: [PATCH] =?UTF-8?q?iter(g42):=20pagination=20=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E6=AF=8F=E9=A1=B5=E6=9D=A1=E6=95=B0=E5=88=87=E6=8D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .ua/backlog.json | 8 +++++ .ua/index.json | 20 +++++++++--- apps/demo/main.ts | 12 +++++++ llms-full.txt | 9 ++++-- llms.txt | 2 +- packages/skin/ui/src/pagination.ts | 50 ++++++++++++++++++++++-------- packages/skin/ui/src/types.ts | 2 ++ packages/skin/ui/ua.manifest.json | 18 +++++++++-- packages/skin/ui/ui.css | 6 ++++ 9 files changed, 103 insertions(+), 24 deletions(-) diff --git a/.ua/backlog.json b/.ua/backlog.json index 9b30d93..e4cad25 100644 --- a/.ua/backlog.json +++ b/.ua/backlog.json @@ -692,6 +692,14 @@ "acceptance": "verify 解析分组行并比对清单集合,漏写/多写均报错(formItem 作为别名放行);负向删除 tree 即失败;npm run check 全绿", "status": "done", "notes": "自补:门禁改进;把手工维护的第二份清单纳入自动校验" + }, + { + "id": "g42", + "title": "pagination 支持每页条数切换", + "motivation": "分页器只能固定 pageSize,列表页要改条数得业务另做下拉并自算总页数;且每次翻页都在重建跳页输入框", + "acceptance": "pagination({ pageSizes, onPageSizeChange }) 渲染条数切换器、切换后回第 1 页并按新条数重算总页数;跳页区只创建一次;npm run check 全绿", + "status": "open", + "notes": "自补:组件补充" } ] } diff --git a/.ua/index.json b/.ua/index.json index 74be2bd..6631521 100644 --- a/.ua/index.json +++ b/.ua/index.json @@ -1,6 +1,6 @@ { "$schema": "ui-arch-registry-index/0.1", - "generatedAt": "2026-10-26T03:51:18.113Z", + "generatedAt": "2026-10-26T03:53:26.496Z", "repoRoot": ".", "packages": [ { @@ -164,7 +164,7 @@ "tooltip(props, trigger):悬浮提示,包裹触发器,hover / focus 延迟 150ms 出场,移出 / 失焦 / Esc 收起;视口一侧空间不足时自动翻转", "popover(props, trigger):可交互气泡卡片,包裹触发器,默认点击开合 + 点外部 / Esc 收起,content 可放按钮等交互元素;视口一侧空间不足时自动翻转", "skeleton(props):骨架屏占位,variant = text | rect | circle,text 支持 rows 多行,扫光微动画", - "pagination(props):独立分页器,page 信号双向绑定,页码过多折叠省略号,首末页禁用翻页,jumpable 时末尾可输入页码直接跳转,键盘可达", + "pagination(props):独立分页器,page 信号双向绑定,页码过多折叠省略号,首末页禁用翻页,jumpable 时末尾可输入页码直接跳转,键盘可达;pageSizes 可切每页条数", "spinner(props):加载指示器,size 控制直径", "progress(props):进度条,value 信号驱动 0-100(满格切 success 色);不传 value 退化为不确定态无限往复", "tabs(props):标签页,active 信号双向绑定,滑动指示条 + 方向键环形导航,支持禁用页签", @@ -1339,7 +1339,7 @@ { "id": "pagination", "title": "分页器 Pagination", - "demo": "sec-table", + "demo": "sec-pagination", "props": [ { "n": "page", @@ -1364,6 +1364,17 @@ "t": "(page: number) => void", "k": 1, "d": "翻页回调" + }, + { + "n": "pageSizes", + "t": "number[]", + "d": "每页条数候选(如 [10, 20, 50]);传了才渲染条数切换器" + }, + { + "n": "onPageSizeChange", + "t": "(size: number) => void", + "k": 1, + "d": "切换每页条数回调,同时自动回到第 1 页" } ], "example": "const page = signal(1); pagination({ page, pageSize: 3, total: 24, onChange: (p) => console.log(p) })", @@ -1372,7 +1383,8 @@ "页码 ≤7 全部展开;更多时只保留首尾与当前页 ±1,缺口折叠为省略号", "首末页自动禁用 ‹ › 翻页按钮,当前页禁点并标记 aria-current", "与 dataTable 联动:把同一个 page 信号传给 dataTable 的 page 参数,两处翻页自动同步", - "键盘可达:全部是原生 button,Tab 聚焦后 Enter / Space 触发" + "键盘可达:全部是原生 button,Tab 聚焦后 Enter / Space 触发", + "pageSizes 传候选数组即出现「N 条/页」切换器,切换后自动回第 1 页;onPageSizeChange 拿新条数重算数据" ] }, { diff --git a/apps/demo/main.ts b/apps/demo/main.ts index 0f0dad0..a3b3088 100644 --- a/apps/demo/main.ts +++ b/apps/demo/main.ts @@ -182,6 +182,7 @@ const listPick = signal(''); // 列表点击选中的条目标题 const slideIndex = signal(0); // 轮播当前下标(外部受控演示) const treePick = signal('core'); // 树形选中节点 key const cityPick = signal(''); // 可搜索下拉的选中城市 +const pagerPage = signal(1); // 独立分页器当前页 const pickedFiles = signal(''); // 文件选择结果(名称串) // —— 禁用态演示:开关信号同时绑定 switch 的 checked 与 checkbox 的 disabled —— @@ -685,6 +686,17 @@ const content = h( result({ status: 'danger', title: '校验未通过', description: '发票金额与报销金额不一致,请核对后重新提交。' }), ), ), + section( + 'sec-pagination', + '分页', + 'pagination:受控页码信号,>7 页自动省略号折叠;jumpable 跳页输入、pageSizes 切每页条数(切换后回第 1 页)。', + h( + 'div', + { class: 'row' }, + pagination({ page: pagerPage, pageSize: 10, total: 137, jumpable: true, pageSizes: [10, 20, 50], onChange: (p) => pagerPage(p) }), + h('span', { class: 'out' }, () => `第 ${pagerPage()} 页`), + ), + ), section( 'sec-menu', '下拉菜单', diff --git a/llms-full.txt b/llms-full.txt index 93031e7..97a15f0 100644 --- a/llms-full.txt +++ b/llms-full.txt @@ -1,6 +1,6 @@ # @ui-arch 组件库 Agent 文档(无图形版) -> 自动生成于 2026-10-26T03:51:18.113Z,数据源:各包 ua.manifest.json,勿手改。人类版文档站:apps/docs/index.html。 +> 自动生成于 2026-10-26T03:53:26.496Z,数据源:各包 ua.manifest.json,勿手改。人类版文档站:apps/docs/index.html。 > 组件 = 普通函数,传 props 返回 DOM;状态用 signal 双向绑定:读 signal() 写 signal(v)。 ## 快速上手 @@ -766,6 +766,8 @@ divider({ label: '更多信息' }) / divider({ vertical: true }) - pageSize(必填):number —— 每页条数 - total(必填):number —— 总条数 - onChange(回调):(page: number) => void —— 翻页回调 +- pageSizes:number[] —— 每页条数候选(如 [10, 20, 50]);传了才渲染条数切换器 +- onPageSizeChange(回调):(size: number) => void —— 切换每页条数回调,同时自动回到第 1 页 示例: @@ -780,8 +782,9 @@ const page = signal(1); pagination({ page, pageSize: 3, total: 24, onChange: (p) 3. 首末页自动禁用 ‹ › 翻页按钮,当前页禁点并标记 aria-current 4. 与 dataTable 联动:把同一个 page 信号传给 dataTable 的 page 参数,两处翻页自动同步 5. 键盘可达:全部是原生 button,Tab 聚焦后 Enter / Space 触发 +6. pageSizes 传候选数组即出现「N 条/页」切换器,切换后自动回第 1 页;onPageSizeChange 拿新条数重算数据 -现场演示:apps/docs/demo.html#sec-table +现场演示:apps/docs/demo.html#sec-pagination ### modal — 弹窗 Modal @@ -1195,7 +1198,7 @@ API 速览: - tooltip(props, trigger):悬浮提示,包裹触发器,hover / focus 延迟 150ms 出场,移出 / 失焦 / Esc 收起;视口一侧空间不足时自动翻转 - popover(props, trigger):可交互气泡卡片,包裹触发器,默认点击开合 + 点外部 / Esc 收起,content 可放按钮等交互元素;视口一侧空间不足时自动翻转 - skeleton(props):骨架屏占位,variant = text | rect | circle,text 支持 rows 多行,扫光微动画 -- pagination(props):独立分页器,page 信号双向绑定,页码过多折叠省略号,首末页禁用翻页,jumpable 时末尾可输入页码直接跳转,键盘可达 +- pagination(props):独立分页器,page 信号双向绑定,页码过多折叠省略号,首末页禁用翻页,jumpable 时末尾可输入页码直接跳转,键盘可达;pageSizes 可切每页条数 - spinner(props):加载指示器,size 控制直径 - progress(props):进度条,value 信号驱动 0-100(满格切 success 色);不传 value 退化为不确定态无限往复 - tabs(props):标签页,active 信号双向绑定,滑动指示条 + 方向键环形导航,支持禁用页签 diff --git a/llms.txt b/llms.txt index b642e18..3ba4568 100644 --- a/llms.txt +++ b/llms.txt @@ -69,7 +69,7 @@ - tooltip(props, trigger):悬浮提示,包裹触发器,hover / focus 延迟 150ms 出场,移出 / 失焦 / Esc 收起;视口一侧空间不足时自动翻转 - popover(props, trigger):可交互气泡卡片,包裹触发器,默认点击开合 + 点外部 / Esc 收起,content 可放按钮等交互元素;视口一侧空间不足时自动翻转 - skeleton(props):骨架屏占位,variant = text | rect | circle,text 支持 rows 多行,扫光微动画 - - pagination(props):独立分页器,page 信号双向绑定,页码过多折叠省略号,首末页禁用翻页,jumpable 时末尾可输入页码直接跳转,键盘可达 + - pagination(props):独立分页器,page 信号双向绑定,页码过多折叠省略号,首末页禁用翻页,jumpable 时末尾可输入页码直接跳转,键盘可达;pageSizes 可切每页条数 - spinner(props):加载指示器,size 控制直径 - progress(props):进度条,value 信号驱动 0-100(满格切 success 色);不传 value 退化为不确定态无限往复 - tabs(props):标签页,active 信号双向绑定,滑动指示条 + 方向键环形导航,支持禁用页签 diff --git a/packages/skin/ui/src/pagination.ts b/packages/skin/ui/src/pagination.ts index 0e40361..c24db72 100644 --- a/packages/skin/ui/src/pagination.ts +++ b/packages/skin/ui/src/pagination.ts @@ -1,5 +1,6 @@ -import { h, renderList } from '@ui-arch/frame'; +import { h, renderList, signal } from '@ui-arch/frame'; import type { PaginationProps } from './types.js'; +import { select } from './select.js'; /*** 页码序列:≤7 页全展开;否则保留首尾与当前页 ±1 窗口,缺口折叠为省略号(null 占位) ***/ function pageSequence(page: number, totalPages: number): (number | null)[] { @@ -15,37 +16,60 @@ function pageSequence(page: number, totalPages: number): (number | null)[] { return seq; } -/*** 独立分页器:受控组件,page 为双向绑定信号;页码过多折叠省略号,首末页禁用翻页,jumpable 时末尾可跳页,原生 button 保证键盘可达 ***/ +/*** 独立分页器:受控组件,page 为双向绑定信号;页码过多折叠省略号,首末页禁用翻页,jumpable 可跳页,pageSizes 可切每页条数 ***/ + export function pagination(props: PaginationProps): HTMLElement { - const totalPages = Math.max(1, Math.ceil(props.total / props.pageSize)); // 总页数 + // —— 状态集中声明 —— + const size = signal(props.pageSize); // 每页条数:可从 switcher 改动,故用信号持有 + const totalPages = () => Math.max(1, Math.ceil(props.total / size())); // 总页数随时可重算 + const pages = h('div', { class: 'ua-pagination-pages' }); // 页码区,随页码重绘 + const tail = h('div', { class: 'ua-pagination-tail' }); // 附加区(条数切换 + 跳页),只创建一次 const go = (p: number) => { - const next = Math.min(totalPages, Math.max(1, p)); // 钳制到有效页码范围 + const next = Math.min(totalPages(), Math.max(1, p)); // 钳制到有效页码范围 props.page(next); props.onChange?.(next); }; - // —— 跳页输入:jumpable 且多页时渲染在末尾,回车跳转 —— - const jump = props.jumpable && totalPages > 1 ? (h('input', { class: 'ua-page-jump', inputmode: 'numeric', 'aria-label': `跳转到指定页,共 ${totalPages} 页` }) as HTMLInputElement) : null; - if (jump) { + + // —— 每页条数切换器:传了候选才渲染,切换即回第 1 页 —— + if ((props.pageSizes ?? []).length > 1) { + const sizeValue = signal(String(size())); + tail.append( + select({ + options: props.pageSizes!.map((n) => ({ value: String(n), label: `${n} 条/页` })), + value: sizeValue, + onChange: (v) => { + size(Number(v)); + props.onPageSizeChange?.(Number(v)); + go(1); // 条数变了旧页码无意义,统一回首页 + }, + }), + ); + } + + // —— 跳页输入:jumpable 且多页时渲染,回车跳转 —— + if (props.jumpable) { + const jump = h('input', { class: 'ua-page-jump', inputmode: 'numeric', 'aria-label': '跳转到指定页' }) as HTMLInputElement; jump.addEventListener('change', () => { const n = parseInt(jump.value, 10); jump.value = ''; // 跳转后清空输入,避免残留旧页码 if (!Number.isNaN(n)) go(n); // 非数字忽略,由 go 钳制范围 }); + tail.append(h('span', { class: 'ua-page-jump-wrap' }, jump, h('span', { class: 'ua-page-total' }, () => `/ ${totalPages()} 页`))); } - const nav = h('nav', { class: 'ua-pagination', 'aria-label': '分页' }); - renderList(nav, () => { - const page = Math.min(totalPages, Math.max(1, props.page())); + const nav = h('nav', { class: 'ua-pagination', 'aria-label': '分页' }, pages, tail); + renderList(pages, () => { + const totalNow = totalPages(); + const page = Math.min(totalNow, Math.max(1, props.page())); const items: HTMLElement[] = [h('button', { class: 'ua-page-btn', disabled: page <= 1, 'aria-label': '上一页', onClick: () => go(page - 1) }, '‹')]; - for (const it of pageSequence(page, totalPages)) { + for (const it of pageSequence(page, totalNow)) { items.push( it == null ? h('span', { class: 'ua-page-gap', 'aria-hidden': 'true' }, '…') : h('button', { class: `ua-page-btn${it === page ? ' ua-active' : ''}`, disabled: it === page, 'aria-current': it === page ? 'page' : null, 'aria-label': `第 ${it} 页`, onClick: () => go(it) }, String(it)), ); } - items.push(h('button', { class: 'ua-page-btn', disabled: page >= totalPages, 'aria-label': '下一页', onClick: () => go(page + 1) }, '›')); - if (jump) items.push(h('span', { class: 'ua-page-jump-wrap' }, jump, h('span', { class: 'ua-page-total' }, `/ ${totalPages} 页`))); // 跳页入口固定在末尾 + items.push(h('button', { class: 'ua-page-btn', disabled: page >= totalNow, 'aria-label': '下一页', onClick: () => go(page + 1) }, '›')); return items; }); return nav; diff --git a/packages/skin/ui/src/types.ts b/packages/skin/ui/src/types.ts index 39d2d00..8452fcc 100644 --- a/packages/skin/ui/src/types.ts +++ b/packages/skin/ui/src/types.ts @@ -281,6 +281,8 @@ export interface PaginationProps { pageSize: number; // 每页条数 total: number; // 总条数 jumpable?: boolean; // 开启后在末尾显示跳页输入框(总页数 > 1 才出现) + pageSizes?: number[]; // 每页条数候选(如 [10, 20, 50]);传了才渲染条数切换器 + onPageSizeChange?: (size: number) => void; // 切换每页条数回调(同时自动回到第 1 页) onChange?: (page: number) => void; // 翻页回调 } diff --git a/packages/skin/ui/ua.manifest.json b/packages/skin/ui/ua.manifest.json index 6666242..6d51b6c 100644 --- a/packages/skin/ui/ua.manifest.json +++ b/packages/skin/ui/ua.manifest.json @@ -35,7 +35,7 @@ "tooltip(props, trigger):悬浮提示,包裹触发器,hover / focus 延迟 150ms 出场,移出 / 失焦 / Esc 收起;视口一侧空间不足时自动翻转", "popover(props, trigger):可交互气泡卡片,包裹触发器,默认点击开合 + 点外部 / Esc 收起,content 可放按钮等交互元素;视口一侧空间不足时自动翻转", "skeleton(props):骨架屏占位,variant = text | rect | circle,text 支持 rows 多行,扫光微动画", - "pagination(props):独立分页器,page 信号双向绑定,页码过多折叠省略号,首末页禁用翻页,jumpable 时末尾可输入页码直接跳转,键盘可达", + "pagination(props):独立分页器,page 信号双向绑定,页码过多折叠省略号,首末页禁用翻页,jumpable 时末尾可输入页码直接跳转,键盘可达;pageSizes 可切每页条数", "spinner(props):加载指示器,size 控制直径", "progress(props):进度条,value 信号驱动 0-100(满格切 success 色);不传 value 退化为不确定态无限往复", "tabs(props):标签页,active 信号双向绑定,滑动指示条 + 方向键环形导航,支持禁用页签", @@ -1210,7 +1210,7 @@ { "id": "pagination", "title": "分页器 Pagination", - "demo": "sec-table", + "demo": "sec-pagination", "props": [ { "n": "page", @@ -1235,6 +1235,17 @@ "t": "(page: number) => void", "k": 1, "d": "翻页回调" + }, + { + "n": "pageSizes", + "t": "number[]", + "d": "每页条数候选(如 [10, 20, 50]);传了才渲染条数切换器" + }, + { + "n": "onPageSizeChange", + "t": "(size: number) => void", + "k": 1, + "d": "切换每页条数回调,同时自动回到第 1 页" } ], "example": "const page = signal(1); pagination({ page, pageSize: 3, total: 24, onChange: (p) => console.log(p) })", @@ -1243,7 +1254,8 @@ "页码 ≤7 全部展开;更多时只保留首尾与当前页 ±1,缺口折叠为省略号", "首末页自动禁用 ‹ › 翻页按钮,当前页禁点并标记 aria-current", "与 dataTable 联动:把同一个 page 信号传给 dataTable 的 page 参数,两处翻页自动同步", - "键盘可达:全部是原生 button,Tab 聚焦后 Enter / Space 触发" + "键盘可达:全部是原生 button,Tab 聚焦后 Enter / Space 触发", + "pageSizes 传候选数组即出现「N 条/页」切换器,切换后自动回第 1 页;onPageSizeChange 拿新条数重算数据" ] }, { diff --git a/packages/skin/ui/ui.css b/packages/skin/ui/ui.css index 3ee5257..5b09407 100644 --- a/packages/skin/ui/ui.css +++ b/packages/skin/ui/ui.css @@ -1026,6 +1026,12 @@ button.ua-step-dot:hover { align-items: center; gap: var(--ua-space-1); } +.ua-pagination-pages, +.ua-pagination-tail { + display: inline-flex; /* 页码区与附加区分组,附加项只创建一次 */ + align-items: center; + gap: var(--ua-space-1); +} .ua-page-jump-wrap { display: inline-flex; /* 跳页输入 + 总页数说明 */ align-items: center;