144 lines
4.0 KiB
TypeScript
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>
|
|
);
|
|
}
|