iter: b04 新增独立 pagination 分页器并与 dataTable 页码联动

This commit is contained in:
HIsheR
2026-10-23 16:00:13 +08:00
parent 28820574e8
commit 8c9a63b5d2
12 changed files with 261 additions and 12 deletions
+13
View File
@@ -44,6 +44,19 @@ export function dataTable<T extends { id: RowKey }>(props: DataTableProps<T>): H
});
const selectedCount = computed(() => state().selection.length);
// —— 受控页码桥接:外部信号 ↔ 内核分页;外部越界先钳制,signal 等值写回不触发,双向同步收敛安全 ——
if (props.page) {
const pageSig = props.page;
effect(() => {
const external = Math.min(api.getPageInfo().totalPages, Math.max(1, pageSig())); // 越界钳制,防无效派发
if (external !== info().page) table.dispatch({ type: 'setPage', page: external }); // 外部翻页 → 内核
});
effect(() => {
const internal = info().page;
if (internal !== pageSig()) pageSig(internal); // 内部翻页(筛选/内置翻页器)→ 写回外部
});
}
const filterKey = props.filterKey ?? props.columns[0].key;
const colspan = props.columns.length + (selectable ? 1 : 0);
+1
View File
@@ -12,6 +12,7 @@ export { alert } from './alert.js';
export { toast, showToast } from './toast.js';
export { tooltip } from './tooltip.js';
export { skeleton } from './skeleton.js';
export { pagination } from './pagination.js';
export { spinner } from './spinner.js';
export { progress } from './progress.js';
export { tabs } from './tabs.js';
+42
View File
@@ -0,0 +1,42 @@
import { h, renderList } from '@ui-arch/frame';
import type { PaginationProps } from './types.js';
/*** 页码序列:≤7 页全展开;否则保留首尾与当前页 ±1 窗口,缺口折叠为省略号(null 占位) ***/
function pageSequence(page: number, totalPages: number): (number | null)[] {
if (totalPages <= 7) return Array.from({ length: totalPages }, (_, i) => i + 1);
const marks = [1, 2, page - 1, page, page + 1, totalPages - 1, totalPages].filter((p) => p >= 1 && p <= totalPages);
const seq: (number | null)[] = [];
let prev = 0;
for (const p of [...new Set(marks)].sort((a, b) => a - b)) {
if (p - prev > 1) seq.push(null); // 缺口 → 省略号
seq.push(p);
prev = p;
}
return seq;
}
/*** 独立分页器:受控组件,page 为双向绑定信号;页码过多折叠省略号,首末页禁用翻页,原生 button 保证键盘可达 ***/
export function pagination(props: PaginationProps): HTMLElement {
const totalPages = Math.max(1, Math.ceil(props.total / props.pageSize)); // 总页数
const go = (p: number) => {
const next = Math.min(totalPages, Math.max(1, p)); // 钳制到有效页码范围
props.page(next);
props.onChange?.(next);
};
const nav = h('nav', { class: 'ua-pagination', 'aria-label': '分页' });
renderList(nav, () => {
const page = Math.min(totalPages, 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)) {
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) }, '›'));
return items;
});
return nav;
}
+9
View File
@@ -34,6 +34,7 @@ export interface DataTableProps<T> {
filterKey?: string; // 工具栏筛选的列;缺省取第一列
pageSize?: number; // 每页行数;缺省 5
selectable?: boolean; // 是否启用行选择列(含全选三态与已选计数);缺省 false
page?: Signal<number>; // 受控页码信号:与内核分页双向同步,可接独立 pagination 组件联动
}
/*** 选项条目:select / radioGroup / tabs 共用的选项形状 ***/
@@ -98,6 +99,14 @@ export interface SkeletonProps {
rows?: number; // text 形状的行数;缺省 3
}
/*** 分页器入参 ***/
export interface PaginationProps {
page: Signal<number>; // 当前页码信号,双向绑定(外部赋值自动翻页)
pageSize: number; // 每页条数
total: number; // 总条数
onChange?: (page: number) => void; // 翻页回调
}
/*** toast 消息提示入参 ***/
export interface ToastProps {
message: string; // 提示文案