export type ParentLinkedRow = { id: string; parent_id?: string | null; }; /** * 收集以 rootId 为根的整棵子树(包含根节点本身)。 * * 说明: * - 仅依赖 (id, parent_id) 字段,便于在 Convex 与前端共用同一套遍历逻辑。 * - 会做去重与环检测,避免异常数据导致死循环。 */ export function collectSubtree(rows: readonly T[], rootId: string): T[] { const byParentId = new Map(); 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(); 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; }