11 KiB
Design Document: 为始祖添加父母时的代数重新计算提醒
Overview
本设计文档描述了在家族树应用中,当用户尝试给始祖(第1代成员)添加父母时,显示确认对话框的功能实现。该功能旨在提醒用户此操作将导致全族代数重新计算,确保用户了解操作的影响后再执行。
设计目标
- 在所有添加父母的入口点(成员页面、族谱页面、添加关系对话框)统一实现确认提醒
- 提供清晰的信息说明代数变化的影响
- 保持与现有 UI 组件风格一致
- 不影响非始祖成员的正常添加父母操作
Architecture
组件架构
┌─────────────────────────────────────────────────────────────┐
│ 用户界面层 │
├─────────────────────────────────────────────────────────────┤
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │
│ │ MemberCard │ │ D3OrgChart │ │ AddRelationDialog │ │
│ │ (成员页面) │ │ (族谱页面) │ │ (添加关系对话框) │ │
│ └──────┬──────┘ └──────┬──────┘ └──────────┬──────────┘ │
│ │ │ │ │
│ └────────────────┼─────────────────────┘ │
│ │ │
│ ▼ │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ GenerationWarningDialog (新组件) │ │
│ │ - 显示代数重新计算警告 │ │
│ │ - 提供确认/取消操作 │ │
│ └───────────────────────────────────────────────────────┘ │
├─────────────────────────────────────────────────────────────┤
│ 工具函数层 │
├─────────────────────────────────────────────────────────────┤
│ ┌───────────────────────────────────────────────────────┐ │
│ │ isFirstGenerationMember() (新函数) │ │
│ │ - 检查成员是否为第1代 │ │
│ │ - 判断是否需要显示警告 │ │
│ └───────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
数据流
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 组件
新建确认对话框组件,用于显示代数重新计算警告。
// 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代成员的工具函数。
// 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 组件修改
// 在 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 组件修改
// 在 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 类型:
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
错误场景
-
成员数据缺失
- 场景:成员对象缺少
generation字段 - 处理:默认不显示警告,允许操作继续
- 场景:成员对象缺少
-
对话框状态异常
- 场景:对话框打开时成员数据变化
- 处理:关闭对话框,提示用户重新操作
-
导航失败
- 场景:确认后跳转失败
- 处理:显示错误提示,保持对话框打开状态
错误处理代码示例
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
单元测试
-
isFirstGenerationMember 函数测试
- 测试 generation = 1 返回 true
- 测试 generation > 1 返回 false
- 测试边界值(generation = 0, 负数等)
-
shouldShowGenerationWarning 函数测试
- 测试第1代成员 + father/mother 返回 true
- 测试非第1代成员返回 false
- 测试其他关系类型(spouse, child)返回 false
属性测试
使用 fast-check 进行属性测试:
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代成员右键添加父母显示对话框
- 验证确认后正确跳转
-
对话框交互测试
- 验证对话框内容正确显示
- 验证确认按钮触发跳转
- 验证取消按钮关闭对话框