feat: DAG画布嵌入项目详情页,任务节点点击弹详情Modal

This commit is contained in:
2026-08-13 23:12:53 +08:00
parent c4020f2725
commit bd220050c7
+140
View File
@@ -0,0 +1,140 @@
import { useEffect, useState } from 'react';
import { Modal, Descriptions, Spin, Tag, Typography, Empty } from 'antd';
import { taskApi } from '@/api';
import type { Task } from '@/types';
import { TaskStatusTag, PriorityTag } from '@/components/constants';
import dayjs from 'dayjs';
const { Paragraph, Text } = Typography;
interface Props {
taskId: number | null;
open: boolean;
onClose: () => void;
}
/** 任务详情弹窗:在 DAG 画布/项目页内点击任务卡片时弹出,不跳转页面 */
export default function TaskDetailModal({ taskId, open, onClose }: Props) {
const [task, setTask] = useState<Task | null>(null);
const [loading, setLoading] = useState(false);
useEffect(() => {
if (!open || taskId == null) return;
setLoading(true);
setTask(null);
taskApi
.get(taskId)
.then(setTask)
.catch(() => setTask(null))
.finally(() => setLoading(false));
}, [open, taskId]);
const renderJsonBlock = (raw?: string) => {
if (!raw) return <Text type="secondary"></Text>;
let pretty = raw;
try {
pretty = JSON.stringify(JSON.parse(raw), null, 2);
} catch {
/* keep raw */
}
return (
<pre
style={{
background: '#f6f8fa',
border: '1px solid #e8e8e8',
borderRadius: 6,
padding: 12,
maxHeight: 260,
overflow: 'auto',
fontSize: 12,
margin: 0,
whiteSpace: 'pre-wrap',
wordBreak: 'break-all',
}}
>
{pretty}
</pre>
);
};
return (
<Modal
title={task ? `任务 #${task.id} ${task.title}` : '任务详情'}
open={open}
onCancel={onClose}
footer={null}
width={720}
destroyOnClose
>
{loading ? (
<div style={{ textAlign: 'center', padding: 40 }}>
<Spin />
</div>
) : !task ? (
<Empty description="任务不存在或加载失败" />
) : (
<div>
<Descriptions column={2} bordered size="small">
<Descriptions.Item label="状态">
<TaskStatusTag status={task.status} />
</Descriptions.Item>
<Descriptions.Item label="优先级">
<PriorityTag priority={task.priority} />
</Descriptions.Item>
<Descriptions.Item label="任务类型">{task.task_type}</Descriptions.Item>
<Descriptions.Item label="需要审核">
{task.requires_review ? (
<Tag color="purple"></Tag>
) : (
<Tag></Tag>
)}
</Descriptions.Item>
{task.review_status && (
<Descriptions.Item label="审核状态">{task.review_status}</Descriptions.Item>
)}
{task.worker_id && (
<Descriptions.Item label="AI Worker">#{task.worker_id}</Descriptions.Item>
)}
{task.due_date && (
<Descriptions.Item label="截止时间">
{dayjs(task.due_date).format('YYYY-MM-DD HH:mm')}
</Descriptions.Item>
)}
{task.started_at && (
<Descriptions.Item label="开始时间">
{dayjs(task.started_at).format('YYYY-MM-DD HH:mm')}
</Descriptions.Item>
)}
{task.completed_at && (
<Descriptions.Item label="完成时间">
{dayjs(task.completed_at).format('YYYY-MM-DD HH:mm')}
</Descriptions.Item>
)}
<Descriptions.Item label="创建时间">
{dayjs(task.created_at).format('YYYY-MM-DD HH:mm')}
</Descriptions.Item>
{task.description && (
<Descriptions.Item label="描述" span={2}>
{task.description}
</Descriptions.Item>
)}
</Descriptions>
<div style={{ marginTop: 16 }}>
<Paragraph strong style={{ marginBottom: 8 }}>
Prompt /
</Paragraph>
{renderJsonBlock(task.input_data)}
</div>
<div style={{ marginTop: 16 }}>
<Paragraph strong style={{ marginBottom: 8 }}>
</Paragraph>
{renderJsonBlock(task.output_data)}
</div>
</div>
)}
</Modal>
);
}