Files
chinese-family-tree-2/components/members/member-form.tsx
T
freedakgmail 86e889cc2a 0.3.1.2
2025-11-30 19:10:30 +08:00

1000 lines
43 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client"
import type React from "react"
import { useState, useEffect } from "react"
import type { FamilyMember, FamilyPhoto } from "@/types/family"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
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 { 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"
import { StoryManager } from "./story-manager"
import { DateInputWithLunar } from "@/components/ui/date-input-with-lunar"
import { MemberNameWithStatus } from "@/components/member-name-with-status"
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
interface MemberFormProps {
initialData?: Partial<FamilyMember>
existingMembers?: FamilyMember[]
onSubmit: (data: FamilyMember) => void
onCancel: () => void
isFounder?: boolean
hideTabs?: boolean
activeTab?: string
}
export function MemberForm({ initialData, existingMembers = [], onSubmit, onCancel, isFounder = false, hideTabs = false, activeTab = "basic" }: MemberFormProps) {
// 计算最大世系
const maxGeneration = existingMembers.length > 0
? Math.max(...existingMembers.map(m => m.generation || 1))
: 1
// 获取始祖的姓氏(第1世成员)
const ancestorSurname = existingMembers.find(m => m.generation === 1)?.surname || ""
// 始祖专用样式
const founderCardClass = isFounder ? "border-2 border-amber-400 shadow-md" : ""
// 生成兼容的UUID
const generateUUID = () => {
if (typeof crypto !== 'undefined' && crypto.randomUUID) {
return crypto.randomUUID()
}
// 降级方案:生成简单的UUID
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
const r = Math.random() * 16 | 0
const v = c === 'x' ? r : (r & 0x3 | 0x8)
return v.toString(16)
})
}
// Initialize state with default values or initialData
const [formData, setFormData] = useState<Partial<FamilyMember>>(() => {
const defaults = {
id: generateUUID(),
surname: ancestorSurname,
givenName: "",
fullName: "",
gender: "MALE" as "MALE" | "FEMALE",
generation: maxGeneration,
spouseIds: [],
childrenIds: [],
tags: [],
photos: [] as FamilyPhoto[],
photoIds: [] as string[],
}
if (!initialData) return defaults
const normalizedPhotos: FamilyPhoto[] = initialData.photos && initialData.photos.length > 0
? initialData.photos
: (initialData.photoIds || []).map((url) => ({
url,
uploadedAt: initialData.updatedAt || new Date().toISOString(),
}))
return {
...defaults,
...initialData,
gender: (initialData.gender || "MALE").toUpperCase() as "MALE" | "FEMALE",
spouseIds: initialData.spouseIds || [],
childrenIds: initialData.childrenIds || [],
tags: initialData.tags || [],
fatherId: initialData.fatherId || undefined,
motherId: initialData.motherId || undefined,
photos: normalizedPhotos,
photoIds: initialData.photoIds || normalizedPhotos.map((photo) => photo.url),
}
})
const [errors, setErrors] = useState<string[]>([])
// 监听 initialData 变化,更新 formData
useEffect(() => {
if (!initialData) return
setFormData((prev) => {
const normalizedPhotos: FamilyPhoto[] = initialData.photos && initialData.photos.length > 0
? initialData.photos
: (initialData.photoIds || []).map((url) => ({
url,
uploadedAt: initialData.updatedAt || new Date().toISOString(),
}))
// 优先使用 initialData 中的 motherId,如果没有才使用之前的值
const newMotherId = initialData.motherId !== undefined ? initialData.motherId : prev.motherId
console.log('Calculating new motherId:', {
initial: initialData.motherId,
prev: prev.motherId,
result: newMotherId,
hasInitialMother: initialData.motherId !== undefined
})
return {
...prev,
...initialData,
// 确保特定字段被正确更新
gender: initialData.gender ? (initialData.gender.toUpperCase() as "MALE" | "FEMALE") : prev.gender,
spouseIds: initialData.spouseIds || prev.spouseIds || [],
childrenIds: initialData.childrenIds || prev.childrenIds || [],
tags: initialData.tags || prev.tags || [],
fatherId: initialData.fatherId || prev.fatherId,
motherId: newMotherId,
photos: initialData.photos ? normalizedPhotos : prev.photos,
photoIds: initialData.photoIds || (initialData.photos ? normalizedPhotos.map((p) => p.url) : prev.photoIds),
}
})
}, [initialData])
const handleChange = (field: keyof FamilyMember, value: any) => {
console.log('handleChange:', field, value)
// 防御性检查:防止 Select 组件意外清空 initialData 中已有的值
// 这可能是由于 Select 组件在某些情况下(如选项尚未完全渲染)触发了无效的 onChange
if ((field === 'motherId' || field === 'fatherId') && !value) {
const initialValue = initialData?.[field]
const currentValue = formData[field]
// 只有当当前值等于初始值且尝试清空时才阻止
if (initialValue && currentValue === initialValue) {
console.warn(`Prevented accidental clearing of ${field}`)
return
}
// 如果当前值不为空,但尝试清空为空字符串,也阻止(因为Select组件可能发送空字符串而不是undefined)
if (currentValue && value === '') {
console.warn(`Prevented clearing of ${field} to empty string`)
return
}
}
setFormData((prev) => {
const newData = { ...prev, [field]: value }
// 自动更新全名
if (field === "surname" || field === "givenName") {
newData.fullName = `${newData.surname || ""}${newData.givenName || ""}`
}
// 自动设置母亲从父亲
if (field === "fatherId" && value) {
const father = existingMembers.find(m => m.id === value)
if (father && father.spouseIds && father.spouseIds.length > 0) {
const motherId = father.spouseIds[0]
if (!newData.motherId) { // Only auto-set if motherId is not already present
newData.motherId = motherId
console.log('Auto-set mother from father:', { fatherId: value, motherId })
}
}
}
// 自动设置父亲从母亲
if (field === "motherId" && value) {
const mother = existingMembers.find(m => m.id === value)
if (mother && mother.spouseIds && mother.spouseIds.length > 0) {
const fatherId = mother.spouseIds[0]
if (!newData.fatherId) { // Only auto-set if fatherId is not already present
newData.fatherId = fatherId
console.log('Auto-set father from mother:', { motherId: value, fatherId })
}
}
}
return newData
})
}
const handlePhotosChange = (photos: FamilyPhoto[]) => {
setFormData((prev) => ({
...prev,
photos,
photoIds: photos.map(p => p.url),
}))
}
const handleSpouseAdd = (spouseId: string) => {
if (spouseId === "none") return
setFormData((prev) => ({
...prev,
spouseIds: [...(prev.spouseIds || []), spouseId],
}))
}
const handleSpouseRemove = (spouseId: string) => {
setFormData((prev) => ({
...prev,
spouseIds: (prev.spouseIds || []).filter((id) => id !== spouseId),
}))
}
const handleChildAdd = (childId: string) => {
if (childId === "none") return
setFormData((prev) => ({
...prev,
childrenIds: [...(prev.childrenIds || []), childId],
}))
}
const handleChildRemove = (childId: string) => {
setFormData((prev) => ({
...prev,
childrenIds: (prev.childrenIds || []).filter((id) => id !== childId),
}))
}
const validateForm = (): boolean => {
const newErrors: string[] = []
if (!formData.surname?.trim()) {
newErrors.push("请填写姓氏")
}
if (!formData.givenName?.trim()) {
newErrors.push("请填写名字")
}
if (!formData.gender) {
newErrors.push("请选择性别")
}
if (!formData.generation || formData.generation < 1) {
newErrors.push("请填写正确的世系")
}
// 关系验证:非始祖必须与家族建立联系
if (!isFounder) {
// 1. 检查是否有主要成员的父母
const hasValidFather = formData.fatherId && existingMembers.some(m => m.id === formData.fatherId)
const hasValidMother = formData.motherId && existingMembers.some(m => m.id === formData.motherId)
// 2. 检查是否有主要成员的配偶
const hasValidSpouse = formData.spouseIds && formData.spouseIds.length > 0 &&
formData.spouseIds.some(id => existingMembers.some(m => m.id === id))
if (!hasValidFather && !hasValidMother && !hasValidSpouse) {
newErrors.push("孤立成员:请选择家族成员作为父母,或添加家族成员为配偶")
}
}
setErrors(newErrors)
return newErrors.length === 0
}
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault()
if (!validateForm()) {
return
}
// 确保所有必填字段都有值
const memberData: FamilyMember = {
id: formData.id || generateUUID(),
surname: formData.surname || "",
givenName: formData.givenName || "",
fullName: formData.fullName || `${formData.surname || ""}${formData.givenName || ""}`,
gender: formData.gender as "MALE" | "FEMALE",
generation: formData.generation || 1,
spouseIds: formData.spouseIds || [],
childrenIds: formData.childrenIds || [],
tags: formData.tags || [],
photos: formData.photos || [],
photoIds: formData.photoIds || [],
fatherId: formData.fatherId,
motherId: formData.motherId,
spouseFatherName: formData.spouseFatherName,
spouseMotherName: formData.spouseMotherName,
phone: formData.phone,
telephone: formData.telephone,
email: formData.email,
occupation: formData.occupation,
courtesyName: formData.courtesyName,
artName: formData.artName,
generationName: formData.generationName,
posthumousName: formData.posthumousName,
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,
stories: formData.stories || [],
createdAt: formData.createdAt || new Date().toISOString(),
updatedAt: new Date().toISOString(),
}
onSubmit(memberData)
}
const potentialRelatives = existingMembers.filter((m) => m.id !== formData.id)
// 调试日志
console.log('MemberForm render:', {
motherId: formData.motherId,
existingMembersCount: existingMembers.length,
motherInList: existingMembers.find(m => m.id === formData.motherId)
})
return (
<form onSubmit={handleSubmit} className="w-full">
{errors.length > 0 && (
<div className="bg-destructive/15 text-destructive px-4 py-3 rounded-md border border-destructive/20 mb-6">
<p className="font-bold mb-1"></p>
<ul className="list-disc list-inside text-sm">
{errors.map((err, index) => (
<li key={index}>{err}</li>
))}
</ul>
</div>
)}
<Tabs defaultValue="info" value={hideTabs ? activeTab : undefined} className="w-full flex flex-col h-full">
{!hideTabs && (
<div className="flex-shrink-0">
<TabsList className="grid w-full grid-cols-4">
<TabsTrigger value="info"></TabsTrigger>
<TabsTrigger value="details"></TabsTrigger>
<TabsTrigger value="photos"></TabsTrigger>
<TabsTrigger value="stories"></TabsTrigger>
</TabsList>
</div>
)}
<div className="flex-1 overflow-y-auto">
{/* 基本信息Tab - 包含基本信息和家庭关系 */}
<TabsContent value="info" className="space-y-6 mt-6 w-full">
{/* 基本信息卡片 */}
<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" />
</ChineseCardTitle>
</ChineseCardHeader>
<ChineseCardContent className="space-y-6">
{/* 头像和基本信息两栏布局 */}
<div className="grid grid-cols-1 lg:grid-cols-[200px_1fr] gap-8">
{/* 左侧:头像 */}
<div className="flex flex-col items-center">
<ImageUpload
value={formData.avatarUrl}
onChange={(imageUrl) => handleChange("avatarUrl", imageUrl || null)}
/>
</div>
{/* 右侧:表单 */}
<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-red-500">*</span>
</Label>
<Input
id="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-red-500">*</span>
</Label>
<Input
id="givenName"
value={formData.givenName || ""}
onChange={(e) => handleChange("givenName", e.target.value)}
placeholder="例如:明"
className={errors.includes("givenName") ? "border-red-500" : ""}
/>
</div>
</div>
<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="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>
</ChineseCardContent>
</ChineseCard>
{/* 家庭关系 - 左右两个独立卡片 */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
{/* 左栏:父母 */}
<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" />
</ChineseCardTitle>
{!isFounder && (
<ChineseCardDescription className="text-xs">
</ChineseCardDescription>
)}
</ChineseCardHeader>
<ChineseCardContent>
<div className="space-y-3">
<div className="space-y-2">
<Label htmlFor="fatherId"></Label>
{isFounder ? (
<Input
placeholder="填写父亲姓名"
value={formData.spouseFatherName || ""}
onChange={(e) => handleChange("spouseFatherName", e.target.value)}
/>
) : (
<div className="flex gap-2">
<Select
value={formData.fatherId || "none"}
onValueChange={(val) => {
const newValue = val === "none" ? undefined : val
console.log('Father select change:', { val, newValue })
handleChange("fatherId", newValue)
}}
>
<SelectTrigger>
<SelectValue placeholder="选择父亲" />
</SelectTrigger>
<SelectContent>
<SelectItem value="none"></SelectItem>
{potentialRelatives
.filter((m) => {
if (m.id === formData.fatherId) return true
return m.gender === "MALE" && m.generation === (formData.generation || 1) - 1
})
.map((m) => (
<SelectItem key={m.id} value={m.id}>
<MemberNameWithStatus
name={m.fullName}
isDead={!!m.deathDate}
/> ({m.generation})
</SelectItem>
))}
</SelectContent>
</Select>
{!formData.fatherId && (
<Input
placeholder="或填写姓名"
value={formData.spouseFatherName || ""}
onChange={(e) => handleChange("spouseFatherName", e.target.value)}
className="w-32"
/>
)}
</div>
)}
</div>
<div className="space-y-2">
<Label htmlFor="motherId"></Label>
{isFounder ? (
<Input
placeholder="填写母亲姓名"
value={formData.spouseMotherName || ""}
onChange={(e) => handleChange("spouseMotherName", e.target.value)}
/>
) : (
<div className="flex gap-2">
<Select
value={formData.motherId || "none"}
onValueChange={(val) => {
const newValue = val === "none" ? undefined : val
console.log('Mother select change:', { val, newValue })
handleChange("motherId", newValue)
}}
>
<SelectTrigger>
<SelectValue placeholder="选择母亲" />
</SelectTrigger>
<SelectContent>
<SelectItem value="none"></SelectItem>
{potentialRelatives
.filter((m) => {
if (m.id === formData.motherId) {
console.log('Mother already selected, showing:', { id: m.id, name: m.fullName })
return true
}
const isMatch = m.gender === "FEMALE" && m.generation === (formData.generation || 1) - 1
if (isMatch) {
console.log('Mother match found:', { id: m.id, name: m.fullName, gender: m.gender, generation: m.generation })
}
return isMatch
})
.map((m) => (
<SelectItem key={m.id} value={m.id}>
<MemberNameWithStatus
name={m.fullName}
isDead={!!m.deathDate}
/> ({m.generation})
</SelectItem>
))}
</SelectContent>
</Select>
{!formData.motherId && (
<Input
placeholder="或填写姓名"
value={formData.spouseMotherName || ""}
onChange={(e) => handleChange("spouseMotherName", e.target.value)}
className="w-32"
/>
)}
</div>
)}
</div>
</div>
</ChineseCardContent>
</ChineseCard>
{/* 右栏:配偶和子女 */}
<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" />
</ChineseCardTitle>
{!isFounder && (
<ChineseCardDescription className="text-xs">
</ChineseCardDescription>
)}
</ChineseCardHeader>
<ChineseCardContent>
<div className="space-y-3">
<div className="space-y-2">
<Label htmlFor="spouseId"></Label>
<Select value="none" onValueChange={handleSpouseAdd}>
<SelectTrigger>
<SelectValue placeholder="添加配偶..." />
</SelectTrigger>
<SelectContent>
<SelectItem value="none"></SelectItem>
{potentialRelatives
.filter((m) => {
if (formData.spouseIds?.includes(m.id)) return false
if (formData.fatherId === m.id || formData.motherId === m.id) return false
if (formData.childrenIds?.includes(m.id)) return false
if (m.generation !== (formData.generation || 1)) return false
if (m.gender === formData.gender) return false
return true
})
.map((m) => (
<SelectItem key={m.id} value={m.id}>
<MemberNameWithStatus
name={m.fullName}
isDead={!!m.deathDate}
/> ({m.generation})
</SelectItem>
))}
</SelectContent>
</Select>
{formData.spouseIds && formData.spouseIds.length > 0 && (
<div className="mt-2 space-y-1">
{formData.spouseIds.map((spouseId, index) => {
const spouse = existingMembers.find(m => m.id === spouseId)
if (!spouse) return null
return (
<div key={spouseId} className="flex items-center justify-between p-2 bg-muted rounded text-sm">
<span>
{index + 1}. <MemberNameWithStatus
name={spouse.fullName}
isDead={!!spouse.deathDate}
/>
</span>
<Button
type="button"
variant="ghost"
size="sm"
onClick={() => handleSpouseRemove(spouseId)}
className="h-6 w-6 p-0 hover:bg-destructive hover:text-destructive-foreground"
>
×
</Button>
</div>
)
})}
</div>
)}
</div>
<div className="space-y-2">
<Label htmlFor="childId"></Label>
<Select value="none" onValueChange={handleChildAdd}>
<SelectTrigger>
<SelectValue placeholder="添加子女..." />
</SelectTrigger>
<SelectContent>
<SelectItem value="none"></SelectItem>
{potentialRelatives
.filter((m) => {
if (m.generation !== (formData.generation || 1) + 1) return false
if (formData.childrenIds?.includes(m.id)) return false
if (formData.spouseIds?.includes(m.id)) return false
const isSpouse = !m.fatherId && !m.motherId && m.spouseIds && m.spouseIds.length > 0
if (isSpouse) return false
if (formData.gender === 'MALE') {
if (m.fatherId) return false
} else if (formData.gender === 'FEMALE') {
if (m.motherId) return false
}
return true
})
.map((m) => (
<SelectItem key={m.id} value={m.id}>
<MemberNameWithStatus
name={m.fullName}
isDead={!!m.deathDate}
/> ({m.generation})
</SelectItem>
))}
</SelectContent>
</Select>
{formData.childrenIds && formData.childrenIds.length > 0 && (
<div className="mt-2 space-y-1">
{formData.childrenIds.map((childId, index) => {
const child = existingMembers.find(m => m.id === childId)
if (!child) return null
return (
<div key={childId} className="flex items-center justify-between p-2 bg-muted rounded text-sm">
<span>
{index + 1}. <MemberNameWithStatus
name={child.fullName}
isDead={!!child.deathDate}
/>
</span>
<Button
type="button"
variant="ghost"
size="sm"
onClick={() => handleChildRemove(childId)}
className="h-6 w-6 p-0 hover:bg-destructive hover:text-destructive-foreground"
>
×
</Button>
</div>
)
})}
</div>
)}
</div>
</div>
</ChineseCardContent>
</ChineseCard>
</div>
</TabsContent>
{/* 详细资料Tab */}
<TabsContent value="details" className="space-y-6 mt-6 w-full">
{/* 使用两列布局放置两个卡片 */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
<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" />
</ChineseCardTitle>
</ChineseCardHeader>
<ChineseCardContent className="space-y-4">
<div className="grid grid-cols-2 gap-4">
<div className="space-y-2">
<Label htmlFor="courtesyName"></Label>
<Input
id="courtesyName"
value={formData.courtesyName || ""}
onChange={(e) => handleChange("courtesyName", e.target.value)}
placeholder="例如:伯虎"
/>
</div>
<div className="space-y-2">
<Label htmlFor="artName"></Label>
<Input
id="artName"
value={formData.artName || ""}
onChange={(e) => handleChange("artName", e.target.value)}
placeholder="例如:六如居士"
/>
</div>
</div>
<div className="grid grid-cols-2 gap-4">
<div className="space-y-2">
<Label htmlFor="generationName"></Label>
<Input
id="generationName"
value={formData.generationName || ""}
onChange={(e) => handleChange("generationName", e.target.value)}
/>
</div>
<div className="space-y-2">
<Label htmlFor="posthumousName"></Label>
<Input
id="posthumousName"
value={formData.posthumousName || ""}
onChange={(e) => handleChange("posthumousName", e.target.value)}
/>
</div>
</div>
</ChineseCardContent>
</ChineseCard>
<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" />
</ChineseCardTitle>
</ChineseCardHeader>
<ChineseCardContent className="space-y-4">
<div className="grid grid-cols-1 gap-4">
<DateInputWithLunar
id="birthDate"
label="出生日期"
value={formData.birthDate || ""}
isLunar={formData.isLunarDate || false}
onChange={(value) => handleChange("birthDate", value)}
showLunarToggle={false}
/>
<DateInputWithLunar
id="deathDate"
label="逝世日期"
value={formData.deathDate || ""}
isLunar={formData.isLunarDate || false}
onChange={(value) => handleChange("deathDate", value)}
showLunarToggle={false}
/>
</div>
</ChineseCardContent>
</ChineseCard>
</div>
{/* 地点和简介卡片 */}
<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" />
</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
id="birthPlace"
value={formData.birthPlace || ""}
onChange={(e) => handleChange("birthPlace", e.target.value)}
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
id="deathPlace"
value={formData.deathPlace || ""}
onChange={(e) => handleChange("deathPlace", e.target.value)}
placeholder="请输入逝世地"
/>
</div>
<div className="space-y-2">
<Label htmlFor="burialPlace"></Label>
<Input
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>
<div className="space-y-2">
<Label htmlFor="bio"></Label>
<Textarea
id="bio"
value={formData.bio || ""}
onChange={(e) => handleChange("bio", e.target.value)}
placeholder="请输入个人简介"
rows={4}
/>
</div>
</ChineseCardContent>
</ChineseCard>
</TabsContent>
{/* 生平影像Tab */}
<TabsContent value="photos" className="space-y-6 mt-6 w-full">
<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" />
</ChineseCardTitle>
<ChineseCardDescription></ChineseCardDescription>
</ChineseCardHeader>
<ChineseCardContent>
<PhotoGallery
photos={formData.photos}
photoIds={formData.photoIds}
onChange={handlePhotosChange}
/>
</ChineseCardContent>
</ChineseCard>
</TabsContent>
{/* 家族故事Tab */}
<TabsContent value="stories" className="space-y-6 mt-6 w-full">
<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" />
</ChineseCardTitle>
<ChineseCardDescription></ChineseCardDescription>
</ChineseCardHeader>
<ChineseCardContent>
<StoryManager
stories={formData.stories || []}
onChange={(stories) => handleChange("stories", stories)}
/>
</ChineseCardContent>
</ChineseCard>
<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" />
</ChineseCardTitle>
<ChineseCardDescription>便</ChineseCardDescription>
</ChineseCardHeader>
<ChineseCardContent className="space-y-4">
<div className="space-y-3">
<div className="flex gap-2">
<Input
placeholder="输入标签,按回车添加..."
onKeyDown={(e) => {
if (e.key === 'Enter') {
e.preventDefault()
const input = e.currentTarget
const value = input.value.trim()
if (value && !(formData.tags || []).includes(value)) {
handleChange('tags', [...(formData.tags || []), value])
input.value = ''
}
}
}}
/>
</div>
{formData.tags && formData.tags.length > 0 && (
<div className="flex flex-wrap gap-2">
{formData.tags.map((tag, index) => (
<span
key={index}
className="inline-flex items-center gap-1 px-2 py-1 bg-primary/10 text-primary text-sm rounded-md border border-primary/20"
>
{tag}
<button
type="button"
onClick={() => {
const newTags = formData.tags?.filter((_, i) => i !== index) || []
handleChange('tags', newTags)
}}
className="ml-1 hover:text-destructive"
>
×
</button>
</span>
))}
</div>
)}
</div>
</ChineseCardContent>
</ChineseCard>
</TabsContent>
</div>
</Tabs>
</form>
)
}