0.0.6.0
This commit is contained in:
+3
-3
@@ -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
@@ -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
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user