322 lines
11 KiB
Markdown
322 lines
11 KiB
Markdown
# 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. **对话框交互测试**
|
|
- 验证对话框内容正确显示
|
|
- 验证确认按钮触发跳转
|
|
- 验证取消按钮关闭对话框
|