0.8.0.0
This commit is contained in:
@@ -10,6 +10,8 @@ import { MemberNameWithStatus } from "@/components/member-name-with-status"
|
||||
import { useFamily } from "@/context/family-context"
|
||||
import { useToast } from "@/components/ui/use-toast"
|
||||
import type { FamilyMember } from "@/types/family"
|
||||
import { GenerationWarningDialog } from "@/components/tree/generation-warning-dialog"
|
||||
import { shouldShowGenerationWarning } from "@/lib/generation-utils"
|
||||
|
||||
export type RelationType = "child" | "spouse" | "father" | "mother"
|
||||
|
||||
@@ -38,11 +40,17 @@ export function AddRelationDialog({
|
||||
const { toast } = useToast()
|
||||
const [query, setQuery] = useState("")
|
||||
const [linkingId, setLinkingId] = useState<string | null>(null)
|
||||
|
||||
// 代数重新计算警告对话框状态
|
||||
const [showWarningDialog, setShowWarningDialog] = useState(false)
|
||||
const [pendingAction, setPendingAction] = useState<{ type: 'link' | 'create', target?: FamilyMember } | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) {
|
||||
setQuery("")
|
||||
setLinkingId(null)
|
||||
setShowWarningDialog(false)
|
||||
setPendingAction(null)
|
||||
}
|
||||
}, [open])
|
||||
|
||||
@@ -72,7 +80,6 @@ export function AddRelationDialog({
|
||||
if (member.gender === "MALE" && !fatherMatches) return false
|
||||
if (member.gender === "FEMALE" && !motherMatches) return false
|
||||
if (member.gender !== "MALE" && member.gender !== "FEMALE") {
|
||||
// 非男女成员,允许填补任一缺失的父母
|
||||
if (candidate.fatherId && candidate.motherId) return false
|
||||
}
|
||||
} else if (relationType === "spouse") {
|
||||
@@ -81,12 +88,10 @@ export function AddRelationDialog({
|
||||
if (candidate.spouseIds?.includes(member.id)) return false
|
||||
if (candidate.gender === member.gender) return false
|
||||
} else if (relationType === "father") {
|
||||
// 父亲必须是男性,世代比当前成员小1
|
||||
if (candidate.gender !== "MALE") return false
|
||||
if (candidate.generation !== (member.generation - 1)) return false
|
||||
if (member.fatherId === candidate.id) return false
|
||||
} else if (relationType === "mother") {
|
||||
// 母亲必须是女性,世代比当前成员小1
|
||||
if (candidate.gender !== "FEMALE") return false
|
||||
if (candidate.generation !== (member.generation - 1)) return false
|
||||
if (member.motherId === candidate.id) return false
|
||||
@@ -103,6 +108,17 @@ export function AddRelationDialog({
|
||||
}, [member, relationType, treeData.members, normalizedQuery])
|
||||
|
||||
const handleLink = async (target: FamilyMember) => {
|
||||
// 检查是否需要显示代数重新计算警告
|
||||
if ((relationType === 'father' || relationType === 'mother') && shouldShowGenerationWarning(member, relationType)) {
|
||||
setPendingAction({ type: 'link', target })
|
||||
setShowWarningDialog(true)
|
||||
return
|
||||
}
|
||||
|
||||
await performLink(target)
|
||||
}
|
||||
|
||||
const performLink = async (target: FamilyMember) => {
|
||||
try {
|
||||
setLinkingId(target.id)
|
||||
if (relationType === "child") {
|
||||
@@ -161,80 +177,111 @@ export function AddRelationDialog({
|
||||
|
||||
const handleCreateNew = () => {
|
||||
if (!normalizedQuery) return
|
||||
|
||||
// 检查是否需要显示代数重新计算警告
|
||||
if ((relationType === 'father' || relationType === 'mother') && shouldShowGenerationWarning(member, relationType)) {
|
||||
setPendingAction({ type: 'create' })
|
||||
setShowWarningDialog(true)
|
||||
return
|
||||
}
|
||||
|
||||
onCreateNew(query.trim(), relationType)
|
||||
}
|
||||
|
||||
// 确认添加父母后的处理
|
||||
const handleConfirmAddParent = () => {
|
||||
if (pendingAction?.type === 'link' && pendingAction.target) {
|
||||
performLink(pendingAction.target)
|
||||
} else if (pendingAction?.type === 'create') {
|
||||
onCreateNew(query.trim(), relationType)
|
||||
}
|
||||
setPendingAction(null)
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className="sm:max-w-lg">
|
||||
<DialogHeader>
|
||||
<DialogTitle>添加{relationType === "child" ? "子女" : relationType === "spouse" ? "配偶" : relationType === "father" ? "父亲" : "母亲"}</DialogTitle>
|
||||
<DialogDescription>
|
||||
先输入姓名,系统会检索已有成员。若没有匹配,可直接新建。
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<>
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className="sm:max-w-lg">
|
||||
<DialogHeader>
|
||||
<DialogTitle>添加{relationType === "child" ? "子女" : relationType === "spouse" ? "配偶" : relationType === "father" ? "父亲" : "母亲"}</DialogTitle>
|
||||
<DialogDescription>
|
||||
先输入姓名,系统会检索已有成员。若没有匹配,可直接新建。
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="relation-name">成员姓名</Label>
|
||||
<Input
|
||||
id="relation-name"
|
||||
placeholder="请输入姓名或字号"
|
||||
value={query}
|
||||
autoFocus
|
||||
onChange={(e) => setQuery(e.target.value)}
|
||||
/>
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="relation-name">成员姓名</Label>
|
||||
<Input
|
||||
id="relation-name"
|
||||
placeholder="请输入姓名或字号"
|
||||
value={query}
|
||||
autoFocus
|
||||
onChange={(e) => setQuery(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{normalizedQuery ? (
|
||||
candidates.length > 0 ? (
|
||||
<div>
|
||||
<p className="text-sm text-muted-foreground mb-2">找到以下可能的匹配:</p>
|
||||
<ScrollArea className="max-h-64 rounded-md border">
|
||||
<div className="divide-y">
|
||||
{candidates.map(({ member: candidate, fatherName, motherName }) => (
|
||||
<div key={candidate.id} className="p-3 flex flex-col gap-2">
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<div>
|
||||
<MemberNameWithStatus name={candidate.fullName} isDead={!!candidate.deathDate} />
|
||||
<p className="text-xs text-muted-foreground">
|
||||
第 {candidate.generation} 世 · {candidate.gender === "FEMALE" ? "女" : "男"}
|
||||
</p>
|
||||
</div>
|
||||
<Button
|
||||
size="sm"
|
||||
onClick={() => handleLink(candidate)}
|
||||
disabled={linkingId === candidate.id}
|
||||
>
|
||||
{linkingId === candidate.id ? "关联中..." : "关联"}
|
||||
</Button>
|
||||
</div>
|
||||
<div className="text-xs text-muted-foreground">
|
||||
<div>父亲:{fatherName || "未登记"}</div>
|
||||
<div>母亲:{motherName || "未登记"}</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</ScrollArea>
|
||||
</div>
|
||||
) : (
|
||||
<p className="text-sm text-muted-foreground">未找到匹配成员,可直接创建新成员。</p>
|
||||
)
|
||||
) : (
|
||||
<p className="text-sm text-muted-foreground">请输入姓名以搜索已有成员。</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{normalizedQuery ? (
|
||||
candidates.length > 0 ? (
|
||||
<div>
|
||||
<p className="text-sm text-muted-foreground mb-2">找到以下可能的匹配:</p>
|
||||
<ScrollArea className="max-h-64 rounded-md border">
|
||||
<div className="divide-y">
|
||||
{candidates.map(({ member: candidate, fatherName, motherName }) => (
|
||||
<div key={candidate.id} className="p-3 flex flex-col gap-2">
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<div>
|
||||
<MemberNameWithStatus name={candidate.fullName} isDead={!!candidate.deathDate} />
|
||||
<p className="text-xs text-muted-foreground">
|
||||
第 {candidate.generation} 世 · {candidate.gender === "FEMALE" ? "女" : "男"}
|
||||
</p>
|
||||
</div>
|
||||
<Button
|
||||
size="sm"
|
||||
onClick={() => handleLink(candidate)}
|
||||
disabled={linkingId === candidate.id}
|
||||
>
|
||||
{linkingId === candidate.id ? "关联中..." : "关联"}
|
||||
</Button>
|
||||
</div>
|
||||
<div className="text-xs text-muted-foreground">
|
||||
<div>父亲:{fatherName || "未登记"}</div>
|
||||
<div>母亲:{motherName || "未登记"}</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</ScrollArea>
|
||||
</div>
|
||||
) : (
|
||||
<p className="text-sm text-muted-foreground">未找到匹配成员,可直接创建新成员。</p>
|
||||
)
|
||||
) : (
|
||||
<p className="text-sm text-muted-foreground">请输入姓名以搜索已有成员。</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<DialogFooter className="flex flex-col gap-2 sm:flex-row sm:justify-between">
|
||||
<Button variant="outline" onClick={() => onOpenChange(false)} className="sm:flex-1">
|
||||
取消
|
||||
</Button>
|
||||
<Button onClick={handleCreateNew} disabled={!normalizedQuery} className="sm:flex-1">
|
||||
新建“{query || "成员"}”
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
<DialogFooter className="flex flex-col gap-2 sm:flex-row sm:justify-between">
|
||||
<Button variant="outline" onClick={() => onOpenChange(false)} className="sm:flex-1">
|
||||
取消
|
||||
</Button>
|
||||
<Button onClick={handleCreateNew} disabled={!normalizedQuery} className="sm:flex-1">
|
||||
新建"{query || "成员"}"
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
{/* 代数重新计算警告对话框 */}
|
||||
{pendingAction && (relationType === 'father' || relationType === 'mother') && (
|
||||
<GenerationWarningDialog
|
||||
open={showWarningDialog}
|
||||
onOpenChange={setShowWarningDialog}
|
||||
memberName={member.fullName}
|
||||
relationType={relationType}
|
||||
onConfirm={handleConfirmAddParent}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -9,6 +9,8 @@ import type { FamilyMember } from "@/types/family"
|
||||
import jsPDF from "jspdf"
|
||||
import { useRouter } from "next/navigation"
|
||||
import { useFamily } from "@/context/family-context"
|
||||
import { GenerationWarningDialog } from "@/components/tree/generation-warning-dialog"
|
||||
import { shouldShowGenerationWarning } from "@/lib/generation-utils"
|
||||
|
||||
// 全局类型声明
|
||||
declare global {
|
||||
@@ -99,6 +101,10 @@ const D3OrgChartFlowComponent = forwardRef<D3OrgChartRef, D3OrgChartFlowProps>(
|
||||
const [contextMenuPosition, setContextMenuPosition] = useState({ x: 0, y: 0 })
|
||||
const router = useRouter()
|
||||
const { currentTree } = useFamily()
|
||||
|
||||
// 代数重新计算警告对话框状态
|
||||
const [showWarningDialog, setShowWarningDialog] = useState(false)
|
||||
const [pendingAddMember, setPendingAddMember] = useState<{ type: 'father' | 'mother', member: FamilyMember } | null>(null)
|
||||
|
||||
// 构建新增成员的 URL
|
||||
const buildAddUrl = (type: 'father' | 'mother' | 'spouse' | 'child', member: FamilyMember) => {
|
||||
@@ -111,7 +117,8 @@ const D3OrgChartFlowComponent = forwardRef<D3OrgChartRef, D3OrgChartFlowProps>(
|
||||
case 'father':
|
||||
params.set('childId', member.id)
|
||||
params.set('gender', 'MALE')
|
||||
params.set('generation', (member.generation - 1).toString())
|
||||
// 如果是第1代成员,新父母的世代也设为1(后端会重新计算所有成员世代)
|
||||
params.set('generation', member.generation === 1 ? '1' : (member.generation - 1).toString())
|
||||
// 如果已有母亲,设为配偶
|
||||
if (member.motherId) {
|
||||
params.set('spouseId', member.motherId)
|
||||
@@ -120,7 +127,8 @@ const D3OrgChartFlowComponent = forwardRef<D3OrgChartRef, D3OrgChartFlowProps>(
|
||||
case 'mother':
|
||||
params.set('childId', member.id)
|
||||
params.set('gender', 'FEMALE')
|
||||
params.set('generation', (member.generation - 1).toString())
|
||||
// 如果是第1代成员,新父母的世代也设为1(后端会重新计算所有成员世代)
|
||||
params.set('generation', member.generation === 1 ? '1' : (member.generation - 1).toString())
|
||||
// 如果已有父亲,设为配偶
|
||||
if (member.fatherId) {
|
||||
params.set('spouseId', member.fatherId)
|
||||
@@ -163,6 +171,13 @@ const D3OrgChartFlowComponent = forwardRef<D3OrgChartRef, D3OrgChartFlowProps>(
|
||||
}
|
||||
|
||||
const handleAddMember = (type: 'father' | 'mother' | 'spouse' | 'child', member: FamilyMember) => {
|
||||
// 检查是否需要显示代数重新计算警告
|
||||
if ((type === 'father' || type === 'mother') && shouldShowGenerationWarning(member, type)) {
|
||||
setPendingAddMember({ type, member })
|
||||
setShowWarningDialog(true)
|
||||
return
|
||||
}
|
||||
|
||||
const url = buildAddUrl(type, member)
|
||||
console.log('D3 handleAddMember called:', type, url)
|
||||
// 使用 setTimeout 延迟跳转,确保菜单关闭后再执行
|
||||
@@ -171,6 +186,18 @@ const D3OrgChartFlowComponent = forwardRef<D3OrgChartRef, D3OrgChartFlowProps>(
|
||||
window.location.href = url
|
||||
}, 100)
|
||||
}
|
||||
|
||||
// 确认添加父母后的处理
|
||||
const handleConfirmAddParent = () => {
|
||||
if (pendingAddMember) {
|
||||
const url = buildAddUrl(pendingAddMember.type, pendingAddMember.member)
|
||||
console.log('Confirmed add parent, navigating to:', url)
|
||||
setTimeout(() => {
|
||||
window.location.href = url
|
||||
}, 100)
|
||||
setPendingAddMember(null)
|
||||
}
|
||||
}
|
||||
|
||||
// 判断可以添加的关系
|
||||
const canAddFather = (member: FamilyMember) => !member.fatherId
|
||||
@@ -277,7 +304,7 @@ const D3OrgChartFlowComponent = forwardRef<D3OrgChartRef, D3OrgChartFlowProps>(
|
||||
spouseCount: spouses.length,
|
||||
hasChildren: Object.values(members).some(m => m.fatherId === member.id || m.motherId === member.id),
|
||||
ageText: ageText,
|
||||
isFounder: member.isFounder === true
|
||||
isFounder: member.id === rootId // 使用 rootId 判断始祖
|
||||
})
|
||||
|
||||
// 添加子节点到队列
|
||||
@@ -1022,6 +1049,17 @@ const D3OrgChartFlowComponent = forwardRef<D3OrgChartRef, D3OrgChartFlowProps>(
|
||||
onClick={() => setContextMenuNode(null)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* 代数重新计算警告对话框 */}
|
||||
{pendingAddMember && (
|
||||
<GenerationWarningDialog
|
||||
open={showWarningDialog}
|
||||
onOpenChange={setShowWarningDialog}
|
||||
memberName={pendingAddMember.member.fullName}
|
||||
relationType={pendingAddMember.type}
|
||||
onConfirm={handleConfirmAddParent}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
})
|
||||
|
||||
+121
-78
@@ -1,5 +1,6 @@
|
||||
"use client"
|
||||
|
||||
import { useState } from "react"
|
||||
import type { FamilyMember } from "@/types/family"
|
||||
import { cn } from "@/lib/utils"
|
||||
import { useAvatarCache } from "@/hooks/use-avatar-cache"
|
||||
@@ -15,6 +16,8 @@ import {
|
||||
ContextMenuLabel,
|
||||
ContextMenuTrigger,
|
||||
} from "@/components/ui/context-menu"
|
||||
import { GenerationWarningDialog } from "@/components/tree/generation-warning-dialog"
|
||||
import { shouldShowGenerationWarning } from "@/lib/generation-utils"
|
||||
|
||||
interface FamilyNodeProps {
|
||||
member: FamilyMember
|
||||
@@ -90,6 +93,10 @@ function PersonCard({
|
||||
const router = useRouter()
|
||||
const { currentTree } = useFamily()
|
||||
|
||||
// 代数重新计算警告对话框状态
|
||||
const [showWarningDialog, setShowWarningDialog] = useState(false)
|
||||
const [pendingRelationType, setPendingRelationType] = useState<'father' | 'mother' | null>(null)
|
||||
|
||||
const birthYear = formatYear(member.birthDate)
|
||||
const deathYear = formatYear(member.deathDate)
|
||||
const age = calculateAge(member.birthDate, member.deathDate)
|
||||
@@ -123,7 +130,8 @@ function PersonCard({
|
||||
case 'father':
|
||||
params.set('childId', member.id)
|
||||
params.set('gender', 'MALE')
|
||||
params.set('generation', (member.generation - 1).toString())
|
||||
// 如果是第1代成员,新父母的世代也设为1(后端会重新计算所有成员世代)
|
||||
params.set('generation', member.generation === 1 ? '1' : (member.generation - 1).toString())
|
||||
// 如果已有母亲,设为配偶
|
||||
if (member.motherId) {
|
||||
params.set('spouseId', member.motherId)
|
||||
@@ -132,7 +140,8 @@ function PersonCard({
|
||||
case 'mother':
|
||||
params.set('childId', member.id)
|
||||
params.set('gender', 'FEMALE')
|
||||
params.set('generation', (member.generation - 1).toString())
|
||||
// 如果是第1代成员,新父母的世代也设为1(后端会重新计算所有成员世代)
|
||||
params.set('generation', member.generation === 1 ? '1' : (member.generation - 1).toString())
|
||||
// 如果已有父亲,设为配偶
|
||||
if (member.fatherId) {
|
||||
params.set('spouseId', member.fatherId)
|
||||
@@ -175,6 +184,13 @@ function PersonCard({
|
||||
}
|
||||
|
||||
const handleAddMember = (type: 'father' | 'mother' | 'spouse' | 'child') => {
|
||||
// 检查是否需要显示代数重新计算警告
|
||||
if ((type === 'father' || type === 'mother') && shouldShowGenerationWarning(member, type)) {
|
||||
setPendingRelationType(type)
|
||||
setShowWarningDialog(true)
|
||||
return
|
||||
}
|
||||
|
||||
const url = buildAddUrl(type)
|
||||
console.log('handleAddMember called:', type, url)
|
||||
// 使用 setTimeout 延迟跳转,确保菜单关闭后再执行
|
||||
@@ -183,6 +199,18 @@ function PersonCard({
|
||||
window.location.href = url
|
||||
}, 100)
|
||||
}
|
||||
|
||||
// 确认添加父母后的处理
|
||||
const handleConfirmAddParent = () => {
|
||||
if (pendingRelationType) {
|
||||
const url = buildAddUrl(pendingRelationType)
|
||||
console.log('Confirmed add parent, navigating to:', url)
|
||||
setTimeout(() => {
|
||||
window.location.href = url
|
||||
}, 100)
|
||||
setPendingRelationType(null)
|
||||
}
|
||||
}
|
||||
|
||||
// 是否显示右键菜单(有可添加的选项时才显示)
|
||||
const hasAddOptions = canEdit && (canAddFather || canAddMother || canAddSpouse || canAddChild)
|
||||
@@ -193,7 +221,8 @@ function PersonCard({
|
||||
// 庄严正式的中国风配色
|
||||
const isDead = !!member.deathDate
|
||||
const isMale = member.gender === 'MALE'
|
||||
const isFounder = member.isFounder === true
|
||||
// 使用 isRoot prop 而不是 isFounder 字段
|
||||
const isFounder = isRoot === true
|
||||
|
||||
const cardContent = (
|
||||
<div
|
||||
@@ -378,81 +407,94 @@ function PersonCard({
|
||||
|
||||
// 有可添加选项时,包裹右键菜单
|
||||
return (
|
||||
<ContextMenu>
|
||||
<ContextMenuTrigger>
|
||||
{cardContent}
|
||||
</ContextMenuTrigger>
|
||||
<ContextMenuContent className="w-48">
|
||||
<ContextMenuLabel className="text-xs text-muted-foreground">
|
||||
为 {member.fullName} 添加
|
||||
</ContextMenuLabel>
|
||||
<ContextMenuSeparator />
|
||||
|
||||
{canAddFather && (
|
||||
<ContextMenuItem
|
||||
className="gap-2 cursor-pointer"
|
||||
onPointerDown={(e) => {
|
||||
e.preventDefault()
|
||||
handleAddMember('father')
|
||||
}}
|
||||
>
|
||||
<UserPlus className="h-4 w-4 text-blue-500" />
|
||||
添加父亲
|
||||
</ContextMenuItem>
|
||||
)}
|
||||
|
||||
{canAddMother && (
|
||||
<ContextMenuItem
|
||||
className="gap-2 cursor-pointer"
|
||||
onPointerDown={(e) => {
|
||||
e.preventDefault()
|
||||
handleAddMember('mother')
|
||||
}}
|
||||
>
|
||||
<UserPlus className="h-4 w-4 text-pink-500" />
|
||||
添加母亲
|
||||
</ContextMenuItem>
|
||||
)}
|
||||
|
||||
{(canAddFather || canAddMother) && (canAddSpouse || canAddChild) && (
|
||||
<>
|
||||
<ContextMenu>
|
||||
<ContextMenuTrigger>
|
||||
{cardContent}
|
||||
</ContextMenuTrigger>
|
||||
<ContextMenuContent className="w-48">
|
||||
<ContextMenuLabel className="text-xs text-muted-foreground">
|
||||
为 {member.fullName} 添加
|
||||
</ContextMenuLabel>
|
||||
<ContextMenuSeparator />
|
||||
)}
|
||||
|
||||
{canAddSpouse && (
|
||||
<ContextMenuItem
|
||||
className="gap-2 cursor-pointer"
|
||||
onPointerDown={(e) => {
|
||||
e.preventDefault()
|
||||
if (onAddRelation) {
|
||||
onAddRelation('spouse')
|
||||
} else {
|
||||
handleAddMember('spouse')
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Users className="h-4 w-4 text-red-500" />
|
||||
添加配偶
|
||||
</ContextMenuItem>
|
||||
)}
|
||||
|
||||
{canAddChild && (
|
||||
<ContextMenuItem
|
||||
className="gap-2 cursor-pointer"
|
||||
onPointerDown={(e) => {
|
||||
e.preventDefault()
|
||||
if (onAddRelation) {
|
||||
onAddRelation('child')
|
||||
} else {
|
||||
handleAddMember('child')
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Baby className="h-4 w-4 text-green-500" />
|
||||
添加子女
|
||||
</ContextMenuItem>
|
||||
)}
|
||||
</ContextMenuContent>
|
||||
</ContextMenu>
|
||||
|
||||
{canAddFather && (
|
||||
<ContextMenuItem
|
||||
className="gap-2 cursor-pointer"
|
||||
onPointerDown={(e) => {
|
||||
e.preventDefault()
|
||||
handleAddMember('father')
|
||||
}}
|
||||
>
|
||||
<UserPlus className="h-4 w-4 text-blue-500" />
|
||||
添加父亲
|
||||
</ContextMenuItem>
|
||||
)}
|
||||
|
||||
{canAddMother && (
|
||||
<ContextMenuItem
|
||||
className="gap-2 cursor-pointer"
|
||||
onPointerDown={(e) => {
|
||||
e.preventDefault()
|
||||
handleAddMember('mother')
|
||||
}}
|
||||
>
|
||||
<UserPlus className="h-4 w-4 text-pink-500" />
|
||||
添加母亲
|
||||
</ContextMenuItem>
|
||||
)}
|
||||
|
||||
{(canAddFather || canAddMother) && (canAddSpouse || canAddChild) && (
|
||||
<ContextMenuSeparator />
|
||||
)}
|
||||
|
||||
{canAddSpouse && (
|
||||
<ContextMenuItem
|
||||
className="gap-2 cursor-pointer"
|
||||
onPointerDown={(e) => {
|
||||
e.preventDefault()
|
||||
if (onAddRelation) {
|
||||
onAddRelation('spouse')
|
||||
} else {
|
||||
handleAddMember('spouse')
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Users className="h-4 w-4 text-red-500" />
|
||||
添加配偶
|
||||
</ContextMenuItem>
|
||||
)}
|
||||
|
||||
{canAddChild && (
|
||||
<ContextMenuItem
|
||||
className="gap-2 cursor-pointer"
|
||||
onPointerDown={(e) => {
|
||||
e.preventDefault()
|
||||
if (onAddRelation) {
|
||||
onAddRelation('child')
|
||||
} else {
|
||||
handleAddMember('child')
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Baby className="h-4 w-4 text-green-500" />
|
||||
添加子女
|
||||
</ContextMenuItem>
|
||||
)}
|
||||
</ContextMenuContent>
|
||||
</ContextMenu>
|
||||
|
||||
{/* 代数重新计算警告对话框 */}
|
||||
{pendingRelationType && (
|
||||
<GenerationWarningDialog
|
||||
open={showWarningDialog}
|
||||
onOpenChange={setShowWarningDialog}
|
||||
memberName={member.fullName}
|
||||
relationType={pendingRelationType}
|
||||
onConfirm={handleConfirmAddParent}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -496,7 +538,8 @@ export function FamilyNode({
|
||||
// 有配偶时,合并到一个卡片中显示(类似D3布局)
|
||||
const isMale = member.gender === 'MALE'
|
||||
const isDead = !!member.deathDate
|
||||
const isFounder = member.isFounder === true
|
||||
// 使用 isRoot prop 而不是 isFounder 字段
|
||||
const isFounder = isRoot === true
|
||||
const avatarUrl = member.avatarUrl
|
||||
|
||||
// 计算生卒年份
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
"use client"
|
||||
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogAction,
|
||||
AlertDialogCancel,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogFooter,
|
||||
AlertDialogHeader,
|
||||
AlertDialogTitle,
|
||||
} from "@/components/ui/alert-dialog"
|
||||
import { AlertTriangle } from "lucide-react"
|
||||
|
||||
interface GenerationWarningDialogProps {
|
||||
open: boolean
|
||||
onOpenChange: (open: boolean) => void
|
||||
memberName: string
|
||||
relationType: 'father' | 'mother'
|
||||
onConfirm: () => void
|
||||
}
|
||||
|
||||
/**
|
||||
* 代数重新计算警告对话框
|
||||
* 当用户尝试给第1代成员(始祖)添加父母时显示此对话框
|
||||
*/
|
||||
export function GenerationWarningDialog({
|
||||
open,
|
||||
onOpenChange,
|
||||
memberName,
|
||||
relationType,
|
||||
onConfirm,
|
||||
}: GenerationWarningDialogProps) {
|
||||
const relationText = relationType === 'father' ? '父亲' : '母亲'
|
||||
|
||||
const handleConfirm = () => {
|
||||
onOpenChange(false)
|
||||
onConfirm()
|
||||
}
|
||||
|
||||
return (
|
||||
<AlertDialog open={open} onOpenChange={onOpenChange}>
|
||||
<AlertDialogContent className="max-w-md">
|
||||
<AlertDialogHeader>
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="flex h-10 w-10 items-center justify-center rounded-full bg-amber-100 dark:bg-amber-900/30">
|
||||
<AlertTriangle className="h-5 w-5 text-amber-600 dark:text-amber-500" />
|
||||
</div>
|
||||
<AlertDialogTitle className="text-lg">
|
||||
全族代数将重新计算
|
||||
</AlertDialogTitle>
|
||||
</div>
|
||||
<AlertDialogDescription asChild>
|
||||
<div className="space-y-3 pt-2">
|
||||
<p>
|
||||
您正在给 <span className="font-semibold text-foreground">{memberName}</span> 添加{relationText},
|
||||
这将导致<span className="text-amber-600 dark:text-amber-500 font-medium">全族代数重新计算</span>。
|
||||
</p>
|
||||
|
||||
<div className="rounded-lg bg-amber-50 dark:bg-amber-900/20 border border-amber-200 dark:border-amber-800 p-3">
|
||||
<p className="text-sm text-amber-800 dark:text-amber-200">
|
||||
<span className="font-medium">代数变化说明:</span>
|
||||
<br />
|
||||
原第1代将变为第2代,以此类推。新添加的{relationText}将成为新的第1代始祖。
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<p className="text-sm text-muted-foreground">
|
||||
此操作不可撤销,请确认是否继续?
|
||||
</p>
|
||||
</div>
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>取消</AlertDialogCancel>
|
||||
<AlertDialogAction
|
||||
onClick={handleConfirm}
|
||||
className="bg-amber-600 hover:bg-amber-700 text-white"
|
||||
>
|
||||
确认添加
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
)
|
||||
}
|
||||
@@ -333,7 +333,8 @@ export function TreeLayout({
|
||||
})
|
||||
}
|
||||
} else if (relationType === "father") {
|
||||
params.set("generation", String(relationDialog.member.generation - 1))
|
||||
// 如果是第1代成员,新父母的世代也设为1(后端会重新计算所有成员世代)
|
||||
params.set("generation", String(relationDialog.member.generation === 1 ? 1 : relationDialog.member.generation - 1))
|
||||
params.set("childId", relationDialog.member.id)
|
||||
params.set("gender", "MALE")
|
||||
// 如果已有母亲,设为配偶
|
||||
@@ -341,7 +342,8 @@ export function TreeLayout({
|
||||
params.set("spouseId", relationDialog.member.motherId)
|
||||
}
|
||||
} else if (relationType === "mother") {
|
||||
params.set("generation", String(relationDialog.member.generation - 1))
|
||||
// 如果是第1代成员,新父母的世代也设为1(后端会重新计算所有成员世代)
|
||||
params.set("generation", String(relationDialog.member.generation === 1 ? 1 : relationDialog.member.generation - 1))
|
||||
params.set("childId", relationDialog.member.id)
|
||||
params.set("gender", "FEMALE")
|
||||
// 如果已有父亲,设为配偶
|
||||
|
||||
Reference in New Issue
Block a user