gongxiangp2/components/Header.tsx
2025-10-26 09:11:04 +08:00

240 lines
10 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'use client'
import Image from 'next/image'
import Link from 'next/link'
import { signOut } from 'next-auth/react'
import { Session } from 'next-auth'
import { useState, useRef, useEffect } from 'react'
import { initializeNetwork } from '@/lib/network'
interface HeaderProps {
session: Session | null
}
export default function Header({ session }: HeaderProps) {
const isAdmin = session ? (session.user as any)?.role === 'ADMIN' : false
const [showDropdown, setShowDropdown] = useState(false)
const [isInternalNetwork, setIsInternalNetwork] = useState(false)
const [networkChecked, setNetworkChecked] = useState(false)
const [showNetworkBanner, setShowNetworkBanner] = useState(false)
const [isAccessingFromExternal, setIsAccessingFromExternal] = useState(false)
const dropdownRef = useRef<HTMLDivElement>(null)
// 检测网络状态
useEffect(() => {
const detectNetwork = async () => {
try {
const networkStatus = await initializeNetwork()
setIsInternalNetwork(networkStatus.isInternal)
setNetworkChecked(true)
// 检查当前是否从外网访问
const currentHost = window.location.hostname
const isExternal = currentHost !== '192.168.58.20' && currentHost !== 'localhost'
setIsAccessingFromExternal(isExternal)
// 如果内网可达但当前从外网访问,显示切换提示(除非用户已关闭)
const bannerDismissed = sessionStorage.getItem('networkBannerDismissed')
if (networkStatus.isInternal && isExternal && !bannerDismissed) {
setShowNetworkBanner(true)
}
} catch (error) {
console.error('网络检测失败:', error)
setIsInternalNetwork(false)
setNetworkChecked(true)
}
}
detectNetwork()
}, [])
// 切换到内网地址
const switchToInternal = () => {
const currentPath = window.location.pathname + window.location.search
window.location.href = `http://192.168.58.20:4320${currentPath}`
}
// 关闭横幅
const dismissBanner = () => {
setShowNetworkBanner(false)
sessionStorage.setItem('networkBannerDismissed', 'true')
}
// 点击外部关闭下拉菜单
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) {
setShowDropdown(false)
}
}
document.addEventListener('mousedown', handleClickOutside)
return () => document.removeEventListener('mousedown', handleClickOutside)
}, [])
// 获取用户名首字母
const getInitials = (name: string) => {
return name?.charAt(0).toUpperCase() || 'U'
}
return (
<>
{/* 网络切换提示横幅 */}
{showNetworkBanner && (
<div className="fixed top-0 left-0 right-0 z-[60] bg-gradient-to-r from-green-500 to-emerald-500 text-white shadow-lg">
<div className="container mx-auto px-4 py-3 max-w-7xl">
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<svg className="w-6 h-6 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13 10V3L4 14h7v7l9-11h-7z" />
</svg>
<div>
<p className="font-semibold">检测到您在内网环境</p>
<p className="text-sm text-white/90">切换到内网地址可获得更快的上传下载速度,并避免登录问题</p>
</div>
</div>
<div className="flex items-center gap-2">
<button
onClick={switchToInternal}
className="px-4 py-2 bg-white text-green-600 rounded-lg font-medium hover:bg-green-50 transition-all shadow-sm"
>
立即切换
</button>
<button
onClick={dismissBanner}
className="px-3 py-2 text-white/80 hover:text-white transition-all"
aria-label="关闭"
>
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
</div>
</div>
</div>
)}
<header className={`sticky z-50 bg-gray-900 dark:bg-gray-950 shadow-sm ${showNetworkBanner ? 'top-[72px]' : 'top-0'}`}>
<div className="container mx-auto px-4 py-4 max-w-7xl">
<div className="flex items-center justify-between">
{/* Logo */}
<Link href="/" className="flex items-center space-x-3">
<Image
src="http://s3.1080p.hk/shuzhou/2025/05/18/f6f20d120a4e45aeaae3411fe0000b6a.png"
alt="由壹点3D素材"
width={40}
height={40}
className="rounded-lg"
/>
<span className="text-2xl font-semibold text-white">
由壹点3D素材
</span>
</Link>
{/* Navigation */}
<nav className="flex items-center space-x-4">
{/* Network Status Indicator */}
{networkChecked && (
<div className={`flex items-center gap-1.5 px-3 py-1.5 rounded-full text-xs font-medium transition-all ${
isInternalNetwork
? 'bg-green-500/10 text-green-400 border border-green-500/20'
: 'bg-blue-500/10 text-blue-400 border border-blue-500/20'
}`}>
<svg className="w-3 h-3" fill="currentColor" viewBox="0 0 20 20">
<circle cx="10" cy="10" r="4"/>
</svg>
<span>{isInternalNetwork ? '内网' : '公网'}</span>
</div>
)}
{/* User Dropdown */}
{session && (
<div className="relative" ref={dropdownRef}>
<button
onClick={() => setShowDropdown(!showDropdown)}
className="flex items-center p-1.5 rounded-lg hover:bg-gray-800 dark:hover:bg-gray-900 transition-all"
>
{/* 圆形头像 */}
<div className="w-8 h-8 rounded-full bg-white dark:bg-gray-300 border-2 border-gray-700 dark:border-gray-600 flex items-center justify-center text-gray-900 dark:text-gray-800 font-semibold text-base shadow-lg hover:border-gray-500 dark:hover:border-gray-400 transition-all">
{getInitials(session.user?.name || '')}
</div>
</button>
{/* 下拉菜单 */}
{showDropdown && (
<div className="absolute right-0 mt-2 w-48 bg-white dark:bg-gray-800 rounded-lg shadow-lg py-2 z-50 border border-gray-200 dark:border-gray-700">
<div className="px-4 py-2 border-b border-gray-100 dark:border-gray-700">
<p className="text-sm font-medium text-gray-900 dark:text-white">
{session.user?.name}
</p>
<p className="text-xs text-gray-500 dark:text-gray-400">
{session.user?.email}
</p>
</div>
{/* 系统设置(管理员) */}
{isAdmin && (
<Link
href="/admin"
onClick={() => setShowDropdown(false)}
className="w-full text-left px-4 py-2 text-sm text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 transition-all flex items-center space-x-2"
>
<svg
className="w-4 h-4"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"
/>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
/>
</svg>
<span>系统设置</span>
</Link>
)}
{/* 退出登录 */}
<button
onClick={() => {
setShowDropdown(false)
signOut({ callbackUrl: '/login', redirect: true })
}}
className="w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 transition-all flex items-center space-x-2"
>
<svg
className="w-4 h-4"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1"
/>
</svg>
<span>退出登录</span>
</button>
</div>
)}
</div>
)}
</nav>
</div>
</div>
</header>
</>
)
}