import React, { useContext, useEffect, useState, useRef } from 'react'; import { Link, useNavigate, useLocation } from 'react-router-dom'; import { UserContext } from '../../context/User/index.js'; import { useSetTheme, useTheme } from '../../context/Theme/index.js'; import { useTranslation } from 'react-i18next'; import { API, getLogo, getSystemName, showSuccess, stringToColor } from '../../helpers/index.js'; import fireworks from 'react-fireworks'; import { CN, GB } from 'country-flag-icons/react/3x2'; import NoticeModal from './NoticeModal.js'; import { IconClose, IconMenu, IconLanguage, IconChevronDown, IconSun, IconMoon, IconExit, IconUserSetting, IconCreditCard, IconKey, IconBell, } from '@douyinfe/semi-icons'; import { Avatar, Button, Dropdown, Tag, Typography, Skeleton, Badge, } from '@douyinfe/semi-ui'; import { StatusContext } from '../../context/Status/index.js'; import { useIsMobile } from '../../hooks/useIsMobile.js'; import { useSidebarCollapsed } from '../../hooks/useSidebarCollapsed.js'; const HeaderBar = ({ onMobileMenuToggle, drawerOpen }) => { const { t, i18n } = useTranslation(); const [userState, userDispatch] = useContext(UserContext); const [statusState, statusDispatch] = useContext(StatusContext); const isMobile = useIsMobile(); const [collapsed, toggleCollapsed] = useSidebarCollapsed(); const [isLoading, setIsLoading] = useState(true); let navigate = useNavigate(); const [currentLang, setCurrentLang] = useState(i18n.language); const [mobileMenuOpen, setMobileMenuOpen] = useState(false); const location = useLocation(); const [noticeVisible, setNoticeVisible] = useState(false); const [unreadCount, setUnreadCount] = useState(0); const loadingStartRef = useRef(Date.now()); const systemName = getSystemName(); const logo = getLogo(); const currentDate = new Date(); const isNewYear = currentDate.getMonth() === 0 && currentDate.getDate() === 1; const isSelfUseMode = statusState?.status?.self_use_mode_enabled || false; const docsLink = statusState?.status?.docs_link || ''; const isDemoSiteMode = statusState?.status?.demo_site_enabled || false; const isConsoleRoute = location.pathname.startsWith('/console'); const theme = useTheme(); const setTheme = useSetTheme(); const announcements = statusState?.status?.announcements || []; const getAnnouncementKey = (a) => `${a?.publishDate || ''}-${(a?.content || '').slice(0, 30)}`; const calculateUnreadCount = () => { if (!announcements.length) return 0; let readKeys = []; try { readKeys = JSON.parse(localStorage.getItem('notice_read_keys')) || []; } catch (_) { readKeys = []; } const readSet = new Set(readKeys); return announcements.filter((a) => !readSet.has(getAnnouncementKey(a))).length; }; const getUnreadKeys = () => { if (!announcements.length) return []; let readKeys = []; try { readKeys = JSON.parse(localStorage.getItem('notice_read_keys')) || []; } catch (_) { readKeys = []; } const readSet = new Set(readKeys); return announcements.filter((a) => !readSet.has(getAnnouncementKey(a))).map(getAnnouncementKey); }; useEffect(() => { setUnreadCount(calculateUnreadCount()); // eslint-disable-next-line react-hooks/exhaustive-deps }, [announcements]); const mainNavLinks = [ { text: t('首页'), itemKey: 'home', to: '/', }, { text: t('控制台'), itemKey: 'console', to: '/console', }, { text: t('定价'), itemKey: 'pricing', to: '/pricing', }, ...(docsLink ? [ { text: t('文档'), itemKey: 'docs', isExternal: true, externalLink: docsLink, }, ] : []), { text: t('关于'), itemKey: 'about', to: '/about', }, ]; async function logout() { await API.get('/api/user/logout'); showSuccess(t('注销成功!')); userDispatch({ type: 'logout' }); localStorage.removeItem('user'); navigate('/login'); setMobileMenuOpen(false); } const handleNewYearClick = () => { fireworks.init('root', {}); fireworks.start(); setTimeout(() => { fireworks.stop(); }, 3000); }; const handleNoticeOpen = () => { setNoticeVisible(true); }; const handleNoticeClose = () => { setNoticeVisible(false); if (announcements.length) { let readKeys = []; try { readKeys = JSON.parse(localStorage.getItem('notice_read_keys')) || []; } catch (_) { readKeys = []; } const mergedKeys = Array.from(new Set([...readKeys, ...announcements.map(getAnnouncementKey)])); localStorage.setItem('notice_read_keys', JSON.stringify(mergedKeys)); } setUnreadCount(0); }; useEffect(() => { if (theme === 'dark') { document.body.setAttribute('theme-mode', 'dark'); document.documentElement.classList.add('dark'); } else { document.body.removeAttribute('theme-mode'); document.documentElement.classList.remove('dark'); } const iframe = document.querySelector('iframe'); if (iframe) { iframe.contentWindow.postMessage({ themeMode: theme }, '*'); } }, [theme, isNewYear]); useEffect(() => { const handleLanguageChanged = (lng) => { setCurrentLang(lng); const iframe = document.querySelector('iframe'); if (iframe) { iframe.contentWindow.postMessage({ lang: lng }, '*'); } }; i18n.on('languageChanged', handleLanguageChanged); return () => { i18n.off('languageChanged', handleLanguageChanged); }; }, [i18n]); useEffect(() => { if (statusState?.status !== undefined) { const elapsed = Date.now() - loadingStartRef.current; const remaining = Math.max(0, 500 - elapsed); const timer = setTimeout(() => { setIsLoading(false); }, remaining); return () => clearTimeout(timer); } }, [statusState?.status]); const handleLanguageChange = (lang) => { i18n.changeLanguage(lang); setMobileMenuOpen(false); }; const handleNavLinkClick = (itemKey) => { if (itemKey === 'home') { // styleDispatch(styleActions.setSider(false)); // This line is removed } setMobileMenuOpen(false); }; const renderNavLinks = (isMobileView = false, isLoading = false) => { if (isLoading) { const skeletonLinkClasses = isMobileView ? 'flex items-center gap-1 p-3 w-full rounded-md' : 'flex items-center gap-1 p-2 rounded-md'; return Array(4) .fill(null) .map((_, index) => (