iter(g247): 表格支持整体换数据源,dataTable 可接信号数据

This commit is contained in:
HIsheR
2026-10-26 22:03:37 +08:00
parent 19741b52b6
commit 42ea31d5b5
11 changed files with 103 additions and 27 deletions
@@ -172,4 +172,25 @@ assert(
);
assert(all.getPageInfo().totalPages === 1, 'getAllRows 不影响分页快照(2 行 / 每页 2 = 1 页)');
// setRows:整体换数据源,选择状态按 rowKey 剔除失效行,页码越界由插件收敛
const swapped = createTable({
data: users,
columns: [{ key: 'name', header: 'Name' }],
rowKey: 'id',
plugins: [selectionPlugin(), paginationPlugin()],
initialState: { pagination: { page: 1, pageSize: 2 } },
});
swapped.dispatch({ type: 'toggleSelect', key: 1 }); // 选中两行,后面验证失效行被剔除
swapped.dispatch({ type: 'toggleSelect', key: 4 });
assert(swapped.isRowSelected(1) && swapped.isRowSelected(4), 'setRows 前:两行已选');
swapped.setRows(users.filter((u) => u.id !== 4));
assert(swapped.getVisibleRows().total === 4, 'setRows 后:total 跟随新数据(5 行 → 4 行)');
assert(swapped.isRowSelected(1) && !swapped.isRowSelected(4), 'setRows 后:失效行键从选择里剔除,有效行保留');
assert(swapped.getState().pagination.page === 1, 'setRows 不动页码(用户翻页意图不被打断)');
const sameRows = users.filter((u) => u.id !== 4);
let notified = 0;
swapped.subscribe(() => (notified += 1));
swapped.setRows(sameRows);
assert(notified === 1 && swapped.getVisibleRows().total === 4, 'setRows 换引用:通知一次并重算 total');
console.log('SMOKE OK');
+17 -1
View File
@@ -13,7 +13,8 @@ import type {
export function createTable<T>(
options: TableOptions<T>,
): TableInstance<T> & Record<string, unknown> {
const { data, columns, plugins = [], onStateChange } = options;
let data = options.data; // 可变:setRows 整体换数据源(筛选结果、轮询刷新),派生管线照旧跑
const { columns, plugins = [], onStateChange } = options;
const rowKey = options.rowKey ?? ('id' as keyof T & string); // 默认行标识
const getKey: (row: T) => RowKey =
@@ -91,10 +92,25 @@ export function createTable<T>(
return model;
}
/*** 整体替换数据源:引用未变或内容长度与行键全同则不动(避免无意义重算),变了就通知订阅方
* 选择状态按 rowKey 存活:新数据里不再存在的行键会从 selection 里剔除,避免批量操作命中幽灵行。 ***/
function setRows(next: T[]): void {
if (next === data) return;
data = next;
const keys = new Set(next.map(getKey));
const kept = state.selection.filter((key) => keys.has(key));
if (kept.length !== state.selection.length) {
state = { ...state, selection: kept }; // 只动选择:排序 / 筛选 / 页码保持用户原意
}
onStateChange?.(state);
for (const fn of listeners) fn();
}
const instance: TableInstance<T> = {
getState: () => state,
dispatch,
subscribe,
setRows,
getVisibleRows,
getAllRows: () => getVisibleRows(false), // 筛选 + 排序后、未分页的全量行(导出等场景)
getColumns: () => columns,
+1
View File
@@ -89,6 +89,7 @@ export interface TableInstance<T = unknown> {
getState(): TableState; // 当前状态快照
dispatch(action: Action): void; // 派发动作,唯一变更入口
subscribe(fn: () => void): () => void; // 订阅变更,返回取消函数
setRows(rows: T[]): void; // 整体换数据源(筛选结果 / 轮询刷新),选择状态按 rowKey 剔除失效行
getVisibleRows(): RowModel<T>; // 可见行模型(过完整管线)
getAllRows(): RowModel<T>; // 筛选 + 排序后、未分页的全量行(导出用)
getColumns(): ColumnDef<T>[]; // 列定义