From e7374b977ef898773c1cc6442b38ff3be8ffca06 Mon Sep 17 00:00:00 2001 From: huangzhuang_3rd Date: Wed, 12 Aug 2026 13:31:18 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20AI=20Worker=20=E5=B9=B3=E5=8F=B0=20MVP?= =?UTF-8?q?=20v1.0.0=20-=20=E5=A4=9A=E7=A7=9F=E6=88=B7/=E9=A1=B9=E7=9B=AE?= =?UTF-8?q?=E7=AE=A1=E7=90=86/AI=20Worker/=E4=BB=BB=E5=8A=A1=E7=BC=96?= =?UTF-8?q?=E6=8E=92/HITL=E5=AE=A1=E6=A0=B8/=E6=88=90=E6=9C=AC=E6=B2=BB?= =?UTF-8?q?=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/pages/ProjectDetail.tsx | 227 +++++++++++++++++++++++++++ 1 file changed, 227 insertions(+) create mode 100644 frontend/src/pages/ProjectDetail.tsx diff --git a/frontend/src/pages/ProjectDetail.tsx b/frontend/src/pages/ProjectDetail.tsx new file mode 100644 index 0000000..7a60b86 --- /dev/null +++ b/frontend/src/pages/ProjectDetail.tsx @@ -0,0 +1,227 @@ +import { useEffect, useState } from 'react'; +import { + Card, + Descriptions, + Spin, + Typography, + Button, + Space, + Row, + Col, + Statistic, + Tag, + Empty, + List, +} from 'antd'; +import { + ArrowLeftOutlined, + ReloadOutlined, +} from '@ant-design/icons'; +import { useNavigate, useParams } from 'react-router-dom'; +import { projectApi, taskApi, artifactApi } from '@/api'; +import type { Project, ProjectStats, Task, Artifact } from '@/types'; +import { ProjectStatusTag, TaskStatusTag } from '@/components/constants'; +import dayjs from 'dayjs'; + +const { Title, Text } = Typography; + +export default function ProjectDetail() { + const { id } = useParams<{ id: string }>(); + const navigate = useNavigate(); + const [loading, setLoading] = useState(true); + const [project, setProject] = useState(null); + const [stats, setStats] = useState(null); + const [tasks, setTasks] = useState([]); + const [artifacts, setArtifacts] = useState([]); + + useEffect(() => { + if (!id) return; + setLoading(true); + Promise.all([ + projectApi.get(id), + projectApi.stats(id), + taskApi.list({ project_id: id }), + artifactApi.list({ project_id: id }), + ]) + .then(([p, s, t, a]) => { + setProject(p); + setStats(s); + setTasks(t); + setArtifacts(a); + }) + .catch(() => {}) + .finally(() => setLoading(false)); + }, [id]); + + if (loading) { + return ( +
+ +
+ ); + } + + if (!project) { + return ; + } + + return ( +
+
+ + + + {project.name} + + + + +
+ + + + {project.name} + + + + + {project.budget != null ? `¥${project.budget.toFixed(2)}` : '-'} + + + {dayjs(project.created_at).format('YYYY-MM-DD HH:mm')} + + {project.start_date && ( + + {dayjs(project.start_date).format('YYYY-MM-DD')} + + )} + {project.end_date && ( + + {dayjs(project.end_date).format('YYYY-MM-DD')} + + )} + + {project.description || '暂无描述'} + + + + + {stats && ( + + + + + + + + + + + + + + + + + + + + + )} + + {stats && ( + + + {Object.entries(stats.tasks_by_status || {}).map(([status, count]) => ( + + {count} + + ))} + {Object.keys(stats.tasks_by_status || {}).length === 0 && ( + 暂无任务数据 + )} + + + )} + + + + + {tasks.length > 0 ? ( + ( + ]} + > + navigate('/tasks')}>{task.title} + } + description={dayjs(task.created_at).format('MM-DD HH:mm')} + /> + + )} + /> + ) : ( + + )} + + + + + {artifacts.length > 0 ? ( + ( + + + + )} + /> + ) : ( + + )} + + + +
+ ); +}