"use client" import { useState, useMemo, useEffect, useCallback, memo } from "react" import { SiteHeader } from "@/components/site-header" import { useFamily } from "@/context/family-context" import { useRouter } from "next/navigation" import { Input } from "@/components/ui/input" import { Button } from "@/components/ui/button" import { ChineseCard, ChineseCardContent } from "@/components/ui/chinese-card" import { Search, Plus, Filter, X, Users, User, UserPlus, Baby } from "lucide-react" import Link from "next/link" import { AvatarDisplay } from "@/components/ui/avatar-display" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" import { Slider } from "@/components/ui/slider" import { Label } from "@/components/ui/label" import { Badge } from "@/components/ui/badge" import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover" import type { FamilyMember } from "@/types/family" import { permissions } from "@/lib/permissions" import { MemberNameWithStatus } from "@/components/member-name-with-status" import { useSession } from "next-auth/react" import { EmptyStateBadge } from "@/components/empty-state-badge" import { AddRelationDialog, type RelationType } from "@/components/tree/add-relation-dialog" import { ContextMenu, ContextMenuContent, ContextMenuItem, ContextMenuLabel, ContextMenuSeparator, ContextMenuTrigger, } from "@/components/ui/context-menu" import { GenerationWarningDialog } from "@/components/tree/generation-warning-dialog" import { isFirstGenerationMember, shouldShowGenerationWarning } from "@/lib/generation-utils" // 占位符类型 interface PlaceholderMember { id: string isPlaceholder: true } type MemberOrPlaceholder = FamilyMember | PlaceholderMember // 提取配偶添加逻辑为独立函数(移除占位符,让卡片自然流动排列) function addSpousesToFamilyUnit( member: FamilyMember, spouses: FamilyMember[], processedSpouses: Set ): MemberOrPlaceholder[] { const result: MemberOrPlaceholder[] = [member] // 添加配偶(紧跟在主成员后面) if (member.spouseIds && member.spouseIds.length > 0) { member.spouseIds.forEach(spouseId => { const spouse = spouses.find(m => m.id === spouseId) if (spouse && !processedSpouses.has(spouseId)) { result.push(spouse) processedSpouses.add(spouseId) } }) } return result } // 判断是否是占位符 function isPlaceholder(member: MemberOrPlaceholder): member is PlaceholderMember { return 'isPlaceholder' in member && member.isPlaceholder === true } // 成员卡片组件 - 使用 memo 优化 const MemberCard = memo(function MemberCard({ member, allMembers, onLocateInTree, currentTreeId, canEdit, onOpenAddDialog, }: { member: FamilyMember allMembers: FamilyMember[] onLocateInTree: (id: string) => void currentTreeId?: string canEdit: boolean onOpenAddDialog: (member: FamilyMember, type: RelationType) => void }) { // 代数重新计算警告对话框状态 const [showWarningDialog, setShowWarningDialog] = useState(false) const [pendingRelationType, setPendingRelationType] = useState<'father' | 'mother' | null>(null) // 判断是否是始祖(使用 rootMemberId 而不是 isFounder 字段) const isFounder = member.id === currentTreeId // 判断是否是配偶 const isSpouse = !member.fatherId && !member.motherId && member.spouseIds && member.spouseIds.length > 0 && !isFounder // 判断是否是孤立成员(没有父母、没有配偶、不是始祖) const isOrphan = !member.fatherId && !member.motherId && (!member.spouseIds || member.spouseIds.length === 0) && !isFounder // 获取配偶信息 const spouse = isSpouse && member.spouseIds?.[0] ? allMembers.find(m => m.id === member.spouseIds[0]) : null const spouseName = spouse ? spouse.fullName : null // 获取父母信息 const father = member.fatherId ? allMembers.find(m => m.id === member.fatherId) : null const mother = member.motherId ? allMembers.find(m => m.id === member.motherId) : null // 判断是否已故 const isDead = !!member.deathDate // 根据在世/已故状态设置卡片样式 // 优先级:始祖 > 孤立 > 已故 > 在世 const cardStyle = isFounder ? '!border-2 !border-amber-400 !bg-amber-50/30 dark:!bg-amber-900/20 shadow-md' // 始祖:金色边框(优先级最高) : isOrphan ? '!border-2 !border-orange-400 !bg-orange-50/30 dark:!bg-orange-900/20' // 孤立:橙色边框提醒 : isDead ? '!border-2 !border-gray-400 dark:!border-gray-600 !bg-gray-50/50 dark:!bg-gray-900/30' // 已故:灰色深边框 : '!border-2 !border-amber-300/70 !bg-card dark:!border-amber-600/50' // 在世:浅金色边框 // 判断可以添加的关系 const canAddFather = !member.fatherId const canAddMother = !member.motherId const canAddSpouse = true // 配偶总是可以添加 const canAddChild = true // 子女总是可以添加 // 构建新增成员的 URL const buildAddUrl = (type: 'father' | 'mother' | 'spouse' | 'child') => { const params = new URLSearchParams() if (currentTreeId) { params.set('treeId', currentTreeId) } switch (type) { case 'father': params.set('childId', member.id) params.set('gender', 'MALE') // 如果是第1代成员,新父母的世代也设为1(后端会重新计算所有成员世代) params.set('generation', member.generation === 1 ? '1' : (member.generation - 1).toString()) // 如果已有母亲,设为配偶 if (member.motherId) { params.set('spouseId', member.motherId) } break case 'mother': params.set('childId', member.id) params.set('gender', 'FEMALE') // 如果是第1代成员,新父母的世代也设为1(后端会重新计算所有成员世代) params.set('generation', member.generation === 1 ? '1' : (member.generation - 1).toString()) // 如果已有父亲,设为配偶 if (member.fatherId) { params.set('spouseId', member.fatherId) } break case 'spouse': params.set('spouseId', member.id) params.set('generation', member.generation.toString()) // 配偶性别与当前成员相反 params.set('gender', member.gender === 'MALE' ? 'FEMALE' : 'MALE') // 如果当前成员有子女,将他们也作为新配偶的子女 if (member.childrenIds && member.childrenIds.length > 0) { member.childrenIds.forEach(id => { params.append('childrenIds', id) }) } break case 'child': // 根据当前成员性别设置父/母 if (member.gender === 'MALE') { params.set('fatherId', member.id) // 如果父亲有配偶,设置配偶为母亲 if (member.spouseIds && member.spouseIds.length > 0) { params.set('motherId', member.spouseIds[0]) } } else { params.set('motherId', member.id) // 如果母亲有配偶,设置配偶为父亲 if (member.spouseIds && member.spouseIds.length > 0) { params.set('fatherId', member.spouseIds[0]) } } params.set('generation', (member.generation + 1).toString()) break } return `/members/new?${params.toString()}` } const handleAddMember = (type: RelationType) => { // 检查是否需要显示代数重新计算警告 if ((type === 'father' || type === 'mother') && shouldShowGenerationWarning(member, type)) { setPendingRelationType(type) setShowWarningDialog(true) return } onOpenAddDialog(member, type) } // 确认添加父母后的处理 const handleConfirmAddParent = () => { if (pendingRelationType) { onOpenAddDialog(member, pendingRelationType) setPendingRelationType(null) } } // 卡片内容 const cardContent = (

