'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 BrandLogo from '@/components/BrandLogo' import SystemPerformanceCard, { type SystemPerformanceData } from '@/components/board/SystemPerformanceCard' import { DEFAULT_LOGIN_LOGO_URL } from '@/lib/brand-assets' /* * MaterialsHeader —— 创意资产库板块顶栏(看板语言 · Zen Minimal) * 移植自 components/Header.tsx 的全部功能(淘宝搜索/内外网状态/管理员性能监控/用户菜单/更新日志), * 视觉统一到品牌中心白皮书语言:浅底、发丝线、墨字。 * 用户菜单是“个人工作台”的统一入口;链接保留原有权限与业务行为,只迁移其入口体验。 */ interface MaterialsHeaderProps { session: Session | null /** 板块模式:传入板块名显示面包屑;不传则首页模式(显示五大板块导航) */ sectionLabel?: string } const BOARD_NAV: Array<{ key: string; label: string; href?: string; planned?: boolean; adminOnly?: boolean }> = [ { key: 'dashboard', label: '中心仪表盘', href: '/' }, { key: 'assets', label: '创意资产库', href: '/materials' }, { key: 'organization', label: '组织管理', href: '/organization', adminOnly: true }, { key: 'business', label: '业务经营', planned: true }, { key: 'governance', label: '治理与设置', planned: true }, ] export default function MaterialsHeader({ session, sectionLabel }: MaterialsHeaderProps) { 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) const dropdownRef = useRef(null) // 性能监控悬停卡片状态 const [showPerfCard, setShowPerfCard] = useState(false) const [perfLoading, setPerfLoading] = useState(false) const [perfError, setPerfError] = useState(null) const [perfData, setPerfData] = useState(null) const perfHoverTimerRef = useRef(null) const perfHideTimerRef = useRef(null) const perfIntervalRef = useRef(null) const { showModal: showUpdateLogModal, currentLog, showAllHistory, handleClose: handleUpdateLogClose } = useUpdateLog() // 品牌设置(logo):浅底表面用浅底适配版 const [brandLogoUrl, setBrandLogoUrl] = useState(DEFAULT_LOGIN_LOGO_URL) useEffect(() => { const loadSettings = async () => { try { const response = await fetch('/api/system-settings') if (response.ok) { const settings = await response.json() setBrandLogoUrl(settings.loginLogoUrl || DEFAULT_LOGIN_LOGO_URL) } } catch { // 设置加载失败时保留默认 logo } } void loadSettings() }, []) // 更新浏览器标题(板块身份) useEffect(() => { document.title = sectionLabel ? `由壹点·品牌中心 · ${sectionLabel}` : '由壹点·品牌中心' }, [sectionLabel]) // 权威档案身份行(菜单头:花名下的职位·部门;无绑定时回退邮箱) const [employeeLine, setEmployeeLine] = useState(null) useEffect(() => { const load = async () => { try { const res = await fetch('/api/user/profile') if (!res.ok) return const data = await res.json() const employee = data?.employee if (employee) { const dept = employee.departmentPath ? employee.departmentPath.split('/').filter(Boolean).slice(-2).join(' / ') : '' const line = [employee.positionTitle, dept].filter(Boolean).join(' · ') setEmployeeLine(line || employee.jobNumber || null) } } catch { // 档案行加载失败时回退邮箱展示 } } void load() }, []) // 检测网络状态 useEffect(() => { const detectNetwork = async () => { try { const networkStatus = await initializeNetwork() setIsInternalNetwork(networkStatus.isInternal) setNetworkChecked(true) } catch (error) { console.error('网络检测失败:', error) setIsInternalNetwork(false) setNetworkChecked(true) } } detectNetwork() setupNetworkChangeDetection() const unsubscribe = addNetworkChangeListener((status) => { setIsInternalNetwork(status.isInternal) }) return () => { unsubscribe() } }, []) // 点击外部或按 Escape 关闭用户菜单 useEffect(() => { const handleClickOutside = (event: MouseEvent) => { if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) { setShowDropdown(false) } } const handleKeyDown = (event: KeyboardEvent) => { if (event.key === 'Escape') setShowDropdown(false) } document.addEventListener('mousedown', handleClickOutside) document.addEventListener('keydown', handleKeyDown) return () => { document.removeEventListener('mousedown', handleClickOutside) document.removeEventListener('keydown', handleKeyDown) } }, []) // 性能监控:根据 showPerfCard 控制轮询(2 秒) useEffect(() => { const clearIntervalSafe = () => { if (perfIntervalRef.current !== null) { window.clearInterval(perfIntervalRef.current) perfIntervalRef.current = null } } if (!canViewPerformance || !showPerfCard) { clearIntervalSafe() return } const fetchPerf = async (isInitial: boolean) => { try { if (isInitial) 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) } } void fetchPerf(true) perfIntervalRef.current = window.setInterval(() => { void fetchPerf(false) }, 2000) return () => { clearIntervalSafe() } }, [canViewPerformance, showPerfCard]) // 悬停事件处理:延迟显示 / 隐藏性能卡片 const handlePerfMouseEnter = () => { if (!canViewPerformance) return if (perfHideTimerRef.current !== null) { window.clearTimeout(perfHideTimerRef.current) perfHideTimerRef.current = null } 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 } 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 menuItemCls = 'group flex w-full items-start gap-3 rounded-md px-3 py-3 text-left text-[#1a1c1d] transition-colors hover:bg-[#f1f2f4] focus:outline-none focus-visible:ring-2 focus-visible:ring-[#1a1c1d]/70 focus-visible:ring-offset-2 focus-visible:ring-offset-white' const menuIconCls = 'mt-0.5 h-4 w-4 shrink-0 text-[#6b7075] transition-colors group-hover:text-[#1a1c1d]' return ( <>
{/* 品牌 */} 由壹点·品牌中心 由壹点·品牌中心 {sectionLabel ? ( /* 板块模式:面包屑 */ <> {sectionLabel} ) : ( /* 首页模式:五大板块导航 */ )}
) }