// DataTable groupBy 的纯逻辑:按键分组(保持首次出现顺序或自定义顺序)与展开态持久化。 export type RowGroup = { key: string; rows: T[] }; /** 按 key 分组;缺省按首次出现顺序,给 order 时按 order 排组(组内保持原顺序) */ export function groupRows( rows: readonly T[], keyOf: (row: T) => string, order?: (a: string, b: string) => number, ): RowGroup[] { const map = new Map(); for (const r of rows) { const k = keyOf(r); const list = map.get(k); if (list) list.push(r); else map.set(k, [r]); } const groups = [...map].map(([key, rs]) => ({ key, rows: rs })); return order ? groups.sort((a, b) => order(a.key, b.key)) : groups; } const PREFIX = "umt.table-groups:"; type Store = Pick; const defaultStore = (): Store | undefined => globalThis.localStorage; /** 读折叠的组(localStorage 不可用或内容损坏时为 null,由调用方取缺省) */ export function readCollapsed(storageKey: string | undefined, store = defaultStore): Set | null { if (!storageKey) return null; try { const raw = store()?.getItem(PREFIX + storageKey); if (!raw) return null; const v = JSON.parse(raw) as unknown; return Array.isArray(v) ? new Set(v.filter((x): x is string => typeof x === "string")) : null; } catch { return null; } } export function writeCollapsed( storageKey: string | undefined, collapsed: ReadonlySet, store = defaultStore, ): void { if (!storageKey) return; try { store()?.setItem(PREFIX + storageKey, JSON.stringify([...collapsed])); } catch { // 私密窗口 / 配额满:只是不记住 } } /** 切换一个组的折叠态,返回新集合 */ export function toggleCollapsed(collapsed: ReadonlySet, key: string): Set { const next = new Set(collapsed); if (next.has(key)) next.delete(key); else next.add(key); return next; }