Files
chinese-family-tree-2/components/members/member-form.tsx
T
freedakgmail 0326894b09 0.3.1.3
2025-11-30 20:00:18 +08:00

966 lines
41 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)
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("请填写正确的世系")
}
// 注:允许孤立成员存在,可以后续通过族谱关联功能建立关系
// 这样可以修正错误的关系设置
setErrors(newErrors)
return newErrors.length === 0
}
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault()
if (!validateForm()) {
return
}
// 确保所有必填字段都有值
// 注意:fatherId/motherId 需要显式设为 null 而不是 undefined
// 否则 JSON.stringify 会忽略 undefined 值,导致后端收不到更新
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) return true
return m.gender === "FEMALE" && 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.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>
)
}