feat(web): add login page, auth store, API client, and route guard
- API client with axios interceptors: JWT attach + 401 auto-refresh - Auth store (Zustand): login/logout/loadFromStorage with localStorage - Login page: gradient background, Ant Design form, error handling - Home page: dashboard with statistics cards - App.tsx: PrivateRoute guard, /login route, auth state restoration - MainLayout: dynamic user display, logout dropdown, menu navigation - Users API service: CRUD with pagination support
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { Layout, Menu, theme, Button, Avatar, Badge, Space } from 'antd';
|
||||
import { Layout, Menu, theme, Avatar, Space, Dropdown, Button } from 'antd';
|
||||
import {
|
||||
HomeOutlined,
|
||||
UserOutlined,
|
||||
@@ -7,8 +7,11 @@ import {
|
||||
SettingOutlined,
|
||||
MenuFoldOutlined,
|
||||
MenuUnfoldOutlined,
|
||||
LogoutOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useAppStore } from '../stores/app';
|
||||
import { useAuthStore } from '../stores/auth';
|
||||
|
||||
const { Header, Sider, Content, Footer } = Layout;
|
||||
|
||||
@@ -20,8 +23,22 @@ const menuItems = [
|
||||
];
|
||||
|
||||
export default function MainLayout({ children }: { children: React.ReactNode }) {
|
||||
const { sidebarCollapsed, toggleSidebar, tenantName } = useAppStore();
|
||||
const { sidebarCollapsed, toggleSidebar } = useAppStore();
|
||||
const { user, logout } = useAuthStore();
|
||||
const { token } = theme.useToken();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const userMenuItems = [
|
||||
{
|
||||
key: 'logout',
|
||||
icon: <LogoutOutlined />,
|
||||
label: '退出登录',
|
||||
onClick: async () => {
|
||||
await logout();
|
||||
navigate('/login');
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<Layout style={{ minHeight: '100vh' }}>
|
||||
@@ -39,7 +56,13 @@ export default function MainLayout({ children }: { children: React.ReactNode })
|
||||
>
|
||||
{sidebarCollapsed ? 'E' : 'ERP Platform'}
|
||||
</div>
|
||||
<Menu theme="dark" mode="inline" items={menuItems} defaultSelectedKeys={['/']} />
|
||||
<Menu
|
||||
theme="dark"
|
||||
mode="inline"
|
||||
items={menuItems}
|
||||
defaultSelectedKeys={['/']}
|
||||
onClick={({ key }) => navigate(key)}
|
||||
/>
|
||||
</Sider>
|
||||
<Layout>
|
||||
<Header
|
||||
@@ -60,11 +83,13 @@ export default function MainLayout({ children }: { children: React.ReactNode })
|
||||
/>
|
||||
</Space>
|
||||
<Space size="middle">
|
||||
<Badge count={5}>
|
||||
<BellOutlined style={{ fontSize: 18 }} />
|
||||
</Badge>
|
||||
<Avatar icon={<UserOutlined />} />
|
||||
<span>Admin</span>
|
||||
<BellOutlined style={{ fontSize: 18, cursor: 'pointer' }} />
|
||||
<Dropdown menu={{ items: userMenuItems }} placement="bottomRight">
|
||||
<Space style={{ cursor: 'pointer' }}>
|
||||
<Avatar icon={<UserOutlined />} />
|
||||
<span>{user?.display_name || user?.username || 'User'}</span>
|
||||
</Space>
|
||||
</Dropdown>
|
||||
</Space>
|
||||
</Header>
|
||||
<Content
|
||||
@@ -79,7 +104,7 @@ export default function MainLayout({ children }: { children: React.ReactNode })
|
||||
{children}
|
||||
</Content>
|
||||
<Footer style={{ textAlign: 'center', padding: '8px 16px' }}>
|
||||
{tenantName || 'ERP Platform'} · v0.1.0
|
||||
ERP Platform · v0.1.0
|
||||
</Footer>
|
||||
</Layout>
|
||||
</Layout>
|
||||
|
||||
Reference in New Issue
Block a user