新项目, antd6, react19
This commit is contained in:
129
src/layouts/AppLayout.tsx
Normal file
129
src/layouts/AppLayout.tsx
Normal file
@@ -0,0 +1,129 @@
|
||||
import { Layout, Spin } from 'antd';
|
||||
import { Content, Header } from 'antd/es/layout/layout';
|
||||
import Sider from 'antd/es/layout/Sider';
|
||||
import { useEffect, useState } from 'react';
|
||||
import Footer from '@/components/Footer';
|
||||
import { GapBox } from '@/components/GapBox';
|
||||
import { HeaderUserInfo } from '@/components/Header/HeaderUserInfo';
|
||||
import { NavigateMenuDrawer } from '@/components/SiderMenu/NavigateMenuDrawer';
|
||||
import NavMenu from '@/components/SiderMenu/NavMenu';
|
||||
import TabNavPlugin from '@/components/TabNavPlugin';
|
||||
import { DefaultERPName, headerHeight } from '@/configs/config';
|
||||
import Outlet from '@/router/Outlet';
|
||||
import { useAuthStore } from '@/store/AuthStore';
|
||||
import { useUserStore } from '@/store/UserStore';
|
||||
import { getDevice, toObject } from '@/utils/common';
|
||||
import { loginState } from './base';
|
||||
import ErrorBoundary from './ErrorBoundary';
|
||||
|
||||
const AppLayout = () => {
|
||||
const [loading, setLoading] = useState(true);
|
||||
const isPhone = getDevice() == 'phone';
|
||||
const userStore = useUserStore();
|
||||
const authStore = useAuthStore();
|
||||
|
||||
useEffect(() => {
|
||||
setLoading(true);
|
||||
loginState().then((res) => {
|
||||
// console.log(res);
|
||||
userStore.updateUser(toObject(res.user_info));
|
||||
authStore.updateAuth(toObject(res.auth_info) as any);
|
||||
localStorage.setItem('admin_user_id', res?.user_info?.user_id);
|
||||
setLoading(false);
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
{!loading ? (
|
||||
<Layout style={{ minHeight: '100vh' }}>
|
||||
<Header
|
||||
style={{
|
||||
background: '#fff',
|
||||
position: 'sticky',
|
||||
top: 0,
|
||||
zIndex: 101,
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
paddingLeft: 12,
|
||||
paddingRight: 12,
|
||||
borderBottom: '1px solid #ddd',
|
||||
height: headerHeight,
|
||||
flexShrink: 0,
|
||||
lineHeight: 1,
|
||||
boxSizing: 'border-box',
|
||||
}}
|
||||
>
|
||||
<GapBox style={{ display: 'inline-flex' }}>
|
||||
{isPhone ? (
|
||||
<NavigateMenuDrawer />
|
||||
) : (
|
||||
<div style={{ fontWeight: 'bold', fontSize: 16 }}>{DefaultERPName}</div>
|
||||
)}
|
||||
{Date.now()}
|
||||
</GapBox>
|
||||
<div style={{ display: 'inline-flex' }}>
|
||||
<HeaderUserInfo />
|
||||
</div>
|
||||
</Header>
|
||||
<Layout style={{ justifyContent: 'flex-start' }}>
|
||||
{isPhone ? null : (
|
||||
<Sider
|
||||
// collapsible
|
||||
// onCollapse={(collapsed: boolean) => {
|
||||
// setCollapsed(collapsed);
|
||||
// }}
|
||||
style={{
|
||||
background: '#fff',
|
||||
overflow: 'auto',
|
||||
// height: `calc(100vh - ${headerHeight}px)`,
|
||||
position: 'sticky',
|
||||
// zIndex: 1000,
|
||||
left: 0,
|
||||
// top: headerHeight,
|
||||
}}
|
||||
width={200}
|
||||
// width={window?.dfConfig?.language == 'zh-cn' ? 100 : 240}
|
||||
// collapsed={collapsed}
|
||||
// collapsedWidth={60}
|
||||
>
|
||||
<NavMenu />
|
||||
{/* <NavMenuCard left={100} /> */}
|
||||
</Sider>
|
||||
)}
|
||||
<Content
|
||||
style={{
|
||||
// minHeight: `calc(100vh - ${headerHeight}px)`,
|
||||
padding: isPhone ? '12px 0 0 0' : '12px 12px 0 12px',
|
||||
overflow: 'inherit',
|
||||
background: '#F4F4F4',
|
||||
boxSizing: 'border-box',
|
||||
minWidth: 0,
|
||||
}}
|
||||
>
|
||||
<TabNavPlugin />
|
||||
{/* <SelfAccount /> */}
|
||||
<div style={{ minHeight: `calc(100vh - ${headerHeight}px - 32px - 50px)`, background: '#fff' }}>
|
||||
<ErrorBoundary>
|
||||
<Outlet />
|
||||
</ErrorBoundary>
|
||||
</div>
|
||||
<Footer />
|
||||
</Content>
|
||||
</Layout>
|
||||
</Layout>
|
||||
) : (
|
||||
<Spin
|
||||
style={{ height: '90vh', display: 'flex', alignItems: 'center', justifyContent: 'center', maxHeight: '90vh' }}
|
||||
size='large'
|
||||
tip={<span style={{ color: '#000' }}>加载数据...</span>}
|
||||
>
|
||||
|
||||
</Spin>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default AppLayout;
|
||||
11
src/layouts/EmptyLayout.tsx
Normal file
11
src/layouts/EmptyLayout.tsx
Normal file
@@ -0,0 +1,11 @@
|
||||
import Outlet from '@/router/Outlet';
|
||||
import ErrorBoundary from './ErrorBoundary';
|
||||
|
||||
const EmptyLayout = () => {
|
||||
return (
|
||||
<ErrorBoundary>
|
||||
<Outlet />
|
||||
</ErrorBoundary>
|
||||
);
|
||||
};
|
||||
export default EmptyLayout;
|
||||
81
src/layouts/ErrorBoundary.tsx
Normal file
81
src/layouts/ErrorBoundary.tsx
Normal file
@@ -0,0 +1,81 @@
|
||||
import { Button, Result } from 'antd';
|
||||
import React from 'react';
|
||||
|
||||
type IProps = {
|
||||
fallback?: (error: any, info?: any) => React.ReactNode;
|
||||
children?: React.ReactNode;
|
||||
};
|
||||
|
||||
/** 错误边界组件 */
|
||||
class ErrorBoundary extends React.Component<IProps> {
|
||||
state: { hasError: boolean; errorMsg?: React.ReactNode };
|
||||
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
this.state = { hasError: false };
|
||||
}
|
||||
|
||||
static getDerivedStateFromError(_error: any) {
|
||||
// console.log("error", error);
|
||||
// 更新状态,以便下一次渲染将显示后备 UI。
|
||||
return { hasError: true };
|
||||
}
|
||||
|
||||
componentDidCatch(error: any, info: any) {
|
||||
// console.log(error, info);
|
||||
this.setState({ errorMsg: `${error}${info.componentStack}` });
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.state.hasError) {
|
||||
// 你可以渲染任何自定义后备 UI
|
||||
return (
|
||||
this.props?.fallback?.(this.state.errorMsg) || (
|
||||
<Result
|
||||
status='error'
|
||||
title={
|
||||
<div style={{ fontSize: 20 }}>
|
||||
<div>页面发生异常,错误内容已上报!</div>
|
||||
<div>开发人员紧急修复中,敬请耐心等待!</div>
|
||||
</div>
|
||||
}
|
||||
subTitle={
|
||||
<pre
|
||||
style={{
|
||||
color: '#ff4d4f',
|
||||
whiteSpace: 'break-spaces',
|
||||
padding: 20,
|
||||
textAlign: 'left',
|
||||
margin: 0,
|
||||
}}
|
||||
>
|
||||
{this.state.errorMsg}
|
||||
</pre>
|
||||
}
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
flexDirection: 'column',
|
||||
minHeight: 'calc(100vh - 180px)',
|
||||
}}
|
||||
extra={[
|
||||
<Button
|
||||
type='primary'
|
||||
key='console'
|
||||
onClick={() => {
|
||||
window.location.reload();
|
||||
}}
|
||||
>
|
||||
请刷新重试
|
||||
</Button>,
|
||||
]}
|
||||
/>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return this.props?.children;
|
||||
}
|
||||
}
|
||||
|
||||
export default ErrorBoundary;
|
||||
13
src/layouts/base.ts
Normal file
13
src/layouts/base.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { requestLite } from '@/utils/useRequest';
|
||||
|
||||
export const loginState = async () => {
|
||||
return new Promise<any>((resolve, reject) => {
|
||||
requestLite('/Users/loginState').then((res) => {
|
||||
if (res?.err_code == 0) {
|
||||
resolve(res);
|
||||
} else {
|
||||
reject(null);
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
Reference in New Issue
Block a user