'use client' import Image from 'next/image' import Link from 'next/link' import { signOut } from 'next-auth/react' import { useRouter } from 'next/navigation' import { Session } from 'next-auth' import { useState, useRef, useEffect } from 'react' import { initializeNetwork, setupNetworkChangeDetection, addNetworkChangeListener } from '@/lib/network' import UpdateLogModal, { useUpdateLog, showUpdateLog } from '@/components/UpdateLogModal' import MaintenanceBanner from '@/components/MaintenanceBanner' import HeaderTaobaoSearch from '@/components/HeaderTaobaoSearch' import { formatHumanSpeed, formatTransferProgressSummary } from '@/lib/human-readable-transfer' interface HeaderProps { session: Session | null appearance?: 'default' | 'light' } const DEFAULT_HEADER_LOGO_URL = '/api/favicon' export default function Header({ session, appearance = 'default' }: HeaderProps) { const router = useRouter() const role = session ? String((session.user as any)?.role || '') : '' const isAdmin = ['ADMIN', 'SYSTEM_ADMIN', 'DEPT_ADMIN'].includes(role) const canViewPerformance = ['ADMIN', 'SYSTEM_ADMIN'].includes(role) || Boolean((session?.user as any)?.isSuperAdmin) const [showDropdown, setShowDropdown] = useState(false) const [isInternalNetwork, setIsInternalNetwork] = useState(false) const [networkChecked, setNetworkChecked] = useState(false) // 网络切换横幅已移除:主站统一使用 https://sc.jiansun.vip,旧域名 3d.yydd.ai 仅保留跳转 const [isAccessingFromExternal, setIsAccessingFromExternal] = useState(false) const dropdownRef = useRef(null) // 性能监控悬停卡片状态 const [showPerfCard, setShowPerfCard] = useState(false) const [perfLoading, setPerfLoading] = useState(false) const [perfError, setPerfError] = useState(null) const [perfData, setPerfData] = useState<{ cpu?: { usage: number; loadAverage: number[] } memory?: { used: number; total: number; percentage: number } network?: { uploadSpeed: number; downloadSpeed: number } uploadQueue?: { total: number; processing: any[]; waiting: any[] } transcodeQueue?: { total: number; maxConcurrent: number; processing: any[]; waiting: any[] } } | null>(null) const perfHoverTimerRef = useRef(null) const perfHideTimerRef = useRef(null) const perfIntervalRef = useRef(null) // 使用 useUpdateLog hook 来管理更新日志弹窗 const { showModal: showUpdateLogModal, currentLog, showAllHistory, handleClose: handleUpdateLogClose } = useUpdateLog() // 系统设置 const [headerBgColor, setHeaderBgColor] = useState('#1f2937') const [headerLogoUrl, setHeaderLogoUrl] = useState(DEFAULT_HEADER_LOGO_URL) const [siteName, setSiteName] = useState('由壹点3D素材') const isLight = appearance === 'light' // 加载系统设置 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 || 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 isExternal = currentHost !== '192.168.58.20' && currentHost !== 'localhost' setIsAccessingFromExternal(isExternal) // 网络切换横幅已移除:主站统一使用 https://sc.jiansun.vip,旧域名 3d.yydd.ai 仅保留跳转 } catch (error) { console.error('网络检测失败:', error) setIsInternalNetwork(false) setNetworkChecked(true) } } detectNetwork() // 设置网络变化监听 setupNetworkChangeDetection() // 监听网络状态变化,自动更新UI const unsubscribe = addNetworkChangeListener((status) => { setIsInternalNetwork(status.isInternal) }) return () => { unsubscribe() } }, []) // 内网切换功能已移除:主站统一使用 https://sc.jiansun.vip,旧域名 3d.yydd.ai 仅保留跳转 // 点击外部关闭下拉菜单 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) }, []) // 性能监控:根据 showPerfCard 控制轮询 useEffect(() => { // 清理定时器的工具函数 const clearIntervalSafe = () => { if (perfIntervalRef.current !== null) { window.clearInterval(perfIntervalRef.current) perfIntervalRef.current = null } } if (!canViewPerformance || !showPerfCard) { clearIntervalSafe() return } const fetchPerf = async () => { try { setPerfLoading(true) setPerfError(null) const res = await fetch('/api/performance/quick') if (!res.ok) { throw new Error(`HTTP ${res.status}`) } const data = await res.json() setPerfData(data) } catch (err) { console.error('加载性能数据失败:', err) setPerfError('加载失败') } finally { setPerfLoading(false) } } // 立即拉一次 fetchPerf() // 每 2 秒轮询一次 perfIntervalRef.current = window.setInterval(fetchPerf, 2000) return () => { clearIntervalSafe() } }, [canViewPerformance, showPerfCard]) // 悬停事件处理:延迟显示 / 隐藏性能卡片 const handlePerfMouseEnter = () => { if (!canViewPerformance) return // 取消隐藏定时器 if (perfHideTimerRef.current !== null) { window.clearTimeout(perfHideTimerRef.current) perfHideTimerRef.current = null } // 300ms 后显示,防止误触 if (perfHoverTimerRef.current === null) { perfHoverTimerRef.current = window.setTimeout(() => { setShowPerfCard(true) perfHoverTimerRef.current = null }, 300) } } const handlePerfMouseLeave = () => { if (!canViewPerformance) return // 取消显示定时器 if (perfHoverTimerRef.current !== null) { window.clearTimeout(perfHoverTimerRef.current) perfHoverTimerRef.current = null } // 100ms 延迟隐藏,避免抖动 if (perfHideTimerRef.current !== null) { window.clearTimeout(perfHideTimerRef.current) perfHideTimerRef.current = null } perfHideTimerRef.current = window.setTimeout(() => { setShowPerfCard(false) perfHideTimerRef.current = null }, 100) } // 获取用户名首字母 const getInitials = (name: string) => { return name?.charAt(0).toUpperCase() || 'U' } const formatAgo = (ms: number): string => { if (!Number.isFinite(ms) || ms < 0) return '--' const sec = Math.floor(ms / 1000) if (sec < 60) return `${sec}s` const min = Math.floor(sec / 60) if (min < 60) return `${min}min` const hr = Math.floor(min / 60) return `${hr}h${min % 60}min` } return ( <> {/* 系统维护通知横幅 */} {/* 网络切换横幅已移除:主站统一使用 https://sc.jiansun.vip,旧域名 3d.yydd.ai 仅保留跳转 */}
{/* Logo */} {siteName} {siteName} {/* Navigation */}
{/* 更新记录弹窗 */} ) }