import { useState } from "react"; import { Card, Button, Table, Tag, message, Modal, Tabs } from "antd"; import { PlusOutlined, EditOutlined, FormOutlined, DeleteOutlined, DownloadOutlined, } from "@ant-design/icons"; import { useNavigate } from "react-router"; import { SearchControls } from "@/components/SearchControls"; import CardView from "@/components/CardView"; import useFetchData from "@/hooks/useFetchData"; import { deleteAnnotationTaskByIdUsingDelete, queryAnnotationTasksUsingGet, } from "../annotation.api"; import { AnnotationTypeMap, mapAnnotationTask, type AnnotationTaskListItem, } from "../annotation.const"; import CreateAnnotationTask from "../Create/components/CreateAnnotationTaskDialog"; import ExportAnnotationDialog from "./ExportAnnotationDialog"; import { ColumnType } from "antd/es/table"; import { TemplateList } from "../Template"; // Note: DevelopmentInProgress intentionally not used here type AnnotationTaskRowKey = string | number; type AnnotationTaskOperation = { key: string; label: string; icon: JSX.Element; danger?: boolean; onClick: (task: AnnotationTaskListItem) => void; }; export default function DataAnnotation() { // return ; const navigate = useNavigate(); const [activeTab, setActiveTab] = useState("tasks"); const [viewMode, setViewMode] = useState<"list" | "card">("list"); const [showCreateDialog, setShowCreateDialog] = useState(false); const [exportTask, setExportTask] = useState(null); const [editTask, setEditTask] = useState(null); const { loading, tableData, pagination, searchParams, fetchData, handleFiltersChange, handleKeywordChange, } = useFetchData(queryAnnotationTasksUsingGet, mapAnnotationTask, 30000, true, [], 0); const [selectedRowKeys, setSelectedRowKeys] = useState([]); const [selectedRows, setSelectedRows] = useState([]); const toSafeCount = (value: unknown) => typeof value === "number" && Number.isFinite(value) ? value : 0; const handleAnnotate = (task: AnnotationTaskListItem) => { const projectId = task.id; if (!projectId) { message.error("无法进入标注:缺少标注项目ID"); return; } navigate(`/data/annotation/annotate/${projectId}`); }; const handleExport = (task: AnnotationTaskListItem) => { setExportTask(task); }; const handleEdit = (task: AnnotationTaskListItem) => { setEditTask(task); }; const handleDelete = (task: AnnotationTaskListItem) => { Modal.confirm({ title: `确认删除标注任务「${task.name}」吗?`, content: "删除标注任务不会删除对应数据集,但会删除该任务的所有标注结果。", okText: "删除", okType: "danger", cancelText: "取消", onOk: async () => { try { await deleteAnnotationTaskByIdUsingDelete(task.id); message.success("删除成功"); fetchData(); // clear selection if deleted item was selected setSelectedRowKeys((keys) => keys.filter((k) => k !== task.id)); setSelectedRows((rows) => rows.filter((r) => r.id !== task.id)); } catch (e) { console.error(e); message.error("删除失败,请稍后重试"); } }, }); }; const handleBatchDelete = () => { if (!selectedRows || selectedRows.length === 0) return; Modal.confirm({ title: `确认删除所选 ${selectedRows.length} 个标注任务吗?`, content: "删除标注任务不会删除对应数据集,但会删除这些任务的所有标注结果。", okText: "删除", okType: "danger", cancelText: "取消", onOk: async () => { try { await Promise.all( selectedRows.map((r) => deleteAnnotationTaskByIdUsingDelete(r.id)) ); message.success("批量删除已完成"); fetchData(); setSelectedRowKeys([]); setSelectedRows([]); } catch (e) { console.error(e); message.error("批量删除失败,请稍后重试"); } }, }); }; const operations: AnnotationTaskOperation[] = [ { key: "annotate", label: "标注", icon: ( ), onClick: handleAnnotate, }, { key: "edit", label: "编辑", icon: , onClick: handleEdit, }, { key: "export", label: "导出", icon: , onClick: handleExport, }, { key: "delete", label: "删除", icon: , onClick: handleDelete, }, ]; const columns: ColumnType[] = [ { title: "序号", key: "index", width: 80, align: "center" as const, render: (_value: unknown, _record: AnnotationTaskListItem, index: number) => { const current = pagination.current ?? 1; const pageSize = pagination.pageSize ?? tableData.length ?? 0; return (current - 1) * pageSize + index + 1; }, }, { title: "任务名称", dataIndex: "name", key: "name", fixed: "left" as const, }, { title: "数据集", dataIndex: "datasetName", key: "datasetName", width: 180, }, { title: "标注类型", dataIndex: "labelingType", key: "labelingType", width: 160, render: (value?: string) => { if (!value) { return "-"; } const label = AnnotationTypeMap[value as keyof typeof AnnotationTypeMap]?.label || value; return {label}; }, }, { title: "数据量", dataIndex: "totalCount", key: "totalCount", width: 100, align: "center" as const, }, { title: "已标注", dataIndex: "annotatedCount", key: "annotatedCount", width: 100, align: "center" as const, render: (value: number, record: AnnotationTaskListItem) => { const total = toSafeCount(record.totalCount ?? record.total_count); const annotatedRaw = toSafeCount( value ?? record.annotatedCount ?? record.annotated_count ); const segmentationEnabled = record.segmentationEnabled ?? record.segmentation_enabled; const inProgressRaw = segmentationEnabled ? toSafeCount(record.inProgressCount ?? record.in_progress_count) : 0; const shouldExcludeInProgress = total > 0 && annotatedRaw + inProgressRaw > total; const annotated = shouldExcludeInProgress ? Math.max(annotatedRaw - inProgressRaw, 0) : annotatedRaw; const percent = total > 0 ? Math.round((annotated / total) * 100) : 0; return ( {annotated} ); }, }, { title: "标注中", dataIndex: "inProgressCount", key: "inProgressCount", width: 100, align: "center" as const, render: (value: number, record: AnnotationTaskListItem) => { const segmentationEnabled = record.segmentationEnabled ?? record.segmentation_enabled; if (!segmentationEnabled) return "-"; const resolved = Number.isFinite(value) ? value : record.inProgressCount ?? record.in_progress_count ?? 0; return resolved; }, }, { title: "创建时间", dataIndex: "createdAt", key: "createdAt", width: 180, }, { title: "更新时间", dataIndex: "updatedAt", key: "updatedAt", width: 180, }, { title: "操作", key: "actions", fixed: "right" as const, width: 150, dataIndex: "actions", render: (_value: unknown, task: AnnotationTaskListItem) => ( {operations.map((operation) => ( operation.onClick(task)} title={operation.label} /> ))} ), }, ]; return ( {/* Header */} 数据标注 {/* Tabs */} {/* Search, Filters and Buttons in one row */} {/* Left side: Search and view controls */} {/* Right side: All action buttons */} 批量删除 } onClick={() => setShowCreateDialog(true)} > 创建标注任务 {/* Task List/Card */} {viewMode === "list" ? ( { setSelectedRowKeys(keys); setSelectedRows(rows); }, }} scroll={{ x: "max-content", y: "calc(100vh - 24rem)" }} /> ) : ( )} { setShowCreateDialog(false); setEditTask(null); }} onRefresh={() => fetchData()} editTask={editTask} /> setExportTask(null)} /> ), }, { key: "templates", label: "标注模板", children: , }, ]} /> ); }