0.0.1.2
This commit is contained in:
+87
-41
@@ -17,17 +17,20 @@ import { useFamily } from "@/context/family-context"
|
||||
import { useState, useRef, useEffect, useCallback } from "react"
|
||||
import { useRouter } from "next/navigation"
|
||||
import { useSession, signOut } from "next-auth/react"
|
||||
import { useDialog } from "@/components/ui/alert-dialog-custom"
|
||||
|
||||
interface FamilyTree {
|
||||
id: string
|
||||
name: string
|
||||
description: string | null
|
||||
ownerId: string
|
||||
currentUserRole?: "OWNER" | "EDITOR" | "VIEWER"
|
||||
}
|
||||
|
||||
export function SiteHeader() {
|
||||
const { searchMembers, searchResults } = useFamily()
|
||||
const { data: session, status } = useSession()
|
||||
const { showAlert, showConfirm, showPrompt } = useDialog()
|
||||
const [searchQuery, setSearchQuery] = useState("")
|
||||
const [showResults, setShowResults] = useState(false)
|
||||
const [familyTrees, setFamilyTrees] = useState<FamilyTree[]>([])
|
||||
@@ -36,6 +39,27 @@ export function SiteHeader() {
|
||||
const searchRef = useRef<HTMLDivElement>(null)
|
||||
const router = useRouter()
|
||||
|
||||
// 生成带有 treeId 的 URL
|
||||
const getUrlWithTreeId = (path: string) => {
|
||||
if (currentTree?.id) {
|
||||
return `${path}?treeId=${currentTree.id}`
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
const getRoleBadge = (role?: string) => {
|
||||
switch (role) {
|
||||
case "OWNER":
|
||||
return <span className="text-[10px] px-1.5 py-0.5 rounded bg-purple-100 text-purple-700 border border-purple-200 ml-2">所有者</span>
|
||||
case "EDITOR":
|
||||
return <span className="text-[10px] px-1.5 py-0.5 rounded bg-blue-100 text-blue-700 border border-blue-200 ml-2">编辑</span>
|
||||
case "VIEWER":
|
||||
return <span className="text-[10px] px-1.5 py-0.5 rounded bg-gray-100 text-gray-700 border border-gray-200 ml-2">查看</span>
|
||||
default:
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
const handleSignOut = async () => {
|
||||
await signOut({ callbackUrl: "/auth/signin" })
|
||||
}
|
||||
@@ -57,19 +81,24 @@ export function SiteHeader() {
|
||||
|
||||
// 检查权限
|
||||
if (tree.ownerId !== session?.user?.id) {
|
||||
alert('只有家族树的创建者才能删除')
|
||||
await showAlert('只有家族树的创建者才能删除', '权限不足')
|
||||
return
|
||||
}
|
||||
|
||||
const confirmText = `确定要删除家族树"${tree.name}"吗?\n\n此操作将永久删除所有成员、活动日志和协作者数据,且不可撤销!`
|
||||
if (!confirm(confirmText)) {
|
||||
const confirmed = await showConfirm(confirmText, '删除家族树', 'destructive')
|
||||
if (!confirmed) {
|
||||
return
|
||||
}
|
||||
|
||||
// 二次确认
|
||||
const finalConfirm = prompt(`请输入家族树名称"${tree.name}"以确认删除:`)
|
||||
const finalConfirm = await showPrompt(
|
||||
`请输入家族树名称"${tree.name}"以确认删除:`,
|
||||
'',
|
||||
'确认删除'
|
||||
)
|
||||
if (finalConfirm !== tree.name) {
|
||||
alert('名称不匹配,删除已取消')
|
||||
await showAlert('名称不匹配,删除已取消', '操作已取消')
|
||||
return
|
||||
}
|
||||
|
||||
@@ -93,7 +122,7 @@ export function SiteHeader() {
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('删除家族树失败:', error)
|
||||
alert('删除失败:' + (error as Error).message)
|
||||
await showAlert('删除失败:' + (error as Error).message, '错误')
|
||||
} finally {
|
||||
setDeletingTreeId(null)
|
||||
}
|
||||
@@ -101,37 +130,49 @@ export function SiteHeader() {
|
||||
|
||||
// 获取家族树列表
|
||||
const loadFamilyTrees = useCallback(() => {
|
||||
if (session?.user?.id) {
|
||||
fetch('/api/trees')
|
||||
.then(res => {
|
||||
if (!res.ok) {
|
||||
throw new Error(`HTTP ${res.status}`)
|
||||
}
|
||||
return res.json()
|
||||
})
|
||||
.then(data => {
|
||||
if (data.trees) {
|
||||
setFamilyTrees(data.trees)
|
||||
// 如果 URL 中有 treeId,使用该树,否则使用第一个
|
||||
const urlParams = new URLSearchParams(window.location.search)
|
||||
const treeId = urlParams.get('treeId')
|
||||
if (treeId) {
|
||||
const tree = data.trees.find((t: FamilyTree) => t.id === treeId)
|
||||
if (tree) {
|
||||
setCurrentTree(tree)
|
||||
} else if (data.trees.length > 0) {
|
||||
setCurrentTree(data.trees[0])
|
||||
}
|
||||
// 只在 session 加载完成且用户已登录时才获取
|
||||
if (status === 'loading') {
|
||||
return
|
||||
}
|
||||
|
||||
if (!session?.user?.id) {
|
||||
setFamilyTrees([])
|
||||
setCurrentTree(null)
|
||||
return
|
||||
}
|
||||
|
||||
fetch('/api/trees')
|
||||
.then(res => {
|
||||
if (!res.ok) {
|
||||
throw new Error(`HTTP ${res.status}`)
|
||||
}
|
||||
return res.json()
|
||||
})
|
||||
.then(data => {
|
||||
if (data.trees) {
|
||||
setFamilyTrees(data.trees)
|
||||
// 如果 URL 中有 treeId,使用该树,否则使用第一个
|
||||
const urlParams = new URLSearchParams(window.location.search)
|
||||
const treeId = urlParams.get('treeId')
|
||||
if (treeId) {
|
||||
const tree = data.trees.find((t: FamilyTree) => t.id === treeId)
|
||||
if (tree) {
|
||||
setCurrentTree(tree)
|
||||
} else if (data.trees.length > 0) {
|
||||
setCurrentTree(data.trees[0])
|
||||
}
|
||||
} else if (data.trees.length > 0) {
|
||||
setCurrentTree(data.trees[0])
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
// 只在非 abort 错误时才记录
|
||||
if (err.name !== 'AbortError') {
|
||||
console.error('获取家族树列表失败:', err)
|
||||
})
|
||||
}
|
||||
}, [session?.user?.id])
|
||||
}
|
||||
})
|
||||
}, [session?.user?.id, status])
|
||||
|
||||
useEffect(() => {
|
||||
loadFamilyTrees()
|
||||
@@ -198,11 +239,16 @@ export function SiteHeader() {
|
||||
key={tree.id}
|
||||
onClick={() => {
|
||||
setCurrentTree(tree)
|
||||
router.push(`/?treeId=${tree.id}`)
|
||||
// 保持当前路径,只更新 treeId 参数
|
||||
const currentPath = window.location.pathname
|
||||
router.push(`${currentPath}?treeId=${tree.id}`)
|
||||
}}
|
||||
className="cursor-pointer flex items-center justify-between group"
|
||||
>
|
||||
<span className="flex-1 truncate">{tree.name}</span>
|
||||
<div className="flex items-center overflow-hidden">
|
||||
<span className="truncate">{tree.name}</span>
|
||||
{getRoleBadge(tree.currentUserRole)}
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
{currentTree?.id === tree.id && (
|
||||
<span className="text-primary">✓</span>
|
||||
@@ -236,18 +282,18 @@ export function SiteHeader() {
|
||||
|
||||
<nav className="hidden md:flex items-center gap-6 text-sm font-medium text-muted-foreground">
|
||||
<Link
|
||||
href="/"
|
||||
href={getUrlWithTreeId("/")}
|
||||
className="transition-colors hover:text-foreground data-[active=true]:text-foreground data-[active=true]:font-semibold"
|
||||
>
|
||||
总览 (Overview)
|
||||
</Link>
|
||||
<Link href="/tree" className="transition-colors hover:text-foreground">
|
||||
<Link href={getUrlWithTreeId("/tree")} className="transition-colors hover:text-foreground">
|
||||
族谱 (Tree)
|
||||
</Link>
|
||||
<Link href="/members" className="transition-colors hover:text-foreground">
|
||||
<Link href={getUrlWithTreeId("/members")} className="transition-colors hover:text-foreground">
|
||||
成员 (Members)
|
||||
</Link>
|
||||
<Link href="/timeline" className="transition-colors hover:text-foreground">
|
||||
<Link href={getUrlWithTreeId("/timeline")} className="transition-colors hover:text-foreground">
|
||||
大事记 (Timeline)
|
||||
</Link>
|
||||
</nav>
|
||||
@@ -276,8 +322,8 @@ export function SiteHeader() {
|
||||
<div className="flex-1">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="font-medium">{member.fullName}</span>
|
||||
<span className={`text-xs px-1.5 py-0.5 rounded ${member.gender === "male" ? "bg-blue-100 text-blue-700" : "bg-pink-100 text-pink-700"}`}>
|
||||
{member.gender === "male" ? "男" : "女"}
|
||||
<span className={`text-xs px-1.5 py-0.5 rounded ${member.gender === "MALE" ? "bg-blue-100 text-blue-700" : "bg-pink-100 text-pink-700"}`}>
|
||||
{member.gender === "MALE" ? "男" : "女"}
|
||||
</span>
|
||||
<span className="text-xs text-muted-foreground">第{member.generation}世</span>
|
||||
</div>
|
||||
@@ -338,8 +384,8 @@ export function SiteHeader() {
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem asChild>
|
||||
<Link href="/settings" className="cursor-pointer">
|
||||
<User className="mr-2 h-4 w-4" />
|
||||
<span>个人设置</span>
|
||||
<Settings className="mr-2 h-4 w-4" />
|
||||
<span>系统设置</span>
|
||||
</Link>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
|
||||
Reference in New Issue
Block a user