Files
chinese-family-tree-2/components/tree/tree-node.tsx
T
freedakgmail 77df6a0e74 0.6.0.3
2025-12-10 11:13:16 +08:00

152 lines
4.7 KiB
TypeScript

"use client"
import React, { memo, useCallback } from "react"
import type { FamilyMember } from "@/types/family"
import { FamilyNode } from "./family-node"
interface TreeNodeProps {
member: FamilyMember
spouses: FamilyMember[]
children: FamilyMember[]
isRoot: boolean
isHighlighted: boolean
highlightedMemberId?: string | null
relationMode: boolean
isSelected: boolean
selectedMembers: string[]
canEdit: boolean
hasChildren: boolean
isCollapsed: boolean
collapsedNodes: Set<string>
onNodeClick: (member: FamilyMember) => void
onAddRelation: (member: FamilyMember, type: "child" | "spouse") => void
onToggleCollapse: (memberId: string) => void
onNodeRef: (memberId: string, el: HTMLDivElement | null) => void
getMember: (id: string) => FamilyMember | undefined
sortedChildrenMap: Map<string, string[]>
horizontalGap: number
verticalGap: number
}
// 独立的树节点组件,使用 memo 优化渲染
export const TreeNode = memo(function TreeNode({
member,
spouses,
children,
isRoot,
isHighlighted,
highlightedMemberId,
relationMode,
isSelected,
selectedMembers,
canEdit,
hasChildren,
isCollapsed,
collapsedNodes,
onNodeClick,
onAddRelation,
onToggleCollapse,
onNodeRef,
getMember,
sortedChildrenMap,
horizontalGap,
verticalGap,
}: TreeNodeProps) {
const handleToggleCollapse = useCallback(() => {
onToggleCollapse(member.id)
}, [member.id, onToggleCollapse])
const handleAddRelation = useCallback((type: "child" | "spouse") => {
onAddRelation(member, type)
}, [member, onAddRelation])
// 处理成员点击,接收被点击的成员(可能是主成员或配偶)
const handleSelect = useCallback((clickedMember: FamilyMember) => {
onNodeClick(clickedMember)
}, [onNodeClick])
const handleRef = useCallback((el: HTMLDivElement | null) => {
onNodeRef(member.id, el)
}, [member.id, onNodeRef])
// 计算可见的子节点
const visibleChildren = hasChildren && !isCollapsed
? (sortedChildrenMap.get(member.id) || [])
.map(childId => getMember(childId))
.filter((child): child is FamilyMember => child !== undefined)
: []
return (
<div className="flex flex-col items-center">
{/* 节点本身 */}
<div ref={handleRef} className="relative z-10">
<FamilyNode
member={member}
spouses={spouses}
onSelect={handleSelect}
isRoot={isRoot}
isHighlighted={isHighlighted}
relationMode={relationMode}
isSelected={isSelected}
selectedMembers={selectedMembers}
canEdit={canEdit}
onAddRelation={handleAddRelation}
hasChildren={hasChildren}
isCollapsed={isCollapsed}
onToggleCollapse={handleToggleCollapse}
/>
</div>
{/* 子节点 - 只在未折叠时显示 */}
{visibleChildren.length > 0 && (
<div
className="flex items-start relative"
style={{
marginTop: `${verticalGap}px`,
gap: `${horizontalGap}px`
}}
>
{visibleChildren.map((child) => {
const childSpouses = child.spouseIds && child.spouseIds.length > 0
? child.spouseIds.map(id => getMember(id)).filter((s): s is FamilyMember => s !== undefined)
: []
const childHasChildren = child.childrenIds && child.childrenIds.length > 0
const childChildren = childHasChildren
? (sortedChildrenMap.get(child.id) || [])
.map(id => getMember(id))
.filter((c): c is FamilyMember => c !== undefined)
: []
return (
<TreeNode
key={child.id}
member={child}
spouses={childSpouses}
children={childChildren}
isRoot={false}
isHighlighted={child.id === highlightedMemberId}
highlightedMemberId={highlightedMemberId}
relationMode={relationMode}
isSelected={selectedMembers.includes(child.id)}
selectedMembers={selectedMembers}
canEdit={canEdit}
hasChildren={childHasChildren || false}
isCollapsed={collapsedNodes.has(child.id)}
collapsedNodes={collapsedNodes}
onNodeClick={onNodeClick}
onAddRelation={onAddRelation}
onToggleCollapse={onToggleCollapse}
onNodeRef={onNodeRef}
getMember={getMember}
sortedChildrenMap={sortedChildrenMap}
horizontalGap={horizontalGap}
verticalGap={verticalGap}
/>
)
})}
</div>
)}
</div>
)
})