173 lines
5.3 KiB
TypeScript
173 lines
5.3 KiB
TypeScript
import { useEffect, useState } from 'react';
|
|
import { Row, Col, Card, Statistic, Space, Spin, Typography, Tag } 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';
|
|
|
|
const { Title } = Typography;
|
|
|
|
export default function Dashboard() {
|
|
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.task_status || {});
|
|
|
|
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.cost?.total_cost_cents || 0) / 100}
|
|
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.task_status?.done || 0}</Tag>
|
|
</div>
|
|
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
|
<span>
|
|
<CheckCircleOutlined style={{ marginRight: 8 }} />
|
|
进行中任务
|
|
</span>
|
|
<Tag color="orange">{stats.task_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.cost?.by_model && stats.cost.by_model.length > 0 && (
|
|
<Card title="模型调用统计" style={{ marginTop: 16 }} size="small">
|
|
<Space direction="vertical" style={{ width: '100%' }}>
|
|
{stats.cost.by_model.map((m) => (
|
|
<div
|
|
key={m.model}
|
|
style={{
|
|
display: 'flex',
|
|
justifyContent: 'space-between',
|
|
alignItems: 'center',
|
|
padding: '4px 0',
|
|
}}
|
|
>
|
|
<span style={{ fontWeight: 600 }}>{m.model}</span>
|
|
<Space>
|
|
<Tag>{m.calls} 次调用</Tag>
|
|
<Tag color="orange">¥{(m.cost_cents / 100).toFixed(2)}</Tag>
|
|
</Space>
|
|
</div>
|
|
))}
|
|
</Space>
|
|
</Card>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|