# 始祖显示和删除后重新定位修复 ## 问题描述 ### 问题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 { 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)