175 lines
5.9 KiB
Markdown
175 lines
5.9 KiB
Markdown
# 始祖显示和删除后重新定位修复
|
||
|
||
## 问题描述
|
||
|
||
### 问题1:添加父亲后显示错误
|
||
给始祖(第1代成员)添加父亲后:
|
||
- 新父亲显示为"孤立成员"(橙色边框)
|
||
- 原始祖应该不再按始祖模式显示(金色边框)
|
||
- 新父亲应该作为始祖显示
|
||
|
||
### 问题2:删除父亲后始祖定位错误
|
||
删除始祖后:
|
||
- 应该自动找到下一代中年龄最大的直系成员作为新始祖
|
||
- 显示模式也要相应调整
|
||
|
||
## 根本原因
|
||
|
||
### 原因1:使用静态 isFounder 字段
|
||
前端代码使用 `member.isFounder` 字段来判断始祖,但这个字段:
|
||
- 在数据库中是静态的,创建时设置后不会自动更新
|
||
- 当添加父母后,原始祖的 `isFounder` 仍然是 `true`
|
||
- 新父母的 `isFounder` 是 `false` 或 `undefined`
|
||
- 导致显示状态不正确
|
||
|
||
正确的做法是使用 `treeData.rootId`(来自后端的 `rootMemberId`):
|
||
- 后端在添加/删除成员时会更新 `FamilyTree.rootMemberId`
|
||
- API 返回 `rootId` 字段,存储在 `treeData.rootId` 中
|
||
- 前端应该通过比较 `member.id === treeData.rootId` 来判断始祖
|
||
|
||
### 原因2:findNewRootMember 按创建时间排序
|
||
`findNewRootMember` 函数使用 `createdAt` 排序:
|
||
```typescript
|
||
orderBy: {
|
||
createdAt: 'asc',
|
||
}
|
||
```
|
||
|
||
这会选择最早创建的成员,而不是年龄最大的成员。
|
||
|
||
## 解决方案
|
||
|
||
### 1. 修改前端判断逻辑
|
||
|
||
#### app/members/page.tsx
|
||
- **MemberCard 组件**:使用 `member.id === currentTreeId` 判断始祖
|
||
- 将 `currentTreeId` 改为传递 `treeData.rootId`(而不是 `currentTree?.rootMemberId`)
|
||
- 修改 `isFounder` 判断逻辑
|
||
|
||
- **membersByGeneration 分组逻辑**:使用 `m.id === treeData.rootId` 判断始祖
|
||
- 修改 `familyMembers` 过滤逻辑
|
||
- 修改排序逻辑
|
||
|
||
**重要**:必须使用 `treeData.rootId` 而不是 `currentTree?.rootMemberId`,因为:
|
||
- `treeData.rootId` 是从 API `/api/trees/[treeId]/members` 返回的 `rootId`
|
||
- 这个值在后端添加/删除成员时会实时更新
|
||
- `currentTree.rootMemberId` 可能不会及时更新
|
||
|
||
#### components/tree/family-node.tsx
|
||
- 使用 `isRoot` prop 而不是 `member.isFounder` 字段
|
||
- 修改两处使用 `isFounder` 的地方
|
||
|
||
#### components/tree/d3-org-chart-flow.tsx
|
||
- 使用 `member.id === rootId` 判断始祖
|
||
- 修改构建节点数据时的 `isFounder` 赋值
|
||
|
||
### 2. 修改 findNewRootMember 函数
|
||
|
||
#### lib/generation-utils.ts
|
||
修改排序逻辑,按出生日期排序(年龄最大的优先):
|
||
|
||
```typescript
|
||
export async function findNewRootMember(treeId: string): Promise<string | null> {
|
||
const firstGenMembers = await prisma.familyMember.findMany({
|
||
where: {
|
||
treeId,
|
||
generation: 1,
|
||
fatherId: null,
|
||
motherId: null,
|
||
},
|
||
select: {
|
||
id: true,
|
||
birthDate: true,
|
||
createdAt: true,
|
||
},
|
||
})
|
||
|
||
if (firstGenMembers.length === 0) return null
|
||
|
||
// 按出生日期排序(年龄最大的优先),如果没有出生日期则按创建时间排序
|
||
const sorted = firstGenMembers.sort((a, b) => {
|
||
// 如果都有出生日期,按出生日期排序(早的在前)
|
||
if (a.birthDate && b.birthDate) {
|
||
return a.birthDate.localeCompare(b.birthDate)
|
||
}
|
||
// 如果只有一个有出生日期,有出生日期的优先
|
||
if (a.birthDate) return -1
|
||
if (b.birthDate) return 1
|
||
// 如果都没有出生日期,按创建时间排序
|
||
return a.createdAt.getTime() - b.createdAt.getTime()
|
||
})
|
||
|
||
return sorted[0].id
|
||
}
|
||
```
|
||
|
||
## 修改的文件
|
||
|
||
1. **app/members/page.tsx**
|
||
- 修改 MemberCard 组件的 `isFounder` 判断
|
||
- 修改 `currentTreeId` 传递为 `rootMemberId`
|
||
- 修改 `membersByGeneration` 分组逻辑
|
||
|
||
2. **components/tree/family-node.tsx**
|
||
- 使用 `isRoot` prop 替代 `member.isFounder`
|
||
|
||
3. **components/tree/d3-org-chart-flow.tsx**
|
||
- 使用 `member.id === rootId` 判断始祖
|
||
|
||
4. **lib/generation-utils.ts**
|
||
- 修改 `findNewRootMember` 函数,按出生日期排序
|
||
|
||
## 测试建议
|
||
|
||
### 测试场景1:添加父母后的显示
|
||
1. 创建一个家族树,添加第1代成员(始祖A)
|
||
2. 给始祖A添加子女(第2代)
|
||
3. 给始祖A添加父亲(新成员B)
|
||
4. 验证:
|
||
- 新父亲B显示为始祖(金色边框)
|
||
- 原始祖A不再显示为始祖(普通蓝色/粉色边框)
|
||
- 新父亲B不显示为"孤立成员"
|
||
|
||
### 测试场景2:删除始祖后的重新定位
|
||
1. 创建家族树,有多个第1代成员(兄弟姐妹)
|
||
2. 设置不同的出生日期
|
||
3. 在成员详情页删除当前始祖
|
||
4. 验证:
|
||
- 系统自动选择年龄最大的(出生日期最早的)成员作为新始祖
|
||
- 页面自动跳转到新始祖的详情页(而不是成员列表页)
|
||
- 新始祖显示为金色边框
|
||
- 家族树的 rootMemberId 正确更新
|
||
- highlightedMemberId 更新为新始祖的ID
|
||
|
||
### 测试场景3:在树视图中删除始祖
|
||
1. 在树视图中删除始祖
|
||
2. 验证:
|
||
- 树视图自动高亮显示新始祖
|
||
- 不会尝试定位到已删除的成员
|
||
|
||
### 测试场景4:没有出生日期的情况
|
||
1. 创建多个第1代成员,都没有出生日期
|
||
2. 删除当前始祖
|
||
3. 验证:
|
||
- 系统按创建时间选择最早创建的成员作为新始祖
|
||
|
||
## 技术细节
|
||
|
||
### 为什么使用 rootMemberId 而不是 isFounder
|
||
|
||
1. **动态性**:`rootMemberId` 在后端逻辑中会自动更新,而 `isFounder` 是静态字段
|
||
2. **单一真相源**:`FamilyTree.rootMemberId` 是始祖的唯一标识
|
||
3. **一致性**:后端已经在添加/删除成员时维护 `rootMemberId`,前端应该使用它
|
||
|
||
### 为什么按出生日期排序
|
||
|
||
1. **符合传统**:中国传统家谱中,年长者为尊
|
||
2. **逻辑合理**:删除始祖后,应该由年龄最大的兄弟姐妹继承始祖位置
|
||
3. **用户期望**:用户期望看到年龄最大的成员作为新始祖
|
||
|
||
## 相关功能
|
||
|
||
- 代数重新计算(GENERATION_RECALCULATION_FIX.md)
|
||
- 始祖添加父母警告对话框(GenerationWarningDialog)
|
||
- 成员删除逻辑(app/api/trees/[treeId]/members/[memberId]/route.ts)
|