"use client" import type { FamilyMember } from "@/types/family" import { FamilyNode } from "./family-node" import { useFamily } from "@/context/family-context" import { useEffect, useRef, useState, useCallback } from "react" import { useRouter } from "next/navigation" interface TreeLayoutProps { rootId: string onSelectMember?: (member: FamilyMember) => void relationMode?: boolean selectedMembers?: string[] onMemberClick?: (memberId: string) => void } interface Connection { from: { x: number; y: number } to: { x: number; y: number } type: 'vertical' | 'horizontal' } export function TreeLayout({ rootId, onSelectMember, relationMode = false, selectedMembers = [], onMemberClick }: TreeLayoutProps) { const { getMember, treeData, highlightedMemberId } = useFamily() const root = getMember(rootId) const router = useRouter() const containerRef = useRef(null) const [connections, setConnections] = useState([]) const nodeRefs = useRef>(new Map()) // 节点间距配置 const HORIZONTAL_GAP = 32 // 2rem = 32px const VERTICAL_GAP = 48 // 3rem = 48px const CONNECTOR_HEIGHT = 48 // 连接线垂直高度 // 处理节点点击 const handleNodeClick = useCallback((member: FamilyMember) => { if (relationMode && onMemberClick) { onMemberClick(member.id) } else { const params = new URLSearchParams(window.location.search) router.push(`/members/${member.id}?${params.toString()}`) } }, [relationMode, onMemberClick, router]) // 计算所有连线位置 const calculateConnections = useCallback(() => { const newConnections: Connection[] = [] const containerRect = containerRef.current?.getBoundingClientRect() if (!containerRect) return // 检查是否有足够的节点已注册 if (nodeRefs.current.size === 0) return nodeRefs.current.forEach((nodeElement, nodeId) => { const member = getMember(nodeId) if (!member || !member.childrenIds || member.childrenIds.length === 0) return const parentRect = nodeElement.getBoundingClientRect() const parentCenterX = parentRect.left + parentRect.width / 2 - containerRect.left const parentBottomY = parentRect.bottom - containerRect.top // 父节点到水平线的垂直连线 const verticalLineY = parentBottomY + CONNECTOR_HEIGHT / 2 newConnections.push({ from: { x: parentCenterX, y: parentBottomY }, to: { x: parentCenterX, y: verticalLineY }, type: 'vertical' }) // 获取所有子节点的位置 const childPositions = member.childrenIds .map(childId => { const childElement = nodeRefs.current.get(childId) if (!childElement) return null const childRect = childElement.getBoundingClientRect() return { id: childId, centerX: childRect.left + childRect.width / 2 - containerRect.left, topY: childRect.top - containerRect.top } }) .filter(Boolean) as Array<{ id: string; centerX: number; topY: number }> if (childPositions.length === 0) return // 如果有多个子节点,绘制水平连线 if (childPositions.length > 1) { const leftmostX = Math.min(...childPositions.map(p => p.centerX)) const rightmostX = Math.max(...childPositions.map(p => p.centerX)) newConnections.push({ from: { x: leftmostX, y: verticalLineY }, to: { x: rightmostX, y: verticalLineY }, type: 'horizontal' }) } // 每个子节点的垂直连线 childPositions.forEach(child => { newConnections.push({ from: { x: child.centerX, y: verticalLineY }, to: { x: child.centerX, y: child.topY }, type: 'vertical' }) }) }) setConnections(newConnections) }, [getMember]) useEffect(() => { // 多次尝试计算,确保 DOM 完全渲染 const timers: NodeJS.Timeout[] = [] timers.push(setTimeout(calculateConnections, 50)) timers.push(setTimeout(calculateConnections, 150)) timers.push(setTimeout(calculateConnections, 300)) timers.push(setTimeout(calculateConnections, 500)) // 监听窗口大小变化 window.addEventListener('resize', calculateConnections) return () => { timers.forEach(timer => clearTimeout(timer)) window.removeEventListener('resize', calculateConnections) } }, [calculateConnections, treeData]) if (!root) return null // 递归渲染树节点 const TreeNode = ({ memberId, isRoot = false }: { memberId: string; isRoot?: boolean }) => { const member = getMember(memberId) if (!member) return null const hasChildren = member.childrenIds && member.childrenIds.length > 0 // 获取所有配偶 const spouses = member.spouseIds && member.spouseIds.length > 0 ? member.spouseIds.map(id => getMember(id)).filter(Boolean) as FamilyMember[] : [] return (
{/* 节点本身 */}
{ if (el) { nodeRefs.current.set(memberId, el) } else { nodeRefs.current.delete(memberId) } }} className="relative z-10" >
{/* 子节点 */} {hasChildren && (
{(() => { // 对子节点进行排序:始祖排最左边,其他按出生日期排序 const sortedChildren = [...member.childrenIds].sort((aId, bId) => { const a = getMember(aId) const b = getMember(bId) if (!a || !b) return 0 // 始祖永远排最左边 const aIsFounder = a.tags?.includes('始祖') || a.generation === 1 const bIsFounder = b.tags?.includes('始祖') || b.generation === 1 if (aIsFounder && !bIsFounder) return -1 if (!aIsFounder && bIsFounder) return 1 // 其他按出生日期排序 return (a.birthDate || "").localeCompare(b.birthDate || "") }) return sortedChildren.map((childId) => ( )) })()}
)}
) } return (
{/* 连线层 - 使用 div 绘制 */}
{connections.map((conn, index) => { const isHorizontal = conn.type === 'horizontal' // 使用 1px 但通过 transform scale 缩小来实现细线 const width = isHorizontal ? Math.abs(conn.to.x - conn.from.x) : 1 const height = isHorizontal ? 1 : Math.abs(conn.to.y - conn.from.y) const left = Math.min(conn.from.x, conn.to.x) const top = Math.min(conn.from.y, conn.to.y) // 跳过无效的连线(宽度或高度为0) if (width <= 0 || height <= 0) return null return (
) })}
{/* 树节点层 */}
) }