Skip to content
Toggle navigation
P
Projects
G
Groups
S
Snippets
Help
phsl
/
new-api
This project
Loading...
Sign in
Toggle navigation
Go to a project
Project
Repository
Issues
0
Merge Requests
0
Pipelines
Wiki
Snippets
Members
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Commit
1f78dd59
authored
May 18, 2025
by
Apple\Apple
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
♻
️refactor: HeaderBar
parent
3d6493c3
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
318 additions
and
378 deletions
+318
-378
web/src/components/HeaderBar.js
+316
-376
web/src/i18n/locales/en.json
+2
-2
No files found.
web/src/components/HeaderBar.js
View file @
1f78dd59
...
@@ -3,166 +3,82 @@ import { Link, useNavigate } from 'react-router-dom';
...
@@ -3,166 +3,82 @@ import { Link, useNavigate } from 'react-router-dom';
import
{
UserContext
}
from
'../context/User'
;
import
{
UserContext
}
from
'../context/User'
;
import
{
useSetTheme
,
useTheme
}
from
'../context/Theme'
;
import
{
useSetTheme
,
useTheme
}
from
'../context/Theme'
;
import
{
useTranslation
}
from
'react-i18next'
;
import
{
useTranslation
}
from
'react-i18next'
;
import
{
API
,
getLogo
,
getSystemName
,
showSuccess
}
from
'../helpers'
;
import
{
API
,
getLogo
,
getSystemName
,
isMobile
,
showSuccess
}
from
'../helpers'
;
import
'../index.css'
;
import
fireworks
from
'react-fireworks'
;
import
fireworks
from
'react-fireworks'
;
import
{
CN
,
GB
}
from
'country-flag-icons/react/3x2'
;
import
{
import
{
IconClose
,
IconClose
,
IconHelpCircle
,
IconHome
,
IconHomeStroked
,
IconIndentLeft
,
IconComment
,
IconKey
,
IconMenu
,
IconMenu
,
IconNoteMoneyStroked
,
IconPriceTag
,
IconUser
,
IconLanguage
,
IconLanguage
,
Icon
InfoCircle
,
Icon
ChevronDown
,
Icon
CreditCard
,
Icon
Sun
,
Icon
Terminal
,
Icon
Moon
,
}
from
'@douyinfe/semi-icons'
;
}
from
'@douyinfe/semi-icons'
;
import
{
import
{
Avatar
,
Avatar
,
Button
,
Button
,
Dropdown
,
Dropdown
,
Layout
,
Nav
,
Switch
,
Tag
,
Tag
,
Typography
,
Skeleton
,
}
from
'@douyinfe/semi-ui'
;
}
from
'@douyinfe/semi-ui'
;
import
{
stringToColor
}
from
'../helpers/render'
;
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
{
StatusContext
}
from
'../context/Status/index.js'
;
import
{
StyleContext
}
from
'../context/Style/index.js'
;
// 自定义顶部栏样式
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)'
,
};
// 自定义顶部栏Logo样式
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-secondary))'
,
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
HeaderBar
=
()
=>
{
const
{
t
,
i18n
}
=
useTranslation
();
const
{
t
,
i18n
}
=
useTranslation
();
const
[
userState
,
userDispatch
]
=
useContext
(
UserContext
);
const
[
userState
,
userDispatch
]
=
useContext
(
UserContext
);
const
[
styleState
,
styleDispatch
]
=
useContext
(
StyleContext
);
const
[
statusState
,
statusDispatch
]
=
useContext
(
StatusContext
);
const
[
statusState
,
statusDispatch
]
=
useContext
(
StatusContext
);
const
[
styleState
,
styleDispatch
]
=
useContext
(
StyleContext
);
const
[
isLoading
,
setIsLoading
]
=
useState
(
true
);
let
navigate
=
useNavigate
();
let
navigate
=
useNavigate
();
const
[
currentLang
,
setCurrentLang
]
=
useState
(
i18n
.
language
);
const
[
currentLang
,
setCurrentLang
]
=
useState
(
i18n
.
language
);
const
[
mobileMenuOpen
,
setMobileMenuOpen
]
=
useState
(
false
);
const
systemName
=
getSystemName
();
const
systemName
=
getSystemName
();
const
logo
=
getLogo
();
const
logo
=
getLogo
();
const
currentDate
=
new
Date
();
const
currentDate
=
new
Date
();
// enable fireworks on new year(1.1 and 2.9-2.24)
const
isNewYear
=
currentDate
.
getMonth
()
===
0
&&
currentDate
.
getDate
()
===
1
;
const
isNewYear
=
currentDate
.
getMonth
()
===
0
&&
currentDate
.
getDate
()
===
1
;
// Check if self-use mode is enabled
const
isSelfUseMode
=
statusState
?.
status
?.
self_use_mode_enabled
||
false
;
const
isSelfUseMode
=
statusState
?.
status
?.
self_use_mode_enabled
||
false
;
const
docsLink
=
statusState
?.
status
?.
docs_link
||
''
;
const
docsLink
=
statusState
?.
status
?.
docs_link
||
''
;
const
isDemoSiteMode
=
statusState
?.
status
?.
demo_site_enabled
||
false
;
const
isDemoSiteMode
=
statusState
?.
status
?.
demo_site_enabled
||
false
;
let
buttons
=
[
const
theme
=
useTheme
();
const
setTheme
=
useSetTheme
();
const
mainNavLinks
=
[
{
{
text
:
t
(
'首页'
),
text
:
t
(
'首页'
),
itemKey
:
'home'
,
itemKey
:
'home'
,
to
:
'/'
,
to
:
'/'
,
icon
:
<
IconHome
style
=
{
headerIconStyle
}
/>
,
},
},
{
{
text
:
t
(
'控制台'
),
text
:
t
(
'控制台'
),
itemKey
:
'detail'
,
itemKey
:
'detail'
,
to
:
'/'
,
to
:
'/detail'
,
icon
:
<
IconTerminal
style
=
{
headerIconStyle
}
/>
,
},
},
{
{
text
:
t
(
'定价'
),
text
:
t
(
'定价'
),
itemKey
:
'pricing'
,
itemKey
:
'pricing'
,
to
:
'/pricing'
,
to
:
'/pricing'
,
icon
:
<
IconPriceTag
style
=
{
headerIconStyle
}
/>
,
},
},
// Only include the docs button if docsLink exists
...(
docsLink
...(
docsLink
?
[
?
[
{
{
text
:
t
(
'文档'
),
text
:
t
(
'文档'
),
itemKey
:
'docs'
,
itemKey
:
'docs'
,
isExternal
:
true
,
isExternal
:
true
,
externalLink
:
docsLink
,
externalLink
:
docsLink
,
icon
:
<
IconHelpCircle
style
=
{
headerIconStyle
}
/>
,
},
},
]
]
:
[]),
:
[]),
{
{
text
:
t
(
'关于'
),
text
:
t
(
'关于'
),
itemKey
:
'about'
,
itemKey
:
'about'
,
to
:
'/about'
,
to
:
'/about'
,
icon
:
<
IconInfoCircle
style
=
{
headerIconStyle
}
/>
,
},
},
];
];
...
@@ -172,6 +88,7 @@ const HeaderBar = () => {
...
@@ -172,6 +88,7 @@ const HeaderBar = () => {
userDispatch
({
type
:
'logout'
});
userDispatch
({
type
:
'logout'
});
localStorage
.
removeItem
(
'user'
);
localStorage
.
removeItem
(
'user'
);
navigate
(
'/login'
);
navigate
(
'/login'
);
setMobileMenuOpen
(
false
);
}
}
const
handleNewYearClick
=
()
=>
{
const
handleNewYearClick
=
()
=>
{
...
@@ -179,31 +96,24 @@ const HeaderBar = () => {
...
@@ -179,31 +96,24 @@ const HeaderBar = () => {
fireworks
.
start
();
fireworks
.
start
();
setTimeout
(()
=>
{
setTimeout
(()
=>
{
fireworks
.
stop
();
fireworks
.
stop
();
setTimeout
(()
=>
{
window
.
location
.
reload
();
},
10000
);
},
3000
);
},
3000
);
};
};
const
theme
=
useTheme
();
const
setTheme
=
useSetTheme
();
useEffect
(()
=>
{
useEffect
(()
=>
{
if
(
theme
===
'dark'
)
{
if
(
theme
===
'dark'
)
{
document
.
body
.
setAttribute
(
'theme-mode'
,
'dark'
);
document
.
body
.
setAttribute
(
'theme-mode'
,
'dark'
);
document
.
documentElement
.
classList
.
add
(
'dark'
);
}
else
{
}
else
{
document
.
body
.
removeAttribute
(
'theme-mode'
);
document
.
body
.
removeAttribute
(
'theme-mode'
);
document
.
documentElement
.
classList
.
remove
(
'dark'
);
}
}
// 发送当前主题模式给子页面
const
iframe
=
document
.
querySelector
(
'iframe'
);
const
iframe
=
document
.
querySelector
(
'iframe'
);
if
(
iframe
)
{
if
(
iframe
)
{
iframe
.
contentWindow
.
postMessage
({
themeMode
:
theme
},
'*'
);
iframe
.
contentWindow
.
postMessage
({
themeMode
:
theme
},
'*'
);
}
}
if
(
isNewYear
)
{
},
[
theme
,
isNewYear
]);
console
.
log
(
'Happy New Year!'
);
}
},
[
theme
]);
useEffect
(()
=>
{
useEffect
(()
=>
{
const
handleLanguageChanged
=
(
lng
)
=>
{
const
handleLanguageChanged
=
(
lng
)
=>
{
...
@@ -215,279 +125,309 @@ const HeaderBar = () => {
...
@@ -215,279 +125,309 @@ const HeaderBar = () => {
};
};
i18n
.
on
(
'languageChanged'
,
handleLanguageChanged
);
i18n
.
on
(
'languageChanged'
,
handleLanguageChanged
);
return
()
=>
{
return
()
=>
{
i18n
.
off
(
'languageChanged'
,
handleLanguageChanged
);
i18n
.
off
(
'languageChanged'
,
handleLanguageChanged
);
};
};
},
[
i18n
]);
},
[
i18n
]);
useEffect
(()
=>
{
// 模拟加载用户状态的过程
const
timer
=
setTimeout
(()
=>
{
setIsLoading
(
false
);
},
500
);
return
()
=>
clearTimeout
(
timer
);
},
[]);
const
handleLanguageChange
=
(
lang
)
=>
{
const
handleLanguageChange
=
(
lang
)
=>
{
i18n
.
changeLanguage
(
lang
);
i18n
.
changeLanguage
(
lang
);
setMobileMenuOpen
(
false
);
};
};
return
(
const
handleNavLinkClick
=
(
itemKey
)
=>
{
<>
if
(
itemKey
===
'home'
)
{
<
Layout
>
styleDispatch
({
type
:
'SET_INNER_PADDING'
,
payload
:
false
});
<
div
style
=
{{
width
:
'100%'
}}
>
styleDispatch
({
type
:
'SET_SIDER'
,
payload
:
false
});
<
Nav
}
else
{
className
=
{
'topnav'
}
styleDispatch
({
type
:
'SET_INNER_PADDING'
,
payload
:
true
});
mode
=
{
'horizontal'
}
if
(
!
styleState
.
isMobile
)
{
style
=
{
headerStyle
}
styleDispatch
({
type
:
'SET_SIDER'
,
payload
:
true
});
itemStyle
=
{
headerItemStyle
}
}
hoverStyle
=
{
headerItemHoverStyle
}
}
renderWrapper
=
{({
itemElement
,
isSubNav
,
isInSubNav
,
props
})
=>
{
setMobileMenuOpen
(
false
);
const
routerMap
=
{
};
about
:
'/about'
,
login
:
'/login'
,
const
renderNavLinks
=
(
isMobileView
=
false
)
=>
register
:
'/register'
,
mainNavLinks
.
map
((
link
)
=>
{
pricing
:
'/pricing'
,
const
commonLinkClasses
=
isMobileView
detail
:
'/detail'
,
?
'flex items-center gap-1 p-3 w-full text-gray-700 dark:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-md transition-colors font-semibold'
home
:
'/'
,
:
'flex items-center gap-1 p-2 text-sm text-gray-700 dark:text-gray-300 hover:text-blue-600 dark:hover:text-blue-400 transition-colors rounded-md font-semibold'
;
chat
:
'/chat'
,
};
const
linkContent
=
(
return
(
<
span
>
{
link
.
text
}
<
/span
>
<
div
);
onClick
=
{(
e
)
=>
{
if
(
props
.
itemKey
===
'home'
)
{
if
(
link
.
isExternal
)
{
styleDispatch
({
return
(
type
:
'SET_INNER_PADDING'
,
<
a
payload
:
false
,
key
=
{
link
.
itemKey
}
});
href
=
{
link
.
externalLink
}
styleDispatch
({
type
:
'SET_SIDER'
,
payload
:
false
});
target
=
'_blank'
}
else
{
rel
=
'noopener noreferrer'
styleDispatch
({
className
=
{
commonLinkClasses
}
type
:
'SET_INNER_PADDING'
,
onClick
=
{()
=>
handleNavLinkClick
(
link
.
itemKey
)}
payload
:
true
,
>
});
{
linkContent
}
if
(
!
styleState
.
isMobile
)
{
<
/a
>
styleDispatch
({
type
:
'SET_SIDER'
,
payload
:
true
});
);
}
}
}
return
(
}}
<
Link
key
=
{
link
.
itemKey
}
to
=
{
link
.
to
}
className
=
{
commonLinkClasses
}
onClick
=
{()
=>
handleNavLinkClick
(
link
.
itemKey
)}
>
{
linkContent
}
<
/Link
>
);
});
const
renderUserArea
=
()
=>
{
if
(
isLoading
)
{
return
(
<
div
className
=
"flex items-center p-1 rounded-full bg-semi-color-fill-0 dark:bg-semi-color-fill-1"
>
<
Skeleton
.
Avatar
size
=
"extra-small"
className
=
"shadow-sm"
/>
<
div
className
=
"ml-1.5 mr-1"
>
<
Skeleton
.
Title
style
=
{{
width
:
styleState
.
isMobile
?
15
:
50
,
height
:
12
}}
/
>
<
/div
>
<
/div
>
);
}
if
(
userState
.
user
)
{
return
(
<
Dropdown
position
=
"bottomRight"
render
=
{
<
Dropdown
.
Menu
className
=
"!bg-semi-color-bg-overlay !border-semi-color-border !shadow-lg !rounded-lg dark:!bg-gray-700 dark:!border-gray-600"
>
<
Dropdown
.
Item
onClick
=
{
logout
}
className
=
"!px-3 !py-1.5 !text-sm !text-semi-color-text-0 hover:!bg-semi-color-fill-1 dark:!text-gray-200 dark:hover:!bg-red-500 dark:hover:!text-white"
>
{
t
(
'退出'
)}
<
/Dropdown.Item
>
<
/Dropdown.Menu
>
}
>
<
Button
theme
=
"borderless"
type
=
"tertiary"
className
=
"flex items-center gap-1.5 !p-1 !rounded-full hover:!bg-semi-color-fill-1 dark:hover:!bg-gray-700 !bg-semi-color-fill-0 dark:!bg-semi-color-fill-1 dark:hover:!bg-semi-color-fill-2"
>
<
Avatar
size
=
"extra-small"
color
=
{
stringToColor
(
userState
.
user
.
username
)}
className
=
"shadow-sm mr-1"
>
{
userState
.
user
.
username
[
0
].
toUpperCase
()}
<
/Avatar
>
<
span
className
=
"hidden md:inline"
>
<
Typography
.
Text
className
=
"!text-xs !font-medium !text-semi-color-text-1 dark:!text-gray-300 mr-1"
>
{
userState
.
user
.
username
}
<
/Typography.Text
>
<
/span
>
<
IconChevronDown
className
=
"text-xs text-semi-color-text-2 dark:text-gray-400"
/>
<
/Button
>
<
/Dropdown
>
);
}
else
{
const
showRegisterButton
=
!
isSelfUseMode
;
const
commonSizingAndLayoutClass
=
"flex items-center justify-center !py-[10px] !px-1.5"
;
const
loginButtonSpecificStyling
=
"!bg-semi-color-fill-0 dark:!bg-semi-color-fill-1 hover:!bg-semi-color-fill-1 dark:hover:!bg-gray-700 transition-colors"
;
let
loginButtonClasses
=
`
${
commonSizingAndLayoutClass
}
${
loginButtonSpecificStyling
}
`
;
let
registerButtonClasses
=
`
${
commonSizingAndLayoutClass
}
`
;
const
loginButtonTextSpanClass
=
"!text-xs !text-semi-color-text-1 dark:!text-gray-300 !p-1.5"
;
const
registerButtonTextSpanClass
=
"!text-xs !text-white !p-1.5"
;
if
(
showRegisterButton
)
{
if
(
styleState
.
isMobile
)
{
loginButtonClasses
+=
" !rounded-full"
;
}
else
{
loginButtonClasses
+=
" !rounded-l-full !rounded-r-none"
;
}
registerButtonClasses
+=
" !rounded-r-full !rounded-l-none"
;
}
else
{
loginButtonClasses
+=
" !rounded-full"
;
}
return
(
<
div
className
=
"flex items-center"
>
<
Link
to
=
"/login"
onClick
=
{()
=>
handleNavLinkClick
(
'login'
)}
className
=
"flex"
>
<
Button
theme
=
"borderless"
type
=
"tertiary"
className
=
{
loginButtonClasses
}
>
<
span
className
=
{
loginButtonTextSpanClass
}
>
{
t
(
'登录'
)}
<
/span
>
<
/Button
>
<
/Link
>
{
showRegisterButton
&&
(
<
div
className
=
"hidden md:block"
>
<
Link
to
=
"/register"
onClick
=
{()
=>
handleNavLinkClick
(
'register'
)}
className
=
"flex -ml-px"
>
<
Button
theme
=
"solid"
type
=
"primary"
className
=
{
registerButtonClasses
}
>
>
{
props
.
isExternal
?
(
<
span
className
=
{
registerButtonTextSpanClass
}
>
<
a
{
t
(
'注册'
)}
className
=
'header-bar-text'
<
/span
>
style
=
{{
textDecoration
:
'none'
}}
<
/Button
>
href
=
{
props
.
externalLink
}
<
/Link
>
target
=
'_blank'
<
/div
>
rel
=
'noopener noreferrer'
)}
>
<
/div
>
{
itemElement
}
);
<
/a
>
}
)
:
(
};
<
Link
className
=
'header-bar-text'
return
(
style
=
{{
textDecoration
:
'none'
}}
<
header
className
=
"bg-semi-color-bg-0 text-semi-color-text-0 shadow-sm sticky top-0 z-50 transition-colors duration-300"
>
to
=
{
routerMap
[
props
.
itemKey
]}
<
div
className
=
"w-full px-4"
>
<
div
className
=
"flex items-center justify-between h-16"
>
<
div
className
=
"flex items-center"
>
<
div
className
=
"md:hidden"
>
<
Button
icon
=
{
mobileMenuOpen
?
<
IconClose
className
=
"text-lg"
/>
:
<
IconMenu
className
=
"text-lg"
/>
}
aria
-
label
=
{
mobileMenuOpen
?
t
(
'关闭菜单'
)
:
t
(
'打开菜单'
)}
onClick
=
{()
=>
setMobileMenuOpen
(
!
mobileMenuOpen
)}
theme
=
"borderless"
type
=
"tertiary"
className
=
"!p-2 !text-current focus:!bg-semi-color-fill-1 dark:focus:!bg-gray-700"
/>
<
/div
>
<
Link
to
=
"/"
onClick
=
{()
=>
handleNavLinkClick
(
'home'
)}
className
=
"flex items-center gap-2 group ml-2"
>
<
img
src
=
{
logo
}
alt
=
"logo"
className
=
"h-7 md:h-8 transition-transform duration-300 ease-in-out group-hover:scale-105"
/>
<
div
className
=
"hidden md:flex items-center gap-2"
>
<
div
className
=
"flex items-center gap-2"
>
<
Typography
.
Title
heading
=
{
4
}
className
=
"!text-lg !font-semibold !mb-0
bg-gradient-to-r from-blue-500 to-purple-500 dark:from-blue-400 dark:to-purple-400
bg-clip-text text-transparent"
>
{
systemName
}
<
/Typography.Title
>
{(
isSelfUseMode
||
isDemoSiteMode
)
&&
(
<
Tag
color
=
{
isSelfUseMode
?
'purple'
:
'blue'
}
className
=
"text-xs px-1.5 py-0.5 rounded whitespace-nowrap shadow-sm"
size
=
"small"
>
>
{
i
temElement
}
{
i
sSelfUseMode
?
t
(
'自用模式'
)
:
t
(
'演示站点'
)
}
<
/
Link
>
<
/
Tag
>
)}
)}
<
/div
>
<
/div
>
);
<
/div
>
}}
<
/Link
>
selectedKeys
=
{[]}
{(
isSelfUseMode
||
isDemoSiteMode
)
&&
(
// items={headerButtons}
<
div
className
=
"md:hidden"
>
onSelect
=
{(
key
)
=>
{}}
<
Tag
header
=
{
color
=
{
isSelfUseMode
?
'purple'
:
'blue'
}
styleState
.
isMobile
className
=
"ml-2 text-xs px-1 py-0.5 rounded whitespace-nowrap shadow-sm"
?
{
size
=
"small"
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
:
(
<
div
style
=
{
logoStyle
}
>
<
img
src
=
{
logo
}
alt
=
'logo'
style
=
{{
height
:
'28px'
}}
/
>
<
/div
>
),
text
:
(
<
div
style
=
{{
position
:
'relative'
,
display
:
'inline-block'
,
}}
>
<
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
,
boxShadow
:
'0 0 3px rgba(255, 255, 255, 0.7)'
,
}}
>
{
isSelfUseMode
?
t
(
'自用模式'
)
:
t
(
'演示站点'
)}
<
/Tag
>
)}
<
/div
>
),
}
}
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
{
isSelfUseMode
?
t
(
'自用模式'
)
:
t
(
'演示站点'
)}
itemKey
=
{
'language'
}
<
/Tag
>
icon
=
{
<
IconLanguage
style
=
{
headerIconStyle
}
/>
}
<
/div
>
/>
)}
<
/Dropdown
>
{
userState
.
user
?
(
<
nav
className
=
"hidden md:flex items-center gap-1 lg:gap-2 ml-6"
>
<>
{
renderNavLinks
()}
<
Dropdown
<
/nav
>
position
=
'bottomRight'
<
/div
>
render
=
{
<
Dropdown
.
Menu
style
=
{
dropdownStyle
}
>
<
div
className
=
"flex items-center gap-2 md:gap-3"
>
<
Dropdown
.
Item
onClick
=
{
logout
}
>
{
isNewYear
&&
(
{
t
(
'退出'
)}
<
Dropdown
<
/Dropdown.Item
>
position
=
"bottomRight"
<
/Dropdown.Menu
>
render
=
{
}
<
Dropdown
.
Menu
className
=
"!bg-semi-color-bg-overlay !border-semi-color-border !shadow-lg !rounded-lg dark:!bg-gray-700 dark:!border-gray-600"
>
>
<
Dropdown
.
Item
onClick
=
{
handleNewYearClick
}
className
=
"!text-semi-color-text-0 hover:!bg-semi-color-fill-1 dark:!text-gray-200 dark:hover:!bg-gray-600"
>
<
Avatar
Happy
New
Year
!!!
🎉
size
=
'small'
<
/Dropdown.Item
>
color
=
{
stringToColor
(
userState
.
user
.
username
)}
<
/Dropdown.Menu
>
style
=
{
avatarStyle
}
}
>
>
{
userState
.
user
.
username
[
0
]}
<
Button
<
/Avatar
>
theme
=
"borderless"
{
styleState
.
isMobile
?
null
:
(
type
=
"tertiary"
<
Text
style
=
{{
marginLeft
:
'4px'
,
fontWeight
:
'500'
}}
>
icon
=
{
<
span
className
=
"text-xl"
>
🎉
<
/span>
}
{
userState
.
user
.
username
}
aria
-
label
=
"New Year"
<
/Text
>
className
=
"!p-1.5 !text-current focus:!bg-semi-color-fill-1 dark:focus:!bg-gray-700 rounded-full"
)}
/>
<
/Dropdown
>
<
/Dropdown
>
<
/
>
)}
)
:
(
<>
<
Button
<
Nav
.
Item
icon
=
{
theme
===
'dark'
?
<
IconSun
size
=
"large"
className
=
"text-yellow-500"
/>
:
<
IconMoon
size
=
"large"
className
=
"text-gray-300"
/>
}
itemKey
=
{
'login'
}
aria
-
label
=
{
t
(
'切换主题'
)}
text
=
{
!
styleState
.
isMobile
?
t
(
'登录'
)
:
null
}
onClick
=
{()
=>
setTheme
(
theme
===
'dark'
?
false
:
true
)}
icon
=
{
<
IconUser
style
=
{
headerIconStyle
}
/>
}
theme
=
"borderless"
/>
type
=
"tertiary"
{
className
=
"!p-1.5 !text-current focus:!bg-semi-color-fill-1 dark:focus:!bg-gray-700 !rounded-full bg-semi-color-fill-0 dark:bg-semi-color-fill-1 hover:bg-semi-color-fill-1 dark:hover:bg-semi-color-fill-2"
// Hide register option in self-use mode
/>
!
styleState
.
isMobile
&&
!
isSelfUseMode
&&
(
<
Nav
.
Item
<
Dropdown
itemKey
=
{
'register'
}
position
=
"bottomRight"
text
=
{
t
(
'注册'
)}
render
=
{
icon
=
{
<
IconKey
style
=
{
headerIconStyle
}
/>
}
<
Dropdown
.
Menu
className
=
"!bg-semi-color-bg-overlay !border-semi-color-border !shadow-lg !rounded-lg dark:!bg-gray-700 dark:!border-gray-600"
>
/>
<
Dropdown
.
Item
)
onClick
=
{()
=>
handleLanguageChange
(
'zh'
)}
}
className
=
{
`!flex !items-center !gap-2 !px-3 !py-1.5 !text-sm !text-semi-color-text-0 dark:!text-gray-200
${
currentLang
===
'zh'
?
'!bg-semi-color-primary-light-default dark:!bg-blue-600 !font-semibold'
:
'hover:!bg-semi-color-fill-1 dark:hover:!bg-gray-600'
}
`
}
<
/
>
>
)}
<
CN
title
=
"中文"
className
=
"!w-5 !h-auto"
/>
<
/
>
<
span
>
中文
<
/span
>
}
<
/Dropdown.Item
>
><
/Nav
>
<
Dropdown
.
Item
onClick
=
{()
=>
handleLanguageChange
(
'en'
)}
className
=
{
`!flex !items-center !gap-2 !px-3 !py-1.5 !text-sm !text-semi-color-text-0 dark:!text-gray-200
${
currentLang
===
'en'
?
'!bg-semi-color-primary-light-default dark:!bg-blue-600 !font-semibold'
:
'hover:!bg-semi-color-fill-1 dark:hover:!bg-gray-600'
}
`
}
>
<
GB
title
=
"English"
className
=
"!w-5 !h-auto"
/>
<
span
>
English
<
/span
>
<
/Dropdown.Item
>
<
/Dropdown.Menu
>
}
>
<
Button
icon
=
{
<
IconLanguage
className
=
"text-lg"
/>
}
aria
-
label
=
{
t
(
'切换语言'
)}
theme
=
"borderless"
type
=
"tertiary"
className
=
"!p-1.5 !text-current focus:!bg-semi-color-fill-1 dark:focus:!bg-gray-700 !rounded-full bg-semi-color-fill-0 dark:bg-semi-color-fill-1 hover:bg-semi-color-fill-1 dark:hover:bg-semi-color-fill-2"
/>
<
/Dropdown
>
{
renderUserArea
()}
<
/div
>
<
/div
>
<
/div
>
<
div
className
=
"md:hidden"
>
<
div
className
=
{
`
absolute top-16 left-0 right-0 bg-semi-color-bg-0
shadow-lg p-3
transform transition-all duration-300 ease-in-out
${
mobileMenuOpen
?
'translate-y-0 opacity-100 visible'
:
'-translate-y-4 opacity-0 invisible'
}
`
}
>
<
nav
className
=
"flex flex-col gap-1"
>
{
renderNavLinks
(
true
)}
<
/nav
>
<
/div
>
<
/div
>
<
/
Layout
>
<
/
div
>
<
/
>
<
/
header
>
);
);
};
};
...
...
web/src/i18n/locales/en.json
View file @
1f78dd59
...
@@ -162,8 +162,8 @@
...
@@ -162,8 +162,8 @@
"聊天"
:
"Chat"
,
"聊天"
:
"Chat"
,
"注销成功!"
:
"Logout successful!"
,
"注销成功!"
:
"Logout successful!"
,
"注销"
:
"Logout"
,
"注销"
:
"Logout"
,
"登录"
:
"
Log
in"
,
"登录"
:
"
Sign
in"
,
"注册"
:
"
Register
"
,
"注册"
:
"
Sign up
"
,
"加载{name}中..."
:
"Loading {name}..."
,
"加载{name}中..."
:
"Loading {name}..."
,
"未登录或登录已过期,请重新登录!"
:
"Not logged in or session expired. Please login again!"
,
"未登录或登录已过期,请重新登录!"
:
"Not logged in or session expired. Please login again!"
,
"用户登录"
:
"User Login"
,
"用户登录"
:
"User Login"
,
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment