+
+
+ } onClick={() => navigate("/tasks")}>返回
+ 任务详情
+
+ {task.status === "done" && (
+
+ } onClick={() => window.open(taskApi.getTaskDownloadUrl(task.id))}>
+ DOCX
+
+
+
+ )}
+
+
+
+
+ {task.template_name}
+ {status.label}
+ {new Date(task.created_at).toLocaleString()}
+ {task.finished_at && (
+ {new Date(task.finished_at).toLocaleString()}
+ )}
+ {task.error_msg && (
+
+ {task.error_msg}
+
+ )}
+
+
+
+
AI 生成点 ({task.points.length})
+ {task.points.map((point, idx) => {
+ const modelName = getModelName(point.model_id);
+ const hasFiles = point.ref_file_path && point.ref_file_path !== "[]";
+ let fileNames: string[] = [];
+ if (hasFiles) {
+ try { fileNames = JSON.parse(point.ref_file_path || "[]").map((f: string) => f.split("/").pop() || f); } catch {}
+ }
+
+ return (
+
+ #{idx + 1}
+ {point.prompt.slice(0, 40)}{point.prompt.length > 40 ? "..." : ""}
+
+ }
+ extra={{modelName}}
+ >
+ {point.remark && 备注:{point.remark}
}
+ {point.need_ref_file && (
+
+ {hasFiles && fileNames.length > 0 ? (
+ (
+ {name}
+ )}
+ />
+ ) : (
+ 未上传参考文件
+ )}
+
+ )}
+
+ );
+ })}
+
+ );
+}
diff --git a/web/src/pages/TaskList.tsx b/web/src/pages/TaskList.tsx
new file mode 100644
index 0000000..b3e13f4
--- /dev/null
+++ b/web/src/pages/TaskList.tsx
@@ -0,0 +1,143 @@
+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