Files
chinese-family-tree-2/ROOT_MEMBER_DISPLAY_FIX.md
T
freedakgmail 5c8c70097c 0.8.0.0
2025-12-21 17:32:33 +08:00

5.9 KiB
Raw Blame History

始祖显示和删除后重新定位修复

问题描述

问题1:添加父亲后显示错误

给始祖(第1代成员)添加父亲后:

  • 新父亲显示为"孤立成员"(橙色边框)
  • 原始祖应该不再按始祖模式显示(金色边框)
  • 新父亲应该作为始祖显示

问题2:删除父亲后始祖定位错误

删除始祖后:

  • 应该自动找到下一代中年龄最大的直系成员作为新始祖
  • 显示模式也要相应调整

根本原因

原因1:使用静态 isFounder 字段

前端代码使用 member.isFounder 字段来判断始祖,但这个字段:

  • 在数据库中是静态的,创建时设置后不会自动更新
  • 当添加父母后,原始祖的 isFounder 仍然是 true
  • 新父母的 isFounderfalseundefined
  • 导致显示状态不正确

正确的做法是使用 treeData.rootId(来自后端的 rootMemberId):

  • 后端在添加/删除成员时会更新 FamilyTree.rootMemberId
  • API 返回 rootId 字段,存储在 treeData.rootId
  • 前端应该通过比较 member.id === treeData.rootId 来判断始祖

原因2findNewRootMember 按创建时间排序

findNewRootMember 函数使用 createdAt 排序:

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

修改排序逻辑,按出生日期排序(年龄最大的优先):

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