- Added a server-snapshot fallback (`() => false`) to `useIsMobile` to ensure consistent results between server-side rendering and the browser, preventing hydration mismatches. - Removed a redundant ternary in `PageLayout` sidebar styles, replacing `isMobile ? 'fixed' : 'fixed'` with a single `'fixed'` value for clarity. These changes improve SSR reliability and tidy up inline styles without affecting runtime functionality.
169 lines
5.0 KiB
JavaScript
169 lines
5.0 KiB
JavaScript
import HeaderBar from './HeaderBar.js';
|
|
import { Layout } from '@douyinfe/semi-ui';
|
|
import SiderBar from './SiderBar.js';
|
|
import App from '../../App.js';
|
|
import FooterBar from './Footer.js';
|
|
import { ToastContainer } from 'react-toastify';
|
|
import React, { useContext, useEffect, useState } from 'react';
|
|
import { useIsMobile } from '../../hooks/useIsMobile.js';
|
|
import { useSidebarCollapsed } from '../../hooks/useSidebarCollapsed.js';
|
|
import { useTranslation } from 'react-i18next';
|
|
import { API, getLogo, getSystemName, showError, setStatusData } from '../../helpers/index.js';
|
|
import { UserContext } from '../../context/User/index.js';
|
|
import { StatusContext } from '../../context/Status/index.js';
|
|
import { useLocation } from 'react-router-dom';
|
|
const { Sider, Content, Header } = Layout;
|
|
|
|
const PageLayout = () => {
|
|
const [userState, userDispatch] = useContext(UserContext);
|
|
const [statusState, statusDispatch] = useContext(StatusContext);
|
|
const isMobile = useIsMobile();
|
|
const [collapsed, , setCollapsed] = useSidebarCollapsed();
|
|
const [drawerOpen, setDrawerOpen] = useState(false);
|
|
const { i18n } = useTranslation();
|
|
const location = useLocation();
|
|
|
|
const shouldHideFooter = location.pathname === '/console/playground' || location.pathname.startsWith('/console/chat');
|
|
|
|
const shouldInnerPadding = location.pathname.includes('/console') &&
|
|
!location.pathname.startsWith('/console/chat') &&
|
|
location.pathname !== '/console/playground';
|
|
|
|
const isConsoleRoute = location.pathname.startsWith('/console');
|
|
const showSider = isConsoleRoute && (!isMobile || drawerOpen);
|
|
|
|
// Ensure sidebar not collapsed when opening drawer on mobile
|
|
useEffect(() => {
|
|
if (isMobile && drawerOpen && collapsed) {
|
|
setCollapsed(false);
|
|
}
|
|
}, [isMobile, drawerOpen, collapsed, setCollapsed]);
|
|
|
|
const loadUser = () => {
|
|
let user = localStorage.getItem('user');
|
|
if (user) {
|
|
let data = JSON.parse(user);
|
|
userDispatch({ type: 'login', payload: data });
|
|
}
|
|
};
|
|
|
|
const loadStatus = async () => {
|
|
try {
|
|
const res = await API.get('/api/status');
|
|
const { success, data } = res.data;
|
|
if (success) {
|
|
statusDispatch({ type: 'set', payload: data });
|
|
setStatusData(data);
|
|
} else {
|
|
showError('Unable to connect to server');
|
|
}
|
|
} catch (error) {
|
|
showError('Failed to load status');
|
|
}
|
|
};
|
|
|
|
useEffect(() => {
|
|
loadUser();
|
|
loadStatus().catch(console.error);
|
|
let systemName = getSystemName();
|
|
if (systemName) {
|
|
document.title = systemName;
|
|
}
|
|
let logo = getLogo();
|
|
if (logo) {
|
|
let linkElement = document.querySelector("link[rel~='icon']");
|
|
if (linkElement) {
|
|
linkElement.href = logo;
|
|
}
|
|
}
|
|
// 从localStorage获取上次使用的语言
|
|
const savedLang = localStorage.getItem('i18nextLng');
|
|
if (savedLang) {
|
|
i18n.changeLanguage(savedLang);
|
|
}
|
|
}, [i18n]);
|
|
|
|
return (
|
|
<Layout
|
|
style={{
|
|
height: '100vh',
|
|
display: 'flex',
|
|
flexDirection: 'column',
|
|
overflow: isMobile ? 'visible' : 'hidden',
|
|
}}
|
|
>
|
|
<Header
|
|
style={{
|
|
padding: 0,
|
|
height: 'auto',
|
|
lineHeight: 'normal',
|
|
position: 'fixed',
|
|
width: '100%',
|
|
top: 0,
|
|
zIndex: 100,
|
|
}}
|
|
>
|
|
<HeaderBar onMobileMenuToggle={() => setDrawerOpen(prev => !prev)} drawerOpen={drawerOpen} />
|
|
</Header>
|
|
<Layout
|
|
style={{
|
|
overflow: isMobile ? 'visible' : 'auto',
|
|
display: 'flex',
|
|
flexDirection: 'column',
|
|
}}
|
|
>
|
|
{showSider && (
|
|
<Sider
|
|
style={{
|
|
position: 'fixed',
|
|
left: 0,
|
|
top: '64px',
|
|
zIndex: 99,
|
|
border: 'none',
|
|
paddingRight: '0',
|
|
height: 'calc(100vh - 64px)',
|
|
width: 'var(--sidebar-current-width)',
|
|
}}
|
|
>
|
|
{/* 在移动端点击菜单后关闭侧边栏 */}
|
|
<SiderBar onNavigate={() => { if (isMobile) setDrawerOpen(false); }} />
|
|
</Sider>
|
|
)}
|
|
<Layout
|
|
style={{
|
|
marginLeft: isMobile ? '0' : showSider ? 'var(--sidebar-current-width)' : '0',
|
|
flex: '1 1 auto',
|
|
display: 'flex',
|
|
flexDirection: 'column',
|
|
}}
|
|
>
|
|
<Content
|
|
style={{
|
|
flex: '1 0 auto',
|
|
overflowY: isMobile ? 'visible' : 'hidden',
|
|
WebkitOverflowScrolling: 'touch',
|
|
padding: shouldInnerPadding ? (isMobile ? '5px' : '24px') : '0',
|
|
position: 'relative',
|
|
}}
|
|
>
|
|
<App />
|
|
</Content>
|
|
{!shouldHideFooter && (
|
|
<Layout.Footer
|
|
style={{
|
|
flex: '0 0 auto',
|
|
width: '100%',
|
|
}}
|
|
>
|
|
<FooterBar />
|
|
</Layout.Footer>
|
|
)}
|
|
</Layout>
|
|
</Layout>
|
|
<ToastContainer />
|
|
</Layout>
|
|
);
|
|
};
|
|
|
|
export default PageLayout;
|