0.0.9.0
This commit is contained in:
+190
-81
@@ -1,7 +1,7 @@
|
||||
"use client"
|
||||
|
||||
import Link from "next/link"
|
||||
import { BookOpen, Search, Settings, LogOut, User, ChevronDown, Plus, Trash2, Network, LayoutGrid, Calculator, HelpCircle, Calendar } from "lucide-react"
|
||||
import { BookOpen, Search, Settings, LogOut, User, ChevronDown, Plus, Trash2, Network, LayoutGrid, Calculator, HelpCircle, Calendar, Menu, X, Home, Users as UsersIcon, Clock, TreePine } from "lucide-react"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu"
|
||||
import { Sheet, SheetContent, SheetHeader, SheetTitle, SheetTrigger } from "@/components/ui/sheet"
|
||||
import { useFamily } from "@/context/family-context"
|
||||
import { useState, useRef, useEffect, useCallback } from "react"
|
||||
import { useRouter, usePathname, useSearchParams } from "next/navigation"
|
||||
@@ -44,6 +45,7 @@ export function SiteHeader() {
|
||||
const [treeDropdownOpen, setTreeDropdownOpen] = useState(false)
|
||||
const [currentDate, setCurrentDate] = useState(new Date())
|
||||
const [calendarDialogOpen, setCalendarDialogOpen] = useState(false)
|
||||
const [mobileMenuOpen, setMobileMenuOpen] = useState(false)
|
||||
const searchRef = useRef<HTMLDivElement>(null)
|
||||
const router = useRouter()
|
||||
|
||||
@@ -230,62 +232,158 @@ export function SiteHeader() {
|
||||
setShowResults(false)
|
||||
}
|
||||
|
||||
// 导航菜单项配置
|
||||
const navItems = [
|
||||
{ href: "/", label: "总览", icon: Home },
|
||||
{ href: "/tree", label: "族谱", icon: Network },
|
||||
{ href: "/members", label: "成员", icon: UsersIcon },
|
||||
{ href: "/timeline", label: "大事记", icon: Clock },
|
||||
]
|
||||
|
||||
// 判断当前路由是否激活
|
||||
const isActive = (href: string) => {
|
||||
if (href === "/") return pathname === "/"
|
||||
return pathname.startsWith(href)
|
||||
}
|
||||
|
||||
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">
|
||||
<Link href={getUrlWithTreeId("/")} className="flex items-center gap-2">
|
||||
<div className="flex h-8 w-8 items-center justify-center rounded bg-primary text-primary-foreground">
|
||||
<div className="container mx-auto flex h-16 items-center px-4 gap-4">
|
||||
{/* Logo */}
|
||||
<div className="flex items-center gap-2">
|
||||
<Link href={getUrlWithTreeId("/")} className="flex items-center gap-2 hover:opacity-80 transition-opacity">
|
||||
<div className="flex h-9 w-9 items-center justify-center rounded-lg bg-gradient-to-br from-primary to-primary/80 text-primary-foreground shadow-sm">
|
||||
<BookOpen className="h-5 w-5" />
|
||||
</div>
|
||||
<span className="text-xl font-serif font-bold tracking-tight">华夏家谱</span>
|
||||
<span className="text-xl font-serif font-bold tracking-tight bg-gradient-to-r from-foreground to-foreground/70 bg-clip-text">
|
||||
华夏家谱
|
||||
</span>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<nav className="hidden md:flex items-center gap-3 font-serif" onClick={() => setTreeDropdownOpen(false)}>
|
||||
<Link
|
||||
href={getUrlWithTreeId("/")}
|
||||
className="relative w-20 py-2.5 flex items-center justify-center group"
|
||||
style={{ filter: 'url(#rough-edge)' }}
|
||||
>
|
||||
<span className="relative z-10 text-amber-50 font-black text-base tracking-wider drop-shadow-sm">总览</span>
|
||||
<div className="absolute inset-0 bg-gradient-to-br from-red-700 via-red-600 to-red-700 rounded-sm transition-all group-hover:from-red-800 group-hover:via-red-700 group-hover:to-red-800 shadow-md" style={{ clipPath: 'polygon(1% 0%, 99% 1%, 100% 98%, 2% 99%, 0% 2%)' }}></div>
|
||||
<div className="absolute inset-0 border-2 border-red-900/40 rounded-sm" style={{ clipPath: 'polygon(0.5% 1%, 98.5% 0.5%, 99.5% 99%, 1.5% 98.5%, 1% 1.5%)' }}></div>
|
||||
<div className="absolute inset-0 bg-[radial-gradient(ellipse_at_top,_var(--tw-gradient-stops))] from-red-500/20 via-transparent to-transparent rounded-sm"></div>
|
||||
</Link>
|
||||
<Link
|
||||
href={getUrlWithTreeId("/tree")}
|
||||
className="relative w-20 py-2.5 flex items-center justify-center group"
|
||||
style={{ filter: 'url(#rough-edge)' }}
|
||||
>
|
||||
<span className="relative z-10 text-amber-50 font-black text-base tracking-wider drop-shadow-sm">族谱</span>
|
||||
<div className="absolute inset-0 bg-gradient-to-br from-red-700 via-red-600 to-red-700 rounded-sm transition-all group-hover:from-red-800 group-hover:via-red-700 group-hover:to-red-800 shadow-md" style={{ clipPath: 'polygon(0% 1%, 99% 0%, 100% 99%, 1% 100%, 1% 1%)' }}></div>
|
||||
<div className="absolute inset-0 border-2 border-red-900/40 rounded-sm" style={{ clipPath: 'polygon(1% 0.5%, 98% 1%, 99% 98.5%, 2% 99.5%, 0.5% 2%)' }}></div>
|
||||
<div className="absolute inset-0 bg-[radial-gradient(ellipse_at_top,_var(--tw-gradient-stops))] from-red-500/20 via-transparent to-transparent rounded-sm"></div>
|
||||
</Link>
|
||||
<Link
|
||||
href={getUrlWithTreeId("/members")}
|
||||
className="relative w-20 py-2.5 flex items-center justify-center group"
|
||||
style={{ filter: 'url(#rough-edge)' }}
|
||||
>
|
||||
<span className="relative z-10 text-amber-50 font-black text-base tracking-wider drop-shadow-sm">成员</span>
|
||||
<div className="absolute inset-0 bg-gradient-to-br from-red-700 via-red-600 to-red-700 rounded-sm transition-all group-hover:from-red-800 group-hover:via-red-700 group-hover:to-red-800 shadow-md" style={{ clipPath: 'polygon(1% 1%, 98% 0%, 99% 99%, 0% 98%, 2% 2%)' }}></div>
|
||||
<div className="absolute inset-0 border-2 border-red-900/40 rounded-sm" style={{ clipPath: 'polygon(0.5% 0%, 99% 1.5%, 98.5% 99%, 1% 98%, 1.5% 1%)' }}></div>
|
||||
<div className="absolute inset-0 bg-[radial-gradient(ellipse_at_top,_var(--tw-gradient-stops))] from-red-500/20 via-transparent to-transparent rounded-sm"></div>
|
||||
</Link>
|
||||
<Link
|
||||
href={getUrlWithTreeId("/timeline")}
|
||||
className="relative w-20 py-2.5 flex items-center justify-center group"
|
||||
style={{ filter: 'url(#rough-edge)' }}
|
||||
>
|
||||
<span className="relative z-10 text-amber-50 font-black text-base tracking-wider drop-shadow-sm">大事记</span>
|
||||
<div className="absolute inset-0 bg-gradient-to-br from-red-700 via-red-600 to-red-700 rounded-sm transition-all group-hover:from-red-800 group-hover:via-red-700 group-hover:to-red-800 shadow-md" style={{ clipPath: 'polygon(0% 0%, 100% 1%, 99% 100%, 1% 99%, 0.5% 1.5%)' }}></div>
|
||||
<div className="absolute inset-0 border-2 border-red-900/40 rounded-sm" style={{ clipPath: 'polygon(1.5% 1%, 98.5% 0%, 99.5% 98%, 0.5% 99.5%, 1% 2%)' }}></div>
|
||||
<div className="absolute inset-0 bg-[radial-gradient(ellipse_at_top,_var(--tw-gradient-stops))] from-red-500/20 via-transparent to-transparent rounded-sm"></div>
|
||||
</Link>
|
||||
{/* Desktop Navigation */}
|
||||
<nav className="hidden md:flex items-center gap-1 ml-6" onClick={() => setTreeDropdownOpen(false)}>
|
||||
{navItems.map((item) => {
|
||||
const Icon = item.icon
|
||||
const active = isActive(item.href)
|
||||
return (
|
||||
<Link
|
||||
key={item.href}
|
||||
href={getUrlWithTreeId(item.href)}
|
||||
className={`
|
||||
relative px-4 py-2 rounded-lg font-serif text-sm font-medium
|
||||
transition-all duration-200 flex items-center gap-2
|
||||
${active
|
||||
? 'text-primary bg-primary/10'
|
||||
: 'text-muted-foreground hover:text-foreground hover:bg-muted'
|
||||
}
|
||||
`}
|
||||
>
|
||||
<Icon className="h-4 w-4" />
|
||||
<span>{item.label}</span>
|
||||
{active && (
|
||||
<div className="absolute bottom-0 left-1/2 -translate-x-1/2 w-12 h-0.5 bg-primary rounded-full" />
|
||||
)}
|
||||
</Link>
|
||||
)
|
||||
})}
|
||||
</nav>
|
||||
|
||||
{/* Mobile Menu Button */}
|
||||
<Sheet open={mobileMenuOpen} onOpenChange={setMobileMenuOpen}>
|
||||
<SheetTrigger asChild className="md:hidden">
|
||||
<Button variant="ghost" size="icon" className="ml-auto md:ml-0">
|
||||
<Menu className="h-5 w-5" />
|
||||
<span className="sr-only">打开菜单</span>
|
||||
</Button>
|
||||
</SheetTrigger>
|
||||
<SheetContent side="left" className="w-[280px] sm:w-[320px]">
|
||||
<SheetHeader>
|
||||
<SheetTitle className="flex items-center gap-2 font-serif">
|
||||
<BookOpen className="h-5 w-5 text-primary" />
|
||||
华夏家谱
|
||||
</SheetTitle>
|
||||
</SheetHeader>
|
||||
<div className="flex flex-col gap-4 mt-8">
|
||||
{/* Mobile Navigation Links */}
|
||||
<div className="flex flex-col gap-1">
|
||||
{navItems.map((item) => {
|
||||
const Icon = item.icon
|
||||
const active = isActive(item.href)
|
||||
return (
|
||||
<Link
|
||||
key={item.href}
|
||||
href={getUrlWithTreeId(item.href)}
|
||||
onClick={() => setMobileMenuOpen(false)}
|
||||
className={`
|
||||
flex items-center gap-3 px-4 py-3 rounded-lg font-serif
|
||||
transition-all duration-200
|
||||
${active
|
||||
? 'text-primary bg-primary/10 font-medium'
|
||||
: 'text-muted-foreground hover:text-foreground hover:bg-muted'
|
||||
}
|
||||
`}
|
||||
>
|
||||
<Icon className="h-5 w-5" />
|
||||
<span>{item.label}</span>
|
||||
</Link>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
|
||||
{/* Mobile Utility Links */}
|
||||
<div className="border-t pt-4 flex flex-col gap-1">
|
||||
<Link
|
||||
href={getUrlWithTreeId("/relationship")}
|
||||
onClick={() => setMobileMenuOpen(false)}
|
||||
className="flex items-center gap-3 px-4 py-3 rounded-lg font-serif text-muted-foreground hover:text-foreground hover:bg-muted transition-all"
|
||||
>
|
||||
<Calculator className="h-5 w-5" />
|
||||
<span>关系计算</span>
|
||||
</Link>
|
||||
<Link
|
||||
href="/settings"
|
||||
onClick={() => setMobileMenuOpen(false)}
|
||||
className="flex items-center gap-3 px-4 py-3 rounded-lg font-serif text-muted-foreground hover:text-foreground hover:bg-muted transition-all"
|
||||
>
|
||||
<Settings className="h-5 w-5" />
|
||||
<span>系统设置</span>
|
||||
</Link>
|
||||
<Link
|
||||
href="/help"
|
||||
onClick={() => setMobileMenuOpen(false)}
|
||||
className="flex items-center gap-3 px-4 py-3 rounded-lg font-serif text-muted-foreground hover:text-foreground hover:bg-muted transition-all"
|
||||
>
|
||||
<HelpCircle className="h-5 w-5" />
|
||||
<span>帮助中心</span>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
{/* Mobile User Section */}
|
||||
{session && (
|
||||
<div className="border-t pt-4">
|
||||
<div className="px-4 py-2 text-sm text-muted-foreground">
|
||||
<div className="font-medium text-foreground">{session.user?.name || "用户"}</div>
|
||||
<div className="text-xs">{session.user?.email}</div>
|
||||
</div>
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={() => {
|
||||
setMobileMenuOpen(false)
|
||||
handleSignOut()
|
||||
}}
|
||||
className="w-full justify-start gap-3 px-4 py-3 text-red-600 hover:text-red-700 hover:bg-red-50"
|
||||
>
|
||||
<LogOut className="h-5 w-5" />
|
||||
<span>退出登录</span>
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</SheetContent>
|
||||
</Sheet>
|
||||
|
||||
{/* 家族树选择器 */}
|
||||
{session && familyTrees.length > 0 && (
|
||||
<div className="ml-4 flex-shrink-0">
|
||||
@@ -351,15 +449,16 @@ export function SiteHeader() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="ml-auto flex items-center gap-4">
|
||||
<div className="relative hidden sm:block" ref={searchRef}>
|
||||
<Search className="absolute left-2.5 top-2.5 h-4 w-4 text-muted-foreground" />
|
||||
<div className="ml-auto flex items-center gap-2">
|
||||
{/* Search Bar */}
|
||||
<div className="relative hidden lg:block" ref={searchRef}>
|
||||
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
|
||||
<Input
|
||||
type="search"
|
||||
placeholder="搜索成员..."
|
||||
value={searchQuery}
|
||||
onChange={(e) => handleSearch(e.target.value)}
|
||||
className="h-9 w-64 rounded-full bg-muted pl-9 text-sm focus-visible:ring-primary"
|
||||
className="h-9 w-56 xl:w-64 rounded-lg bg-muted/50 pl-9 text-sm border-0 focus-visible:ring-1 focus-visible:ring-primary/50"
|
||||
/>
|
||||
|
||||
{/* 搜索结果下拉框 */}
|
||||
@@ -406,48 +505,52 @@ export function SiteHeader() {
|
||||
{/* 日期显示 */}
|
||||
<button
|
||||
onClick={() => setCalendarDialogOpen(true)}
|
||||
className="hidden lg:flex items-center gap-2 px-3 py-1.5 rounded-lg bg-gradient-to-br from-red-50 to-amber-50 border border-red-200/50 shadow-sm hover:shadow-md hover:from-red-100 hover:to-amber-100 transition-all cursor-pointer"
|
||||
className="hidden xl:flex items-center gap-2 px-3 py-1.5 rounded-lg bg-gradient-to-br from-red-50/80 to-amber-50/80 dark:from-red-950/20 dark:to-amber-950/20 border border-red-200/50 dark:border-red-800/30 hover:shadow-sm transition-all cursor-pointer"
|
||||
title="点击查询公历农历"
|
||||
>
|
||||
<Calendar className="h-4 w-4 text-red-600" />
|
||||
<Calendar className="h-4 w-4 text-red-600 dark:text-red-400" />
|
||||
<div className="flex flex-col text-xs leading-tight">
|
||||
<span className="font-medium text-gray-900">{solarDateStr}</span>
|
||||
<span className="text-red-700 font-serif">{lunarDateStr}</span>
|
||||
<span className="font-medium text-gray-900 dark:text-gray-100">{solarDateStr}</span>
|
||||
<span className="text-red-700 dark:text-red-400 font-serif">{lunarDateStr}</span>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
<Link href={getUrlWithTreeId("/relationship")}>
|
||||
<Button variant="ghost" size="icon" className="text-muted-foreground">
|
||||
<Calculator className="h-5 w-5" />
|
||||
<span className="sr-only">Relationship Calculator</span>
|
||||
</Button>
|
||||
</Link>
|
||||
{/* Utility Buttons - Desktop Only */}
|
||||
<div className="hidden md:flex items-center gap-1">
|
||||
<Link href={getUrlWithTreeId("/relationship")}>
|
||||
<Button variant="ghost" size="icon" className="text-muted-foreground hover:text-foreground hover:bg-muted" title="关系计算">
|
||||
<Calculator className="h-5 w-5" />
|
||||
<span className="sr-only">关系计算</span>
|
||||
</Button>
|
||||
</Link>
|
||||
|
||||
<Link href="/settings">
|
||||
<Button variant="ghost" size="icon" className="text-muted-foreground">
|
||||
<Settings className="h-5 w-5" />
|
||||
<span className="sr-only">Settings</span>
|
||||
</Button>
|
||||
</Link>
|
||||
<Link href="/settings">
|
||||
<Button variant="ghost" size="icon" className="text-muted-foreground hover:text-foreground hover:bg-muted" title="系统设置">
|
||||
<Settings className="h-5 w-5" />
|
||||
<span className="sr-only">系统设置</span>
|
||||
</Button>
|
||||
</Link>
|
||||
|
||||
<Link href="/help">
|
||||
<Button variant="ghost" size="icon" className="text-muted-foreground">
|
||||
<HelpCircle className="h-5 w-5" />
|
||||
<span className="sr-only">Help</span>
|
||||
</Button>
|
||||
</Link>
|
||||
<Link href="/help">
|
||||
<Button variant="ghost" size="icon" className="text-muted-foreground hover:text-foreground hover:bg-muted" title="帮助中心">
|
||||
<HelpCircle className="h-5 w-5" />
|
||||
<span className="sr-only">帮助中心</span>
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
{/* User Menu */}
|
||||
{status === "loading" ? (
|
||||
<Avatar className="h-8 w-8 border border-border">
|
||||
<AvatarFallback>...</AvatarFallback>
|
||||
</Avatar>
|
||||
<div className="h-9 w-9 rounded-full bg-muted animate-pulse" />
|
||||
) : session ? (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="ghost" className="relative h-8 w-8 rounded-full">
|
||||
<Avatar className="h-8 w-8 border border-border">
|
||||
<Button variant="ghost" className="relative h-9 w-9 rounded-full hover:bg-muted">
|
||||
<Avatar className="h-9 w-9 border-2 border-border">
|
||||
<AvatarImage src={undefined} alt={session.user?.name || "User"} />
|
||||
<AvatarFallback>{getUserInitial()}</AvatarFallback>
|
||||
<AvatarFallback className="bg-primary/10 text-primary font-serif">
|
||||
{getUserInitial()}
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
@@ -467,8 +570,14 @@ export function SiteHeader() {
|
||||
<span>系统设置</span>
|
||||
</Link>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem asChild>
|
||||
<Link href="/help" className="cursor-pointer">
|
||||
<HelpCircle className="mr-2 h-4 w-4" />
|
||||
<span>帮助中心</span>
|
||||
</Link>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem onClick={handleSignOut} className="cursor-pointer text-red-600">
|
||||
<DropdownMenuItem onClick={handleSignOut} className="cursor-pointer text-red-600 focus:text-red-600 focus:bg-red-50">
|
||||
<LogOut className="mr-2 h-4 w-4" />
|
||||
<span>退出登录</span>
|
||||
</DropdownMenuItem>
|
||||
@@ -476,7 +585,7 @@ export function SiteHeader() {
|
||||
</DropdownMenu>
|
||||
) : (
|
||||
<Link href="/auth/signin">
|
||||
<Button variant="default" size="sm">
|
||||
<Button variant="default" size="sm" className="font-serif">
|
||||
登录
|
||||
</Button>
|
||||
</Link>
|
||||
|
||||
Reference in New Issue
Block a user