0.3.1.2
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
import { useEffect, useState } from "react"
|
||||
import { useSession } from "next-auth/react"
|
||||
import Link from "next/link"
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
|
||||
import { ChineseCard, ChineseCardContent, ChineseCardHeader, ChineseCardTitle, ChineseCardDescription } from "@/components/ui/chinese-card"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Users, Calendar, Plus, ArrowRight } from "lucide-react"
|
||||
import { format } from "date-fns"
|
||||
@@ -47,41 +47,41 @@ export function FamilyTreesList() {
|
||||
|
||||
if (!session) {
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>我的家族树</CardTitle>
|
||||
<CardDescription>请先登录查看您的家族树</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<ChineseCard variant="plaque" className="shadow-sm">
|
||||
<ChineseCardHeader>
|
||||
<ChineseCardTitle>我的家族树</ChineseCardTitle>
|
||||
<ChineseCardDescription>请先登录查看您的家族树</ChineseCardDescription>
|
||||
</ChineseCardHeader>
|
||||
<ChineseCardContent>
|
||||
<Link href="/auth/signin">
|
||||
<Button>登录</Button>
|
||||
</Link>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</ChineseCardContent>
|
||||
</ChineseCard>
|
||||
)
|
||||
}
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>我的家族树</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<ChineseCard variant="plaque" className="shadow-sm">
|
||||
<ChineseCardHeader>
|
||||
<ChineseCardTitle>我的家族树</ChineseCardTitle>
|
||||
</ChineseCardHeader>
|
||||
<ChineseCardContent>
|
||||
<p className="text-muted-foreground">加载中...</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</ChineseCardContent>
|
||||
</ChineseCard>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader className="flex flex-row items-center justify-between">
|
||||
<ChineseCard variant="plaque" className="shadow-sm">
|
||||
<ChineseCardHeader className="flex flex-row items-center justify-between">
|
||||
<div>
|
||||
<CardTitle>我的家族树</CardTitle>
|
||||
<CardDescription>
|
||||
<ChineseCardTitle>我的家族树</ChineseCardTitle>
|
||||
<ChineseCardDescription>
|
||||
{trees.length > 0 ? `共 ${trees.length} 个家族树` : '还没有家族树'}
|
||||
</CardDescription>
|
||||
</ChineseCardDescription>
|
||||
</div>
|
||||
<Link href="/trees/new">
|
||||
<Button size="sm">
|
||||
@@ -89,8 +89,8 @@ export function FamilyTreesList() {
|
||||
创建家族树
|
||||
</Button>
|
||||
</Link>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
</ChineseCardHeader>
|
||||
<ChineseCardContent>
|
||||
{trees.length === 0 ? (
|
||||
<div className="text-center py-8">
|
||||
<p className="text-muted-foreground mb-4">您还没有创建任何家族树</p>
|
||||
@@ -106,7 +106,7 @@ export function FamilyTreesList() {
|
||||
{trees.map((tree) => (
|
||||
<div
|
||||
key={tree.id}
|
||||
className="flex items-center justify-between p-4 rounded-lg border bg-card hover:bg-accent/50 transition-colors"
|
||||
className="flex items-center justify-between p-4 rounded-lg border bg-card/50 hover:bg-accent/50 transition-colors"
|
||||
>
|
||||
<div className="flex-1">
|
||||
<h3 className="font-semibold text-lg">{tree.name}</h3>
|
||||
@@ -134,7 +134,7 @@ export function FamilyTreesList() {
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</ChineseCardContent>
|
||||
</ChineseCard>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
import { CanvasRenderer } from "echarts/renderers"
|
||||
import { graphic } from "echarts/core"
|
||||
import type { FamilyMember } from "@/types/family"
|
||||
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card"
|
||||
import { ChineseCard, ChineseCardContent, ChineseCardHeader, ChineseCardTitle, ChineseCardDescription } from "@/components/ui/chinese-card"
|
||||
|
||||
// 注册必要的组件
|
||||
echarts.use([
|
||||
@@ -330,45 +330,45 @@ export function StatisticsCharts({ members }: StatisticsChartsProps) {
|
||||
|
||||
return (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>性别分布</CardTitle>
|
||||
<CardDescription>家族成员性别比例</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<ChineseCard variant="default" cornerOrnament className="shadow-sm">
|
||||
<ChineseCardHeader>
|
||||
<ChineseCardTitle>性别分布</ChineseCardTitle>
|
||||
<ChineseCardDescription>家族成员性别比例</ChineseCardDescription>
|
||||
</ChineseCardHeader>
|
||||
<ChineseCardContent>
|
||||
<div ref={genderChartRef} className="h-[300px]" />
|
||||
</CardContent>
|
||||
</Card>
|
||||
</ChineseCardContent>
|
||||
</ChineseCard>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>世代分布</CardTitle>
|
||||
<CardDescription>各世代人数统计</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<ChineseCard variant="default" cornerOrnament className="shadow-sm">
|
||||
<ChineseCardHeader>
|
||||
<ChineseCardTitle>世代分布</ChineseCardTitle>
|
||||
<ChineseCardDescription>各世代人数统计</ChineseCardDescription>
|
||||
</ChineseCardHeader>
|
||||
<ChineseCardContent>
|
||||
<div ref={generationChartRef} className="h-[300px]" />
|
||||
</CardContent>
|
||||
</Card>
|
||||
</ChineseCardContent>
|
||||
</ChineseCard>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>年龄分布</CardTitle>
|
||||
<CardDescription>按年龄段划分的人数</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<ChineseCard variant="scroll" className="shadow-sm">
|
||||
<ChineseCardHeader className="pt-6">
|
||||
<ChineseCardTitle>年龄分布</ChineseCardTitle>
|
||||
<ChineseCardDescription>按年龄段划分的人数</ChineseCardDescription>
|
||||
</ChineseCardHeader>
|
||||
<ChineseCardContent>
|
||||
<div ref={ageDistributionRef} className="h-[300px]" />
|
||||
</CardContent>
|
||||
</Card>
|
||||
</ChineseCardContent>
|
||||
</ChineseCard>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>出生年代分布</CardTitle>
|
||||
<CardDescription>各年代出生人数趋势</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<ChineseCard variant="scroll" className="shadow-sm">
|
||||
<ChineseCardHeader className="pt-6">
|
||||
<ChineseCardTitle>出生年代分布</ChineseCardTitle>
|
||||
<ChineseCardDescription>各年代出生人数趋势</ChineseCardDescription>
|
||||
</ChineseCardHeader>
|
||||
<ChineseCardContent>
|
||||
<div ref={birthYearChartRef} className="h-[300px]" />
|
||||
</CardContent>
|
||||
</Card>
|
||||
</ChineseCardContent>
|
||||
</ChineseCard>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
+195
-131
@@ -11,7 +11,7 @@ import { Textarea } from "@/components/ui/textarea"
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
|
||||
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"
|
||||
import { CalendarIcon, User, Scroll, Users, Images, BookOpen } from "lucide-react"
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
|
||||
import { ChineseCard, ChineseCardContent, ChineseCardHeader, ChineseCardTitle, ChineseCardDescription } from "@/components/ui/chinese-card"
|
||||
import { Switch } from "@/components/ui/switch"
|
||||
import { ImageUpload } from "@/components/ui/image-upload"
|
||||
import { PhotoGallery } from "./photo-gallery"
|
||||
@@ -290,6 +290,8 @@ export function MemberForm({ initialData, existingMembers = [], onSubmit, onCanc
|
||||
spouseFatherName: formData.spouseFatherName,
|
||||
spouseMotherName: formData.spouseMotherName,
|
||||
phone: formData.phone,
|
||||
telephone: formData.telephone,
|
||||
email: formData.email,
|
||||
occupation: formData.occupation,
|
||||
courtesyName: formData.courtesyName,
|
||||
artName: formData.artName,
|
||||
@@ -298,8 +300,10 @@ export function MemberForm({ initialData, existingMembers = [], onSubmit, onCanc
|
||||
birthDate: formData.birthDate,
|
||||
deathDate: formData.deathDate,
|
||||
isLunarDate: formData.isLunarDate || false,
|
||||
ancestralHome: formData.ancestralHome,
|
||||
birthPlace: formData.birthPlace,
|
||||
deathPlace: formData.deathPlace,
|
||||
burialPlace: formData.burialPlace,
|
||||
address: formData.address,
|
||||
bio: formData.bio,
|
||||
avatarUrl: formData.avatarUrl,
|
||||
@@ -337,7 +341,7 @@ export function MemberForm({ initialData, existingMembers = [], onSubmit, onCanc
|
||||
{!hideTabs && (
|
||||
<div className="flex-shrink-0">
|
||||
<TabsList className="grid w-full grid-cols-4">
|
||||
<TabsTrigger value="info">详细信息</TabsTrigger>
|
||||
<TabsTrigger value="info">基本信息</TabsTrigger>
|
||||
<TabsTrigger value="details">详细资料</TabsTrigger>
|
||||
<TabsTrigger value="photos">生平影像</TabsTrigger>
|
||||
<TabsTrigger value="stories">家族故事</TabsTrigger>
|
||||
@@ -346,17 +350,17 @@ export function MemberForm({ initialData, existingMembers = [], onSubmit, onCanc
|
||||
)}
|
||||
|
||||
<div className="flex-1 overflow-y-auto">
|
||||
{/* 详细信息Tab - 包含基本信息和家庭关系 */}
|
||||
{/* 基本信息Tab - 包含基本信息和家庭关系 */}
|
||||
<TabsContent value="info" className="space-y-6 mt-6 w-full">
|
||||
{/* 基本信息卡片 */}
|
||||
<Card className={`w-full ${founderCardClass}`}>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2 text-lg font-serif">
|
||||
<ChineseCard variant="default" cornerOrnament className="shadow-sm w-full">
|
||||
<ChineseCardHeader>
|
||||
<ChineseCardTitle className="flex items-center gap-2 text-lg font-serif">
|
||||
<User className="h-5 w-5" />
|
||||
基本信息
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-6">
|
||||
</ChineseCardTitle>
|
||||
</ChineseCardHeader>
|
||||
<ChineseCardContent className="space-y-6">
|
||||
{/* 头像和基本信息两栏布局 */}
|
||||
<div className="grid grid-cols-1 lg:grid-cols-[200px_1fr] gap-8">
|
||||
{/* 左侧:头像 */}
|
||||
@@ -367,89 +371,88 @@ export function MemberForm({ initialData, existingMembers = [], onSubmit, onCanc
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* 右侧:基本表单字段 */}
|
||||
<div className="space-y-4">
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||||
{/* 右侧:表单 */}
|
||||
<div className="space-y-6">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="surname">姓 <span className="text-destructive">*</span></Label>
|
||||
<Label htmlFor="surname">
|
||||
姓氏 <span className="text-red-500">*</span>
|
||||
</Label>
|
||||
<Input
|
||||
id="surname"
|
||||
value={formData.surname}
|
||||
value={formData.surname || ""}
|
||||
onChange={(e) => handleChange("surname", e.target.value)}
|
||||
placeholder="例如:李"
|
||||
className={errors.includes("surname") ? "border-red-500" : ""}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="givenName">名 <span className="text-destructive">*</span></Label>
|
||||
<Label htmlFor="givenName">
|
||||
名字 <span className="text-red-500">*</span>
|
||||
</Label>
|
||||
<Input
|
||||
id="givenName"
|
||||
value={formData.givenName}
|
||||
value={formData.givenName || ""}
|
||||
onChange={(e) => handleChange("givenName", e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="gender">性别 <span className="text-destructive">*</span></Label>
|
||||
<Select value={formData.gender} onValueChange={(val) => handleChange("gender", val)}>
|
||||
<SelectTrigger>
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="MALE">男</SelectItem>
|
||||
<SelectItem value="FEMALE">女</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="generation">世系 <span className="text-destructive">*</span></Label>
|
||||
<Input
|
||||
id="generation"
|
||||
type="number"
|
||||
value={formData.generation}
|
||||
onChange={(e) => handleChange("generation", Number.parseInt(e.target.value))}
|
||||
placeholder="例如:明"
|
||||
className={errors.includes("givenName") ? "border-red-500" : ""}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="phone">联系电话</Label>
|
||||
<div className="space-y-2">
|
||||
<Label>性别</Label>
|
||||
<RadioGroup
|
||||
value={formData.gender}
|
||||
onValueChange={(val) => handleChange("gender", val)}
|
||||
className="flex space-x-4"
|
||||
>
|
||||
<div className="flex items-center space-x-2">
|
||||
<RadioGroupItem value="MALE" id="male" />
|
||||
<Label htmlFor="male">男</Label>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<RadioGroupItem value="FEMALE" id="female" />
|
||||
<Label htmlFor="female">女</Label>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="generation">世代 (第几世)</Label>
|
||||
<div className="flex items-center gap-4">
|
||||
<Input
|
||||
id="phone"
|
||||
value={formData.phone || ""}
|
||||
onChange={(e) => handleChange("phone", e.target.value)}
|
||||
placeholder="请输入联系电话"
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="occupation">职业</Label>
|
||||
<Input
|
||||
id="occupation"
|
||||
value={formData.occupation || ""}
|
||||
onChange={(e) => handleChange("occupation", e.target.value)}
|
||||
placeholder="请输入职业"
|
||||
id="generation"
|
||||
type="number"
|
||||
value={formData.generation || ""}
|
||||
onChange={(e) => handleChange("generation", parseInt(e.target.value) || 0)}
|
||||
className="w-32"
|
||||
min={1}
|
||||
/>
|
||||
<span className="text-sm text-muted-foreground">世</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</ChineseCardContent>
|
||||
</ChineseCard>
|
||||
|
||||
{/* 家庭关系 - 左右两个独立卡片 */}
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
{/* 左栏:父母 */}
|
||||
<Card className={`w-full ${founderCardClass}`}>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2 text-lg font-serif">
|
||||
<ChineseCard variant="default" cornerOrnament className="shadow-sm w-full">
|
||||
<ChineseCardHeader>
|
||||
<ChineseCardTitle className="flex items-center gap-2 text-lg font-serif">
|
||||
<Users className="h-5 w-5" />
|
||||
父母
|
||||
</CardTitle>
|
||||
</ChineseCardTitle>
|
||||
{!isFounder && (
|
||||
<CardDescription className="text-xs">
|
||||
<ChineseCardDescription className="text-xs">
|
||||
需关联父母或配偶以连接至家族树
|
||||
</CardDescription>
|
||||
</ChineseCardDescription>
|
||||
)}
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
</ChineseCardHeader>
|
||||
<ChineseCardContent>
|
||||
<div className="space-y-3">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="fatherId">父亲</Label>
|
||||
@@ -558,23 +561,23 @@ export function MemberForm({ initialData, existingMembers = [], onSubmit, onCanc
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</ChineseCardContent>
|
||||
</ChineseCard>
|
||||
|
||||
{/* 右栏:配偶和子女 */}
|
||||
<Card className={`w-full ${founderCardClass}`}>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2 text-lg font-serif">
|
||||
<ChineseCard variant="default" cornerOrnament className="shadow-sm w-full">
|
||||
<ChineseCardHeader>
|
||||
<ChineseCardTitle className="flex items-center gap-2 text-lg font-serif">
|
||||
<Users className="h-5 w-5" />
|
||||
配偶与子女
|
||||
</CardTitle>
|
||||
</ChineseCardTitle>
|
||||
{!isFounder && (
|
||||
<CardDescription className="text-xs">
|
||||
<ChineseCardDescription className="text-xs">
|
||||
需关联父母或配偶以连接至家族树
|
||||
</CardDescription>
|
||||
</ChineseCardDescription>
|
||||
)}
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
</ChineseCardHeader>
|
||||
<ChineseCardContent>
|
||||
<div className="space-y-3">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="spouseId">配偶</Label>
|
||||
@@ -697,8 +700,8 @@ export function MemberForm({ initialData, existingMembers = [], onSubmit, onCanc
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</ChineseCardContent>
|
||||
</ChineseCard>
|
||||
</div>
|
||||
</TabsContent>
|
||||
|
||||
@@ -706,14 +709,14 @@ export function MemberForm({ initialData, existingMembers = [], onSubmit, onCanc
|
||||
<TabsContent value="details" className="space-y-6 mt-6 w-full">
|
||||
{/* 使用两列布局放置两个卡片 */}
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
<Card className={`w-full ${founderCardClass}`}>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2 text-lg font-serif">
|
||||
<ChineseCard variant="default" cornerOrnament className="shadow-sm w-full">
|
||||
<ChineseCardHeader>
|
||||
<ChineseCardTitle className="flex items-center gap-2 text-lg font-serif">
|
||||
<Scroll className="h-5 w-5" />
|
||||
传统称谓
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
</ChineseCardTitle>
|
||||
</ChineseCardHeader>
|
||||
<ChineseCardContent className="space-y-4">
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="courtesyName">字</Label>
|
||||
@@ -752,17 +755,17 @@ export function MemberForm({ initialData, existingMembers = [], onSubmit, onCanc
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</ChineseCardContent>
|
||||
</ChineseCard>
|
||||
|
||||
<Card className={`w-full ${founderCardClass}`}>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2 text-lg font-serif">
|
||||
<ChineseCard variant="default" cornerOrnament className="shadow-sm w-full">
|
||||
<ChineseCardHeader>
|
||||
<ChineseCardTitle className="flex items-center gap-2 text-lg font-serif">
|
||||
<CalendarIcon className="h-5 w-5" />
|
||||
生平日期
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
</ChineseCardTitle>
|
||||
</ChineseCardHeader>
|
||||
<ChineseCardContent className="space-y-4">
|
||||
<div className="grid grid-cols-1 gap-4">
|
||||
<DateInputWithLunar
|
||||
id="birthDate"
|
||||
@@ -781,20 +784,29 @@ export function MemberForm({ initialData, existingMembers = [], onSubmit, onCanc
|
||||
showLunarToggle={false}
|
||||
/>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</ChineseCardContent>
|
||||
</ChineseCard>
|
||||
</div>
|
||||
|
||||
{/* 地点和简介卡片 */}
|
||||
<Card className={`w-full ${founderCardClass}`}>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2 text-lg font-serif">
|
||||
<ChineseCard variant="default" cornerOrnament className="shadow-sm w-full">
|
||||
<ChineseCardHeader>
|
||||
<ChineseCardTitle className="flex items-center gap-2 text-lg font-serif">
|
||||
<Scroll className="h-5 w-5" />
|
||||
地点与简介
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
</ChineseCardTitle>
|
||||
</ChineseCardHeader>
|
||||
<ChineseCardContent className="space-y-4">
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="ancestralHome">籍贯</Label>
|
||||
<Input
|
||||
id="ancestralHome"
|
||||
value={formData.ancestralHome || ""}
|
||||
onChange={(e) => handleChange("ancestralHome", e.target.value)}
|
||||
placeholder="请输入籍贯"
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="birthPlace">出生地</Label>
|
||||
<Input
|
||||
@@ -804,6 +816,18 @@ export function MemberForm({ initialData, existingMembers = [], onSubmit, onCanc
|
||||
placeholder="请输入出生地"
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="address">居住地</Label>
|
||||
<Input
|
||||
id="address"
|
||||
value={formData.address || ""}
|
||||
onChange={(e) => handleChange("address", e.target.value)}
|
||||
placeholder="请输入居住地"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="deathPlace">逝世地</Label>
|
||||
<Input
|
||||
@@ -814,12 +838,52 @@ export function MemberForm({ initialData, existingMembers = [], onSubmit, onCanc
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="address">居住地</Label>
|
||||
<Label htmlFor="burialPlace">安葬地</Label>
|
||||
<Input
|
||||
id="address"
|
||||
value={formData.address || ""}
|
||||
onChange={(e) => handleChange("address", e.target.value)}
|
||||
placeholder="请输入居住地"
|
||||
id="burialPlace"
|
||||
value={formData.burialPlace || ""}
|
||||
onChange={(e) => handleChange("burialPlace", e.target.value)}
|
||||
placeholder="请输入安葬地"
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="occupation">职业</Label>
|
||||
<Input
|
||||
id="occupation"
|
||||
value={formData.occupation || ""}
|
||||
onChange={(e) => handleChange("occupation", e.target.value)}
|
||||
placeholder="请输入职业"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="phone">手机号</Label>
|
||||
<Input
|
||||
id="phone"
|
||||
value={formData.phone || ""}
|
||||
onChange={(e) => handleChange("phone", e.target.value)}
|
||||
placeholder="请输入手机号"
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="telephone">固定电话</Label>
|
||||
<Input
|
||||
id="telephone"
|
||||
value={formData.telephone || ""}
|
||||
onChange={(e) => handleChange("telephone", e.target.value)}
|
||||
placeholder="请输入固定电话"
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="email">电子邮箱</Label>
|
||||
<Input
|
||||
id="email"
|
||||
type="email"
|
||||
value={formData.email || ""}
|
||||
onChange={(e) => handleChange("email", e.target.value)}
|
||||
placeholder="请输入电子邮箱"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -834,57 +898,57 @@ export function MemberForm({ initialData, existingMembers = [], onSubmit, onCanc
|
||||
rows={4}
|
||||
/>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</ChineseCardContent>
|
||||
</ChineseCard>
|
||||
</TabsContent>
|
||||
|
||||
{/* 生平影像Tab */}
|
||||
<TabsContent value="photos" className="space-y-6 mt-6 w-full">
|
||||
<Card className={`w-full ${founderCardClass}`}>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2 text-lg font-serif">
|
||||
<ChineseCard variant="default" cornerOrnament className="shadow-sm w-full">
|
||||
<ChineseCardHeader>
|
||||
<ChineseCardTitle className="flex items-center gap-2 text-lg font-serif">
|
||||
<Images className="h-5 w-5" />
|
||||
照片墙
|
||||
</CardTitle>
|
||||
<CardDescription>上传多张照片记录珍贵时刻</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
</ChineseCardTitle>
|
||||
<ChineseCardDescription>上传多张照片记录珍贵时刻</ChineseCardDescription>
|
||||
</ChineseCardHeader>
|
||||
<ChineseCardContent>
|
||||
<PhotoGallery
|
||||
photos={formData.photos}
|
||||
photoIds={formData.photoIds}
|
||||
onChange={handlePhotosChange}
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</ChineseCardContent>
|
||||
</ChineseCard>
|
||||
</TabsContent>
|
||||
|
||||
{/* 家族故事Tab */}
|
||||
<TabsContent value="stories" className="space-y-6 mt-6 w-full">
|
||||
<Card className={`w-full ${founderCardClass}`}>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2 text-lg font-serif">
|
||||
<ChineseCard variant="default" cornerOrnament className="shadow-sm w-full">
|
||||
<ChineseCardHeader>
|
||||
<ChineseCardTitle className="flex items-center gap-2 text-lg font-serif">
|
||||
<BookOpen className="h-5 w-5" />
|
||||
家族故事
|
||||
</CardTitle>
|
||||
<CardDescription>记录与此成员相关的家族故事</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
</ChineseCardTitle>
|
||||
<ChineseCardDescription>记录与此成员相关的家族故事</ChineseCardDescription>
|
||||
</ChineseCardHeader>
|
||||
<ChineseCardContent>
|
||||
<StoryManager
|
||||
stories={formData.stories || []}
|
||||
onChange={(stories) => handleChange("stories", stories)}
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</ChineseCardContent>
|
||||
</ChineseCard>
|
||||
|
||||
<Card className={`w-full ${founderCardClass}`}>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2 text-lg font-serif">
|
||||
<ChineseCard variant="default" cornerOrnament className="shadow-sm w-full">
|
||||
<ChineseCardHeader>
|
||||
<ChineseCardTitle className="flex items-center gap-2 text-lg font-serif">
|
||||
<Scroll className="h-5 w-5" />
|
||||
标签
|
||||
</CardTitle>
|
||||
<CardDescription>添加标签以便分类和搜索(按回车添加)</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
</ChineseCardTitle>
|
||||
<ChineseCardDescription>添加标签以便分类和搜索(按回车添加)</ChineseCardDescription>
|
||||
</ChineseCardHeader>
|
||||
<ChineseCardContent className="space-y-4">
|
||||
<div className="space-y-3">
|
||||
<div className="flex gap-2">
|
||||
<Input
|
||||
@@ -925,8 +989,8 @@ export function MemberForm({ initialData, existingMembers = [], onSubmit, onCanc
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</ChineseCardContent>
|
||||
</ChineseCard>
|
||||
</TabsContent>
|
||||
</div>
|
||||
</Tabs>
|
||||
|
||||
@@ -146,7 +146,7 @@ export function MemberVersionHistory({ memberId, treeId, onVersionSelect, lastUp
|
||||
{/* 其他信息变更 */}
|
||||
{changedOthers.length > 0 && (
|
||||
<div>
|
||||
<h4 className="text-xs font-semibold text-foreground mb-2">详细信息变更</h4>
|
||||
<h4 className="text-xs font-semibold text-foreground mb-2">基本信息变更</h4>
|
||||
<div className="space-y-2 text-xs">
|
||||
{changedOthers.map(([field, change]) => (
|
||||
<div key={field} className="flex items-start gap-2">
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
"use client"
|
||||
|
||||
import { ArrowLeft } from "lucide-react"
|
||||
import { Button } from "@/components/ui/button"
|
||||
|
||||
interface BackButtonProps {
|
||||
onClick: () => void
|
||||
label?: string
|
||||
className?: string
|
||||
}
|
||||
|
||||
export function BackButton({ onClick, label = "返回", className = "" }: BackButtonProps) {
|
||||
return (
|
||||
<div className="py-4">
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={onClick}
|
||||
className={`gap-2 pl-0 hover:bg-transparent hover:text-primary text-2xl font-bold ${className}`}
|
||||
>
|
||||
<ArrowLeft className="h-6 w-6" />
|
||||
{label}
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,207 @@
|
||||
import * as React from 'react'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
/**
|
||||
* 中国风卡片组件
|
||||
* 提供多种传统中国设计风格变体
|
||||
*/
|
||||
|
||||
type ChineseCardVariant = 'default' | 'scroll' | 'plaque' | 'portrait' | 'bamboo'
|
||||
|
||||
interface ChineseCardProps extends React.ComponentProps<'div'> {
|
||||
variant?: ChineseCardVariant
|
||||
/** 是否显示角饰 */
|
||||
cornerOrnament?: boolean
|
||||
/** 是否显示顶部装饰条 */
|
||||
headerDecoration?: boolean
|
||||
}
|
||||
|
||||
function ChineseCard({
|
||||
className,
|
||||
variant = 'default',
|
||||
cornerOrnament = false,
|
||||
headerDecoration = false,
|
||||
children,
|
||||
...props
|
||||
}: ChineseCardProps) {
|
||||
return (
|
||||
<div
|
||||
data-slot="chinese-card"
|
||||
data-variant={variant}
|
||||
className={cn(
|
||||
// 基础样式
|
||||
'relative bg-card text-card-foreground flex flex-col',
|
||||
// 变体样式
|
||||
variant === 'default' && 'chinese-card-default',
|
||||
variant === 'scroll' && 'chinese-card-scroll',
|
||||
variant === 'plaque' && 'chinese-card-plaque',
|
||||
variant === 'portrait' && 'chinese-card-portrait',
|
||||
variant === 'bamboo' && 'chinese-card-bamboo',
|
||||
// 角饰
|
||||
cornerOrnament && 'chinese-card-corner-ornament',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{/* 顶部装饰条 */}
|
||||
{headerDecoration && (
|
||||
<div className="chinese-card-header-decoration" />
|
||||
)}
|
||||
|
||||
{/* 角饰元素 */}
|
||||
{cornerOrnament && (
|
||||
<>
|
||||
<span className="chinese-corner chinese-corner-tl" />
|
||||
<span className="chinese-corner chinese-corner-tr" />
|
||||
<span className="chinese-corner chinese-corner-bl" />
|
||||
<span className="chinese-corner chinese-corner-br" />
|
||||
</>
|
||||
)}
|
||||
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function ChineseCardHeader({ className, ...props }: React.ComponentProps<'div'>) {
|
||||
return (
|
||||
<div
|
||||
data-slot="chinese-card-header"
|
||||
className={cn(
|
||||
'flex flex-col gap-1.5 px-5 pt-5 pb-3',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function ChineseCardTitle({ className, ...props }: React.ComponentProps<'h3'>) {
|
||||
return (
|
||||
<h3
|
||||
data-slot="chinese-card-title"
|
||||
className={cn(
|
||||
'font-serif text-lg font-semibold leading-tight tracking-wide',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function ChineseCardDescription({ className, ...props }: React.ComponentProps<'p'>) {
|
||||
return (
|
||||
<p
|
||||
data-slot="chinese-card-description"
|
||||
className={cn(
|
||||
'font-serif text-sm text-muted-foreground',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function ChineseCardContent({ className, ...props }: React.ComponentProps<'div'>) {
|
||||
return (
|
||||
<div
|
||||
data-slot="chinese-card-content"
|
||||
className={cn('px-5 pb-5', className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function ChineseCardFooter({ className, ...props }: React.ComponentProps<'div'>) {
|
||||
return (
|
||||
<div
|
||||
data-slot="chinese-card-footer"
|
||||
className={cn(
|
||||
'flex items-center px-5 pb-5 pt-0',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 中国风分隔线
|
||||
*/
|
||||
function ChineseDivider({
|
||||
className,
|
||||
variant = 'wave',
|
||||
...props
|
||||
}: React.ComponentProps<'div'> & {
|
||||
variant?: 'wave' | 'cloud' | 'meander' | 'simple'
|
||||
}) {
|
||||
return (
|
||||
<div
|
||||
data-slot="chinese-divider"
|
||||
className={cn(
|
||||
'chinese-divider',
|
||||
variant === 'wave' && 'chinese-divider-wave',
|
||||
variant === 'cloud' && 'chinese-divider-cloud',
|
||||
variant === 'meander' && 'chinese-divider-meander',
|
||||
variant === 'simple' && 'chinese-divider-simple',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 统计数字组件 - 用于展示大数字
|
||||
*/
|
||||
function ChineseStatNumber({
|
||||
className,
|
||||
children,
|
||||
...props
|
||||
}: React.ComponentProps<'span'>) {
|
||||
return (
|
||||
<span
|
||||
className={cn(
|
||||
'text-3xl font-serif font-bold tracking-tight text-foreground',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 统计标签组件
|
||||
*/
|
||||
function ChineseStatLabel({
|
||||
className,
|
||||
children,
|
||||
...props
|
||||
}: React.ComponentProps<'span'>) {
|
||||
return (
|
||||
<span
|
||||
className={cn(
|
||||
'text-sm font-medium text-muted-foreground uppercase tracking-wider',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
export {
|
||||
ChineseCard,
|
||||
ChineseCardHeader,
|
||||
ChineseCardTitle,
|
||||
ChineseCardDescription,
|
||||
ChineseCardContent,
|
||||
ChineseCardFooter,
|
||||
ChineseDivider,
|
||||
ChineseStatNumber,
|
||||
ChineseStatLabel,
|
||||
type ChineseCardVariant,
|
||||
}
|
||||
Reference in New Issue
Block a user