0.0.1.0
This commit is contained in:
+168
-11
@@ -1,19 +1,95 @@
|
||||
"use client"
|
||||
|
||||
import Link from "next/link"
|
||||
import { BookOpen, Search, Bell, Settings } from "lucide-react"
|
||||
import { BookOpen, Search, Bell, Settings, LogOut, User, ChevronDown, Plus } from "lucide-react"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu"
|
||||
import { useFamily } from "@/context/family-context"
|
||||
import { useState, useRef, useEffect } from "react"
|
||||
import { useState, useRef, useEffect, useCallback } from "react"
|
||||
import { useRouter } from "next/navigation"
|
||||
import { useSession, signOut } from "next-auth/react"
|
||||
|
||||
interface FamilyTree {
|
||||
id: string
|
||||
name: string
|
||||
description: string | null
|
||||
}
|
||||
|
||||
export function SiteHeader() {
|
||||
const { searchMembers, searchResults } = useFamily()
|
||||
const { data: session, status } = useSession()
|
||||
const [searchQuery, setSearchQuery] = useState("")
|
||||
const [showResults, setShowResults] = useState(false)
|
||||
const [familyTrees, setFamilyTrees] = useState<FamilyTree[]>([])
|
||||
const [currentTree, setCurrentTree] = useState<FamilyTree | null>(null)
|
||||
const searchRef = useRef<HTMLDivElement>(null)
|
||||
const router = useRouter()
|
||||
|
||||
const handleSignOut = async () => {
|
||||
await signOut({ callbackUrl: "/auth/signin" })
|
||||
}
|
||||
|
||||
// 获取用户名首字母作为头像
|
||||
const getUserInitial = () => {
|
||||
if (session?.user?.name) {
|
||||
return session.user.name.charAt(0).toUpperCase()
|
||||
}
|
||||
if (session?.user?.email) {
|
||||
return session.user.email.charAt(0).toUpperCase()
|
||||
}
|
||||
return "U"
|
||||
}
|
||||
|
||||
// 获取家族树列表
|
||||
const loadFamilyTrees = useCallback(() => {
|
||||
if (session?.user?.id) {
|
||||
fetch('/api/trees')
|
||||
.then(res => 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 => console.error('获取家族树失败:', err))
|
||||
}
|
||||
}, [session?.user?.id])
|
||||
|
||||
useEffect(() => {
|
||||
loadFamilyTrees()
|
||||
}, [loadFamilyTrees])
|
||||
|
||||
// 监听路由变化,刷新家族树列表
|
||||
useEffect(() => {
|
||||
const handleRouteChange = () => {
|
||||
loadFamilyTrees()
|
||||
}
|
||||
|
||||
window.addEventListener('focus', handleRouteChange)
|
||||
return () => window.removeEventListener('focus', handleRouteChange)
|
||||
}, [loadFamilyTrees])
|
||||
|
||||
// 点击外部关闭搜索结果
|
||||
useEffect(() => {
|
||||
function handleClickOutside(event: MouseEvent) {
|
||||
@@ -40,11 +116,52 @@ export function SiteHeader() {
|
||||
return (
|
||||
<header className="sticky top-0 z-50 w-full border-b border-border/40 bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60">
|
||||
<div className="container mx-auto flex h-16 items-center px-4">
|
||||
<div className="mr-8 flex items-center gap-2">
|
||||
<div className="flex h-8 w-8 items-center justify-center rounded bg-primary text-primary-foreground">
|
||||
<BookOpen className="h-5 w-5" />
|
||||
</div>
|
||||
<span className="text-xl font-serif font-bold tracking-tight">华夏谱 (HuaXiaPu)</span>
|
||||
<div className="mr-8 flex items-center gap-4">
|
||||
<Link href="/" className="flex items-center gap-2">
|
||||
<div className="flex h-8 w-8 items-center justify-center rounded bg-primary text-primary-foreground">
|
||||
<BookOpen className="h-5 w-5" />
|
||||
</div>
|
||||
<span className="text-xl font-serif font-bold tracking-tight">华夏谱</span>
|
||||
</Link>
|
||||
|
||||
{/* 家族树选择器 */}
|
||||
{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>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="start" className="w-56">
|
||||
<DropdownMenuLabel>我的家族树</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
{familyTrees.map((tree) => (
|
||||
<DropdownMenuItem
|
||||
key={tree.id}
|
||||
onClick={() => {
|
||||
setCurrentTree(tree)
|
||||
router.push(`/?treeId=${tree.id}`)
|
||||
}}
|
||||
className="cursor-pointer"
|
||||
>
|
||||
<span className="flex-1 truncate">{tree.name}</span>
|
||||
{currentTree?.id === tree.id && (
|
||||
<span className="text-primary">✓</span>
|
||||
)}
|
||||
</DropdownMenuItem>
|
||||
))}
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem
|
||||
onClick={() => router.push('/trees/new')}
|
||||
className="cursor-pointer text-primary"
|
||||
>
|
||||
<Plus className="h-4 w-4 mr-2" />
|
||||
创建新家族树
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<nav className="hidden md:flex items-center gap-6 text-sm font-medium text-muted-foreground">
|
||||
@@ -125,10 +242,50 @@ export function SiteHeader() {
|
||||
</Button>
|
||||
</Link>
|
||||
|
||||
<Avatar className="h-8 w-8 border border-border">
|
||||
<AvatarImage src="/placeholder-user.jpg" alt="User" />
|
||||
<AvatarFallback>李</AvatarFallback>
|
||||
</Avatar>
|
||||
{status === "loading" ? (
|
||||
<Avatar className="h-8 w-8 border border-border">
|
||||
<AvatarFallback>...</AvatarFallback>
|
||||
</Avatar>
|
||||
) : session ? (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="ghost" className="relative h-8 w-8 rounded-full">
|
||||
<Avatar className="h-8 w-8 border border-border">
|
||||
<AvatarImage src={undefined} alt={session.user?.name || "User"} />
|
||||
<AvatarFallback>{getUserInitial()}</AvatarFallback>
|
||||
</Avatar>
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent className="w-56" align="end" forceMount>
|
||||
<DropdownMenuLabel className="font-normal">
|
||||
<div className="flex flex-col space-y-1">
|
||||
<p className="text-sm font-medium leading-none">{session.user?.name || "用户"}</p>
|
||||
<p className="text-xs leading-none text-muted-foreground">
|
||||
{session.user?.email}
|
||||
</p>
|
||||
</div>
|
||||
</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem asChild>
|
||||
<Link href="/settings" className="cursor-pointer">
|
||||
<User className="mr-2 h-4 w-4" />
|
||||
<span>个人设置</span>
|
||||
</Link>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem onClick={handleSignOut} className="cursor-pointer text-red-600">
|
||||
<LogOut className="mr-2 h-4 w-4" />
|
||||
<span>退出登录</span>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
) : (
|
||||
<Link href="/auth/signin">
|
||||
<Button variant="default" size="sm">
|
||||
登录
|
||||
</Button>
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
Reference in New Issue
Block a user