153 lines
4.8 KiB
TypeScript
153 lines
4.8 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}
|
|
highlightedMemberId={highlightedMemberId}
|
|
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>
|
|
)
|
|
})
|