Files
erp/apps/web/src/layouts/MainLayout.tsx
iven 8a012f6c6a feat(auth): add org/dept/position management, user page, and Phase 2 completion
Complete Phase 2 identity & authentication module:
- Organization CRUD with tree structure (parent_id + materialized path)
- Department CRUD nested under organizations with tree support
- Position CRUD nested under departments
- User management page with table, create/edit modal, role assignment
- Organization architecture page with 3-panel tree layout
- Frontend API layer for orgs/depts/positions
- Sidebar navigation updated with organization menu item
- Fix parse_ttl edge case for strings ending in 'd' (e.g. "invalid")
2026-04-11 04:00:32 +08:00

115 lines
3.4 KiB
TypeScript

import { Layout, Menu, theme, Avatar, Space, Dropdown, Button } from 'antd';
import {
HomeOutlined,
UserOutlined,
SafetyOutlined,
ApartmentOutlined,
BellOutlined,
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;
const menuItems = [
{ key: '/', icon: <HomeOutlined />, label: '首页' },
{ key: '/users', icon: <UserOutlined />, label: '用户管理' },
{ key: '/roles', icon: <SafetyOutlined />, label: '权限管理' },
{ key: '/organizations', icon: <ApartmentOutlined />, label: '组织架构' },
{ key: '/settings', icon: <SettingOutlined />, label: '系统设置' },
];
export default function MainLayout({ children }: { children: React.ReactNode }) {
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' }}>
<Sider trigger={null} collapsible collapsed={sidebarCollapsed} width={220}>
<div
style={{
height: 48,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
color: '#fff',
fontSize: sidebarCollapsed ? 16 : 18,
fontWeight: 'bold',
}}
>
{sidebarCollapsed ? 'E' : 'ERP Platform'}
</div>
<Menu
theme="dark"
mode="inline"
items={menuItems}
defaultSelectedKeys={['/']}
onClick={({ key }) => navigate(key)}
/>
</Sider>
<Layout>
<Header
style={{
padding: '0 16px',
background: token.colorBgContainer,
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
borderBottom: `1px solid ${token.colorBorderSecondary}`,
}}
>
<Space>
<Button
type="text"
icon={sidebarCollapsed ? <MenuUnfoldOutlined /> : <MenuFoldOutlined />}
onClick={toggleSidebar}
/>
</Space>
<Space size="middle">
<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
style={{
margin: 16,
padding: 24,
background: token.colorBgContainer,
borderRadius: token.borderRadiusLG,
minHeight: 280,
}}
>
{children}
</Content>
<Footer style={{ textAlign: 'center', padding: '8px 16px' }}>
ERP Platform · v0.1.0
</Footer>
</Layout>
</Layout>
);
}