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")
115 lines
3.4 KiB
TypeScript
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>
|
|
);
|
|
}
|