0.8.0.0
This commit is contained in:
@@ -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. **对话框交互测试**
|
||||
- 验证对话框内容正确显示
|
||||
- 验证确认按钮触发跳转
|
||||
- 验证取消按钮关闭对话框
|
||||
Reference in New Issue
Block a user