You've already forked DataMate
feat(annotation): 添加标注任务进行中数据显示功能
- 新增 AnnotationTaskListItem 和相关类型定义 - 在前端页面中添加标注中列显示进行中的标注数据量 - 更新数据获取逻辑以支持进行中标注数量统计 - 修改后端服务层添加 in_progress_count 字段映射 - 优化类型安全和代码结构设计
This commit is contained in:
@@ -10,27 +10,35 @@ import {
|
||||
import { useNavigate } from "react-router";
|
||||
import { SearchControls } from "@/components/SearchControls";
|
||||
import CardView from "@/components/CardView";
|
||||
import type { AnnotationTask } from "../annotation.model";
|
||||
import useFetchData from "@/hooks/useFetchData";
|
||||
import {
|
||||
deleteAnnotationTaskByIdUsingDelete,
|
||||
queryAnnotationTasksUsingGet,
|
||||
} from "../annotation.api";
|
||||
import { mapAnnotationTask } from "../annotation.const";
|
||||
import { 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 <DevelopmentInProgress showTime="2025.10.30" />;
|
||||
const navigate = useNavigate();
|
||||
const [activeTab, setActiveTab] = useState("tasks");
|
||||
const [viewMode, setViewMode] = useState<"list" | "card">("list");
|
||||
const [showCreateDialog, setShowCreateDialog] = useState(false);
|
||||
const [exportTask, setExportTask] = useState<AnnotationTask | null>(null);
|
||||
const [editTask, setEditTask] = useState<AnnotationTask | null>(null);
|
||||
const [exportTask, setExportTask] = useState<AnnotationTaskListItem | null>(null);
|
||||
const [editTask, setEditTask] = useState<AnnotationTaskListItem | null>(null);
|
||||
|
||||
const {
|
||||
loading,
|
||||
@@ -40,13 +48,13 @@ export default function DataAnnotation() {
|
||||
fetchData,
|
||||
handleFiltersChange,
|
||||
handleKeywordChange,
|
||||
} = useFetchData(queryAnnotationTasksUsingGet, mapAnnotationTask, 30000, true, [], 0);
|
||||
} = useFetchData<AnnotationTaskListItem>(queryAnnotationTasksUsingGet, mapAnnotationTask, 30000, true, [], 0);
|
||||
|
||||
const [selectedRowKeys, setSelectedRowKeys] = useState<(string | number)[]>([]);
|
||||
const [selectedRows, setSelectedRows] = useState<any[]>([]);
|
||||
const [selectedRowKeys, setSelectedRowKeys] = useState<AnnotationTaskRowKey[]>([]);
|
||||
const [selectedRows, setSelectedRows] = useState<AnnotationTaskListItem[]>([]);
|
||||
|
||||
const handleAnnotate = (task: AnnotationTask) => {
|
||||
const projectId = (task as any)?.id;
|
||||
const handleAnnotate = (task: AnnotationTaskListItem) => {
|
||||
const projectId = task.id;
|
||||
if (!projectId) {
|
||||
message.error("无法进入标注:缺少标注项目ID");
|
||||
return;
|
||||
@@ -54,15 +62,15 @@ export default function DataAnnotation() {
|
||||
navigate(`/data/annotation/annotate/${projectId}`);
|
||||
};
|
||||
|
||||
const handleExport = (task: AnnotationTask) => {
|
||||
const handleExport = (task: AnnotationTaskListItem) => {
|
||||
setExportTask(task);
|
||||
};
|
||||
|
||||
const handleEdit = (task: AnnotationTask) => {
|
||||
const handleEdit = (task: AnnotationTaskListItem) => {
|
||||
setEditTask(task);
|
||||
};
|
||||
|
||||
const handleDelete = (task: AnnotationTask) => {
|
||||
const handleDelete = (task: AnnotationTaskListItem) => {
|
||||
Modal.confirm({
|
||||
title: `确认删除标注任务「${task.name}」吗?`,
|
||||
content: "删除标注任务不会删除对应数据集,但会删除该任务的所有标注结果。",
|
||||
@@ -110,7 +118,7 @@ export default function DataAnnotation() {
|
||||
});
|
||||
};
|
||||
|
||||
const operations = [
|
||||
const operations: AnnotationTaskOperation[] = [
|
||||
{
|
||||
key: "annotate",
|
||||
label: "标注",
|
||||
@@ -142,7 +150,7 @@ export default function DataAnnotation() {
|
||||
},
|
||||
];
|
||||
|
||||
const columns: ColumnType<any>[] = [
|
||||
const columns: ColumnType<AnnotationTaskListItem>[] = [
|
||||
{
|
||||
title: "任务名称",
|
||||
dataIndex: "name",
|
||||
@@ -173,7 +181,7 @@ export default function DataAnnotation() {
|
||||
key: "annotatedCount",
|
||||
width: 100,
|
||||
align: "center" as const,
|
||||
render: (value: number, record: any) => {
|
||||
render: (value: number, record: AnnotationTaskListItem) => {
|
||||
const total = record.totalCount || 0;
|
||||
const annotated = value || 0;
|
||||
const percent = total > 0 ? Math.round((annotated / total) * 100) : 0;
|
||||
@@ -184,6 +192,23 @@ export default function DataAnnotation() {
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
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",
|
||||
@@ -202,14 +227,14 @@ export default function DataAnnotation() {
|
||||
fixed: "right" as const,
|
||||
width: 150,
|
||||
dataIndex: "actions",
|
||||
render: (_: any, task: any) => (
|
||||
render: (_value: unknown, task: AnnotationTaskListItem) => (
|
||||
<div className="flex items-center justify-center space-x-1">
|
||||
{operations.map((operation) => (
|
||||
<Button
|
||||
key={operation.key}
|
||||
type="text"
|
||||
icon={operation.icon}
|
||||
onClick={() => (operation?.onClick as any)?.(task)}
|
||||
onClick={() => operation.onClick(task)}
|
||||
title={operation.label}
|
||||
/>
|
||||
))}
|
||||
@@ -282,9 +307,9 @@ export default function DataAnnotation() {
|
||||
pagination={pagination}
|
||||
rowSelection={{
|
||||
selectedRowKeys,
|
||||
onChange: (keys, rows) => {
|
||||
setSelectedRowKeys(keys as (string | number)[]);
|
||||
setSelectedRows(rows as any[]);
|
||||
onChange: (keys: AnnotationTaskRowKey[], rows: AnnotationTaskListItem[]) => {
|
||||
setSelectedRowKeys(keys);
|
||||
setSelectedRows(rows);
|
||||
},
|
||||
}}
|
||||
scroll={{ x: "max-content", y: "calc(100vh - 24rem)" }}
|
||||
@@ -293,7 +318,7 @@ export default function DataAnnotation() {
|
||||
) : (
|
||||
<CardView
|
||||
data={tableData}
|
||||
operations={operations as any}
|
||||
operations={operations}
|
||||
pagination={pagination}
|
||||
loading={loading}
|
||||
/>
|
||||
@@ -327,4 +352,4 @@ export default function DataAnnotation() {
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,64 @@ import {
|
||||
CloseCircleOutlined,
|
||||
} from "@ant-design/icons";
|
||||
|
||||
type AnnotationTaskStatistics = {
|
||||
accuracy?: number | string;
|
||||
averageTime?: number | string;
|
||||
reviewCount?: number | string;
|
||||
};
|
||||
|
||||
type AnnotationTaskPayload = {
|
||||
id?: string;
|
||||
labelingProjId?: string;
|
||||
labelingProjectId?: string;
|
||||
projId?: string;
|
||||
labeling_project_id?: string;
|
||||
name?: string;
|
||||
description?: string;
|
||||
datasetId?: string;
|
||||
datasetName?: string;
|
||||
dataset_name?: string;
|
||||
totalCount?: number;
|
||||
total_count?: number;
|
||||
annotatedCount?: number;
|
||||
annotated_count?: number;
|
||||
inProgressCount?: number;
|
||||
in_progress_count?: number;
|
||||
segmentationEnabled?: boolean;
|
||||
segmentation_enabled?: boolean;
|
||||
createdAt?: string;
|
||||
created_at?: string;
|
||||
updatedAt?: string;
|
||||
updated_at?: string;
|
||||
status?: string;
|
||||
statistics?: AnnotationTaskStatistics;
|
||||
[key: string]: unknown;
|
||||
};
|
||||
|
||||
export type AnnotationTaskListItem = {
|
||||
id?: string;
|
||||
labelingProjId?: string;
|
||||
projId?: string;
|
||||
name?: string;
|
||||
description?: string;
|
||||
datasetId?: string;
|
||||
datasetName?: string;
|
||||
totalCount?: number;
|
||||
annotatedCount?: number;
|
||||
inProgressCount?: number;
|
||||
segmentationEnabled?: boolean;
|
||||
createdAt?: string;
|
||||
updatedAt?: string;
|
||||
icon?: JSX.Element;
|
||||
iconColor?: string;
|
||||
status?: {
|
||||
label: string;
|
||||
color: string;
|
||||
};
|
||||
statistics?: { label: string; value: string | number }[];
|
||||
[key: string]: unknown;
|
||||
};
|
||||
|
||||
export const AnnotationTaskStatusMap = {
|
||||
[AnnotationTaskStatus.ACTIVE]: {
|
||||
label: "活跃",
|
||||
@@ -27,9 +85,11 @@ export const AnnotationTaskStatusMap = {
|
||||
},
|
||||
};
|
||||
|
||||
export function mapAnnotationTask(task: any) {
|
||||
export function mapAnnotationTask(task: AnnotationTaskPayload): AnnotationTaskListItem {
|
||||
// Normalize labeling project id from possible backend field names
|
||||
const labelingProjId = task?.labelingProjId || task?.labelingProjectId || task?.projId || task?.labeling_project_id || "";
|
||||
const segmentationEnabled = task?.segmentationEnabled ?? task?.segmentation_enabled ?? false;
|
||||
const inProgressCount = task?.inProgressCount ?? task?.in_progress_count ?? 0;
|
||||
|
||||
const statsArray = task?.statistics
|
||||
? [
|
||||
@@ -45,6 +105,8 @@ export function mapAnnotationTask(task: any) {
|
||||
// provide consistent field for components
|
||||
labelingProjId,
|
||||
projId: labelingProjId,
|
||||
segmentationEnabled,
|
||||
inProgressCount,
|
||||
name: task.name,
|
||||
description: task.description || "",
|
||||
datasetName: task.datasetName || task.dataset_name || "-",
|
||||
@@ -478,4 +540,4 @@ export const TemplateTypeMap = {
|
||||
label: "自定义",
|
||||
value: TemplateType.CUSTOM
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user