{isFounder && 👑} {member.courtesyName && ( 字 {member.courtesyName} )}

{/* 父母信息 - 放在姓名右侧 */} {(father || mother) && (
父母: {father && ( { e.preventDefault() e.stopPropagation() window.location.href = `/members/${father.id}${window.location.search}` }} > )} {father && mother && ·} {mother && ( { e.preventDefault() e.stopPropagation() window.location.href = `/members/${mother.id}${window.location.search}` }} > )}
)}
{isFounder ? (

家族始祖

) : isOrphan ? (

⚠️ 孤立成员 - 请关联至族谱

) : isSpouse && spouseName ? (

{spouseName}的配偶

) : (

{member.generationName ? `字辈: ${member.generationName}` : "未录入字辈"}

)} {/* 籍贯和生卒年并排 */}

{member.ancestralHome || "籍贯未知"}

{/* 生卒年和年龄 */} {(member.birthDate || member.deathDate) && (
{member.birthDate && new Date(member.birthDate).getFullYear()} {member.deathDate && ` - ${new Date(member.deathDate).getFullYear()}`} {!member.deathDate && member.birthDate && ' - '} {member.isLunarDate && ( 农历 )}
{(() => { if (!member.birthDate) return null const birthYear = new Date(member.birthDate).getFullYear() const deathYear = member.deathDate ? new Date(member.deathDate).getFullYear() : new Date().getFullYear() const age = deathYear - birthYear return (
{member.deathDate ? `享年${age}岁` : `${age}岁`}
) })()}
)}
{member.tags && member.tags.length > 0 && (
{member.tags.map((tag) => ( {tag === '始祖' ? '👑 始祖' : tag} ))}
)}
) // 如果没有编辑权限,直接返回卡片内容 if (!canEdit) { return cardContent } // 有编辑权限时,包裹右键菜单 return ( <> {cardContent} 为 {member.fullName} 添加 {canAddFather && ( { e.preventDefault() handleAddMember('father') }} > 添加父亲 )} {canAddMother && ( { e.preventDefault() handleAddMember('mother') }} > 添加母亲 )} {(canAddFather || canAddMother) && (canAddSpouse || canAddChild) && ( )} {canAddSpouse && ( { e.preventDefault() handleAddMember('spouse') }} > 添加配偶 )} {canAddChild && ( { e.preventDefault() handleAddMember('child') }} > 添加子女 )} {/* 代数重新计算警告对话框 */} {pendingRelationType && ( )} ) }) export default function MembersPage() { const { treeData, currentTree, searchMembers, setHighlightedMemberId, refreshData } = useFamily() const { data: session, status } = useSession() const router = useRouter() const [query, setQuery] = useState("") const [showFilters, setShowFilters] = useState(false) // 添加成员对话框状态 const [relationDialog, setRelationDialog] = useState<{ member: FamilyMember type: RelationType } | null>(null) // 打开添加成员对话框 const handleOpenAddDialog = useCallback((member: FamilyMember, type: RelationType) => { setRelationDialog({ member, type }) }, []) // 姓名分解函数 const parseName = (fullName: string): { surname: string; givenName: string } => { const name = fullName.trim() if (!name) return { surname: '', givenName: '' } const compoundSurnames = [ '欧阳', '太史', '端木', '上官', '司马', '东方', '独孤', '南宫', '万俟', '闻人', '夏侯', '诸葛', '尉迟', '公羊', '赫连', '澹台', '皇甫', '宗政', '濮阳', '公冶', '太叔', '申屠', '公孙', '慕容', '仲孙', '钟离', '长孙', '宇文', '司徒', '鲜于', '司空', '闾丘', '子车', '亓官', '司寇', '巫马', '公西', '颛孙', '壤驷', '公良', '漆雕', '乐正', '宰父', '谷梁', '拓跋', '夹谷', '轩辕', '令狐', '段干', '百里', '呼延', '东郭', '南门', '羊舌', '微生', '公户', '公玉', '公仪', '梁丘', '公仲', '公上', '公门', '公山', '公坚', '左丘', '公伯', '西门', '公祖', '第五', '公乘', '贯丘', '公皙', '南荣', '东里', '东宫', '仲长', '子书', '子桑', '即墨', '达奚', '褚师' ] for (const compound of compoundSurnames) { if (name.startsWith(compound) && name.length > compound.length) { return { surname: compound, givenName: name.slice(compound.length) } } } if (name.length >= 2) { return { surname: name.charAt(0), givenName: name.slice(1) } } return { surname: name, givenName: '' } } // 创建新成员的处理函数 const handleCreateNewRelation = useCallback((prefillName: string, relationType: RelationType) => { if (!relationDialog) return const params = new URLSearchParams() const { surname, givenName } = parseName(prefillName) if (surname) params.set("surname", surname) if (givenName) params.set("givenName", givenName) if (relationType === "child") { params.set("generation", String(relationDialog.member.generation + 1)) if (relationDialog.member.gender === "MALE") { params.set("fatherId", relationDialog.member.id) if (relationDialog.member.spouseIds?.length) { params.set("motherId", relationDialog.member.spouseIds[0]) } } else if (relationDialog.member.gender === "FEMALE") { params.set("motherId", relationDialog.member.id) if (relationDialog.member.spouseIds?.length) { params.set("fatherId", relationDialog.member.spouseIds[0]) } } } else if (relationType === "spouse") { params.set("generation", String(relationDialog.member.generation)) params.set("spouseId", relationDialog.member.id) params.set("gender", relationDialog.member.gender === "MALE" ? "FEMALE" : "MALE") if (relationDialog.member.childrenIds && relationDialog.member.childrenIds.length > 0) { relationDialog.member.childrenIds.forEach(id => { params.append('childrenIds', id) }) } } else if (relationType === "father") { // 如果是第1代成员,新父母的世代也设为1(后端会重新计算所有成员世代) params.set("generation", String(relationDialog.member.generation === 1 ? 1 : relationDialog.member.generation - 1)) params.set("childId", relationDialog.member.id) params.set("gender", "MALE") if (relationDialog.member.motherId) { params.set("spouseId", relationDialog.member.motherId) } } else if (relationType === "mother") { // 如果是第1代成员,新父母的世代也设为1(后端会重新计算所有成员世代) params.set("generation", String(relationDialog.member.generation === 1 ? 1 : relationDialog.member.generation - 1)) params.set("childId", relationDialog.member.id) params.set("gender", "FEMALE") if (relationDialog.member.fatherId) { params.set("spouseId", relationDialog.member.fatherId) } } if (currentTree?.id) { params.set("treeId", currentTree.id) } setRelationDialog(null) router.push(`/members/new?${params.toString()}`) }, [relationDialog, currentTree?.id, router]) // 未登录时重定向到登录页 useEffect(() => { if (status === 'unauthenticated') { router.push('/auth/signin') } }, [status, router]) // 页面加载时刷新数据 useEffect(() => { if (currentTree?.id) { refreshData() } }, [currentTree?.id, refreshData]) // 从 URL 参数读取搜索关键词 useEffect(() => { const params = new URLSearchParams(window.location.search) const searchParam = params.get('search') if (searchParam) { setQuery(searchParam) } }, []) // Filter States const [selectedSurname, setSelectedSurname] = useState("all") const [selectedHome, setSelectedHome] = useState("all") const [statusFilter, setStatusFilter] = useState("all") const [generationRange, setGenerationRange] = useState<[number, number]>([1, 100]) // Derived Data const allMembers = useMemo(() => Object.values(treeData.members), [treeData]) const { uniqueSurnames, uniqueHomes, minGen, maxGen } = useMemo(() => { const surnames = new Set() const homes = new Set() let min = 1000 let max = 0 allMembers.forEach((m) => { if (m.surname) surnames.add(m.surname) if (m.ancestralHome) homes.add(m.ancestralHome) if (m.generation) { if (m.generation < min) min = m.generation if (m.generation > max) max = m.generation } }) return { uniqueSurnames: Array.from(surnames).sort(), uniqueHomes: Array.from(homes).sort(), minGen: min === 1000 ? 1 : min, maxGen: max === 0 ? 100 : max, } }, [allMembers]) // Initialize slider when data loads - 修复:使用 useEffect 而不是 useMemo useEffect(() => { if (minGen !== 1000 && maxGen !== 0) { // 只在初始加载时设置一次 setGenerationRange([minGen, maxGen]) } }, [minGen, maxGen]) const filteredMembers = useMemo(() => { let result = allMembers // 1. Search Query if (query) { const lowerQuery = query.toLowerCase() result = result.filter( (m) => m.fullName.toLowerCase().includes(lowerQuery) || m.courtesyName?.toLowerCase().includes(lowerQuery) || m.artName?.toLowerCase().includes(lowerQuery), ) } // 2. Surname Filter if (selectedSurname !== "all") { result = result.filter((m) => m.surname === selectedSurname) } // 3. Home Filter if (selectedHome !== "all") { result = result.filter((m) => m.ancestralHome === selectedHome) } // 4. Status Filter if (statusFilter !== "all") { if (statusFilter === "living") { result = result.filter((m) => !m.deathDate) } else if (statusFilter === "deceased") { result = result.filter((m) => !!m.deathDate) } } // 5. Generation Filter result = result.filter((m) => m.generation >= generationRange[0] && m.generation <= generationRange[1]) // Sort return result.sort((a, b) => a.generation - b.generation || (a.birthDate || "").localeCompare(b.birthDate || "")) }, [allMembers, query, selectedSurname, selectedHome, statusFilter, generationRange]) // 按世代分组并排序 - 优化后的版本 const membersByGeneration = useMemo(() => { const groups = new Map() // 第一步:按世代分组 filteredMembers.forEach(member => { const gen = member.generation if (!groups.has(gen)) { groups.set(gen, []) } groups.get(gen)!.push(member) }) // 第二步:转换为数组并排序 return Array.from(groups.entries()) .sort((a, b) => a[0] - b[0]) .map(([generation, members]) => { // 区分家族成员和配偶 // 配偶定义:没有父母但有配偶的成员(嫁入/娶入的成员) // 其他所有成员都应该显示,包括孤立成员 const familyMembers = members.filter(m => { // 使用 treeData.rootId 判断始祖,而不是 isFounder 字段 const isFounder = m.id === treeData.rootId const hasFamilyParents = m.fatherId || m.motherId const hasSpouse = m.spouseIds && m.spouseIds.length > 0 // 始祖或有父母的成员:显示 if (hasFamilyParents || isFounder) return true // 第一代无配偶成员:显示 if (generation === 1 && !hasSpouse) return true // 孤立成员(无父母、无配偶、非始祖):也要显示,方便用户关联 if (!hasFamilyParents && !hasSpouse && !isFounder) return true // 配偶(无父母但有配偶):不在这里显示,会跟随主成员显示 return false }) // 对家族成员按年龄排序 const sortedFamilyMembers = familyMembers.sort((a, b) => { // 使用 treeData.rootId 判断始祖 const aIsFounder = a.id === treeData.rootId const bIsFounder = b.id === treeData.rootId if (aIsFounder && !bIsFounder) return -1 if (!aIsFounder && bIsFounder) return 1 const aDate = a.birthDate || "9999-12-31" const bDate = b.birthDate || "9999-12-31" return aDate.localeCompare(bDate) }) // 添加配偶(紧跟在主成员后面,不使用占位符) const result: MemberOrPlaceholder[] = [] const processedSpouses = new Set() sortedFamilyMembers.forEach(member => { const familyUnit = addSpousesToFamilyUnit(member, members, processedSpouses) result.push(...familyUnit) }) // 计算实际成员数(不包括占位符) const actualMemberCount = result.filter(m => !isPlaceholder(m)).length return { generation, members: result, actualCount: actualMemberCount } }) }, [filteredMembers, treeData.rootId]) const clearFilters = () => { setSelectedSurname("all") setSelectedHome("all") setStatusFilter("all") setGenerationRange([1, 100]) // Reset to wide range setQuery("") } const activeFilterCount = (selectedSurname !== "all" ? 1 : 0) + (selectedHome !== "all" ? 1 : 0) + (statusFilter !== "all" ? 1 : 0) + (generationRange[0] > 1 || generationRange[1] < 100 ? 1 : 0) // 未登录时不渲染内容(已在 useEffect 中重定向) if (status === 'loading' || !session) { return null } return (

家族成员列表

setQuery(e.target.value)} />
{permissions.canCreate(currentTree?.currentUserRole) && ( )}
{/* Filter Bar */} {showFilters && (

高级筛选

{generationRange[0]} - {generationRange[1]} 世
setGenerationRange(val as [number, number])} className="py-4" />
)}
{filteredMembers.length === 0 ? (

未寻得

) : (
{membersByGeneration.map(({ generation, members, actualCount }) => (

第 {generation} 世

共 {actualCount} 人
{members.map((member) => { // 检查是否是占位符 if (isPlaceholder(member)) { return
} // 使用优化后的 MemberCard 组件 return ( { setHighlightedMemberId(id) const params = new URLSearchParams(window.location.search) router.push(`/tree?${params.toString()}`) }} currentTreeId={treeData.rootId || undefined} canEdit={permissions.canCreate(currentTree?.currentUserRole)} onOpenAddDialog={handleOpenAddDialog} /> ) })}
))}
)}
{/* 添加成员对话框 */} {relationDialog && ( { if (!open) setRelationDialog(null) }} member={relationDialog.member} relationType={relationDialog.type} onCreateNew={handleCreateNewRelation} /> )}
) }