iter(g247): 表格支持整体换数据源,dataTable 可接信号数据
This commit is contained in:
@@ -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');
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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>[]; // 列定义
|
||||
|
||||
@@ -4,8 +4,8 @@ import {
|
||||
paginationPlugin,
|
||||
selectionPlugin,
|
||||
sortPlugin,
|
||||
type RowKey,
|
||||
} from '@ui-arch/table-core';
|
||||
import type { RowKey } from '@ui-arch/table-core';
|
||||
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';
|
||||
@@ -13,23 +13,23 @@ import { button } from './button.js';
|
||||
import { skeleton } from './skeleton.js';
|
||||
import { textField } from './textField.js';
|
||||
import { emptyRow } from './tableEmpty.js';
|
||||
import { attachDataSource, readRows } from './tableDataSource.js';
|
||||
import type { DataTableProps, TableApi } from './types.js';
|
||||
|
||||
/*** 数据表格:粘性表头 + 行选择 + 排序 + 筛选 + 分页 + 空状态;状态由 table-core 持有,信号只做桥接渲染 ***/
|
||||
export function dataTable<T extends { id: RowKey }>(props: DataTableProps<T>): HTMLElement {
|
||||
// —— 状态集中声明 ——
|
||||
const selectable = props.selectable ?? false; // 行选择开关
|
||||
const selectable = props.selectable ?? false; // 状态集中声明:行选择开关
|
||||
const keyword = signal(''); // 筛选框关键字:受控信号,空状态里的「清除筛选」靠它一键还原
|
||||
const table = createTable({
|
||||
data: props.data,
|
||||
data: readRows(props.data), // 信号数据源先取当前值,后续靠 attachDataSource 换
|
||||
columns: props.columns,
|
||||
rowKey: 'id',
|
||||
plugins: [filterPlugin<T>(), sortPlugin<T>(), paginationPlugin<T>(), selectionPlugin<T>()],
|
||||
initialState: { pagination: { page: 1, pageSize: props.pageSize ?? 5 } },
|
||||
});
|
||||
const api = table as unknown as TableApi<T>; // 用到的两个取数方法在类型别名里声明
|
||||
const version = signal(0); // 桥接信号:table-core 订阅通知 → 递增触发信号系统
|
||||
table.subscribe(() => version(version() + 1));
|
||||
const version = signal(0); // 桥接信号:table-core 订阅通知 → 递增 → 派生统一重算
|
||||
table.subscribe(() => version(version() + 1)); attachDataSource(table, props.data); // 信号数据源变化即换源(静态数组直接返回)
|
||||
|
||||
// —— 派生:全部由 version 单一来源推导,语义与内核一致 ——
|
||||
const visible = computed(() => {
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import { effect, type Signal } from '@ui-arch/frame';
|
||||
import type { RowKey, TableInstance } from '@ui-arch/table-core';
|
||||
|
||||
/*** 作用:取数据源当前值——传数组直接返回,传信号 / computed 则调用取值(表格创建时用) ***/
|
||||
export function readRows<T>(data: T[] | Signal<T[]>): T[] {
|
||||
return typeof data === 'function' ? data() : data;
|
||||
}
|
||||
|
||||
/*** 作用:给表格挂换源 effect——数据源是信号时信号变化即整体换源,静态数组直接返回(不多挂 effect、不多算一次派生) ***/
|
||||
export function attachDataSource<T extends { id: RowKey }>(
|
||||
table: TableInstance<T>,
|
||||
data: T[] | Signal<T[]>,
|
||||
): void {
|
||||
if (typeof data !== 'function') return; // 卫语句:静态数组无需响应式换源
|
||||
const source = data;
|
||||
effect(() => table.setRows(source())); // setRows 内部按 rowKey 剔除失效选择项,页码与排序保持原意
|
||||
}
|
||||
@@ -61,7 +61,7 @@ export interface TableColumn<T = unknown> extends ColumnDef<T> {
|
||||
|
||||
/*** 数据表格入参 ***/
|
||||
export interface DataTableProps<T> {
|
||||
data: T[]; // 全量行数据,行须含 id 字段作 rowKey
|
||||
data: T[] | Signal<T[]>; // 全量行数据(行须含 id 作 rowKey);传信号 / computed 则数据变化时自动换源
|
||||
columns: TableColumn<T>[]; // 列定义:内核字段 + align / format
|
||||
filterKey?: string; // 工具栏筛选的列;缺省取第一列
|
||||
pageSize?: number; // 每页行数;缺省 5
|
||||
|
||||
@@ -1887,9 +1887,9 @@
|
||||
"props": [
|
||||
{
|
||||
"n": "data",
|
||||
"t": "T[]",
|
||||
"t": "T[] | Signal<T[]>",
|
||||
"r": 1,
|
||||
"d": "全量行数据,每行须含 id 字段作 rowKey"
|
||||
"d": "全量行数据(行须含 id 作 rowKey);传信号或 computed 时数据变化自动换源,排序 / 筛选 / 页码保持用户原意"
|
||||
},
|
||||
{
|
||||
"n": "columns",
|
||||
@@ -1951,6 +1951,7 @@
|
||||
"准备行数据,确保每行有唯一 id(跨页选择与刷新不丢行靠它)",
|
||||
"定义 columns:sortable: true 的列表头可点击排序(升 → 降 → 取消循环),标记箭头 ↑↓↕",
|
||||
"列展示细节用皮肤字段:align 控制表头与单元格对齐,format(value, row) 返回纯文本(走 textContent,不需要转义)",
|
||||
"数据源可以给信号或 computed(如筛选结果):变化时内核整体换源,选择状态按 rowKey 剔除已消失的行,排序与页码不被打断",
|
||||
"列宽用内核字段 width:数字(像素)固定该列,'flex' 或不设则按内容自适应",
|
||||
"selectable: true 开启行选择:表头全选框带 indeterminate 半选态,工具栏显示已选计数",
|
||||
"选中行用低视觉比重样式(淡色底 + 弱化文字),选择状态存 rowKey,跨页不丢失",
|
||||
|
||||
Reference in New Issue
Block a user