| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| import React, { useContext, useEffect, useState } from 'react'; |
| import { Link, useNavigate } from 'react-router-dom'; |
| import { UserContext } from '../context/User'; |
| import { useSetTheme, useTheme } from '../context/Theme'; |
| import { useTranslation } from 'react-i18next'; |
|
|
| import { API, getLogo, getSystemName, isMobile, showSuccess } from '../helpers'; |
| import '../index.css'; |
|
|
| import fireworks from 'react-fireworks'; |
|
|
| import { |
| IconClose, |
| IconHelpCircle, |
| IconHome, |
| IconHomeStroked, |
| IconIndentLeft, |
| IconComment, |
| IconKey, |
| IconMenu, |
| IconNoteMoneyStroked, |
| IconPriceTag, |
| IconUser, |
| IconLanguage, |
| IconInfoCircle, |
| IconCreditCard, |
| IconTerminal, |
| } from '@douyinfe/semi-icons'; |
| import { |
| Avatar, |
| Button, |
| Dropdown, |
| Layout, |
| Nav, |
| Switch, |
| Tag, |
| } from '@douyinfe/semi-ui'; |
| import { stringToColor } from '../helpers/render'; |
| import Text from '@douyinfe/semi-ui/lib/es/typography/text'; |
| import { StyleContext } from '../context/Style/index.js'; |
| import { StatusContext } from '../context/Status/index.js'; |
| import InboxIcon from './InboxIcon'; |
|
|
| |
| const headerStyle = { |
| boxShadow: '0 2px 10px rgba(0, 0, 0, 0.1)', |
| borderBottom: '1px solid var(--semi-color-border)', |
| background: 'var(--semi-color-bg-0)', |
| transition: 'all 0.3s ease', |
| width: '100%', |
| }; |
|
|
| |
| const headerItemStyle = { |
| borderRadius: '4px', |
| margin: '0 4px', |
| transition: 'all 0.3s ease', |
| }; |
|
|
| |
| const headerItemHoverStyle = { |
| backgroundColor: 'var(--semi-color-primary-light-default)', |
| color: 'var(--semi-color-primary)', |
| }; |
|
|
| |
| const logoStyle = { |
| display: 'flex', |
| alignItems: 'center', |
| gap: '10px', |
| padding: '0 10px', |
| height: '100%', |
| }; |
|
|
| |
| const systemNameStyle = { |
| fontWeight: 'bold', |
| fontSize: '18px', |
| background: |
| 'linear-gradient(45deg, var(--semi-color-primary), var(--semi-color-link-hover))', |
| WebkitBackgroundClip: 'text', |
| WebkitTextFillColor: 'transparent', |
| padding: '0 5px', |
| }; |
|
|
| |
| const headerIconStyle = { |
| fontSize: '18px', |
| transition: 'all 0.3s ease', |
| }; |
|
|
| |
| const avatarStyle = { |
| margin: '4px', |
| cursor: 'pointer', |
| boxShadow: '0 2px 8px rgba(0, 0, 0, 0.1)', |
| transition: 'all 0.3s ease', |
| }; |
|
|
| |
| const dropdownStyle = { |
| borderRadius: '8px', |
| boxShadow: '0 4px 12px rgba(0, 0, 0, 0.15)', |
| overflow: 'hidden', |
| }; |
|
|
| |
| const switchStyle = { |
| margin: '0 8px', |
| }; |
|
|
| const HeaderBar = () => { |
| const { t, i18n } = useTranslation(); |
| const [userState, userDispatch] = useContext(UserContext); |
| const [styleState, styleDispatch] = useContext(StyleContext); |
| const [statusState, statusDispatch] = useContext(StatusContext); |
| let navigate = useNavigate(); |
| const [currentLang, setCurrentLang] = useState(i18n.language); |
|
|
| 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; |
|
|
| let buttons = [ |
| { |
| text: t('首页'), |
| itemKey: 'home', |
| to: '/', |
| icon: <IconHome style={headerIconStyle} />, |
| }, |
| { |
| text: t('控制台'), |
| itemKey: 'detail', |
| to: '/', |
| icon: <IconTerminal style={headerIconStyle} />, |
| }, |
| { |
| text: t('定价'), |
| itemKey: 'pricing', |
| to: '/pricing', |
| icon: <IconPriceTag style={headerIconStyle} />, |
| }, |
| |
| ...(docsLink |
| ? [ |
| { |
| text: t('文档'), |
| itemKey: 'docs', |
| isExternal: true, |
| externalLink: docsLink, |
| icon: <IconHelpCircle style={headerIconStyle} />, |
| }, |
| ] |
| : []), |
| { |
| text: t('关于'), |
| itemKey: 'about', |
| to: '/about', |
| icon: <IconInfoCircle style={headerIconStyle} />, |
| }, |
| ]; |
|
|
| async function logout() { |
| await API.get('/api/user/logout'); |
| showSuccess(t('注销成功!')); |
| userDispatch({ type: 'logout' }); |
| localStorage.removeItem('user'); |
| navigate('/login'); |
| } |
|
|
| const handleNewYearClick = () => { |
| fireworks.init('root', {}); |
| fireworks.start(); |
| setTimeout(() => { |
| fireworks.stop(); |
| setTimeout(() => { |
| window.location.reload(); |
| }, 10000); |
| }, 3000); |
| }; |
|
|
| const theme = useTheme(); |
| const setTheme = useSetTheme(); |
|
|
| useEffect(() => { |
| if (theme === 'dark') { |
| document.body.setAttribute('theme-mode', 'dark'); |
| } else { |
| document.body.removeAttribute('theme-mode'); |
| } |
| |
| const iframe = document.querySelector('iframe'); |
| if (iframe) { |
| iframe.contentWindow.postMessage({ themeMode: theme }, '*'); |
| } |
|
|
| if (isNewYear) { |
| console.log('Happy New Year!'); |
| } |
| }, [theme]); |
|
|
| 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]); |
|
|
| const handleLanguageChange = (lang) => { |
| i18n.changeLanguage(lang); |
| }; |
|
|
| return ( |
| <> |
| <Layout> |
| <div style={{ width: '100%' }}> |
| <Nav |
| className={'topnav'} |
| mode={'horizontal'} |
| style={headerStyle} |
| itemStyle={headerItemStyle} |
| hoverStyle={headerItemHoverStyle} |
| renderWrapper={({ itemElement, isSubNav, isInSubNav, props }) => { |
| const routerMap = { |
| about: '/about', |
| login: '/login', |
| register: '/register', |
| pricing: '/pricing', |
| detail: '/app/dashboard', |
| home: '/', |
| chat: '/chat', |
| }; |
| return ( |
| <div |
| onClick={(e) => { |
| if (props.itemKey === 'home') { |
| styleDispatch({ |
| type: 'SET_INNER_PADDING', |
| payload: false, |
| }); |
| styleDispatch({ type: 'SET_SIDER', payload: false }); |
| } else { |
| styleDispatch({ |
| type: 'SET_INNER_PADDING', |
| payload: true, |
| }); |
| if (!styleState.isMobile) { |
| styleDispatch({ type: 'SET_SIDER', payload: true }); |
| } |
| } |
| }} |
| > |
| {props.isExternal ? ( |
| <a |
| className='header-bar-text' |
| style={{ textDecoration: 'none' }} |
| href={props.externalLink} |
| target='_blank' |
| rel='noopener noreferrer' |
| > |
| {itemElement} |
| </a> |
| ) : ( |
| <Link |
| className='header-bar-text' |
| style={{ textDecoration: 'none' }} |
| to={routerMap[props.itemKey]} |
| > |
| {itemElement} |
| </Link> |
| )} |
| </div> |
| ); |
| }} |
| selectedKeys={[]} |
| // items={headerButtons} |
| onSelect={(key) => {}} |
| header={ |
| styleState.isMobile |
| ? { |
| logo: ( |
| <div |
| style={{ |
| display: 'flex', |
| alignItems: 'center', |
| position: 'relative', |
| }} |
| > |
| {!styleState.showSider ? ( |
| <Button |
| icon={<IconMenu />} |
| theme='light' |
| aria-label={t('展开侧边栏')} |
| onClick={() => |
| styleDispatch({ |
| type: 'SET_SIDER', |
| payload: true, |
| }) |
| } |
| /> |
| ) : ( |
| <Button |
| icon={<IconIndentLeft />} |
| theme='light' |
| aria-label={t('闭侧边栏')} |
| onClick={() => |
| styleDispatch({ |
| type: 'SET_SIDER', |
| payload: false, |
| }) |
| } |
| /> |
| )} |
| {(isSelfUseMode || isDemoSiteMode) && ( |
| <Tag |
| color={isSelfUseMode ? 'purple' : 'blue'} |
| style={{ |
| position: 'absolute', |
| top: '-8px', |
| right: '-15px', |
| fontSize: '0.7rem', |
| padding: '0 4px', |
| height: 'auto', |
| lineHeight: '1.2', |
| zIndex: 1, |
| pointerEvents: 'none', |
| }} |
| > |
| {isSelfUseMode ? t('自用模式') : t('演示站点')} |
| </Tag> |
| )} |
| </div> |
| ), |
| } |
| : { |
| logo: ( |
| <Link |
| to="/" |
| style={{ textDecoration: 'none' }} |
| onClick={() => { |
| styleDispatch({ |
| type: 'SET_INNER_PADDING', |
| payload: false, |
| }); |
| styleDispatch({ type: 'SET_SIDER', payload: false }); |
| }} |
| > |
| <div style={{ ...logoStyle, cursor: 'pointer' }}> |
| <img src={logo} alt='logo' style={{ height: '28px' }} /> |
| </div> |
| </Link> |
| ), |
| text: ( |
| <Link |
| to="/" |
| style={{ textDecoration: 'none' }} |
| onClick={() => { |
| styleDispatch({ |
| type: 'SET_INNER_PADDING', |
| payload: false, |
| }); |
| styleDispatch({ type: 'SET_SIDER', payload: false }); |
| }} |
| > |
| <div |
| style={{ |
| position: 'relative', |
| display: 'inline-block', |
| cursor: 'pointer', |
| }} |
| > |
| <span style={systemNameStyle}>{systemName}</span> |
| {(isSelfUseMode || isDemoSiteMode) && ( |
| <Tag |
| color={isSelfUseMode ? 'purple' : 'blue'} |
| style={{ |
| position: 'absolute', |
| top: '-10px', |
| right: '-25px', |
| fontSize: '0.7rem', |
| padding: '0 4px', |
| whiteSpace: 'nowrap', |
| zIndex: 1, |
| }} |
| > |
| {isSelfUseMode ? t('自用模式') : t('演示站点')} |
| </Tag> |
| )} |
| </div> |
| </Link> |
| ), |
| } |
| } |
| items={buttons} |
| footer={ |
| <> |
| {isNewYear && ( |
| // happy new year |
| <Dropdown |
| position='bottomRight' |
| render={ |
| <Dropdown.Menu style={dropdownStyle}> |
| <Dropdown.Item onClick={handleNewYearClick}> |
| Happy New Year!!! |
| </Dropdown.Item> |
| </Dropdown.Menu> |
| } |
| > |
| <Nav.Item itemKey={'new-year'} text={'🎉'} /> |
| </Dropdown> |
| )} |
| {/* <Nav.Item itemKey={'about'} icon={<IconHelpCircle />} /> */} |
| <> |
| <Switch |
| checkedText='🌞' |
| size={styleState.isMobile ? 'default' : 'large'} |
| checked={theme === 'dark'} |
| uncheckedText='🌙' |
| style={switchStyle} |
| onChange={(checked) => { |
| setTheme(checked); |
| }} |
| /> |
| </> |
| <Dropdown |
| position='bottomRight' |
| render={ |
| <Dropdown.Menu style={dropdownStyle}> |
| <Dropdown.Item |
| onClick={() => handleLanguageChange('zh')} |
| type={currentLang === 'zh' ? 'primary' : 'tertiary'} |
| > |
| 中文 |
| </Dropdown.Item> |
| <Dropdown.Item |
| onClick={() => handleLanguageChange('en')} |
| type={currentLang === 'en' ? 'primary' : 'tertiary'} |
| > |
| English |
| </Dropdown.Item> |
| </Dropdown.Menu> |
| } |
| > |
| <Nav.Item |
| itemKey={'language'} |
| icon={<IconLanguage style={headerIconStyle} />} |
| /> |
| </Dropdown> |
| {userState.user ? ( |
| <> |
| <InboxIcon /> |
| <Dropdown |
| position='bottomRight' |
| render={ |
| <Dropdown.Menu style={dropdownStyle}> |
| <Dropdown.Item onClick={logout}> |
| {t('退出')} |
| </Dropdown.Item> |
| </Dropdown.Menu> |
| } |
| > |
| <Avatar |
| size='small' |
| color={stringToColor(userState.user.display_name || userState.user.username)} |
| style={avatarStyle} |
| > |
| {(userState.user.display_name || userState.user.username)[0]} |
| </Avatar> |
| {styleState.isMobile ? null : ( |
| <Text style={{ marginLeft: '4px', fontWeight: '500' }}> |
| {userState.user.display_name || userState.user.username} |
| </Text> |
| )} |
| </Dropdown> |
| </> |
| ) : ( |
| <> |
| <Nav.Item |
| itemKey={'login'} |
| text={!styleState.isMobile ? t('登录') : null} |
| icon={<IconUser style={headerIconStyle} />} |
| /> |
| { |
| // Hide register option in self-use mode |
| !styleState.isMobile && !isSelfUseMode && ( |
| <Nav.Item |
| itemKey={'register'} |
| text={t('注册')} |
| icon={<IconKey style={headerIconStyle} />} |
| /> |
| ) |
| } |
| </> |
| )} |
| </> |
| } |
| ></Nav> |
| </div> |
| </Layout> |
| </> |
| ); |
| }; |
|
|
| export default HeaderBar; |
|
|