doc-forge-reds/web/src/pages/TaskList.tsx

144 lines
4.0 KiB
TypeScript

import { useEffect, useState, useCallback } from "react";
import { useNavigate } from "react-router-dom";
import { Table, Button, Space, Tag, message, Popconfirm } from "antd";
import { DownloadOutlined, StopOutlined, ReloadOutlined, EyeOutlined } from "@ant-design/icons";
import type { GenerationTask } from "../types";
import * as taskApi from "../api/tasks";
const STATUS_MAP: Record<string, { color: string; label: string }> = {
pending: { color: "default", label: "等待中" },
processing: { color: "processing", label: "生成中" },
done: { color: "success", label: "已完成" },
failed: { color: "error", label: "失败" },
};
export default function TaskList() {
const navigate = useNavigate();
const [tasks, setTasks] = useState<GenerationTask[]>([]);
const [loading, setLoading] = useState(false);
const fetchTasks = useCallback(async () => {
setLoading(true);
try {
const data = await taskApi.listTasks();
setTasks(data);
} catch {
message.error("获取任务列表失败");
} finally {
setLoading(false);
}
}, []);
useEffect(() => {
fetchTasks();
}, [fetchTasks]);
const handleCancel = async (id: string) => {
await taskApi.cancelTask(id);
message.success("已取消");
fetchTasks();
};
const columns = [
{
title: "模板 ID",
dataIndex: "template_id",
key: "template_id",
width: 120,
ellipsis: true,
render: (v: string) => v.slice(0, 8) + "...",
},
{
title: "状态",
dataIndex: "status",
key: "status",
width: 100,
render: (v: string) => {
const cfg = STATUS_MAP[v] || { color: "default", label: v };
return <Tag color={cfg.color}>{cfg.label}</Tag>;
},
},
{
title: "错误信息",
dataIndex: "error_msg",
key: "error_msg",
ellipsis: true,
render: (v: string | null) => v || "-",
},
{
title: "创建时间",
dataIndex: "created_at",
key: "created_at",
render: (v: string) => new Date(v).toLocaleString(),
},
{
title: "完成时间",
dataIndex: "finished_at",
key: "finished_at",
render: (v: string | null) => (v ? new Date(v).toLocaleString() : "-"),
},
{
title: "操作",
key: "actions",
width: 200,
render: (_: unknown, record: GenerationTask) => (
<Space>
<Button
size="small"
icon={<EyeOutlined />}
onClick={() => navigate(`/tasks/${record.id}`)}
>
</Button>
{record.status === "done" && (
<>
<Button
size="small"
icon={<DownloadOutlined />}
onClick={() => window.open(taskApi.getTaskDownloadUrl(record.id))}
>
DOCX
</Button>
<Button
size="small"
onClick={() => window.open(taskApi.getTaskDownloadUrl(record.id, "pdf"))}
>
PDF
</Button>
</>
)}
{(record.status === "pending" || record.status === "processing") && (
<Popconfirm title="确定取消此任务?" onConfirm={() => handleCancel(record.id)}>
<Button size="small" danger icon={<StopOutlined />}>
</Button>
</Popconfirm>
)}
</Space>
),
},
];
return (
<div>
<div style={{ marginBottom: 16, display: "flex", justifyContent: "space-between" }}>
<h2></h2>
<Button icon={<ReloadOutlined />} onClick={fetchTasks}>
</Button>
</div>
<Table
columns={columns}
dataSource={tasks}
rowKey="id"
loading={loading}
pagination={{ pageSize: 20 }}
onRow={(record) => ({
style: { cursor: "pointer" },
onClick: () => navigate(`/tasks/${record.id}`),
})}
/>
</div>
);
}