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
94129c48
authored
Sep 02, 2025
by
t0ng7u
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
🤖
fix(web/layout): rename HeaderBar -> headerbar (case sensitive)
parent
c28e89ab
Hide whitespace changes
Inline
Side-by-side
Showing
11 changed files
with
904 additions
and
1 deletions
+904
-1
web/src/components/layout/PageLayout.jsx
+1
-1
web/src/components/layout/headerbar/ActionButtons.jsx
+74
-0
web/src/components/layout/headerbar/HeaderLogo.jsx
+81
-0
web/src/components/layout/headerbar/LanguageSelector.jsx
+59
-0
web/src/components/layout/headerbar/MobileMenuButton.jsx
+56
-0
web/src/components/layout/headerbar/Navigation.jsx
+88
-0
web/src/components/layout/headerbar/NewYearButton.jsx
+62
-0
web/src/components/layout/headerbar/NotificationButton.jsx
+46
-0
web/src/components/layout/headerbar/ThemeToggle.jsx
+109
-0
web/src/components/layout/headerbar/UserArea.jsx
+196
-0
web/src/components/layout/headerbar/index.jsx
+132
-0
No files found.
web/src/components/layout/PageLayout.jsx
View file @
94129c48
...
@@ -17,7 +17,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
...
@@ -17,7 +17,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
For commercial licensing, please contact support@quantumnous.com
*/
*/
import
HeaderBar
from
'./headerbar
/index.jsx
'
;
import
HeaderBar
from
'./headerbar'
;
import
{
Layout
}
from
'@douyinfe/semi-ui'
;
import
{
Layout
}
from
'@douyinfe/semi-ui'
;
import
SiderBar
from
'./SiderBar'
;
import
SiderBar
from
'./SiderBar'
;
import
App
from
'../../App'
;
import
App
from
'../../App'
;
...
...
web/src/components/layout/headerbar/ActionButtons.jsx
0 → 100644
View file @
94129c48
/*
Copyright (C) 2025 QuantumNous
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
*/
import
React
from
'react'
;
import
NewYearButton
from
'./NewYearButton'
;
import
NotificationButton
from
'./NotificationButton'
;
import
ThemeToggle
from
'./ThemeToggle'
;
import
LanguageSelector
from
'./LanguageSelector'
;
import
UserArea
from
'./UserArea'
;
const
ActionButtons
=
({
isNewYear
,
unreadCount
,
onNoticeOpen
,
theme
,
onThemeToggle
,
currentLang
,
onLanguageChange
,
userState
,
isLoading
,
isMobile
,
isSelfUseMode
,
logout
,
navigate
,
t
,
})
=>
{
return
(
<
div
className=
'flex items-center gap-2 md:gap-3'
>
<
NewYearButton
isNewYear=
{
isNewYear
}
/>
<
NotificationButton
unreadCount=
{
unreadCount
}
onNoticeOpen=
{
onNoticeOpen
}
t=
{
t
}
/>
<
ThemeToggle
theme=
{
theme
}
onThemeToggle=
{
onThemeToggle
}
t=
{
t
}
/>
<
LanguageSelector
currentLang=
{
currentLang
}
onLanguageChange=
{
onLanguageChange
}
t=
{
t
}
/>
<
UserArea
userState=
{
userState
}
isLoading=
{
isLoading
}
isMobile=
{
isMobile
}
isSelfUseMode=
{
isSelfUseMode
}
logout=
{
logout
}
navigate=
{
navigate
}
t=
{
t
}
/>
</
div
>
);
};
export
default
ActionButtons
;
web/src/components/layout/headerbar/HeaderLogo.jsx
0 → 100644
View file @
94129c48
/*
Copyright (C) 2025 QuantumNous
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
*/
import
React
from
'react'
;
import
{
Link
}
from
'react-router-dom'
;
import
{
Typography
,
Tag
}
from
'@douyinfe/semi-ui'
;
import
SkeletonWrapper
from
'../components/SkeletonWrapper'
;
const
HeaderLogo
=
({
isMobile
,
isConsoleRoute
,
logo
,
logoLoaded
,
isLoading
,
systemName
,
isSelfUseMode
,
isDemoSiteMode
,
t
,
})
=>
{
if
(
isMobile
&&
isConsoleRoute
)
{
return
null
;
}
return
(
<
Link
to=
'/'
className=
'group flex items-center gap-2'
>
<
div
className=
'relative w-8 h-8 md:w-8 md:h-8'
>
<
SkeletonWrapper
loading=
{
isLoading
||
!
logoLoaded
}
type=
'image'
/>
<
img
src=
{
logo
}
alt=
'logo'
className=
{
`absolute inset-0 w-full h-full transition-all duration-200 group-hover:scale-110 rounded-full ${!isLoading && logoLoaded ? 'opacity-100' : 'opacity-0'}`
}
/>
</
div
>
<
div
className=
'hidden md:flex items-center gap-2'
>
<
div
className=
'flex items-center gap-2'
>
<
SkeletonWrapper
loading=
{
isLoading
}
type=
'title'
width=
{
120
}
height=
{
24
}
>
<
Typography
.
Title
heading=
{
4
}
className=
'!text-lg !font-semibold !mb-0'
>
{
systemName
}
</
Typography
.
Title
>
</
SkeletonWrapper
>
{
(
isSelfUseMode
||
isDemoSiteMode
)
&&
!
isLoading
&&
(
<
Tag
color=
{
isSelfUseMode
?
'purple'
:
'blue'
}
className=
'text-xs px-1.5 py-0.5 rounded whitespace-nowrap shadow-sm'
size=
'small'
shape=
'circle'
>
{
isSelfUseMode
?
t
(
'自用模式'
)
:
t
(
'演示站点'
)
}
</
Tag
>
)
}
</
div
>
</
div
>
</
Link
>
);
};
export
default
HeaderLogo
;
web/src/components/layout/headerbar/LanguageSelector.jsx
0 → 100644
View file @
94129c48
/*
Copyright (C) 2025 QuantumNous
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
*/
import
React
from
'react'
;
import
{
Button
,
Dropdown
}
from
'@douyinfe/semi-ui'
;
import
{
Languages
}
from
'lucide-react'
;
import
{
CN
,
GB
}
from
'country-flag-icons/react/3x2'
;
const
LanguageSelector
=
({
currentLang
,
onLanguageChange
,
t
})
=>
{
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=
{
()
=>
onLanguageChange
(
'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
>
<
Dropdown
.
Item
onClick=
{
()
=>
onLanguageChange
(
'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=
{
<
Languages
size=
{
18
}
/>
}
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
>
);
};
export
default
LanguageSelector
;
web/src/components/layout/headerbar/MobileMenuButton.jsx
0 → 100644
View file @
94129c48
/*
Copyright (C) 2025 QuantumNous
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
*/
import
React
from
'react'
;
import
{
Button
}
from
'@douyinfe/semi-ui'
;
import
{
IconClose
,
IconMenu
}
from
'@douyinfe/semi-icons'
;
const
MobileMenuButton
=
({
isConsoleRoute
,
isMobile
,
drawerOpen
,
collapsed
,
onToggle
,
t
,
})
=>
{
if
(
!
isConsoleRoute
||
!
isMobile
)
{
return
null
;
}
return
(
<
Button
icon=
{
(
isMobile
?
drawerOpen
:
collapsed
)
?
(
<
IconClose
className=
'text-lg'
/>
)
:
(
<
IconMenu
className=
'text-lg'
/>
)
}
aria
-
label=
{
(
isMobile
?
drawerOpen
:
collapsed
)
?
t
(
'关闭侧边栏'
)
:
t
(
'打开侧边栏'
)
}
onClick=
{
onToggle
}
theme=
'borderless'
type=
'tertiary'
className=
'!p-2 !text-current focus:!bg-semi-color-fill-1 dark:focus:!bg-gray-700'
/>
);
};
export
default
MobileMenuButton
;
web/src/components/layout/headerbar/Navigation.jsx
0 → 100644
View file @
94129c48
/*
Copyright (C) 2025 QuantumNous
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
*/
import
React
from
'react'
;
import
{
Link
}
from
'react-router-dom'
;
import
SkeletonWrapper
from
'../components/SkeletonWrapper'
;
const
Navigation
=
({
mainNavLinks
,
isMobile
,
isLoading
,
userState
,
pricingRequireAuth
,
})
=>
{
const
renderNavLinks
=
()
=>
{
const
baseClasses
=
'flex-shrink-0 flex items-center gap-1 font-semibold rounded-md transition-all duration-200 ease-in-out'
;
const
hoverClasses
=
'hover:text-semi-color-primary'
;
const
spacingClasses
=
isMobile
?
'p-1'
:
'p-2'
;
const
commonLinkClasses
=
`
${
baseClasses
}
${
spacingClasses
}
${
hoverClasses
}
`
;
return
mainNavLinks
.
map
((
link
)
=>
{
const
linkContent
=
<
span
>
{
link
.
text
}
</
span
>;
if
(
link
.
isExternal
)
{
return
(
<
a
key=
{
link
.
itemKey
}
href=
{
link
.
externalLink
}
target=
'_blank'
rel=
'noopener noreferrer'
className=
{
commonLinkClasses
}
>
{
linkContent
}
</
a
>
);
}
let
targetPath
=
link
.
to
;
if
(
link
.
itemKey
===
'console'
&&
!
userState
.
user
)
{
targetPath
=
'/login'
;
}
if
(
link
.
itemKey
===
'pricing'
&&
pricingRequireAuth
&&
!
userState
.
user
)
{
targetPath
=
'/login'
;
}
return
(
<
Link
key=
{
link
.
itemKey
}
to=
{
targetPath
}
className=
{
commonLinkClasses
}
>
{
linkContent
}
</
Link
>
);
});
};
return
(
<
nav
className=
'flex flex-1 items-center gap-1 lg:gap-2 mx-2 md:mx-4 overflow-x-auto whitespace-nowrap scrollbar-hide'
>
<
SkeletonWrapper
loading=
{
isLoading
}
type=
'navigation'
count=
{
4
}
width=
{
60
}
height=
{
16
}
isMobile=
{
isMobile
}
>
{
renderNavLinks
()
}
</
SkeletonWrapper
>
</
nav
>
);
};
export
default
Navigation
;
web/src/components/layout/headerbar/NewYearButton.jsx
0 → 100644
View file @
94129c48
/*
Copyright (C) 2025 QuantumNous
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
*/
import
React
from
'react'
;
import
{
Button
,
Dropdown
}
from
'@douyinfe/semi-ui'
;
import
fireworks
from
'react-fireworks'
;
const
NewYearButton
=
({
isNewYear
})
=>
{
if
(
!
isNewYear
)
{
return
null
;
}
const
handleNewYearClick
=
()
=>
{
fireworks
.
init
(
'root'
,
{});
fireworks
.
start
();
setTimeout
(()
=>
{
fireworks
.
stop
();
},
3000
);
};
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=
{
handleNewYearClick
}
className=
'!text-semi-color-text-0 hover:!bg-semi-color-fill-1 dark:!text-gray-200 dark:hover:!bg-gray-600'
>
Happy New Year!!! 🎉
</
Dropdown
.
Item
>
</
Dropdown
.
Menu
>
}
>
<
Button
theme=
'borderless'
type=
'tertiary'
icon=
{
<
span
className=
'text-xl'
>
🎉
</
span
>
}
aria
-
label=
'New Year'
className=
'!p-1.5 !text-current focus:!bg-semi-color-fill-1 dark:focus:!bg-gray-700 rounded-full'
/>
</
Dropdown
>
);
};
export
default
NewYearButton
;
web/src/components/layout/headerbar/NotificationButton.jsx
0 → 100644
View file @
94129c48
/*
Copyright (C) 2025 QuantumNous
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
*/
import
React
from
'react'
;
import
{
Button
,
Badge
}
from
'@douyinfe/semi-ui'
;
import
{
Bell
}
from
'lucide-react'
;
const
NotificationButton
=
({
unreadCount
,
onNoticeOpen
,
t
})
=>
{
const
buttonProps
=
{
icon
:
<
Bell
size=
{
18
}
/>,
'aria-label'
:
t
(
'系统公告'
),
onClick
:
onNoticeOpen
,
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'
,
};
if
(
unreadCount
>
0
)
{
return
(
<
Badge
count=
{
unreadCount
}
type=
'danger'
overflowCount=
{
99
}
>
<
Button
{
...
buttonProps
}
/>
</
Badge
>
);
}
return
<
Button
{
...
buttonProps
}
/>;
};
export
default
NotificationButton
;
web/src/components/layout/headerbar/ThemeToggle.jsx
0 → 100644
View file @
94129c48
/*
Copyright (C) 2025 QuantumNous
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
*/
import
React
,
{
useMemo
}
from
'react'
;
import
{
Button
,
Dropdown
}
from
'@douyinfe/semi-ui'
;
import
{
Sun
,
Moon
,
Monitor
}
from
'lucide-react'
;
import
{
useActualTheme
}
from
'../../../context/Theme'
;
const
ThemeToggle
=
({
theme
,
onThemeToggle
,
t
})
=>
{
const
actualTheme
=
useActualTheme
();
const
themeOptions
=
useMemo
(
()
=>
[
{
key
:
'light'
,
icon
:
<
Sun
size=
{
18
}
/>,
buttonIcon
:
<
Sun
size=
{
18
}
/>,
label
:
t
(
'浅色模式'
),
description
:
t
(
'始终使用浅色主题'
),
},
{
key
:
'dark'
,
icon
:
<
Moon
size=
{
18
}
/>,
buttonIcon
:
<
Moon
size=
{
18
}
/>,
label
:
t
(
'深色模式'
),
description
:
t
(
'始终使用深色主题'
),
},
{
key
:
'auto'
,
icon
:
<
Monitor
size=
{
18
}
/>,
buttonIcon
:
<
Monitor
size=
{
18
}
/>,
label
:
t
(
'自动模式'
),
description
:
t
(
'跟随系统主题设置'
),
},
],
[
t
],
);
const
getItemClassName
=
(
isSelected
)
=>
isSelected
?
'!bg-semi-color-primary-light-default !font-semibold'
:
'hover:!bg-semi-color-fill-1'
;
const
currentButtonIcon
=
useMemo
(()
=>
{
const
currentOption
=
themeOptions
.
find
((
option
)
=>
option
.
key
===
theme
);
return
currentOption
?.
buttonIcon
||
themeOptions
[
2
].
buttonIcon
;
},
[
theme
,
themeOptions
]);
return
(
<
Dropdown
position=
'bottomRight'
render=
{
<
Dropdown
.
Menu
>
{
themeOptions
.
map
((
option
)
=>
(
<
Dropdown
.
Item
key=
{
option
.
key
}
icon=
{
option
.
icon
}
onClick=
{
()
=>
onThemeToggle
(
option
.
key
)
}
className=
{
getItemClassName
(
theme
===
option
.
key
)
}
>
<
div
className=
'flex flex-col'
>
<
span
>
{
option
.
label
}
</
span
>
<
span
className=
'text-xs text-semi-color-text-2'
>
{
option
.
description
}
</
span
>
</
div
>
</
Dropdown
.
Item
>
))
}
{
theme
===
'auto'
&&
(
<>
<
Dropdown
.
Divider
/>
<
div
className=
'px-3 py-2 text-xs text-semi-color-text-2'
>
{
t
(
'当前跟随系统'
)
}
:
{
actualTheme
===
'dark'
?
t
(
'深色'
)
:
t
(
'浅色'
)
}
</
div
>
</>
)
}
</
Dropdown
.
Menu
>
}
>
<
Button
icon=
{
currentButtonIcon
}
aria
-
label=
{
t
(
'切换主题'
)
}
theme=
'borderless'
type=
'tertiary'
className=
'!p-1.5 !text-current focus:!bg-semi-color-fill-1 !rounded-full !bg-semi-color-fill-0 hover:!bg-semi-color-fill-1'
/>
</
Dropdown
>
);
};
export
default
ThemeToggle
;
web/src/components/layout/headerbar/UserArea.jsx
0 → 100644
View file @
94129c48
/*
Copyright (C) 2025 QuantumNous
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
*/
import
React
from
'react'
;
import
{
Link
}
from
'react-router-dom'
;
import
{
Avatar
,
Button
,
Dropdown
,
Typography
}
from
'@douyinfe/semi-ui'
;
import
{
ChevronDown
}
from
'lucide-react'
;
import
{
IconExit
,
IconUserSetting
,
IconCreditCard
,
IconKey
,
}
from
'@douyinfe/semi-icons'
;
import
{
stringToColor
}
from
'../../../helpers'
;
import
SkeletonWrapper
from
'../components/SkeletonWrapper'
;
const
UserArea
=
({
userState
,
isLoading
,
isMobile
,
isSelfUseMode
,
logout
,
navigate
,
t
,
})
=>
{
if
(
isLoading
)
{
return
(
<
SkeletonWrapper
loading=
{
true
}
type=
'userArea'
width=
{
50
}
isMobile=
{
isMobile
}
/>
);
}
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=
{
()
=>
{
navigate
(
'/console/personal'
);
}
}
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-blue-500 dark:hover:!text-white'
>
<
div
className=
'flex items-center gap-2'
>
<
IconUserSetting
size=
'small'
className=
'text-gray-500 dark:text-gray-400'
/>
<
span
>
{
t
(
'个人设置'
)
}
</
span
>
</
div
>
</
Dropdown
.
Item
>
<
Dropdown
.
Item
onClick=
{
()
=>
{
navigate
(
'/console/token'
);
}
}
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-blue-500 dark:hover:!text-white'
>
<
div
className=
'flex items-center gap-2'
>
<
IconKey
size=
'small'
className=
'text-gray-500 dark:text-gray-400'
/>
<
span
>
{
t
(
'令牌管理'
)
}
</
span
>
</
div
>
</
Dropdown
.
Item
>
<
Dropdown
.
Item
onClick=
{
()
=>
{
navigate
(
'/console/topup'
);
}
}
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-blue-500 dark:hover:!text-white'
>
<
div
className=
'flex items-center gap-2'
>
<
IconCreditCard
size=
'small'
className=
'text-gray-500 dark:text-gray-400'
/>
<
span
>
{
t
(
'钱包管理'
)
}
</
span
>
</
div
>
</
Dropdown
.
Item
>
<
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'
>
<
div
className=
'flex items-center gap-2'
>
<
IconExit
size=
'small'
className=
'text-gray-500 dark:text-gray-400'
/>
<
span
>
{
t
(
'退出'
)
}
</
span
>
</
div
>
</
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=
'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
>
<
ChevronDown
size=
{
14
}
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
(
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'
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'
className=
'flex -ml-px'
>
<
Button
theme=
'solid'
type=
'primary'
className=
{
registerButtonClasses
}
>
<
span
className=
{
registerButtonTextSpanClass
}
>
{
t
(
'注册'
)
}
</
span
>
</
Button
>
</
Link
>
</
div
>
)
}
</
div
>
);
}
};
export
default
UserArea
;
web/src/components/layout/headerbar/index.jsx
0 → 100644
View file @
94129c48
/*
Copyright (C) 2025 QuantumNous
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
*/
import
React
from
'react'
;
import
{
useHeaderBar
}
from
'../../../hooks/common/useHeaderBar'
;
import
{
useNotifications
}
from
'../../../hooks/common/useNotifications'
;
import
{
useNavigation
}
from
'../../../hooks/common/useNavigation'
;
import
NoticeModal
from
'../NoticeModal'
;
import
MobileMenuButton
from
'./MobileMenuButton'
;
import
HeaderLogo
from
'./HeaderLogo'
;
import
Navigation
from
'./Navigation'
;
import
ActionButtons
from
'./ActionButtons'
;
const
HeaderBar
=
({
onMobileMenuToggle
,
drawerOpen
})
=>
{
const
{
userState
,
statusState
,
isMobile
,
collapsed
,
logoLoaded
,
currentLang
,
isLoading
,
systemName
,
logo
,
isNewYear
,
isSelfUseMode
,
docsLink
,
isDemoSiteMode
,
isConsoleRoute
,
theme
,
headerNavModules
,
pricingRequireAuth
,
logout
,
handleLanguageChange
,
handleThemeToggle
,
handleMobileMenuToggle
,
navigate
,
t
,
}
=
useHeaderBar
({
onMobileMenuToggle
,
drawerOpen
});
const
{
noticeVisible
,
unreadCount
,
handleNoticeOpen
,
handleNoticeClose
,
getUnreadKeys
,
}
=
useNotifications
(
statusState
);
const
{
mainNavLinks
}
=
useNavigation
(
t
,
docsLink
,
headerNavModules
);
return
(
<
header
className=
'text-semi-color-text-0 sticky top-0 z-50 transition-colors duration-300 bg-white/75 dark:bg-zinc-900/75 backdrop-blur-lg'
>
<
NoticeModal
visible=
{
noticeVisible
}
onClose=
{
handleNoticeClose
}
isMobile=
{
isMobile
}
defaultTab=
{
unreadCount
>
0
?
'system'
:
'inApp'
}
unreadKeys=
{
getUnreadKeys
()
}
/>
<
div
className=
'w-full px-2'
>
<
div
className=
'flex items-center justify-between h-16'
>
<
div
className=
'flex items-center'
>
<
MobileMenuButton
isConsoleRoute=
{
isConsoleRoute
}
isMobile=
{
isMobile
}
drawerOpen=
{
drawerOpen
}
collapsed=
{
collapsed
}
onToggle=
{
handleMobileMenuToggle
}
t=
{
t
}
/>
<
HeaderLogo
isMobile=
{
isMobile
}
isConsoleRoute=
{
isConsoleRoute
}
logo=
{
logo
}
logoLoaded=
{
logoLoaded
}
isLoading=
{
isLoading
}
systemName=
{
systemName
}
isSelfUseMode=
{
isSelfUseMode
}
isDemoSiteMode=
{
isDemoSiteMode
}
t=
{
t
}
/>
</
div
>
<
Navigation
mainNavLinks=
{
mainNavLinks
}
isMobile=
{
isMobile
}
isLoading=
{
isLoading
}
userState=
{
userState
}
pricingRequireAuth=
{
pricingRequireAuth
}
/>
<
ActionButtons
isNewYear=
{
isNewYear
}
unreadCount=
{
unreadCount
}
onNoticeOpen=
{
handleNoticeOpen
}
theme=
{
theme
}
onThemeToggle=
{
handleThemeToggle
}
currentLang=
{
currentLang
}
onLanguageChange=
{
handleLanguageChange
}
userState=
{
userState
}
isLoading=
{
isLoading
}
isMobile=
{
isMobile
}
isSelfUseMode=
{
isSelfUseMode
}
logout=
{
logout
}
navigate=
{
navigate
}
t=
{
t
}
/>
</
div
>
</
div
>
</
header
>
);
};
export
default
HeaderBar
;
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