feat: AI Worker 平台 MVP v1.0.0 - 多租户/项目管理/AI Worker/任务编排/HITL审核/成本治理
This commit is contained in:
@@ -0,0 +1,186 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Row, Col, Card, Statistic, Table, Tag, Space, Spin, Typography } from 'antd';
|
||||
import {
|
||||
ProjectOutlined,
|
||||
CheckCircleOutlined,
|
||||
RobotOutlined,
|
||||
AuditOutlined,
|
||||
DollarOutlined,
|
||||
ClockCircleOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import { dashboardApi } from '@/api';
|
||||
import type { DashboardStats, Task } from '@/types';
|
||||
import { TaskStatusTag } from '@/components/constants';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
const { Title } = Typography;
|
||||
|
||||
export default function Dashboard() {
|
||||
const navigate = useNavigate();
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [stats, setStats] = useState<DashboardStats | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
setLoading(true);
|
||||
dashboardApi
|
||||
.stats()
|
||||
.then(setStats)
|
||||
.finally(() => setLoading(false));
|
||||
}, []);
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div style={{ textAlign: 'center', padding: 48 }}>
|
||||
<Spin size="large" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (!stats) {
|
||||
return <div>暂无数据</div>;
|
||||
}
|
||||
|
||||
const taskStatusCols = Object.entries(stats.tasks_by_status || {});
|
||||
|
||||
const recentTaskColumns = [
|
||||
{
|
||||
title: '任务名称',
|
||||
dataIndex: 'title',
|
||||
key: 'title',
|
||||
render: (text: string, record: Task) => (
|
||||
<a onClick={() => navigate('/tasks')}>{text || record.id.slice(0, 8)}</a>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'status',
|
||||
key: 'status',
|
||||
render: (status: Task['status']) => <TaskStatusTag status={status} />,
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
dataIndex: 'created_at',
|
||||
key: 'created_at',
|
||||
render: (t: string) => dayjs(t).format('YYYY-MM-DD HH:mm'),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Title level={4} style={{ marginBottom: 24 }}>
|
||||
仪表盘
|
||||
</Title>
|
||||
|
||||
<Row gutter={[16, 16]}>
|
||||
<Col xs={12} sm={12} md={6}>
|
||||
<Card>
|
||||
<Statistic
|
||||
title="项目总数"
|
||||
value={stats.projects || 0}
|
||||
prefix={<ProjectOutlined />}
|
||||
/>
|
||||
</Card>
|
||||
</Col>
|
||||
<Col xs={12} sm={12} md={6}>
|
||||
<Card>
|
||||
<Statistic
|
||||
title="活跃 AI 工作者"
|
||||
value={stats.active_workers || 0}
|
||||
prefix={<RobotOutlined />}
|
||||
valueStyle={{ color: '#1677ff' }}
|
||||
/>
|
||||
</Card>
|
||||
</Col>
|
||||
<Col xs={12} sm={12} md={6}>
|
||||
<Card>
|
||||
<Statistic
|
||||
title="待审核"
|
||||
value={stats.pending_reviews || 0}
|
||||
prefix={<AuditOutlined />}
|
||||
valueStyle={{ color: '#722ed1' }}
|
||||
/>
|
||||
</Card>
|
||||
</Col>
|
||||
<Col xs={12} sm={12} md={6}>
|
||||
<Card>
|
||||
<Statistic
|
||||
title="总花费"
|
||||
value={stats.total_cost || 0}
|
||||
precision={2}
|
||||
prefix={<DollarOutlined />}
|
||||
valueStyle={{ color: '#fa8c16' }}
|
||||
suffix="元"
|
||||
/>
|
||||
</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<Row gutter={[16, 16]} style={{ marginTop: 16 }}>
|
||||
<Col xs={24} md={12}>
|
||||
<Card title="任务状态分布" size="small">
|
||||
<Space direction="vertical" style={{ width: '100%' }}>
|
||||
{taskStatusCols.length > 0 ? (
|
||||
taskStatusCols.map(([status, count]) => (
|
||||
<div
|
||||
key={status}
|
||||
style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
padding: '4px 0',
|
||||
}}
|
||||
>
|
||||
<TaskStatusTag status={status as Task['status']} />
|
||||
<span style={{ fontWeight: 600 }}>{count}</span>
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
<span style={{ color: '#999' }}>暂无任务数据</span>
|
||||
)}
|
||||
</Space>
|
||||
</Card>
|
||||
</Col>
|
||||
<Col xs={24} md={12}>
|
||||
<Card title="快速统计" size="small">
|
||||
<Space direction="vertical" style={{ width: '100%' }}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
||||
<span>
|
||||
<ClockCircleOutlined style={{ marginRight: 8 }} />
|
||||
已完成任务
|
||||
</span>
|
||||
<Tag color="green">{stats.tasks_by_status?.done || 0}</Tag>
|
||||
</div>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
||||
<span>
|
||||
<CheckCircleOutlined style={{ marginRight: 8 }} />
|
||||
进行中任务
|
||||
</span>
|
||||
<Tag color="orange">{stats.tasks_by_status?.in_progress || 0}</Tag>
|
||||
</div>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
||||
<span>
|
||||
<ProjectOutlined style={{ marginRight: 8 }} />
|
||||
项目总数
|
||||
</span>
|
||||
<Tag color="blue">{stats.projects || 0}</Tag>
|
||||
</div>
|
||||
</Space>
|
||||
</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
{stats.recent_tasks && stats.recent_tasks.length > 0 && (
|
||||
<Card title="最近任务" style={{ marginTop: 16 }}>
|
||||
<Table
|
||||
dataSource={stats.recent_tasks}
|
||||
columns={recentTaskColumns}
|
||||
rowKey="id"
|
||||
pagination={{ pageSize: 5 }}
|
||||
size="small"
|
||||
/>
|
||||
</Card>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user