feat: AI Worker 平台 MVP v1.0.0 - 多租户/项目管理/AI Worker/任务编排/HITL审核/成本治理
This commit is contained in:
@@ -0,0 +1,194 @@
|
||||
import { useState } from 'react';
|
||||
import { Outlet, useNavigate, useLocation } from 'react-router-dom';
|
||||
import {
|
||||
Layout,
|
||||
Menu,
|
||||
Avatar,
|
||||
Dropdown,
|
||||
Space,
|
||||
Typography,
|
||||
Tag,
|
||||
theme,
|
||||
} from 'antd';
|
||||
import {
|
||||
DashboardOutlined,
|
||||
ProjectOutlined,
|
||||
CheckSquareOutlined,
|
||||
RobotOutlined,
|
||||
AuditOutlined,
|
||||
LogoutOutlined,
|
||||
MenuFoldOutlined,
|
||||
MenuUnfoldOutlined,
|
||||
UserOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import type { MenuProps } from 'antd';
|
||||
import { useAuthStore } from '@/stores/auth';
|
||||
import type { UserRole } from '@/types';
|
||||
|
||||
const { Header, Sider, Content } = Layout;
|
||||
const { Text } = Typography;
|
||||
|
||||
const roleLabels: Record<UserRole, string> = {
|
||||
super_admin: '超级管理员',
|
||||
tenant_admin: '租户管理员',
|
||||
project_manager: '项目经理',
|
||||
reviewer: '审核员',
|
||||
worker: '执行者',
|
||||
};
|
||||
|
||||
const roleColors: Record<UserRole, string> = {
|
||||
super_admin: 'red',
|
||||
tenant_admin: 'orange',
|
||||
project_manager: 'blue',
|
||||
reviewer: 'purple',
|
||||
worker: 'green',
|
||||
};
|
||||
|
||||
const menuItems: MenuProps['items'] = [
|
||||
{
|
||||
key: '/dashboard',
|
||||
icon: <DashboardOutlined />,
|
||||
label: '仪表盘',
|
||||
},
|
||||
{
|
||||
key: '/projects',
|
||||
icon: <ProjectOutlined />,
|
||||
label: '项目管理',
|
||||
},
|
||||
{
|
||||
key: '/tasks',
|
||||
icon: <CheckSquareOutlined />,
|
||||
label: '任务管理',
|
||||
},
|
||||
{
|
||||
key: '/workers',
|
||||
icon: <RobotOutlined />,
|
||||
label: 'AI 工作者',
|
||||
},
|
||||
{
|
||||
key: '/reviews',
|
||||
icon: <AuditOutlined />,
|
||||
label: '审核管理',
|
||||
},
|
||||
];
|
||||
|
||||
export default function AppLayout() {
|
||||
const [collapsed, setCollapsed] = useState(false);
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
const { user, logout } = useAuthStore();
|
||||
|
||||
const handleMenuClick: MenuProps['onClick'] = ({ key }) => {
|
||||
navigate(key);
|
||||
};
|
||||
|
||||
const handleLogout = () => {
|
||||
logout();
|
||||
navigate('/login');
|
||||
};
|
||||
|
||||
const userMenuItems: MenuProps['items'] = [
|
||||
{
|
||||
key: 'user-info',
|
||||
label: (
|
||||
<Space direction="vertical" size={0}>
|
||||
<Text strong>{user?.username}</Text>
|
||||
<Text type="secondary">{user?.email}</Text>
|
||||
</Space>
|
||||
),
|
||||
disabled: true,
|
||||
},
|
||||
{ type: 'divider' },
|
||||
{
|
||||
key: 'logout',
|
||||
icon: <LogoutOutlined />,
|
||||
label: '退出登录',
|
||||
onClick: handleLogout,
|
||||
},
|
||||
];
|
||||
|
||||
const selectedKey = '/' + location.pathname.split('/')[1];
|
||||
|
||||
return (
|
||||
<Layout style={{ minHeight: '100vh' }}>
|
||||
<Sider
|
||||
trigger={null}
|
||||
collapsible
|
||||
collapsed={collapsed}
|
||||
theme="dark"
|
||||
width={220}
|
||||
style={{
|
||||
overflow: 'auto',
|
||||
height: '100vh',
|
||||
position: 'sticky',
|
||||
top: 0,
|
||||
left: 0,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
height: 64,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
color: '#fff',
|
||||
fontSize: collapsed ? 14 : 18,
|
||||
fontWeight: 700,
|
||||
borderBottom: '1px solid rgba(255,255,255,0.1)',
|
||||
}}
|
||||
>
|
||||
{collapsed ? 'AI' : 'AI Worker 平台'}
|
||||
</div>
|
||||
<Menu
|
||||
theme="dark"
|
||||
mode="inline"
|
||||
selectedKeys={[selectedKey]}
|
||||
items={menuItems}
|
||||
onClick={handleMenuClick}
|
||||
/>
|
||||
</Sider>
|
||||
<Layout>
|
||||
<Header
|
||||
style={{
|
||||
padding: '0 24px',
|
||||
background: '#fff',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
boxShadow: '0 1px 4px rgba(0,21,41,0.08)',
|
||||
position: 'sticky',
|
||||
top: 0,
|
||||
zIndex: 9,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{ cursor: 'pointer', fontSize: 18 }}
|
||||
onClick={() => setCollapsed(!collapsed)}
|
||||
>
|
||||
{collapsed ? <MenuUnfoldOutlined /> : <MenuFoldOutlined />}
|
||||
</div>
|
||||
<Dropdown menu={{ items: userMenuItems }} placement="bottomRight">
|
||||
<Space style={{ cursor: 'pointer' }}>
|
||||
<Avatar icon={<UserOutlined />} style={{ backgroundColor: '#1677ff' }} />
|
||||
<span>{user?.username || '用户'}</span>
|
||||
{user?.role && (
|
||||
<Tag color={roleColors[user.role]}>{roleLabels[user.role]}</Tag>
|
||||
)}
|
||||
</Space>
|
||||
</Dropdown>
|
||||
</Header>
|
||||
<Content
|
||||
style={{
|
||||
margin: 24,
|
||||
padding: 24,
|
||||
background: '#fff',
|
||||
borderRadius: 8,
|
||||
minHeight: 280,
|
||||
}}
|
||||
>
|
||||
<Outlet />
|
||||
</Content>
|
||||
</Layout>
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user