# Design Document: 为始祖添加父母时的代数重新计算提醒 ## Overview 本设计文档描述了在家族树应用中,当用户尝试给始祖(第1代成员)添加父母时,显示确认对话框的功能实现。该功能旨在提醒用户此操作将导致全族代数重新计算,确保用户了解操作的影响后再执行。 ### 设计目标 1. 在所有添加父母的入口点(成员页面、族谱页面、添加关系对话框)统一实现确认提醒 2. 提供清晰的信息说明代数变化的影响 3. 保持与现有 UI 组件风格一致 4. 不影响非始祖成员的正常添加父母操作 ## Architecture ### 组件架构 ``` ┌─────────────────────────────────────────────────────────────┐ │ 用户界面层 │ ├─────────────────────────────────────────────────────────────┤ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │ │ │ MemberCard │ │ D3OrgChart │ │ AddRelationDialog │ │ │ │ (成员页面) │ │ (族谱页面) │ │ (添加关系对话框) │ │ │ └──────┬──────┘ └──────┬──────┘ └──────────┬──────────┘ │ │ │ │ │ │ │ └────────────────┼─────────────────────┘ │ │ │ │ │ ▼ │ │ ┌───────────────────────────────────────────────────────┐ │ │ │ GenerationWarningDialog (新组件) │ │ │ │ - 显示代数重新计算警告 │ │ │ │ - 提供确认/取消操作 │ │ │ └───────────────────────────────────────────────────────┘ │ ├─────────────────────────────────────────────────────────────┤ │ 工具函数层 │ ├─────────────────────────────────────────────────────────────┤ │ ┌───────────────────────────────────────────────────────┐ │ │ │ isFirstGenerationMember() (新函数) │ │ │ │ - 检查成员是否为第1代 │ │ │ │ - 判断是否需要显示警告 │ │ │ └───────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────┘ ``` ### 数据流 ```mermaid sequenceDiagram participant User as 用户 participant UI as UI组件 participant Check as 检查函数 participant Dialog as 警告对话框 participant Nav as 导航 User->>UI: 右键点击成员 → 添加父母 UI->>Check: isFirstGenerationMember(member) Check-->>UI: true/false alt 是第1代成员 UI->>Dialog: 显示警告对话框 Dialog-->>User: 展示代数变化说明 alt 用户确认 User->>Dialog: 点击"确认添加" Dialog->>Nav: 跳转到新增成员页面 else 用户取消 User->>Dialog: 点击"取消" Dialog->>UI: 关闭对话框 end else 不是第1代成员 UI->>Nav: 直接跳转到新增成员页面 end ``` ## Components and Interfaces ### 1. GenerationWarningDialog 组件 新建确认对话框组件,用于显示代数重新计算警告。 ```typescript // components/tree/generation-warning-dialog.tsx interface GenerationWarningDialogProps { open: boolean onOpenChange: (open: boolean) => void memberName: string relationType: 'father' | 'mother' onConfirm: () => void } export function GenerationWarningDialog({ open, onOpenChange, memberName, relationType, onConfirm, }: GenerationWarningDialogProps) { // 实现对话框内容 } ``` ### 2. isFirstGenerationMember 工具函数 检查成员是否为第1代成员的工具函数。 ```typescript // lib/generation-utils.ts (扩展现有文件) /** * 检查成员是否为第1代成员(需要显示代数重新计算警告) * @param member 家族成员对象 * @returns 是否为第1代成员 */ export function isFirstGenerationMember(member: FamilyMember): boolean { return member.generation === 1 } /** * 检查添加父母操作是否需要显示警告 * @param member 目标成员 * @param relationType 关系类型 * @returns 是否需要显示警告 */ export function shouldShowGenerationWarning( member: FamilyMember, relationType: 'father' | 'mother' ): boolean { return isFirstGenerationMember(member) } ``` ### 3. 组件集成接口 #### MemberCard 组件修改 ```typescript // 在 MemberCard 组件中添加状态管理 const [showWarningDialog, setShowWarningDialog] = useState(false) const [pendingRelationType, setPendingRelationType] = useState<'father' | 'mother' | null>(null) // 修改 handleAddMember 函数 const handleAddMember = (type: RelationType) => { if ((type === 'father' || type === 'mother') && isFirstGenerationMember(member)) { setPendingRelationType(type) setShowWarningDialog(true) } else { onOpenAddDialog(member, type) } } ``` #### D3OrgChartFlow 组件修改 ```typescript // 在 D3OrgChartFlow 组件中添加状态管理 const [showWarningDialog, setShowWarningDialog] = useState(false) const [pendingAction, setPendingAction] = useState<{ type: 'father' | 'mother' member: FamilyMember } | null>(null) // 修改 handleAddMember 函数 const handleAddMember = (type: 'father' | 'mother' | 'spouse' | 'child', member: FamilyMember) => { if ((type === 'father' || type === 'mother') && isFirstGenerationMember(member)) { setPendingAction({ type, member }) setShowWarningDialog(true) } else { // 原有逻辑 const url = buildAddUrl(type, member) window.location.href = url } } ``` ## Data Models 本功能不需要新增数据模型,使用现有的 `FamilyMember` 类型: ```typescript interface FamilyMember { id: string generation: number // 用于判断是否为第1代 fullName: string fatherId?: string motherId?: string // ... 其他字段 } ``` ## Correctness Properties *A property is a characteristic or behavior that should hold true across all valid executions of a system—essentially, a formal statement about what the system should do. Properties serve as the bridge between human-readable specifications and machine-verifiable correctness guarantees.* ### Property 1: 第1代成员检测正确性 *For any* 家族成员,`isFirstGenerationMember` 函数返回 `true` 当且仅当该成员的 `generation` 值等于 1。 **Validates: Requirements 1.1, 1.2, 1.3, 1.4** ### Property 2: 警告显示条件正确性 *For any* 添加父母操作,`shouldShowGenerationWarning` 函数返回 `true` 当且仅当目标成员是第1代成员且关系类型为 'father' 或 'mother'。 **Validates: Requirements 1.3, 2.1** ### Property 3: 非第1代成员不触发警告 *For any* 成员,如果其 `generation` 值大于 1,则添加父母操作不应触发警告对话框。 **Validates: Requirements 1.4** ## Error Handling ### 错误场景 1. **成员数据缺失** - 场景:成员对象缺少 `generation` 字段 - 处理:默认不显示警告,允许操作继续 2. **对话框状态异常** - 场景:对话框打开时成员数据变化 - 处理:关闭对话框,提示用户重新操作 3. **导航失败** - 场景:确认后跳转失败 - 处理:显示错误提示,保持对话框打开状态 ### 错误处理代码示例 ```typescript const handleConfirm = () => { try { if (!pendingAction) { console.error('No pending action') return } const url = buildAddUrl(pendingAction.type, pendingAction.member) setShowWarningDialog(false) setPendingAction(null) // 使用 setTimeout 确保对话框关闭后再跳转 setTimeout(() => { window.location.href = url }, 100) } catch (error) { console.error('Navigation failed:', error) toast({ title: '操作失败', description: '无法跳转到新增成员页面,请重试', variant: 'destructive' }) } } ``` ## Testing Strategy ### 单元测试 1. **isFirstGenerationMember 函数测试** - 测试 generation = 1 返回 true - 测试 generation > 1 返回 false - 测试边界值(generation = 0, 负数等) 2. **shouldShowGenerationWarning 函数测试** - 测试第1代成员 + father/mother 返回 true - 测试非第1代成员返回 false - 测试其他关系类型(spouse, child)返回 false ### 属性测试 使用 fast-check 进行属性测试: ```typescript import fc from 'fast-check' // Property 1: 第1代成员检测 fc.assert( fc.property( fc.integer({ min: 1, max: 100 }), (generation) => { const member = { generation } as FamilyMember return isFirstGenerationMember(member) === (generation === 1) } ), { numRuns: 100 } ) // Property 2: 警告显示条件 fc.assert( fc.property( fc.integer({ min: 1, max: 100 }), fc.constantFrom('father', 'mother', 'spouse', 'child'), (generation, relationType) => { const member = { generation } as FamilyMember const shouldShow = shouldShowGenerationWarning(member, relationType as any) const expected = generation === 1 && (relationType === 'father' || relationType === 'mother') return shouldShow === expected } ), { numRuns: 100 } ) ``` ### 集成测试 1. **成员页面右键菜单测试** - 验证第1代成员右键添加父母显示对话框 - 验证非第1代成员右键添加父母直接跳转 2. **族谱页面右键菜单测试** - 验证第1代成员右键添加父母显示对话框 - 验证确认后正确跳转 3. **对话框交互测试** - 验证对话框内容正确显示 - 验证确认按钮触发跳转 - 验证取消按钮关闭对话框