PageLayout.js 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  1. import HeaderBar from './HeaderBar.js';
  2. import { Layout } from '@douyinfe/semi-ui';
  3. import SiderBar from './SiderBar.js';
  4. import App from '../../App.js';
  5. import FooterBar from './Footer.js';
  6. import { ToastContainer } from 'react-toastify';
  7. import React, { useContext, useEffect, useState } from 'react';
  8. import { useIsMobile } from '../../hooks/useIsMobile.js';
  9. import { useSidebarCollapsed } from '../../hooks/useSidebarCollapsed.js';
  10. import { useTranslation } from 'react-i18next';
  11. import { API, getLogo, getSystemName, showError, setStatusData } from '../../helpers/index.js';
  12. import { UserContext } from '../../context/User/index.js';
  13. import { StatusContext } from '../../context/Status/index.js';
  14. import { useLocation } from 'react-router-dom';
  15. const { Sider, Content, Header } = Layout;
  16. const PageLayout = () => {
  17. const [userState, userDispatch] = useContext(UserContext);
  18. const [statusState, statusDispatch] = useContext(StatusContext);
  19. const isMobile = useIsMobile();
  20. const [collapsed, , setCollapsed] = useSidebarCollapsed();
  21. const [drawerOpen, setDrawerOpen] = useState(false);
  22. const { i18n } = useTranslation();
  23. const location = useLocation();
  24. const shouldHideFooter = location.pathname === '/console/playground' || location.pathname.startsWith('/console/chat');
  25. const shouldInnerPadding = location.pathname.includes('/console') &&
  26. !location.pathname.startsWith('/console/chat') &&
  27. location.pathname !== '/console/playground';
  28. const isConsoleRoute = location.pathname.startsWith('/console');
  29. const showSider = isConsoleRoute && (!isMobile || drawerOpen);
  30. // Ensure sidebar not collapsed when opening drawer on mobile
  31. useEffect(() => {
  32. if (isMobile && drawerOpen && collapsed) {
  33. setCollapsed(false);
  34. }
  35. }, [isMobile, drawerOpen, collapsed, setCollapsed]);
  36. const loadUser = () => {
  37. let user = localStorage.getItem('user');
  38. if (user) {
  39. let data = JSON.parse(user);
  40. userDispatch({ type: 'login', payload: data });
  41. }
  42. };
  43. const loadStatus = async () => {
  44. try {
  45. const res = await API.get('/api/status');
  46. const { success, data } = res.data;
  47. if (success) {
  48. statusDispatch({ type: 'set', payload: data });
  49. setStatusData(data);
  50. } else {
  51. showError('Unable to connect to server');
  52. }
  53. } catch (error) {
  54. showError('Failed to load status');
  55. }
  56. };
  57. useEffect(() => {
  58. loadUser();
  59. loadStatus().catch(console.error);
  60. let systemName = getSystemName();
  61. if (systemName) {
  62. document.title = systemName;
  63. }
  64. let logo = getLogo();
  65. if (logo) {
  66. let linkElement = document.querySelector("link[rel~='icon']");
  67. if (linkElement) {
  68. linkElement.href = logo;
  69. }
  70. }
  71. // 从localStorage获取上次使用的语言
  72. const savedLang = localStorage.getItem('i18nextLng');
  73. if (savedLang) {
  74. i18n.changeLanguage(savedLang);
  75. }
  76. }, [i18n]);
  77. return (
  78. <Layout
  79. style={{
  80. height: '100vh',
  81. display: 'flex',
  82. flexDirection: 'column',
  83. overflow: isMobile ? 'visible' : 'hidden',
  84. }}
  85. >
  86. <Header
  87. style={{
  88. padding: 0,
  89. height: 'auto',
  90. lineHeight: 'normal',
  91. position: 'fixed',
  92. width: '100%',
  93. top: 0,
  94. zIndex: 100,
  95. }}
  96. >
  97. <HeaderBar onMobileMenuToggle={() => setDrawerOpen(prev => !prev)} drawerOpen={drawerOpen} />
  98. </Header>
  99. <Layout
  100. style={{
  101. overflow: isMobile ? 'visible' : 'auto',
  102. display: 'flex',
  103. flexDirection: 'column',
  104. }}
  105. >
  106. {showSider && (
  107. <Sider
  108. style={{
  109. position: isMobile ? 'fixed' : 'fixed',
  110. left: 0,
  111. top: '64px',
  112. zIndex: 99,
  113. border: 'none',
  114. paddingRight: '0',
  115. height: 'calc(100vh - 64px)',
  116. width: 'var(--sidebar-current-width)',
  117. }}
  118. >
  119. <SiderBar />
  120. </Sider>
  121. )}
  122. <Layout
  123. style={{
  124. marginLeft: isMobile ? '0' : showSider ? 'var(--sidebar-current-width)' : '0',
  125. flex: '1 1 auto',
  126. display: 'flex',
  127. flexDirection: 'column',
  128. }}
  129. >
  130. <Content
  131. style={{
  132. flex: '1 0 auto',
  133. overflowY: isMobile ? 'visible' : 'hidden',
  134. WebkitOverflowScrolling: 'touch',
  135. padding: shouldInnerPadding ? (isMobile ? '5px' : '24px') : '0',
  136. position: 'relative',
  137. }}
  138. >
  139. <App />
  140. </Content>
  141. {!shouldHideFooter && (
  142. <Layout.Footer
  143. style={{
  144. flex: '0 0 auto',
  145. width: '100%',
  146. }}
  147. >
  148. <FooterBar />
  149. </Layout.Footer>
  150. )}
  151. </Layout>
  152. </Layout>
  153. <ToastContainer />
  154. </Layout>
  155. );
  156. };
  157. export default PageLayout;