This commit is contained in:
freedakgmail
2025-11-30 10:52:36 +08:00
parent aaae24c68b
commit 72a59b62a0
165 changed files with 705 additions and 655 deletions
+430 -423
View File
@@ -7,6 +7,7 @@ import { useFamily } from "@/context/family-context"
import { SiteHeader } from "@/components/site-header"
import { MemberForm } from "@/components/members/member-form"
import { Button } from "@/components/ui/button"
import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs"
import { ArrowLeft, Edit, Trash, Share2, Printer, CircleUser, CircleUserRound, Images, BookOpen, UserPlus, Camera } from "lucide-react"
import type { FamilyMember } from "@/types/family"
import { PhotoGallery } from "@/components/members/photo-gallery"
@@ -253,458 +254,464 @@ export default function MemberProfilePage() {
<div className="min-h-screen flex flex-col bg-background">
<SiteHeader />
<main className="container mx-auto py-8 px-4 md:px-6">
{/* 返回按钮 */}
<div className="mb-6">
<Button
variant="ghost"
onClick={() => router.push(`/members${typeof window !== 'undefined' ? window.location.search : ''}`)}
className="gap-2 pl-0 hover:bg-transparent hover:text-primary text-2xl font-bold"
>
<ArrowLeft className="h-6 w-6" />
</Button>
</div>
<Tabs defaultValue="family" className="flex flex-col">
{/* 固定头部区域 - top-16 对应 SiteHeader 的 h-16 高度 */}
<div className="sticky top-16 z-30 bg-background pb-4 -mx-4 px-4 md:-mx-6 md:px-6 border-b border-border -mt-8 pt-8">
{/* 返回按钮 */}
<div className="py-4">
<Button
variant="ghost"
onClick={() => router.push(`/members${typeof window !== 'undefined' ? window.location.search : ''}`)}
className="gap-2 pl-0 hover:bg-transparent hover:text-primary text-2xl font-bold"
>
<ArrowLeft className="h-6 w-6" />
</Button>
</div>
{/* 始祖特殊提示框 */}
{member.isFounder && (
<div className="mb-6 p-6 bg-gradient-to-br from-amber-50 via-orange-50 to-amber-50 border-2 border-amber-400 rounded-xl shadow-md">
<div className="flex items-start gap-4">
<div className="flex-shrink-0 w-14 h-14 bg-gradient-to-br from-amber-200 to-orange-200 rounded-full flex items-center justify-center shadow-sm">
<span className="text-3xl">👑</span>
</div>
<div className="flex-1">
<h2 className="text-2xl font-serif font-bold text-amber-900 mb-3">
</h2>
<p className="text-base text-amber-800 leading-relaxed font-serif">
<span className="font-bold text-amber-900"></span>
<span className="font-bold text-amber-900"></span>
<br />
</p>
</div>
</div>
</div>
)}
{/* Header Section */}
<div className="flex flex-col md:flex-row justify-between items-start gap-6 mb-8 border-b border-border pb-8">
<div className="flex items-start gap-6">
<div className="relative group">
<div className="h-32 w-32 rounded-lg bg-muted flex items-center justify-center border-2 border-primary/20 overflow-hidden">
{avatarBlobUrl ? (
<img
src={avatarBlobUrl}
alt={member.fullName}
className="h-full w-full object-cover"
/>
) : (
member.gender === 'FEMALE' ? (
<CircleUserRound className="h-20 w-20 text-pink-400" />
) : (
<CircleUser className="h-20 w-20 text-blue-400" />
)
)}
</div>
{permissions.canUpdate(role) && (
<label className="absolute inset-0 flex items-center justify-center bg-black/50 opacity-0 group-hover:opacity-100 transition-opacity cursor-pointer rounded-lg">
<Camera className="h-8 w-8 text-white" />
<input
type="file"
accept="image/*"
className="hidden"
onChange={handleAvatarUpload}
disabled={isUploadingAvatar}
/>
</label>
)}
{isUploadingAvatar && (
<div className="absolute inset-0 flex items-center justify-center bg-black/50 rounded-lg">
<div className="text-white text-xs">...</div>
{/* Header Section */}
<div className="flex flex-col md:flex-row justify-between items-start gap-6 pb-4">
<div className="flex items-start gap-6">
<div className="relative group">
<div className="h-24 w-24 rounded-lg bg-muted flex items-center justify-center border-2 border-primary/20 overflow-hidden">
{avatarBlobUrl ? (
<img
src={avatarBlobUrl}
alt={member.fullName}
className="h-full w-full object-cover"
/>
) : (
member.gender === 'FEMALE' ? (
<CircleUserRound className="h-16 w-16 text-pink-400" />
) : (
<CircleUser className="h-16 w-16 text-blue-400" />
)
)}
</div>
{permissions.canUpdate(role) && (
<label className="absolute inset-0 flex items-center justify-center bg-black/50 opacity-0 group-hover:opacity-100 transition-opacity cursor-pointer rounded-lg">
<Camera className="h-6 w-6 text-white" />
<input
type="file"
accept="image/*"
className="hidden"
onChange={handleAvatarUpload}
disabled={isUploadingAvatar}
/>
</label>
)}
{isUploadingAvatar && (
<div className="absolute inset-0 flex items-center justify-center bg-black/50 rounded-lg">
<div className="text-white text-xs">...</div>
</div>
)}
</div>
)}
</div>
<div>
<div className="flex items-center gap-3 mb-2">
<h1 className="text-4xl font-bold font-serif text-foreground flex items-center gap-2">
{member.isFounder && <span className="text-amber-600 text-3xl">👑</span>}
<MemberNameWithStatus
name={member.fullName}
isDead={!!member.deathDate}
/>
</h1>
{member.isFounder ? (
<span className="px-3 py-1 bg-gradient-to-r from-amber-100 to-orange-100 text-amber-800 text-sm font-semibold rounded-full border-2 border-amber-300 shadow-sm">
· {member.generation}
</span>
) : (
<span className="px-2 py-1 bg-primary/10 text-primary text-sm font-medium rounded-full border border-primary/20">
{member.generation}
</span>
<div>
<div className="flex items-center gap-3 mb-2">
<h1 className="text-3xl font-bold font-serif text-foreground flex items-center gap-2">
{member.isFounder && <span className="text-amber-600 text-2xl">👑</span>}
<MemberNameWithStatus
name={member.fullName}
isDead={!!member.deathDate}
memberId={member.id}
treeId={currentTree?.id}
/>
</h1>
{member.isFounder ? (
<span className="px-3 py-1 bg-gradient-to-r from-amber-100 to-orange-100 text-amber-800 text-sm font-semibold rounded-full border-2 border-amber-300 shadow-sm">
· {member.generation}
</span>
) : (
<span className="px-2 py-1 bg-primary/10 text-primary text-sm font-medium rounded-full border border-primary/20">
{member.generation}
</span>
)}
</div>
<div className="flex flex-wrap gap-x-4 gap-y-1 text-sm text-muted-foreground">
{member.courtesyName && <span>{member.courtesyName}</span>}
{member.artName && <span>{member.artName}</span>}
{member.generationName && <span>{member.generationName}</span>}
{ageInfo && <span>{ageInfo.type === 'deceased' ? '享年' : '年龄'}{ageInfo.age}</span>}
</div>
</div>
</div>
<div className="flex gap-2 flex-wrap">
{permissions.canCreate(role) && (
<>
{!member.isFounder && !member.fatherId && (
<Button variant="outline" size="sm" onClick={handleQuickAddFather} className="gap-1">
<UserPlus className="h-4 w-4" />
</Button>
)}
{!member.isFounder && !member.motherId && (
<Button variant="outline" size="sm" onClick={handleQuickAddMother} className="gap-1">
<UserPlus className="h-4 w-4" />
</Button>
)}
<Button variant="default" size="sm" onClick={handleQuickAddChild} className="gap-1">
<UserPlus className="h-4 w-4" />
</Button>
<Button variant="secondary" size="sm" onClick={handleQuickAddSpouse} className="gap-1">
<UserPlus className="h-4 w-4" />
</Button>
</>
)}
<Button variant="outline" size="icon" onClick={() => window.print()} title="打印">
<Printer className="h-4 w-4" />
</Button>
<Button variant="outline" size="icon" title="分享">
<Share2 className="h-4 w-4" />
</Button>
{permissions.canUpdate(role) && (
<Button variant="outline" size="sm" onClick={() => setIsEditing(true)} className="gap-1">
<Edit className="h-4 w-4" />
</Button>
)}
{permissions.canDeleteMember(role) && (
<Button variant="destructive" size="icon" onClick={handleDelete} title="删除">
<Trash className="h-4 w-4" />
</Button>
)}
</div>
<div className="space-y-1 text-muted-foreground">
{member.isFounder && (
<p className="text-amber-700 font-semibold text-sm flex items-center gap-1">
<span></span>
<span className="text-amber-600"></span>
<span></span>
</div>
{/* Tab 导航栏 */}
<TabsList className="flex flex-wrap gap-2 bg-muted/70 p-1 w-fit">
<TabsTrigger value="family"></TabsTrigger>
<TabsTrigger value="details"></TabsTrigger>
<TabsTrigger value="life"></TabsTrigger>
<TabsTrigger value="stories"></TabsTrigger>
<TabsTrigger value="history"></TabsTrigger>
</TabsList>
</div>
{/* 始祖特殊提示框 */}
{member.isFounder && (
<div className="mt-6 p-6 bg-gradient-to-br from-amber-50 via-orange-50 to-amber-50 border-2 border-amber-400 rounded-xl shadow-md">
<div className="flex items-start gap-4">
<div className="flex-shrink-0 w-14 h-14 bg-gradient-to-br from-amber-200 to-orange-200 rounded-full flex items-center justify-center shadow-sm">
<span className="text-3xl">👑</span>
</div>
<div className="flex-1">
<h2 className="text-2xl font-serif font-bold text-amber-900 mb-3">
</h2>
<p className="text-base text-amber-800 leading-relaxed font-serif">
<span className="font-bold text-amber-900"></span>
<span className="font-bold text-amber-900"></span>
<br />
</p>
</div>
</div>
</div>
)}
{/* Tab 内容区域 */}
<TabsContent value="family" className="mt-6 space-y-6">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className={`bg-card p-4 rounded-lg ${founderBorderClass}`}>
<h3 className="font-medium text-muted-foreground mb-3 text-sm uppercase tracking-wider">
</h3>
<div className="grid gap-3 sm:grid-cols-2">
<div>
<p className="text-xs text-muted-foreground uppercase tracking-widest mb-1"></p>
{member.fatherId ? (
<Link
href={`/members/${member.fatherId}${typeof window !== 'undefined' ? window.location.search : ''}`}
className="text-lg font-serif font-bold hover:underline text-primary"
>
<MemberNameWithStatus
name={getMember(member.fatherId)?.fullName || member.fatherId}
isDead={!!getMember(member.fatherId)?.deathDate}
/>
</Link>
) : member.spouseFatherName ? (
<span className="text-lg font-serif font-bold text-foreground">
{member.spouseFatherName}
</span>
) : (
<span className="text-muted-foreground">-</span>
)}
</div>
<div>
<p className="text-xs text-muted-foreground uppercase tracking-widest mb-1"></p>
{member.motherId ? (
<Link
href={`/members/${member.motherId}${typeof window !== 'undefined' ? window.location.search : ''}`}
className="text-lg font-serif font-bold hover:underline text-primary"
>
<MemberNameWithStatus
name={getMember(member.motherId)?.fullName || member.motherId}
isDead={!!getMember(member.motherId)?.deathDate}
/>
</Link>
) : member.spouseMotherName ? (
<span className="text-lg font-serif font-bold text-foreground">
{member.spouseMotherName}
</span>
) : (
<span className="text-muted-foreground">-</span>
)}
</div>
</div>
</div>
<div className={`bg-card p-4 rounded-lg ${founderBorderClass}`}>
<h3 className="font-medium text-muted-foreground mb-3 text-sm uppercase tracking-wider">
</h3>
<div className="grid gap-4 sm:grid-cols-2">
<div>
<p className="text-xs text-muted-foreground uppercase tracking-widest mb-2"></p>
<div className="flex flex-wrap gap-2">
{member.spouseIds?.length > 0 ? (
member.spouseIds.map((spouseId) => (
<Link
href={`/members/${spouseId}${typeof window !== 'undefined' ? window.location.search : ''}`}
key={spouseId}
className="px-3 py-1 bg-muted rounded hover:bg-muted/80 text-sm font-medium"
>
<MemberNameWithStatus
name={getMember(spouseId)?.fullName || spouseId}
isDead={!!getMember(spouseId)?.deathDate}
/>
</Link>
))
) : (
<span className="text-muted-foreground"></span>
)}
</div>
</div>
<div>
<p className="text-xs text-muted-foreground uppercase tracking-widest mb-2"></p>
<div className="flex flex-wrap gap-2">
{member.childrenIds?.length > 0 ? (
member.childrenIds.map((childId) => (
<Link
href={`/members/${childId}${typeof window !== 'undefined' ? window.location.search : ''}`}
key={childId}
className="px-3 py-1 bg-muted rounded hover:bg-muted/80 text-sm font-medium"
>
<MemberNameWithStatus
name={getMember(childId)?.fullName || childId}
isDead={!!getMember(childId)?.deathDate}
/>
</Link>
))
) : (
<span className="text-muted-foreground"></span>
)}
</div>
</div>
</div>
</div>
</div>
</TabsContent>
<TabsContent value="details" className="mt-6 space-y-6">
<div className={`bg-card rounded-lg ${founderBorderClass} p-6 shadow-sm w-full`}>
<h3 className="text-lg font-serif font-semibold mb-4"></h3>
<dl className="grid grid-cols-1 sm:grid-cols-2 xl:grid-cols-3 2xl:grid-cols-6 gap-x-8 gap-y-4 text-sm">
<div>
<dt className="text-muted-foreground mb-1"></dt>
<dd className="font-medium space-y-1">
<div className="flex flex-wrap items-center gap-2">
<span>{member.birthDate || "-"}</span>
{member.birthDate && member.isLunarDate && (
<span className="text-xs text-amber-600 bg-amber-50 px-2 py-0.5 rounded border border-amber-200">
</span>
)}
</div>
<div className="flex flex-wrap items-center gap-2">
<span>{member.deathDate || "-"}</span>
{member.deathDate && member.isLunarDate && (
<span className="text-xs text-amber-600 bg-amber-50 px-2 py-0.5 rounded border border-amber-200">
</span>
)}
</div>
</dd>
</div>
{ageInfo && (
<div>
<dt className="text-muted-foreground mb-1">{ageInfo.type === 'deceased' ? '享年' : '年龄'}</dt>
<dd className="font-medium text-primary">{ageInfo.age} </dd>
</div>
)}
{member.deathDate && (
<>
<div>
<dt className="text-muted-foreground mb-1"></dt>
<dd className="font-medium">{member.posthumousName || "-"}</dd>
</div>
<div>
<dt className="text-muted-foreground mb-1"></dt>
<dd className="font-medium">{member.burialPlace || "-"}</dd>
</div>
</>
)}
{member.generationName && (
<div>
<dt className="text-muted-foreground mb-1"></dt>
<dd className="font-medium">{member.generationName}</dd>
</div>
)}
{member.courtesyName && (
<p>
<span className="text-foreground">{member.courtesyName}</span>
</p>
<div>
<dt className="text-muted-foreground mb-1"></dt>
<dd className="font-medium">{member.courtesyName}</dd>
</div>
)}
{member.artName && (
<p>
<span className="text-foreground">{member.artName}</span>
</p>
<div>
<dt className="text-muted-foreground mb-1"></dt>
<dd className="font-medium">{member.artName}</dd>
</div>
)}
{member.ancestralHome && <p>{member.ancestralHome}</p>}
</div>
{member.tags && member.tags.length > 0 && (
<div className="flex flex-wrap gap-2 mt-3">
{member.ancestralHome && (
<div>
<dt className="text-muted-foreground mb-1"></dt>
<dd className="font-medium">{member.ancestralHome}</dd>
</div>
)}
{member.birthPlace && (
<div>
<dt className="text-muted-foreground mb-1"></dt>
<dd className="font-medium">{member.birthPlace}</dd>
</div>
)}
{member.address && (
<div>
<dt className="text-muted-foreground mb-1"></dt>
<dd className="font-medium">{member.address}</dd>
</div>
)}
{member.phone && (
<div>
<dt className="text-muted-foreground mb-1"></dt>
<dd className="font-medium">
<a href={`tel:${member.phone}`} className="hover:text-primary">
{member.phone}
</a>
</dd>
</div>
)}
{member.telephone && (
<div>
<dt className="text-muted-foreground mb-1"></dt>
<dd className="font-medium">
<a href={`tel:${member.telephone}`} className="hover:text-primary">
{member.telephone}
</a>
</dd>
</div>
)}
{member.email && (
<div>
<dt className="text-muted-foreground mb-1"></dt>
<dd className="font-medium">
<a href={`mailto:${member.email}`} className="hover:text-primary">
{member.email}
</a>
</dd>
</div>
)}
</dl>
</div>
{member.tags && member.tags.length > 0 && (
<div className={`bg-card rounded-lg ${founderBorderClass} p-6 shadow-sm`}>
<h3 className="text-lg font-serif font-semibold mb-4"></h3>
<div className="flex flex-wrap gap-2">
{member.tags.map((tag) => (
<span
key={tag}
className="px-2 py-1 bg-primary/10 text-primary text-xs font-medium rounded-md border border-primary/20"
className="px-3 py-1 bg-primary/10 text-primary text-sm font-medium rounded-md border border-primary/20"
>
{tag}
</span>
))}
</div>
</div>
)}
</TabsContent>
<TabsContent value="life" className="mt-6 space-y-6">
{/* 生平事迹 */}
<div className={`prose prose-stone dark:prose-invert max-w-none bg-card p-6 rounded-lg ${founderBorderClass} shadow-sm w-full`}>
<h3 className="text-lg font-serif font-semibold mb-4"></h3>
{member.bio ? (
<p className="whitespace-pre-line leading-relaxed">{member.bio}</p>
) : (
<p className="text-sm text-muted-foreground text-center py-10"></p>
)}
</div>
</div>
<div className="flex gap-2 flex-wrap">
{permissions.canCreate(role) && (
<>
{!member.isFounder && !member.fatherId && (
<Button variant="outline" onClick={handleQuickAddFather} className="gap-2">
<UserPlus className="h-4 w-4" />
</Button>
)}
{!member.isFounder && !member.motherId && (
<Button variant="outline" onClick={handleQuickAddMother} className="gap-2">
<UserPlus className="h-4 w-4" />
</Button>
)}
<Button variant="default" onClick={handleQuickAddChild} className="gap-2">
<UserPlus className="h-4 w-4" />
</Button>
<Button variant="secondary" onClick={handleQuickAddSpouse} className="gap-2">
<UserPlus className="h-4 w-4" />
</Button>
</>
)}
<Button variant="outline" size="icon" onClick={() => window.print()} title="打印">
<Printer className="h-4 w-4" />
</Button>
<Button variant="outline" size="icon" title="分享">
<Share2 className="h-4 w-4" />
</Button>
{permissions.canUpdate(role) && (
<Button variant="outline" onClick={() => setIsEditing(true)} className="gap-2">
<Edit className="h-4 w-4" />
</Button>
)}
{permissions.canDeleteMember(role) && (
<Button variant="destructive" size="icon" onClick={handleDelete} title="删除">
<Trash className="h-4 w-4" />
</Button>
)}
</div>
</div>
{/* Content Grid */}
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
{/* Left Column - Details and Biography */}
<div className="lg:col-span-2 space-y-8">
{/* 家族关系 */}
<section>
<h2 className="text-2xl font-serif font-bold mb-4 flex items-center gap-2">
<span className="w-1 h-6 bg-secondary rounded-full"></span>
</h2>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className={`bg-card p-4 rounded-lg ${founderBorderClass}`}>
<h3 className="font-medium text-muted-foreground mb-3 text-sm uppercase tracking-wider">
</h3>
<div className="grid gap-3 sm:grid-cols-2">
<div>
<p className="text-xs text-muted-foreground uppercase tracking-widest mb-1"></p>
{member.fatherId ? (
<Link
href={`/members/${member.fatherId}${typeof window !== 'undefined' ? window.location.search : ''}`}
className="text-lg font-serif font-bold hover:underline text-primary"
>
<MemberNameWithStatus
name={getMember(member.fatherId)?.fullName || member.fatherId}
isDead={!!getMember(member.fatherId)?.deathDate}
/>
</Link>
) : member.spouseFatherName ? (
<span className="text-lg font-serif font-bold text-foreground">
{member.spouseFatherName}
</span>
) : (
<span className="text-muted-foreground">-</span>
)}
</div>
<div>
<p className="text-xs text-muted-foreground uppercase tracking-widest mb-1"></p>
{member.motherId ? (
<Link
href={`/members/${member.motherId}${typeof window !== 'undefined' ? window.location.search : ''}`}
className="text-lg font-serif font-bold hover:underline text-primary"
>
<MemberNameWithStatus
name={getMember(member.motherId)?.fullName || member.motherId}
isDead={!!getMember(member.motherId)?.deathDate}
/>
</Link>
) : member.spouseMotherName ? (
<span className="text-lg font-serif font-bold text-foreground">
{member.spouseMotherName}
</span>
) : (
<span className="text-muted-foreground">-</span>
)}
</div>
</div>
{/* 影像记忆 */}
<div className={`bg-card p-6 rounded-lg ${founderBorderClass} shadow-sm w-full`}>
<h3 className="text-lg font-serif font-semibold mb-4 flex items-center gap-2">
<Images className="h-5 w-5 text-primary" />
</h3>
{member.photoIds && member.photoIds.length > 0 ? (
<PhotoGallery
photoIds={member.photoIds}
onChange={async (photoIds) => {
await updateMember(member.id, { photoIds })
setLastSaveTime(Date.now())
}}
readonly={false}
/>
) : (
<div className="text-sm text-muted-foreground text-center py-10">
</div>
<div className={`bg-card p-4 rounded-lg ${founderBorderClass}`}>
<h3 className="font-medium text-muted-foreground mb-3 text-sm uppercase tracking-wider">
</h3>
<div className="grid gap-4 sm:grid-cols-2">
<div>
<p className="text-xs text-muted-foreground uppercase tracking-widest mb-2"></p>
<div className="flex flex-wrap gap-2">
{member.spouseIds?.length > 0 ? (
member.spouseIds.map((spouseId) => (
<Link
href={`/members/${spouseId}${typeof window !== 'undefined' ? window.location.search : ''}`}
key={spouseId}
className="px-3 py-1 bg-muted rounded hover:bg-muted/80 text-sm font-medium"
>
<MemberNameWithStatus
name={getMember(spouseId)?.fullName || spouseId}
isDead={!!getMember(spouseId)?.deathDate}
/>
</Link>
))
) : (
<span className="text-muted-foreground"></span>
)}
</div>
</div>
<div>
<p className="text-xs text-muted-foreground uppercase tracking-widest mb-2"></p>
<div className="flex flex-wrap gap-2">
{member.childrenIds?.length > 0 ? (
member.childrenIds.map((childId) => (
<Link
href={`/members/${childId}${typeof window !== 'undefined' ? window.location.search : ''}`}
key={childId}
className="px-3 py-1 bg-muted rounded hover:bg-muted/80 text-sm font-medium"
>
<MemberNameWithStatus
name={getMember(childId)?.fullName || childId}
isDead={!!getMember(childId)?.deathDate}
/>
</Link>
))
) : (
<span className="text-muted-foreground"></span>
)}
</div>
</div>
</div>
)}
</div>
</TabsContent>
<TabsContent value="stories" className="mt-6 space-y-6">
<div className={`bg-card p-6 rounded-lg ${founderBorderClass} shadow-sm w-full`}>
<h3 className="text-lg font-serif font-semibold mb-4 flex items-center gap-2">
<BookOpen className="h-5 w-5 text-primary" />
</h3>
{member.stories && member.stories.length > 0 ? (
<StoryManager
stories={member.stories}
onChange={async (stories) => {
await updateMember(member.id, { stories })
setLastSaveTime(Date.now())
}}
readonly={false}
/>
) : (
<div className="text-sm text-muted-foreground text-center py-10">
</div>
</div>
</section>
)}
</div>
</TabsContent>
{/* 详细信息 */}
<section>
<h2 className="text-2xl font-serif font-bold mb-4 flex items-center gap-2">
<span className="w-1 h-6 bg-secondary rounded-full"></span>
</h2>
<div className={`bg-card rounded-lg ${founderBorderClass} p-6 shadow-sm`}>
<dl className="grid grid-cols-1 sm:grid-cols-2 xl:grid-cols-3 2xl:grid-cols-6 gap-x-8 gap-y-4 text-sm">
<div>
<dt className="text-muted-foreground mb-1"></dt>
<dd className="font-medium space-y-1">
<div className="flex flex-wrap items-center gap-2">
<span>{member.birthDate || "-"}</span>
{member.birthDate && member.isLunarDate && (
<span className="text-xs text-amber-600 bg-amber-50 px-2 py-0.5 rounded border border-amber-200">
</span>
)}
</div>
<div className="flex flex-wrap items-center gap-2">
<span>{member.deathDate || "-"}</span>
{member.deathDate && member.isLunarDate && (
<span className="text-xs text-amber-600 bg-amber-50 px-2 py-0.5 rounded border border-amber-200">
</span>
)}
</div>
</dd>
</div>
{ageInfo && (
<div>
<dt className="text-muted-foreground mb-1">{ageInfo.type === 'deceased' ? '享年' : '年龄'}</dt>
<dd className="font-medium text-primary">{ageInfo.age} </dd>
</div>
)}
{member.deathDate && (
<>
<div>
<dt className="text-muted-foreground mb-1"></dt>
<dd className="font-medium">{member.posthumousName || "-"}</dd>
</div>
<div>
<dt className="text-muted-foreground mb-1"></dt>
<dd className="font-medium">{member.burialPlace || "-"}</dd>
</div>
</>
)}
{member.generationName && (
<div>
<dt className="text-muted-foreground mb-1"></dt>
<dd className="font-medium">{member.generationName}</dd>
</div>
)}
</dl>
</div>
</section>
{/* 联系方式 */}
{(member.phone || member.telephone || member.email || member.address) && (
<section>
<h2 className="text-2xl font-serif font-bold mb-4 flex items-center gap-2">
<span className="w-1 h-6 bg-secondary rounded-full"></span>
</h2>
<div className={`bg-card rounded-lg ${founderBorderClass} p-6 shadow-sm`}>
<dl className="space-y-4 text-sm">
{member.phone && (
<div>
<dt className="text-muted-foreground"></dt>
<dd className="font-medium">
<a href={`tel:${member.phone}`} className="hover:text-primary">
{member.phone}
</a>
</dd>
</div>
)}
{member.telephone && (
<div>
<dt className="text-muted-foreground"></dt>
<dd className="font-medium">
<a href={`tel:${member.telephone}`} className="hover:text-primary">
{member.telephone}
</a>
</dd>
</div>
)}
{member.email && (
<div>
<dt className="text-muted-foreground"></dt>
<dd className="font-medium">
<a href={`mailto:${member.email}`} className="hover:text-primary">
{member.email}
</a>
</dd>
</div>
)}
{member.address && (
<div>
<dt className="text-muted-foreground"></dt>
<dd className="font-medium">{member.address}</dd>
</div>
)}
</dl>
</div>
</section>
)}
<section>
<h2 className="text-2xl font-serif font-bold mb-4 flex items-center gap-2">
<span className="w-1 h-6 bg-primary rounded-full"></span>
</h2>
<div className={`prose prose-stone dark:prose-invert max-w-none bg-card p-6 rounded-lg ${founderBorderClass} shadow-sm`}>
{member.bio ? (
<p className="whitespace-pre-line leading-relaxed">{member.bio}</p>
) : (
<p className="text-sm text-foreground font-light tracking-wide text-center py-8"></p>
)}
</div>
</section>
{/* Photo Gallery */}
{member.photoIds && member.photoIds.length > 0 && (
<section>
<h2 className="text-2xl font-serif font-bold mb-4 flex items-center gap-2">
<Images className="h-6 w-6 text-primary" />
</h2>
<div className={`bg-card p-6 rounded-lg ${founderBorderClass} shadow-sm`}>
<PhotoGallery
photoIds={member.photoIds}
onChange={async (photoIds) => {
await updateMember(member.id, { photoIds })
setLastSaveTime(Date.now())
}}
readonly={false}
/>
</div>
</section>
)}
{/* Family Stories */}
{member.stories && member.stories.length > 0 && (
<section>
<h2 className="text-2xl font-serif font-bold mb-4 flex items-center gap-2">
<BookOpen className="h-6 w-6 text-primary" />
</h2>
<div className={`bg-card p-6 rounded-lg ${founderBorderClass} shadow-sm`}>
<StoryManager
stories={member.stories}
onChange={async (stories) => {
await updateMember(member.id, { stories })
setLastSaveTime(Date.now())
}}
readonly={false}
/>
</div>
</section>
)}
</div>
{/* Right Column - Version History */}
<div className="space-y-8">
<section>
<h2 className="text-2xl font-serif font-bold mb-4 flex items-center gap-2">
<span className="w-1 h-6 bg-secondary rounded-full"></span>
</h2>
<TabsContent value="history" className="mt-6 space-y-6">
<div className={`bg-card rounded-lg ${founderBorderClass} p-6 shadow-sm w-full`}>
<h3 className="text-lg font-serif font-semibold mb-2"></h3>
<p className="text-sm text-muted-foreground">
{new Date(lastSaveTime).toLocaleString('zh-CN')}
</p>
</div>
<div className={`bg-card rounded-lg ${founderBorderClass} p-6 shadow-sm w-full`}>
<h3 className="text-lg font-serif font-semibold mb-4"></h3>
<MemberVersionHistory
memberId={member.id}
treeId={currentTree?.id || ''}
lastUpdated={lastSaveTime}
/>
</section>
</div>
</div>
</div>
</TabsContent>
</Tabs>
</main>
</div>
)
+43
View File
@@ -86,6 +86,10 @@ const MemberCard = memo(function MemberCard({
: 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
return (
<div className="relative group">
<Link href={`/members/${member.id}${typeof window !== 'undefined' ? window.location.search : ''}`}>
@@ -118,6 +122,45 @@ const MemberCard = memo(function MemberCard({
</span>
)}
</h3>
{/* 父母信息 - 放在姓名右侧 */}
{(father || mother) && (
<div className="flex items-center gap-1 text-xs text-muted-foreground flex-shrink-0">
<span className="text-muted-foreground/70">:</span>
{father && (
<span
className="hover:text-primary hover:underline cursor-pointer"
onClick={(e) => {
e.preventDefault()
e.stopPropagation()
window.location.href = `/members/${father.id}${window.location.search}`
}}
>
<MemberNameWithStatus
name={father.fullName}
isDead={!!father.deathDate}
dotPosition="top-left"
/>
</span>
)}
{father && mother && <span className="text-muted-foreground/50">·</span>}
{mother && (
<span
className="hover:text-primary hover:underline cursor-pointer"
onClick={(e) => {
e.preventDefault()
e.stopPropagation()
window.location.href = `/members/${mother.id}${window.location.search}`
}}
>
<MemberNameWithStatus
name={mother.fullName}
isDead={!!mother.deathDate}
dotPosition="top-left"
/>
</span>
)}
</div>
)}
</div>
{isFounder ? (
<p className="text-xs text-amber-700 font-semibold mt-0.5 truncate">