This commit is contained in:
freedakgmail
2025-11-23 08:04:43 +08:00
parent e92884ae53
commit 81dbf709aa
95 changed files with 166729 additions and 2241 deletions
+81 -11
View File
@@ -1,7 +1,7 @@
"use client"
import Link from "next/link"
import { BookOpen, Search, Bell, Settings, LogOut, User, ChevronDown, Plus } from "lucide-react"
import { BookOpen, Search, Bell, Settings, LogOut, User, ChevronDown, Plus, Trash2 } from "lucide-react"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
@@ -22,6 +22,7 @@ interface FamilyTree {
id: string
name: string
description: string | null
ownerId: string
}
export function SiteHeader() {
@@ -31,6 +32,7 @@ export function SiteHeader() {
const [showResults, setShowResults] = useState(false)
const [familyTrees, setFamilyTrees] = useState<FamilyTree[]>([])
const [currentTree, setCurrentTree] = useState<FamilyTree | null>(null)
const [deletingTreeId, setDeletingTreeId] = useState<string | null>(null)
const searchRef = useRef<HTMLDivElement>(null)
const router = useRouter()
@@ -49,11 +51,64 @@ export function SiteHeader() {
return "U"
}
// 删除家族树
const handleDeleteTree = async (tree: FamilyTree, e: React.MouseEvent) => {
e.stopPropagation() // 阻止触发选择事件
// 检查权限
if (tree.ownerId !== session?.user?.id) {
alert('只有家族树的创建者才能删除')
return
}
const confirmText = `确定要删除家族树"${tree.name}"吗?\n\n此操作将永久删除所有成员、活动日志和协作者数据,且不可撤销!`
if (!confirm(confirmText)) {
return
}
// 二次确认
const finalConfirm = prompt(`请输入家族树名称"${tree.name}"以确认删除:`)
if (finalConfirm !== tree.name) {
alert('名称不匹配,删除已取消')
return
}
setDeletingTreeId(tree.id)
try {
const response = await fetch(`/api/trees/${tree.id}`, {
method: 'DELETE',
})
if (!response.ok) {
const error = await response.json()
throw new Error(error.error || '删除失败')
}
// 刷新家族树列表
await loadFamilyTrees()
// 如果删除的是当前树,跳转到首页
if (currentTree?.id === tree.id) {
window.location.href = '/'
}
} catch (error) {
console.error('删除家族树失败:', error)
alert('删除失败:' + (error as Error).message)
} finally {
setDeletingTreeId(null)
}
}
// 获取家族树列表
const loadFamilyTrees = useCallback(() => {
if (session?.user?.id) {
fetch('/api/trees')
.then(res => res.json())
.then(res => {
if (!res.ok) {
throw new Error(`HTTP ${res.status}`)
}
return res.json()
})
.then(data => {
if (data.trees) {
setFamilyTrees(data.trees)
@@ -72,7 +127,9 @@ export function SiteHeader() {
}
}
})
.catch(err => console.error('获取家族树失败:', err))
.catch(err => {
console.error('获取家族树列表失败:', err)
})
}
}, [session?.user?.id])
@@ -128,12 +185,12 @@ export function SiteHeader() {
{session && familyTrees.length > 0 && (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" className="gap-2">
<span className="max-w-[150px] truncate">{currentTree?.name || '选择家族树'}</span>
<ChevronDown className="h-4 w-4" />
<Button variant="outline" className="gap-2 min-w-[200px]">
<span className="flex-1 truncate text-left">{currentTree?.name || '选择家族树'}</span>
<ChevronDown className="h-4 w-4 flex-shrink-0" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="start" className="w-56">
<DropdownMenuContent align="start" className="w-[var(--radix-dropdown-menu-trigger-width)] min-w-[200px]">
<DropdownMenuLabel></DropdownMenuLabel>
<DropdownMenuSeparator />
{familyTrees.map((tree) => (
@@ -143,12 +200,25 @@ export function SiteHeader() {
setCurrentTree(tree)
router.push(`/?treeId=${tree.id}`)
}}
className="cursor-pointer"
className="cursor-pointer flex items-center justify-between group"
>
<span className="flex-1 truncate">{tree.name}</span>
{currentTree?.id === tree.id && (
<span className="text-primary"></span>
)}
<div className="flex items-center gap-2">
{currentTree?.id === tree.id && (
<span className="text-primary"></span>
)}
{tree.ownerId === session?.user?.id && (
<Button
variant="ghost"
size="sm"
className="h-6 w-6 p-0 opacity-0 group-hover:opacity-100 transition-opacity hover:bg-destructive hover:text-destructive-foreground"
onClick={(e) => handleDeleteTree(tree, e)}
disabled={deletingTreeId === tree.id}
>
<Trash2 className="h-3 w-3" />
</Button>
)}
</div>
</DropdownMenuItem>
))}
<DropdownMenuSeparator />