45 lines
1.3 KiB
TypeScript
45 lines
1.3 KiB
TypeScript
export type ParentLinkedRow = {
|
|||
|
|
id: string;
|
||
|
|
parent_id?: string | null;
|
||
|
|
};
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 收集以 rootId 为根的整棵子树(包含根节点本身)。
|
||
|
|
*
|
||
|
|
* 说明:
|
||
|
|
* - 仅依赖 (id, parent_id) 字段,便于在 Convex 与前端共用同一套遍历逻辑。
|
||
|
|
* - 会做去重与环检测,避免异常数据导致死循环。
|
||
|
|
*/
|
||
|
|
export function collectSubtree<T extends ParentLinkedRow>(rows: readonly T[], rootId: string): T[] {
|
||
|
|
const byParentId = new Map<string | null, T[]>();
|
||
|
|
for (const row of rows) {
|
||
|
|
const parentId = (row.parent_id ?? null) as string | null;
|
||
|
|
const bucket = byParentId.get(parentId);
|
||
|
|
if (bucket) bucket.push(row);
|
||
|
|
else byParentId.set(parentId, [row]);
|
||
|
|
}
|
||
|
|
|
||
|
|
const root = rows.find((r) => r.id === rootId);
|
||
|
|
if (!root) return [];
|
||
|
|
|
||
|
|
const visited = new Set<string>();
|
||
|
|
const stack: T[] = [root];
|
||
|
|
const result: T[] = [];
|
||
|
|
|
||
|
|
while (stack.length > 0) {
|
||
|
|
const current = stack.pop()!;
|
||
|
|
if (visited.has(current.id)) continue;
|
||
|
|
visited.add(current.id);
|
||
|
|
result.push(current);
|
||
|
|
|
||
|
|
const children = byParentId.get(current.id) ?? [];
|
||
|
|
// 倒序入栈,保持更接近“原列表顺序”的遍历结果(不影响正确性)
|
||
|
|
for (let i = children.length - 1; i >= 0; i -= 1) {
|
||
|
|
stack.push(children[i]!);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return result;
|
||
|
|
}
|
||
|
|
|