Hair Keeper v1.0.0:一个高度集成、深度定制、约定优于配置的全栈Web应用模板,旨在保持灵活性的同时提供一套基于成熟架构的开发底座,自带身份认证、权限控制、丰富前端组件、文件上传、后台任务、智能体开发等丰富功能,提供AI开发辅助,免于纠结功能如何实现,可快速上手专注于业务逻辑

This commit is contained in:
2025-11-13 15:24:54 +08:00
commit 42be39b343
249 changed files with 38843 additions and 0 deletions

View File

@@ -0,0 +1,134 @@
'use client'
import { useState } from 'react'
import { User, LogOut, KeyRound } from 'lucide-react'
import { DevPanel } from '@/app/(main)/dev/panel'
import { ChangePasswordDialog } from '@/components/layout/change-password-dialog'
import { UserProfileDialog } from '@/components/layout/user-profile-dialog'
import { Button } from '@/components/ui/button'
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu'
import { Badge } from '@/components/ui/badge'
import { Separator } from '@/components/ui/separator'
import { SidebarTrigger } from '@/components/ui/sidebar'
import { signOut } from 'next-auth/react'
import { useRouter, usePathname } from 'next/navigation'
import { useTheme } from 'next-themes'
import { ThemeToggleButton, useThemeTransition } from '@/components/common/theme-toggle-button'
import { getMenuTitle } from '@/constants/menu'
import type { User as AppUser } from '@/types/user'
interface HeaderProps {
user?: AppUser
}
export function Header({ user }: HeaderProps) {
const router = useRouter()
const pathname = usePathname()
const { theme, setTheme } = useTheme()
const { startTransition } = useThemeTransition()
const [isChangePasswordOpen, setIsChangePasswordOpen] = useState(false)
const [isUserProfileOpen, setIsUserProfileOpen] = useState(false)
const pageTitle = getMenuTitle(pathname, 2) // 只匹配到第二级菜单
const handleThemeToggle = () => {
startTransition(() => {
setTheme(theme === 'dark' ? 'light' : 'dark')
})
}
const handleLogout = async () => {
await signOut({ redirect: false })
router.push('/login')
}
// 如果没有用户信息不显示Header应该被中间件重定向
if (!user) {
return null
}
return (
<header className="flex h-16 shrink-0 items-center gap-2 border-b px-4">
<SidebarTrigger />
<Separator orientation="vertical" className="mr-2 h-4" />
<div className="flex items-center justify-between flex-1">
<h2 className="text-lg font-semibold">
{pageTitle}
</h2>
<div className="flex items-center space-x-4">
{/* 主题切换按钮 */}
<ThemeToggleButton
theme={theme === 'dark' ? 'dark' : 'light'}
variant="polygon"
onClick={handleThemeToggle}
className="border-0 shadow-none"
/>
{/* 开发者工具按钮 - 仅开发环境 */}
{process.env.NODE_ENV === 'development' && user.isSuperAdmin && <DevPanel />}
{/* 用户菜单 */}
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" className="flex items-center space-x-2 px-3">
<div className="w-8 h-8 bg-primary/10 rounded-full flex items-center justify-center">
<User className="h-4 w-4 text-primary" />
</div>
<div className="text-left hidden sm:block">
<p className="text-sm font-medium">{user.name}</p>
<p className="text-xs text-muted-foreground">
{user.isSuperAdmin ? '超级管理员' : (Array.isArray(user.roles) ? user.roles.join('、') : user.roles)}
</p>
</div>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-56">
<DropdownMenuLabel></DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuItem onClick={() => setIsUserProfileOpen(true)}>
<User className="mr-2 h-4 w-4" />
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setIsChangePasswordOpen(true)}>
<KeyRound className="mr-2 h-4 w-4" />
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuLabel></DropdownMenuLabel>
<DropdownMenuItem disabled>
{user.isSuperAdmin ? '超级管理员' : (Array.isArray(user.roles) ? user.roles.join('、') : user.roles)}
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem
className="text-red-600 cursor-pointer"
onClick={handleLogout}
>
<LogOut className="mr-2 h-4 w-4" />
退
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
</div>
{/* 修改密码对话框 */}
<ChangePasswordDialog
isOpen={isChangePasswordOpen}
onClose={() => setIsChangePasswordOpen(false)}
/>
{/* 用户资料对话框 */}
<UserProfileDialog
isOpen={isUserProfileOpen}
onClose={() => setIsUserProfileOpen(false)}
/>
</header>
)
}