This commit is contained in:
freedakgmail
2025-12-21 17:32:33 +08:00
parent cffdd75e96
commit 5c8c70097c
190 changed files with 2746 additions and 893 deletions
@@ -0,0 +1,321 @@
# 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. **对话框交互测试**
- 验证对话框内容正确显示
- 验证确认按钮触发跳转
- 验证取消按钮关闭对话框