/** * Header 组件 * * 全局顶部导航栏组件 * 包含Logo、主题切换、用户下拉菜单等功能 * 使用三层架构样式系统,支持深浅色主题 * * @created 2025-01-XX * @component */ '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, useCallback } from 'react' import { initializeNetwork } from '@/lib/network' import { useThemeStyles, cn } from '@/lib/ui-styles' import Avatar from '@/components/ui/Avatar' interface HeaderProps { session: Session | null } const THEME_STORAGE_KEY = 'admin-theme-preference' export default function Header({ session }: HeaderProps) { const styles = useThemeStyles() const isAdmin = session ? ((session.user as any)?.role === 'ADMIN' || (session.user as any)?.isSuperAdmin) : 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(null) // 系统设置 const [headerBgColor, setHeaderBgColor] = useState('#1f2937') const [headerLogoUrl, setHeaderLogoUrl] = useState(process.env.NEXT_PUBLIC_DEFAULT_HEADER_LOGO_URL || '') const [siteName, setSiteName] = useState('由壹点3D素材') // Logo 加载状态 const [logoError, setLogoError] = useState(false) const [logoRetryCount, setLogoRetryCount] = useState(0) const [theme, setTheme] = useState<'light' | 'dark'>('light') const [themeReady, setThemeReady] = useState(false) const applyTheme = useCallback((nextTheme: 'light' | 'dark') => { if (typeof document === 'undefined') return const root = document.documentElement if (nextTheme === 'dark') { root.classList.add('dark') } else { root.classList.remove('dark') } root.dataset.theme = nextTheme try { window.localStorage.setItem(THEME_STORAGE_KEY, nextTheme) } catch { // ignore storage errors } }, []) useEffect(() => { if (typeof window === 'undefined') return let initial: 'light' | 'dark' = 'light' try { const stored = window.localStorage.getItem(THEME_STORAGE_KEY) if (stored === 'light' || stored === 'dark') { initial = stored } else { initial = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light' } } catch { initial = 'light' } setTheme(initial) applyTheme(initial) setThemeReady(true) }, [applyTheme]) useEffect(() => { if (!headerLogoUrl) return try { const parsed = new URL(headerLogoUrl) const host = parsed.hostname.toLowerCase() const hostIsLocal = host === 'localhost' || host === '127.0.0.1' if (hostIsLocal && typeof window !== 'undefined') { const currentHost = window.location.hostname.toLowerCase() if (currentHost !== host) { console.warn( `[Header] Logo 地址 ${headerLogoUrl} 指向本地服务,但当前访问域名为 ${currentHost},自动降级为占位图` ) setLogoError(true) } } } catch (error) { console.warn('[Header] Logo 地址解析失败,自动降级为占位图:', headerLogoUrl, error) setLogoError(true) } }, [headerLogoUrl]) // 加载系统设置 useEffect(() => { const loadSettings = async () => { try { const response = await fetch('/api/system-settings') if (response.ok) { const settings = await response.json() setHeaderBgColor(settings.headerBgColor || '#1f2937') setHeaderLogoUrl(settings.headerLogoUrl || process.env.NEXT_PUBLIC_DEFAULT_HEADER_LOGO_URL || '') setSiteName(settings.siteName || '由壹点3D素材') } } catch (error) { console.error('加载系统设置失败:', error) } } loadSettings() }, []) // 更新浏览器标题 useEffect(() => { if (siteName) { document.title = siteName } }, [siteName]) // 检测网络状态 useEffect(() => { const detectNetwork = async () => { try { const networkStatus = await initializeNetwork() setIsInternalNetwork(networkStatus.isInternal) setNetworkChecked(true) // 检查当前是否从外网访问 const currentHost = window.location.hostname const intranet = (process.env.NEXT_PUBLIC_INTRANET_URL || '').replace(/^https?:\/\//, '').split('/')[0] const isExternal = currentHost !== intranet && 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 const base = process.env.NEXT_PUBLIC_INTRANET_URL if (base) { window.location.href = `${base}${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) }, []) return ( <> {/* 网络切换提示横幅 */} {showNetworkBanner && (

检测到您在内网环境

切换到内网地址可获得更快的上传下载速度,并避免登录问题

)}
{/* Logo */} {headerLogoUrl && !logoError ? ( 0 ? `?retry=${logoRetryCount}` : '')} alt={siteName} width={40} height={40} unoptimized className="rounded-lg" onError={(e) => { try { const img = e.target as HTMLImageElement if (!img) return // 检查是否是 localhost 地址但当前访问域名不是 localhost const currentHost = typeof window !== 'undefined' ? window.location.hostname : '' const checkLogoUrl = (url: string, host: string) => { try { const logoHost = new URL(url).hostname return (logoHost === 'localhost' || logoHost === '127.0.0.1') && host !== 'localhost' && host !== '127.0.0.1' } catch { return true // 如果 URL 无效,视为错误 } } if (checkLogoUrl(headerLogoUrl, currentHost)) { console.warn(`[Header] Logo 地址 ${headerLogoUrl} 指向本地服务,但当前访问域名为 ${currentHost},自动降级为占位图`) setLogoError(true) if (img) img.style.display = 'none' return } setLogoRetryCount((prevCount) => { const newCount = prevCount + 1 if (newCount <= 3) { // 重试加载(最多3次) console.warn(`Logo 加载失败,正在重试 (${newCount}/3):`, headerLogoUrl) setTimeout(() => { if (img && img.parentNode) { img.src = headerLogoUrl + `?retry=${newCount}` } }, 1000 * newCount) // 递增延迟:1s, 2s, 3s return newCount } else { // 重试失败,记录错误并显示占位符 console.error('Logo 加载失败,已重试3次:', headerLogoUrl) setLogoError(true) if (img) img.style.display = 'none' return prevCount } }) } catch (error) { // 捕获所有错误,防止崩溃 console.error('[Header] Logo 错误处理异常:', error) setLogoError(true) } }} /> ) : (
{siteName.charAt(0)}
)} {siteName} {/* Navigation */}
) }