This commit is contained in:
freedakgmail
2025-11-30 14:35:05 +08:00
parent b7f3592db3
commit f34a94f330
137 changed files with 1044 additions and 1019 deletions
+282 -277
View File
@@ -25,7 +25,7 @@ export default function MemberProfilePage() {
const router = useRouter()
const { data: session, status } = useSession()
const [isEditing, setIsEditing] = useState(false)
const [editActiveTab, setEditActiveTab] = useState("basic")
const [editActiveTab, setEditActiveTab] = useState("info")
const [avatarBlobUrl, setAvatarBlobUrl] = useState<string | undefined>(undefined)
const [isUploadingAvatar, setIsUploadingAvatar] = useState(false)
const [lastSaveTime, setLastSaveTime] = useState(Date.now())
@@ -260,7 +260,10 @@ export default function MemberProfilePage() {
<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>}
: {member.fullName}
<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">
@@ -301,10 +304,10 @@ export default function MemberProfilePage() {
{/* Tab 导航栏 */}
<TabsList className="flex flex-wrap gap-2 bg-muted/70 p-1 w-fit">
<TabsTrigger value="basic"></TabsTrigger>
<TabsTrigger value="family"></TabsTrigger>
<TabsTrigger value="info"></TabsTrigger>
<TabsTrigger value="details"></TabsTrigger>
<TabsTrigger value="media"></TabsTrigger>
<TabsTrigger value="photos"></TabsTrigger>
<TabsTrigger value="stories"></TabsTrigger>
</TabsList>
{/* 始祖特殊提示框 */}
@@ -352,7 +355,7 @@ 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">
<Tabs defaultValue="family" className="flex flex-col h-[calc(100vh-8rem)]">
<Tabs defaultValue="info" className="flex flex-col h-[calc(100vh-8rem)]">
{/* 固定头部区域 */}
<div className="flex-shrink-0 pb-4 border-b border-border">
{/* 返回按钮 */}
@@ -476,11 +479,11 @@ export default function MemberProfilePage() {
{/* 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="info"></TabsTrigger>
<TabsTrigger value="details"></TabsTrigger>
<TabsTrigger value="photos"></TabsTrigger>
<TabsTrigger value="stories"></TabsTrigger>
<TabsTrigger value="history"></TabsTrigger>
<TabsTrigger value="history">线</TabsTrigger>
</TabsList>
{/* 始祖特殊提示框 */}
@@ -508,303 +511,305 @@ export default function MemberProfilePage() {
{/* Tab 内容区域 - 可滚动 */}
<div className="flex-1 overflow-y-auto">
<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">
{/* 详细信息Tab - 包含基本信息和家庭关系 */}
<TabsContent value="info" 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-4 gap-x-8 gap-y-4 text-sm">
<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>
)}
<dt className="text-muted-foreground mb-1"></dt>
<dd className="font-medium text-lg">{member.fullName}</dd>
</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>
)}
<dt className="text-muted-foreground mb-1"></dt>
<dd className="font-medium">{member.gender === 'MALE' ? '男' : '女'}</dd>
</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>
)}
<dt className="text-muted-foreground mb-1"></dt>
<dd className="font-medium"> {member.generation} </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>
</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>
)}
)}
{member.occupation && (
<div>
<dt className="text-muted-foreground mb-1"></dt>
<dd className="font-medium">{member.occupation}</dd>
</div>
</div>
</div>
)}
</dl>
</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">
{/* 家庭关系 */}
<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 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">
<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>
</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>
<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>
{/* 详细资料Tab */}
<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.generationName && (
<div>
<dt className="text-muted-foreground mb-1"></dt>
<dd className="font-medium">{member.generationName}</dd>
</div>
)}
{member.courtesyName && (
<div>
<dt className="text-muted-foreground mb-1"></dt>
<dd className="font-medium">{member.courtesyName}</dd>
</div>
)}
{member.artName && (
<div>
<dt className="text-muted-foreground mb-1"></dt>
<dd className="font-medium">{member.artName}</dd>
</div>
)}
{member.posthumousName && (
<div>
<dt className="text-muted-foreground mb-1"></dt>
<dd className="font-medium">{member.posthumousName || "-"}</dd>
<dd className="font-medium">{member.posthumousName}</dd>
</div>
)}
{member.birthPlace && (
<div>
<dt className="text-muted-foreground mb-1"></dt>
<dd className="font-medium">{member.burialPlace || "-"}</dd>
<dt className="text-muted-foreground mb-1"></dt>
<dd className="font-medium">{member.birthPlace}</dd>
</div>
</>
)}
{member.generationName && (
<div>
<dt className="text-muted-foreground mb-1"></dt>
<dd className="font-medium">{member.generationName}</dd>
</div>
)}
{member.courtesyName && (
<div>
<dt className="text-muted-foreground mb-1"></dt>
<dd className="font-medium">{member.courtesyName}</dd>
</div>
)}
{member.artName && (
<div>
<dt className="text-muted-foreground mb-1"></dt>
<dd className="font-medium">{member.artName}</dd>
</div>
)}
{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-3 py-1 bg-primary/10 text-primary text-sm font-medium rounded-md border border-primary/20"
>
{tag}
</span>
))}
</div>
)}
{member.deathPlace && (
<div>
<dt className="text-muted-foreground mb-1"></dt>
<dd className="font-medium">{member.deathPlace}</dd>
</div>
)}
{member.address && (
<div>
<dt className="text-muted-foreground mb-1"></dt>
<dd className="font-medium">{member.address}</dd>
</div>
)}
</dl>
</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>
{/* 个人简介 */}
{member.bio && (
<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>
<p className="whitespace-pre-line leading-relaxed">{member.bio}</p>
</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>
<PhotoGallery
photos={member.photos}
photoIds={member.photoIds}
onChange={async (photos) => {
await updateMember(member.id, { photos, photoIds: undefined })
setLastSaveTime(Date.now())
}}
readonly={false}
/>
</div>
</TabsContent>
{/* 标签 */}
{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-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="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 })
{/* 生平影像Tab */}
<TabsContent value="photos" 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">
<Images className="h-5 w-5 text-primary" />
</h3>
<PhotoGallery
photos={member.photos}
photoIds={member.photoIds}
onChange={async (photos) => {
await updateMember(member.id, { photos, photoIds: undefined })
setLastSaveTime(Date.now())
}}
readonly={false}
/>
) : (
<div className="text-sm text-muted-foreground text-center py-10">
</div>
)}
</div>
</TabsContent>
</div>
</TabsContent>
<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}
/>
</div>
</TabsContent>
{/* 家族故事Tab */}
<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>
</TabsContent>
{/* 时间线Tab - 仅显示视图 */}
<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}
/>
</div>
</TabsContent>
</div>
</Tabs>
</main>
+4 -4
View File
@@ -16,7 +16,7 @@ export default function NewMemberPage() {
const searchParams = useSearchParams()
const { addMember, treeData, getMember } = useFamily()
const { data: session, status } = useSession()
const [activeTab, setActiveTab] = useState("basic")
const [activeTab, setActiveTab] = useState("info")
// 未登录时重定向到登录页
useEffect(() => {
@@ -208,10 +208,10 @@ export default function NewMemberPage() {
{/* Tab 导航栏 */}
<TabsList className="flex flex-wrap gap-2 bg-muted/70 p-1 w-fit">
<TabsTrigger value="basic"></TabsTrigger>
<TabsTrigger value="family"></TabsTrigger>
<TabsTrigger value="info"></TabsTrigger>
<TabsTrigger value="details"></TabsTrigger>
<TabsTrigger value="media"></TabsTrigger>
<TabsTrigger value="photos"></TabsTrigger>
<TabsTrigger value="stories"></TabsTrigger>
</TabsList>
{/* 始祖特殊提示框 */}