Files
chinese-family-tree-2/components/members/member-form.tsx
T
freedakgmail b7f3592db3 0.3.0.6
2025-11-30 14:00:42 +08:00

916 lines
37 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 { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/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("请填写正确的世系")
}
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,
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,
birthPlace: formData.birthPlace,
deathPlace: formData.deathPlace,
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="basic" 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="basic"></TabsTrigger>
<TabsTrigger value="family"></TabsTrigger>
<TabsTrigger value="details"></TabsTrigger>
<TabsTrigger value="media"></TabsTrigger>
</TabsList>
</div>
)}
<div className="flex-1 overflow-y-auto">
<TabsContent value="basic" 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">
<User className="h-5 w-5" />
</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div className="flex justify-center mb-4">
<ImageUpload
value={formData.avatarUrl}
onChange={(imageUrl) => handleChange("avatarUrl", imageUrl || null)}
/>
</div>
<div className="grid grid-cols-2 gap-4">
<div className="space-y-2">
<Label htmlFor="surname"></Label>
<Input
id="surname"
value={formData.surname}
onChange={(e) => handleChange("surname", e.target.value)}
/>
</div>
<div className="space-y-2">
<Label htmlFor="givenName"></Label>
<Input
id="givenName"
value={formData.givenName}
onChange={(e) => handleChange("givenName", e.target.value)}
/>
</div>
</div>
<div className="grid grid-cols-2 gap-4">
<div className="space-y-2">
<Label htmlFor="gender"></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"></Label>
<Input
id="generation"
type="number"
value={formData.generation}
onChange={(e) => handleChange("generation", Number.parseInt(e.target.value))}
/>
</div>
</div>
<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="occupation"></Label>
<Input
id="occupation"
value={formData.occupation || ""}
onChange={(e) => handleChange("occupation", e.target.value)}
placeholder="请输入职业"
/>
</div>
</CardContent>
</Card>
</TabsContent>
<TabsContent value="family" 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">
<Users className="h-5 w-5" />
</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<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 className="flex-1">
<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="flex-1"
/>
)}
</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 className="flex-1">
<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="flex-1"
/>
)}
</div>
)}
</div>
<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-2">
{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">
<span className="text-sm">
{index + 1}. <MemberNameWithStatus
name={spouse.fullName}
isDead={!!spouse.deathDate}
/> ({spouse.generation})
</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-2">
{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">
<span className="text-sm">
{index + 1}. <MemberNameWithStatus
name={child.fullName}
isDead={!!child.deathDate}
/> ({child.generation})
</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>
</CardContent>
</Card>
</TabsContent>
<TabsContent value="details" 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">
<Scroll className="h-5 w-5" />
</CardTitle>
</CardHeader>
<CardContent 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>
</CardContent>
</Card>
<Card className={`w-full ${founderCardClass}`}>
<CardHeader>
<CardTitle className="flex items-center gap-2 text-lg font-serif">
<CalendarIcon className="h-5 w-5" />
</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<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>
{(formData.birthDate || formData.deathDate) && (
<div className="space-y-2 pt-2">
<Label className="text-sm font-medium"></Label>
<RadioGroup
value={formData.isLunarDate ? "lunar" : "solar"}
onValueChange={(val) => handleChange("isLunarDate", val === "lunar")}
className="flex flex-row gap-6"
>
<div className="flex items-center space-x-2">
<RadioGroupItem value="solar" id="date-type-solar" />
<Label htmlFor="date-type-solar" className="text-sm font-normal cursor-pointer">
()
</Label>
</div>
<div className="flex items-center space-x-2">
<RadioGroupItem value="lunar" id="date-type-lunar" />
<Label htmlFor="date-type-lunar" className="text-sm font-normal cursor-pointer">
()
</Label>
</div>
</RadioGroup>
</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="deathPlace"></Label>
<Input
id="deathPlace"
value={formData.deathPlace || ""}
onChange={(e) => handleChange("deathPlace", 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 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>
</CardContent>
</Card>
</TabsContent>
<TabsContent value="media" 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">
<Images className="h-5 w-5" />
</CardTitle>
<CardDescription></CardDescription>
</CardHeader>
<CardContent>
<PhotoGallery
photos={formData.photos}
photoIds={formData.photoIds}
onChange={handlePhotosChange}
/>
</CardContent>
</Card>
<Card className={`w-full ${founderCardClass}`}>
<CardHeader>
<CardTitle className="flex items-center gap-2 text-lg font-serif">
<BookOpen className="h-5 w-5" />
</CardTitle>
<CardDescription></CardDescription>
</CardHeader>
<CardContent>
<StoryManager
stories={formData.stories || []}
onChange={(stories) => handleChange("stories", stories)}
/>
</CardContent>
</Card>
<Card className={`w-full ${founderCardClass}`}>
<CardHeader>
<CardTitle 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">
<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>
</CardContent>
</Card>
</TabsContent>
</div>
</Tabs>
</form>
)
}