This commit is contained in:
freedakgmail
2025-11-23 16:41:14 +08:00
parent bae0e2c45a
commit 8c53014714
34 changed files with 780 additions and 5315 deletions
+3 -3
View File
@@ -12,14 +12,14 @@ const _geist = Geist({ subsets: ["latin"] })
const _geistMono = Geist_Mono({ subsets: ["latin"] })
export const metadata: Metadata = {
title: "华夏谱 (HuaXiaPu) - 家族树管理系统",
description: "现代中国家族树管理系统",
title: "华夏谱 - 中国家族树管理系统",
description: "现代化的中国家族树管理系统,支持多用户协作、权限管理、数据导入导出等功能",
generator: "v0.app",
manifest: "/manifest.json",
appleWebApp: {
capable: true,
statusBarStyle: "default",
title: "华夏谱",
title: "华夏谱",
},
icons: {
icon: [
+41 -11
View File
@@ -128,26 +128,44 @@ export default function MembersPage() {
return Array.from(groups.entries())
.sort((a, b) => a[0] - b[0])
.map(([generation, members]) => {
// 对每个世代的成员进行排序
const sortedMembers = [...members].sort((a, b) => {
// 区分家族成员和配偶
const familyMembers = members.filter(m => {
// 家族成员:有父母关系 或 明确标记为始祖
const isFounder = m.tags?.includes('始祖')
const hasFamilyParents = m.fatherId || m.motherId
// 如果有父母关系,肯定是家族成员
if (hasFamilyParents) return true
// 如果明确标记为始祖,是家族成员
if (isFounder) return true
// 第1世且没有配偶的,可能是始祖(兼容没有标签的情况)
if (generation === 1 && (!m.spouseIds || m.spouseIds.length === 0)) return true
// 其他情况(如第1世但有配偶且没有父母的)是配偶身份
return false
})
// 对家族成员按年龄排序(出生日期从早到晚 = 年龄从高到低)
const sortedFamilyMembers = [...familyMembers].sort((a, b) => {
// 1. 始祖永远排第一
const aIsFounder = a.tags?.includes('始祖') || a.generation === 1
const bIsFounder = b.tags?.includes('始祖') || b.generation === 1
const aIsFounder = a.tags?.includes('始祖')
const bIsFounder = b.tags?.includes('始祖')
if (aIsFounder && !bIsFounder) return -1
if (!aIsFounder && bIsFounder) return 1
// 2. 按出生日期排序
return (a.birthDate || "").localeCompare(b.birthDate || "")
// 2. 按出生日期排序(早出生的在前 = 年龄大的在前)
const aDate = a.birthDate || "9999-12-31"
const bDate = b.birthDate || "9999-12-31"
return aDate.localeCompare(bDate)
})
// 3. 将配偶插入到主成员后面
const result: typeof members = []
const processedSpouses = new Set<string>()
sortedMembers.forEach(member => {
// 跳过已经作为配偶处理过的成员
if (processedSpouses.has(member.id)) return
sortedFamilyMembers.forEach(member => {
result.push(member)
// 如果有配偶,将配偶紧跟在后面
@@ -159,6 +177,9 @@ export default function MembersPage() {
processedSpouses.add(spouseId)
}
})
} else {
// 没有配偶,添加空白占位符
result.push({ id: `placeholder-${member.id}`, isPlaceholder: true } as any)
}
})
@@ -316,6 +337,11 @@ export default function MembersPage() {
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
{members.map((member) => {
// 检查是否是占位符
if ((member as any).isPlaceholder) {
return <div key={member.id} className="invisible"></div>
}
// 判断是否是配偶:
// 1. 没有父母ID
// 2. 有配偶ID
@@ -332,7 +358,11 @@ export default function MembersPage() {
return (
<div key={member.id} className="relative group">
<Link href={`/members/${member.id}`}>
<Card className="hover:border-primary transition-colors cursor-pointer h-full">
<Card className={`hover:border-primary transition-colors cursor-pointer h-full ${
isSpouse
? 'bg-pink-50/50 dark:bg-pink-950/20 border-pink-200/50 dark:border-pink-800/30'
: 'bg-blue-50/50 dark:bg-blue-950/20 border-blue-200/50 dark:border-blue-800/30'
}`}>
<CardContent className="p-3 flex items-center gap-3">
<div className="relative flex-shrink-0">
<AvatarDisplay
+7 -45
View File
@@ -241,54 +241,16 @@ export default function DashboardPage() {
<Users className="h-4 w-4" />
</div>
</div>
<div className="flex flex-col space-y-4">
<div className="flex items-center justify-center gap-3 py-2">
<span className="text-5xl font-serif font-bold text-primary tracking-wider">
1
</span>
<span className="text-4xl font-bold text-muted-foreground">:</span>
<span className="text-5xl font-serif font-bold text-primary tracking-wider">
<div className="flex flex-col space-y-1">
<div className="flex items-center justify-center gap-3">
<span className="text-3xl font-serif font-bold text-primary tracking-tight">1</span>
<span className="text-2xl font-bold text-muted-foreground">:</span>
<span className="text-3xl font-serif font-bold text-primary tracking-tight">
{stats.maleCount > 0 ? (stats.femaleCount / stats.maleCount).toFixed(2) : '0'}
</span>
</div>
<div className="text-center text-xs text-muted-foreground/80 font-medium -mt-2">
男性 : 女性
</div>
{/* 双色进度条 */}
<div className="space-y-2">
<div className="flex items-center justify-between text-xs">
<span className="text-primary/80 font-medium flex items-center gap-1">
<span className="w-2 h-2 rounded-full bg-primary/60"></span>
{stats.maleCount}
</span>
<span className="text-primary/80 font-medium flex items-center gap-1">
{stats.femaleCount}
<span className="w-2 h-2 rounded-full bg-primary/60"></span>
</span>
</div>
<div className="h-3 bg-background/60 border border-border/50 rounded-full overflow-hidden flex">
<div
className="h-full bg-primary/70 transition-all duration-500 flex items-center justify-center"
style={{ width: `${stats.totalMembers > 0 ? (stats.maleCount / stats.totalMembers * 100) : 0}%` }}
>
{stats.totalMembers > 0 && stats.maleCount > 0 && (
<span className="text-[10px] text-primary-foreground font-bold px-1">
{Math.round(stats.maleCount / stats.totalMembers * 100)}%
</span>
)}
</div>
<div
className="h-full bg-primary/40 transition-all duration-500 flex items-center justify-center"
style={{ width: `${stats.totalMembers > 0 ? (stats.femaleCount / stats.totalMembers * 100) : 0}%` }}
>
{stats.totalMembers > 0 && stats.femaleCount > 0 && (
<span className="text-[10px] text-primary-foreground font-bold px-1">
{Math.round(stats.femaleCount / stats.totalMembers * 100)}%
</span>
)}
</div>
</div>
<div className="text-center text-xs text-muted-foreground/80 font-medium mt-1">
{stats.maleCount} · {stats.femaleCount}
</div>
</div>
</CardContent>