useHeaderBar.js 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  1. /*
  2. Copyright (C) 2025 QuantumNous
  3. This program is free software: you can redistribute it and/or modify
  4. it under the terms of the GNU Affero General Public License as
  5. published by the Free Software Foundation, either version 3 of the
  6. License, or (at your option) any later version.
  7. This program is distributed in the hope that it will be useful,
  8. but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  10. GNU Affero General Public License for more details.
  11. You should have received a copy of the GNU Affero General Public License
  12. along with this program. If not, see <https://www.gnu.org/licenses/>.
  13. For commercial licensing, please contact support@quantumnous.com
  14. */
  15. import { useState, useEffect, useContext, useCallback, useMemo } from 'react';
  16. import { useNavigate, useLocation } from 'react-router-dom';
  17. import { useTranslation } from 'react-i18next';
  18. import { UserContext } from '../../context/User';
  19. import { StatusContext } from '../../context/Status';
  20. import { useSetTheme, useTheme, useActualTheme } from '../../context/Theme';
  21. import { getLogo, getSystemName, API, showSuccess } from '../../helpers';
  22. import { useIsMobile } from './useIsMobile';
  23. import { useSidebarCollapsed } from './useSidebarCollapsed';
  24. import { useMinimumLoadingTime } from './useMinimumLoadingTime';
  25. export const useHeaderBar = ({ onMobileMenuToggle, drawerOpen }) => {
  26. const { t, i18n } = useTranslation();
  27. const [userState, userDispatch] = useContext(UserContext);
  28. const [statusState] = useContext(StatusContext);
  29. const isMobile = useIsMobile();
  30. const [collapsed, toggleCollapsed] = useSidebarCollapsed();
  31. const [logoLoaded, setLogoLoaded] = useState(false);
  32. const navigate = useNavigate();
  33. const [currentLang, setCurrentLang] = useState(i18n.language);
  34. const location = useLocation();
  35. const loading = statusState?.status === undefined;
  36. const isLoading = useMinimumLoadingTime(loading);
  37. const systemName = getSystemName();
  38. const logo = getLogo();
  39. const currentDate = new Date();
  40. const isNewYear = currentDate.getMonth() === 0 && currentDate.getDate() === 1;
  41. const isSelfUseMode = statusState?.status?.self_use_mode_enabled || false;
  42. const docsLink = statusState?.status?.docs_link || '';
  43. const isDemoSiteMode = statusState?.status?.demo_site_enabled || false;
  44. // 获取顶栏模块配置
  45. const headerNavModulesConfig = statusState?.status?.HeaderNavModules;
  46. // 使用useMemo确保headerNavModules正确响应statusState变化
  47. const headerNavModules = useMemo(() => {
  48. if (headerNavModulesConfig) {
  49. try {
  50. const modules = JSON.parse(headerNavModulesConfig);
  51. // 处理向后兼容性:如果pricing是boolean,转换为对象格式
  52. if (typeof modules.pricing === 'boolean') {
  53. modules.pricing = {
  54. enabled: modules.pricing,
  55. requireAuth: false, // 默认不需要登录鉴权
  56. };
  57. }
  58. return modules;
  59. } catch (error) {
  60. console.error('解析顶栏模块配置失败:', error);
  61. return null;
  62. }
  63. }
  64. return null;
  65. }, [headerNavModulesConfig]);
  66. // 获取模型广场权限配置
  67. const pricingRequireAuth = useMemo(() => {
  68. if (headerNavModules?.pricing) {
  69. return typeof headerNavModules.pricing === 'object'
  70. ? headerNavModules.pricing.requireAuth
  71. : false; // 默认不需要登录
  72. }
  73. return false; // 默认不需要登录
  74. }, [headerNavModules]);
  75. const isConsoleRoute = location.pathname.startsWith('/console');
  76. const theme = useTheme();
  77. const actualTheme = useActualTheme();
  78. const setTheme = useSetTheme();
  79. // Logo loading effect
  80. useEffect(() => {
  81. setLogoLoaded(false);
  82. if (!logo) return;
  83. const img = new Image();
  84. img.src = logo;
  85. img.onload = () => setLogoLoaded(true);
  86. }, [logo]);
  87. // Send theme to iframe
  88. useEffect(() => {
  89. try {
  90. const iframe = document.querySelector('iframe');
  91. const cw = iframe && iframe.contentWindow;
  92. if (cw) {
  93. cw.postMessage({ themeMode: actualTheme }, '*');
  94. }
  95. } catch (e) {
  96. // Silently ignore cross-origin or access errors
  97. }
  98. }, [actualTheme]);
  99. // Language change effect
  100. useEffect(() => {
  101. const handleLanguageChanged = (lng) => {
  102. setCurrentLang(lng);
  103. try {
  104. const iframe = document.querySelector('iframe');
  105. const cw = iframe && iframe.contentWindow;
  106. if (cw) {
  107. cw.postMessage({ lang: lng }, '*');
  108. }
  109. } catch (e) {
  110. // Silently ignore cross-origin or access errors
  111. }
  112. };
  113. i18n.on('languageChanged', handleLanguageChanged);
  114. return () => {
  115. i18n.off('languageChanged', handleLanguageChanged);
  116. };
  117. }, [i18n]);
  118. // Actions
  119. const logout = useCallback(async () => {
  120. await API.get('/api/user/logout');
  121. showSuccess(t('注销成功!'));
  122. userDispatch({ type: 'logout' });
  123. localStorage.removeItem('user');
  124. navigate('/login');
  125. }, [navigate, t, userDispatch]);
  126. const handleLanguageChange = useCallback(
  127. (lang) => {
  128. i18n.changeLanguage(lang);
  129. },
  130. [i18n],
  131. );
  132. const handleThemeToggle = useCallback(
  133. (newTheme) => {
  134. if (
  135. !newTheme ||
  136. (newTheme !== 'light' && newTheme !== 'dark' && newTheme !== 'auto')
  137. ) {
  138. return;
  139. }
  140. setTheme(newTheme);
  141. },
  142. [setTheme],
  143. );
  144. const handleMobileMenuToggle = useCallback(() => {
  145. if (isMobile) {
  146. onMobileMenuToggle();
  147. } else {
  148. toggleCollapsed();
  149. }
  150. }, [isMobile, onMobileMenuToggle, toggleCollapsed]);
  151. return {
  152. // State
  153. userState,
  154. statusState,
  155. isMobile,
  156. collapsed,
  157. logoLoaded,
  158. currentLang,
  159. location,
  160. isLoading,
  161. systemName,
  162. logo,
  163. isNewYear,
  164. isSelfUseMode,
  165. docsLink,
  166. isDemoSiteMode,
  167. isConsoleRoute,
  168. theme,
  169. drawerOpen,
  170. headerNavModules,
  171. pricingRequireAuth,
  172. // Actions
  173. logout,
  174. handleLanguageChange,
  175. handleThemeToggle,
  176. handleMobileMenuToggle,
  177. navigate,
  178. t,
  179. };
  180. };