iter(g256): 抽出分页条并加每页条数切换

This commit is contained in:
HIsheR
2026-10-26 22:19:14 +08:00
parent 8e97e99cf4
commit 036e044070
6 changed files with 64 additions and 15 deletions
+2 -1
View File
@@ -1,6 +1,6 @@
{
"$schema": "ui-arch-registry-index/0.1",
"generatedAt": "2026-10-26T14:17:04.411Z",
"generatedAt": "2026-10-26T14:19:01.143Z",
"repoRoot": ".",
"packages": [
{
@@ -2087,6 +2087,7 @@
"工具栏输入框即筛选,输入即时过滤并回第 1 页;无匹配时显示空状态行",
"表头粘性固定(滚动容器 max-height 420px),斑马纹 + 悬停高亮由 CSS 提供",
"数据在途时传 loading(布尔或信号):表格体换成骨架行、筛选与翻页禁用,返回后置回 false 即恢复",
"分页条自带每页条数下拉(5 / 10 / 20 条,调用方传的 pageSize 若不在候选里会自动补进去):切换即改页大小并回第 1 页,首末页时上/下一页置灰不再派发无效页码",
"需要独立分页器联动时传 page 信号:const page = signal(1),dataTable({ page }) 与 pagination({ page }) 共用",
"需要批量操作时传 selected 信号:内部勾选实时写回,外部置空或赋值即同步整表(跨页保留,按 rowKey 存)",
"可排序表头的 aria-sort 跟随排序态同步(ascending / descending / none),读屏能直接播报当前列序,箭头只是视觉补充",
+7 -6
View File
@@ -1,6 +1,6 @@
# @ui-arch 组件库 Agent 文档(无图形版)
> 自动生成于 2026-10-26T14:17:04.411Z,数据源:各包 ua.manifest.json,勿手改。人类版文档站:apps/docs/index.html。
> 自动生成于 2026-10-26T14:19:01.143Z,数据源:各包 ua.manifest.json,勿手改。人类版文档站:apps/docs/index.html。
> 组件 = 普通函数,传 props 返回 DOM;状态用 signal 双向绑定:读 signal() 写 signal(v)。
## 快速上手
@@ -1037,11 +1037,12 @@ dataTable({ data: users, selectable: true, columns: [{ key: 'name', header: '姓
8. 工具栏输入框即筛选,输入即时过滤并回第 1 页;无匹配时显示空状态行
9. 表头粘性固定(滚动容器 max-height 420px),斑马纹 + 悬停高亮由 CSS 提供
10. 数据在途时传 loading(布尔或信号):表格体换成骨架行、筛选与翻页禁用,返回后置回 false 即恢复
11. 需要独立分页器联动时传 page 信号:const page = signal(1),dataTable({ page }) 与 pagination({ page }) 共用
12. 需要批量操作时传 selected 信号:内部勾选实时写回,外部置空或赋值即同步整表(跨页保留,按 rowKey 存)
13. 可排序表头的 aria-sort 跟随排序态同步(ascending / descending / none),读屏能直接播报当前列序,箭头只是视觉补充
14. 需要导出时开 exportable:按钮导的是筛选+排序后的全量行,与当前页无关,单元格按 format 输出
15. 整行可点(进详情、选中一行做后续操作)传 onRowClick:行会变成可聚焦项,Enter / 空格与点击等价;不传时行不进 Tab 序列,纯展示表不会被读屏当成按钮
11. 分页条自带每页条数下拉(5 / 10 / 20 条,调用方传的 pageSize 若不在候选里会自动补进去):切换即改页大小并回第 1 页,首末页时上/下一页置灰不再派发无效页码
12. 需要独立分页器联动时传 page 信号:const page = signal(1),dataTable({ page }) 与 pagination({ page }) 共用
13. 需要批量操作时传 selected 信号:内部勾选实时写回,外部置空或赋值即同步整表(跨页保留,按 rowKey 存)
14. 可排序表头的 aria-sort 跟随排序态同步(ascending / descending / none),读屏能直接播报当前列序,箭头只是视觉补充
15. 需要导出时开 exportable:按钮导的是筛选+排序后的全量行,与当前页无关,单元格按 format 输出
16. 整行可点(进详情、选中一行做后续操作)传 onRowClick:行会变成可聚焦项,Enter / 空格与点击等价;不传时行不进 Tab 序列,纯展示表不会被读屏当成按钮
现场演示:apps/docs/demo.html#sec-table
+8 -8
View File
@@ -9,10 +9,10 @@ import {
import { iconSearch } from './icons.js'; import { computed, effect, h, renderList, signal } from '@ui-arch/frame';
import { attachResize } from './columnResize.js';
import { exportButton } from './csv.js';
import { button } from './button.js';
import { skeleton } from './skeleton.js';
import { textField } from './textField.js';
import { emptyRow } from './tableEmpty.js';
import { tablePager } from './tablePager.js';
import { attachDataSource, readRows } from './tableDataSource.js';
import type { DataTableProps, TableApi } from './types.js';
@@ -188,12 +188,12 @@ export function dataTable<T extends { id: RowKey }>(props: DataTableProps<T>): H
props.exportable ? exportButton(table, props.columns, props.exportName ?? 'table.csv') : null, // 导出筛选+排序后的全量行
),
h('div', { class: 'ua-table-scroll' }, h('table', null, colgroup, h('thead', null, h('tr', null, selectable ? headCheck : null, ...props.columns.map(th))), tbody)),
h(
'div',
{ class: 'ua-pager', role: 'navigation', 'aria-label': '分页' },
button({ label: '上一页', variant: 'ghost', disabled: props.loading, onClick: () => table.dispatch({ type: 'setPage', page: info().page - 1 }) }),
h('span', { class: 'ua-page-info', role: 'status', 'aria-live': 'polite' }, () => `第 ${info().page} / ${info().totalPages} 页 · 共 ${visible().total} 行`), // 翻页/筛选后面向读屏播报
button({ label: '下一页', variant: 'ghost', disabled: props.loading, onClick: () => table.dispatch({ type: 'setPage', page: info().page + 1 }) }),
),
tablePager({
info,
total: () => visible().total,
loading: busy,
onPage: (page) => table.dispatch({ type: 'setPage', page }),
onPageSize: (pageSize) => table.dispatch({ type: 'setPageSize', pageSize }),
}),
);
}
+36
View File
@@ -0,0 +1,36 @@
import { effect, h, signal } from '@ui-arch/frame';
import { button } from './button.js';
import { select } from './select.js';
import type { TablePagerProps } from './types.js';
/*** 表格分页条:上一页 / 页码播报 / 下一页 + 每页条数切换;页码与总页数都是内核派生快照,这里只读不持有 ***/
export function tablePager(props: TablePagerProps): HTMLElement {
const sizes = props.pageSizes ?? [5, 10, 20]; // 每页条数候选:默认三档,够覆盖表格常见密度
const initial = props.info().pageSize; // 调用方在建表时可能传了候选外的页大小,补进候选避免下拉显示空白
const list = sizes.includes(initial) ? sizes : [...sizes, initial].sort((a, b) => a - b);
const sizeSig = signal(String(initial)); // 下拉受控值:外部(内核)改页大小时由下方 effect 同步
const atFirst = () => props.info().page <= 1; // 首/末页边界:按钮置灰,避免派发无效页码
const atLast = () => props.info().page >= props.info().totalPages;
effect(() => {
const cur = String(props.info().pageSize);
if (cur !== sizeSig()) sizeSig(cur); // 内核页大小变化(含列表恢复默认)→ 回写下拉
});
const sizeSelect = select({
size: 'sm',
options: list.map((n) => ({ value: String(n), label: `${n} 条/页` })),
value: sizeSig,
disabled: () => props.loading(), // 加载中不改页大小:避免与在途结果竞争
onChange: (v) => props.onPageSize(Number(v)),
});
return h(
'div',
{ class: 'ua-pager', role: 'navigation', 'aria-label': '分页' },
button({ label: '上一页', variant: 'ghost', disabled: () => props.loading() || atFirst(), onClick: () => props.onPage(Math.max(1, props.info().page - 1)) }),
h('span', { class: 'ua-page-info', role: 'status', 'aria-live': 'polite' }, () => `第 ${props.info().page} / ${props.info().totalPages} 页 · 共 ${props.total()} 行`), // 翻页 / 筛选后面向读屏播报
button({ label: '下一页', variant: 'ghost', disabled: () => props.loading() || atLast(), onClick: () => props.onPage(Math.min(props.info().totalPages, props.info().page + 1)) }),
sizeSelect,
);
}
+10
View File
@@ -813,6 +813,16 @@ export interface SpaceProps {
/*** 表格扩展接口:table-core 实例 + 皮肤层用到的两个取数方法 ***/
export type TableApi<T> = TableInstance<T> & { getPageInfo(): PageInfo; selectAllVisible(): void };
/*** 分页条入参:取值一律走读取器,保持响应式依赖收集 ***/
export interface TablePagerProps {
info: () => PageInfo; // 分页快照读取器(page / pageSize / totalPages)
total: () => number; // 筛选后总行数,只用于播报文案
loading: () => boolean; // 加载态:翻页与改页大小一并禁用
pageSizes?: number[]; // 每页条数候选;缺省 [5, 10, 20]
onPage: (page: number) => void; // 翻页回调(入参已按边界钳制)
onPageSize: (pageSize: number) => void; // 改页大小回调
}
/*** 锚点导航项 ***/
export interface AnchorItem {
href: string; // 目标区块地址(#id);对应元素须存在于页面上
+1
View File
@@ -1958,6 +1958,7 @@
"工具栏输入框即筛选,输入即时过滤并回第 1 页;无匹配时显示空状态行",
"表头粘性固定(滚动容器 max-height 420px),斑马纹 + 悬停高亮由 CSS 提供",
"数据在途时传 loading(布尔或信号):表格体换成骨架行、筛选与翻页禁用,返回后置回 false 即恢复",
"分页条自带每页条数下拉(5 / 10 / 20 条,调用方传的 pageSize 若不在候选里会自动补进去):切换即改页大小并回第 1 页,首末页时上/下一页置灰不再派发无效页码",
"需要独立分页器联动时传 page 信号:const page = signal(1),dataTable({ page }) 与 pagination({ page }) 共用",
"需要批量操作时传 selected 信号:内部勾选实时写回,外部置空或赋值即同步整表(跨页保留,按 rowKey 存)",
"可排序表头的 aria-sort 跟随排序态同步(ascending / descending / none),读屏能直接播报当前列序,箭头只是视觉补充",