You've already forked DataMate
init datamate
This commit is contained in:
571
frontend/src/pages/RatioTask/CreateRatioTask.tsx
Normal file
571
frontend/src/pages/RatioTask/CreateRatioTask.tsx
Normal file
@@ -0,0 +1,571 @@
|
||||
import { useState } from "react";
|
||||
import {
|
||||
Button,
|
||||
Card,
|
||||
Input,
|
||||
Select,
|
||||
Badge,
|
||||
Progress,
|
||||
Checkbox,
|
||||
Switch,
|
||||
Form,
|
||||
Divider,
|
||||
message,
|
||||
} from "antd";
|
||||
import {
|
||||
ArrowLeft,
|
||||
Play,
|
||||
Search as SearchIcon,
|
||||
Database,
|
||||
BarChart3,
|
||||
Shuffle,
|
||||
PieChart,
|
||||
} from "lucide-react";
|
||||
import type { RatioConfig, RatioTask } from "@/pages/RatioTask/ratio";
|
||||
import { mockRatioTasks } from "@/mock/ratio";
|
||||
import type { Dataset } from "@/pages/DataManagement/dataset.model";
|
||||
import { useNavigate } from "react-router";
|
||||
import DevelopmentInProgress from "@/components/DevelopmentInProgress";
|
||||
|
||||
const { TextArea } = Input;
|
||||
const { Option } = Select;
|
||||
|
||||
export default function CreateRatioTask() {
|
||||
return <DevelopmentInProgress />;
|
||||
|
||||
const navigate = useNavigate();
|
||||
const [form] = Form.useForm();
|
||||
// 配比任务相关状态
|
||||
const [ratioTaskForm, setRatioTaskForm] = useState({
|
||||
name: "",
|
||||
description: "",
|
||||
ratioType: "dataset" as "dataset" | "label",
|
||||
selectedDatasets: [] as string[],
|
||||
ratioConfigs: [] as RatioConfig[],
|
||||
totalTargetCount: 10000,
|
||||
autoStart: true,
|
||||
});
|
||||
|
||||
const [tasks, setTasks] = useState<RatioTask[]>(mockRatioTasks);
|
||||
const [datasets] = useState<Dataset[]>([]);
|
||||
|
||||
const handleCreateRatioTask = async () => {
|
||||
try {
|
||||
const values = await form.validateFields();
|
||||
if (!ratioTaskForm.ratioConfigs.length) {
|
||||
message.error("请配置配比项");
|
||||
return;
|
||||
}
|
||||
const newTask: RatioTask = {
|
||||
id: Date.now(),
|
||||
name: values.name,
|
||||
status: ratioTaskForm.autoStart ? "pending" : "paused",
|
||||
progress: 0,
|
||||
sourceDatasets: ratioTaskForm.selectedDatasets,
|
||||
targetCount: values.totalTargetCount,
|
||||
generatedCount: 0,
|
||||
createdAt: new Date().toISOString().split("T")[0],
|
||||
ratioType: ratioTaskForm.ratioType,
|
||||
estimatedTime: "预计 20 分钟",
|
||||
ratioConfigs: ratioTaskForm.ratioConfigs,
|
||||
};
|
||||
|
||||
setTasks([newTask, ...tasks]);
|
||||
setRatioTaskForm({
|
||||
name: "",
|
||||
description: "",
|
||||
ratioType: "dataset",
|
||||
selectedDatasets: [],
|
||||
ratioConfigs: [],
|
||||
totalTargetCount: 10000,
|
||||
autoStart: true,
|
||||
});
|
||||
form.resetFields();
|
||||
message.success("配比任务创建成功");
|
||||
navigate("/data/ratio-task");
|
||||
} catch {
|
||||
// 校验失败
|
||||
}
|
||||
};
|
||||
|
||||
const handleDatasetSelection = (datasetId: string, checked: boolean) => {
|
||||
if (checked) {
|
||||
setRatioTaskForm((prev) => ({
|
||||
...prev,
|
||||
selectedDatasets: [...prev.selectedDatasets, datasetId],
|
||||
}));
|
||||
} else {
|
||||
setRatioTaskForm((prev) => ({
|
||||
...prev,
|
||||
selectedDatasets: prev.selectedDatasets.filter(
|
||||
(id) => id !== datasetId
|
||||
),
|
||||
ratioConfigs: prev.ratioConfigs.filter(
|
||||
(config) => config.source !== datasetId
|
||||
),
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
const updateRatioConfig = (source: string, quantity: number) => {
|
||||
setRatioTaskForm((prev) => {
|
||||
const existingIndex = prev.ratioConfigs.findIndex(
|
||||
(config) => config.source === source
|
||||
);
|
||||
const totalOtherQuantity = prev.ratioConfigs
|
||||
.filter((config) => config.source !== source)
|
||||
.reduce((sum, config) => sum + config.quantity, 0);
|
||||
|
||||
const newConfig: RatioConfig = {
|
||||
id: source,
|
||||
name: source,
|
||||
type: prev.ratioType,
|
||||
quantity: Math.min(
|
||||
quantity,
|
||||
prev.totalTargetCount - totalOtherQuantity
|
||||
),
|
||||
percentage: Math.round((quantity / prev.totalTargetCount) * 100),
|
||||
source,
|
||||
};
|
||||
|
||||
if (existingIndex >= 0) {
|
||||
const newConfigs = [...prev.ratioConfigs];
|
||||
newConfigs[existingIndex] = newConfig;
|
||||
return { ...prev, ratioConfigs: newConfigs };
|
||||
} else {
|
||||
return { ...prev, ratioConfigs: [...prev.ratioConfigs, newConfig] };
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const generateAutoRatio = () => {
|
||||
const selectedCount = ratioTaskForm.selectedDatasets.length;
|
||||
if (selectedCount === 0) return;
|
||||
|
||||
const baseQuantity = Math.floor(
|
||||
ratioTaskForm.totalTargetCount / selectedCount
|
||||
);
|
||||
const remainder = ratioTaskForm.totalTargetCount % selectedCount;
|
||||
|
||||
const newConfigs: RatioConfig[] = ratioTaskForm.selectedDatasets.map(
|
||||
(datasetId, index) => {
|
||||
const quantity = baseQuantity + (index < remainder ? 1 : 0);
|
||||
return {
|
||||
id: datasetId,
|
||||
name: datasetId,
|
||||
type: ratioTaskForm.ratioType,
|
||||
quantity,
|
||||
percentage: Math.round(
|
||||
(quantity / ratioTaskForm.totalTargetCount) * 100
|
||||
),
|
||||
source: datasetId,
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
setRatioTaskForm((prev) => ({ ...prev, ratioConfigs: newConfigs }));
|
||||
};
|
||||
|
||||
const handleValuesChange = (_, allValues) => {
|
||||
setRatioTaskForm({ ...ratioTaskForm, ...allValues });
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="min-h-screen">
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<div className="flex items-center">
|
||||
<Button
|
||||
type="text"
|
||||
onClick={() => navigate("/data/synthesis/ratio-task")}
|
||||
>
|
||||
<ArrowLeft className="w-4 h-4 mr-1" />
|
||||
</Button>
|
||||
<h1 className="text-xl font-bold bg-clip-text">创建配比任务</h1>
|
||||
</div>
|
||||
</div>
|
||||
<Card className="overflow-y-auto p-2">
|
||||
<Form
|
||||
form={form}
|
||||
initialValues={ratioTaskForm}
|
||||
onValuesChange={handleValuesChange}
|
||||
layout="vertical"
|
||||
>
|
||||
<div className="grid grid-cols-12 gap-6">
|
||||
{/* 左侧:数据集选择 */}
|
||||
<div className="col-span-5">
|
||||
<h2 className="font-medium text-gray-900 text-lg mb-2 flex items-center gap-2">
|
||||
<Database className="w-5 h-5" />
|
||||
数据集选择
|
||||
</h2>
|
||||
<Card>
|
||||
<div className="flex items-center gap-4 mb-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-sm">配比方式:</span>
|
||||
<Form.Item name="ratioType" noStyle>
|
||||
<Select
|
||||
style={{ width: 120 }}
|
||||
onChange={(value: "dataset" | "label") =>
|
||||
setRatioTaskForm({
|
||||
...ratioTaskForm,
|
||||
ratioType: value,
|
||||
ratioConfigs: [],
|
||||
})
|
||||
}
|
||||
>
|
||||
<Option value="dataset">按数据集</Option>
|
||||
<Option value="label">按标签</Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
</div>
|
||||
<Input
|
||||
prefix={<SearchIcon className="text-gray-400" />}
|
||||
placeholder="搜索数据集"
|
||||
style={{ width: 180 }}
|
||||
// 可加搜索逻辑
|
||||
/>
|
||||
</div>
|
||||
<div style={{ maxHeight: 500, overflowY: "auto" }}>
|
||||
{datasets.map((dataset) => (
|
||||
<Card
|
||||
key={dataset.id}
|
||||
size="small"
|
||||
className={`mb-2 cursor-pointer ${
|
||||
ratioTaskForm.selectedDatasets.includes(dataset.id)
|
||||
? "border-blue-500"
|
||||
: "hover:border-blue-200"
|
||||
}`}
|
||||
onClick={() =>
|
||||
handleDatasetSelection(
|
||||
dataset.id,
|
||||
!ratioTaskForm.selectedDatasets.includes(dataset.id)
|
||||
)
|
||||
}
|
||||
>
|
||||
<div className="flex items-start gap-3">
|
||||
<Checkbox
|
||||
checked={ratioTaskForm.selectedDatasets.includes(
|
||||
dataset.id
|
||||
)}
|
||||
onChange={(e) =>
|
||||
handleDatasetSelection(dataset.id, e.target.checked)
|
||||
}
|
||||
/>
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="font-medium text-sm truncate">
|
||||
{dataset.name}
|
||||
</span>
|
||||
<Badge color="blue">{dataset.type}</Badge>
|
||||
</div>
|
||||
<div className="text-xs text-gray-500 mt-1">
|
||||
{dataset.description}
|
||||
</div>
|
||||
<div className="flex items-center gap-4 mt-2 text-xs text-gray-500">
|
||||
<span>{dataset.records?.toLocaleString()}条</span>
|
||||
<span>{dataset.size}</span>
|
||||
<span>{dataset.format}</span>
|
||||
</div>
|
||||
{ratioTaskForm.ratioType === "label" &&
|
||||
dataset.labels && (
|
||||
<div className="flex flex-wrap gap-1 mt-2">
|
||||
{dataset.labels.map((label, index) => (
|
||||
<Badge key={index} color="gray">
|
||||
{label}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
<div className="flex items-center justify-between p-3 bg-gray-50 rounded-lg mt-4">
|
||||
<span className="text-sm text-gray-600">
|
||||
已选择 {ratioTaskForm.selectedDatasets.length} 个数据集
|
||||
</span>
|
||||
<Button
|
||||
size="small"
|
||||
onClick={() =>
|
||||
setRatioTaskForm({
|
||||
...ratioTaskForm,
|
||||
selectedDatasets: [],
|
||||
ratioConfigs: [],
|
||||
})
|
||||
}
|
||||
>
|
||||
清空选择
|
||||
</Button>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
{/* 右侧:配比配置 */}
|
||||
<div className="col-span-7">
|
||||
<h2 className="font-medium text-gray-900 text-lg mb-2 flex items-center gap-2">
|
||||
<PieChart className="w-5 h-5" />
|
||||
配比配置
|
||||
</h2>
|
||||
<Card>
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<div>
|
||||
<span className="flex items-center gap-2 font-semibold">
|
||||
<BarChart3 className="w-5 h-5" />
|
||||
配比设置
|
||||
</span>
|
||||
<div className="text-gray-500 text-xs">
|
||||
设置每个数据集的配比数量
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
icon={<Shuffle />}
|
||||
size="small"
|
||||
onClick={generateAutoRatio}
|
||||
disabled={ratioTaskForm.selectedDatasets.length === 0}
|
||||
>
|
||||
平均分配
|
||||
</Button>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-4 mb-4">
|
||||
<Form.Item
|
||||
label="任务名称"
|
||||
name="name"
|
||||
rules={[{ required: true, message: "请输入配比任务名称" }]}
|
||||
>
|
||||
<Input
|
||||
placeholder="输入配比任务名称"
|
||||
value={ratioTaskForm.name}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="目标总数量"
|
||||
name="totalTargetCount"
|
||||
rules={[{ required: true, message: "请输入目标总数量" }]}
|
||||
>
|
||||
<Input
|
||||
type="number"
|
||||
placeholder="目标总数量"
|
||||
min={1}
|
||||
value={ratioTaskForm.totalTargetCount}
|
||||
/>
|
||||
</Form.Item>
|
||||
</div>
|
||||
<Form.Item label="任务描述" name="description">
|
||||
<TextArea
|
||||
placeholder="描述配比任务的目的和要求(可选)"
|
||||
rows={2}
|
||||
value={ratioTaskForm.description}
|
||||
/>
|
||||
</Form.Item>
|
||||
<div className="mb-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-sm font-medium">配比设置</span>
|
||||
<span className="text-xs text-gray-500">
|
||||
已配置:{" "}
|
||||
{ratioTaskForm.ratioConfigs.reduce(
|
||||
(sum, config) => sum + config.quantity,
|
||||
0
|
||||
)}{" "}
|
||||
/ {ratioTaskForm.totalTargetCount}
|
||||
</span>
|
||||
</div>
|
||||
{ratioTaskForm.selectedDatasets.length === 0 ? (
|
||||
<div className="text-center py-8 text-gray-500">
|
||||
<BarChart3 className="w-12 h-12 mx-auto mb-2 text-gray-300" />
|
||||
<p className="text-sm">请先选择数据集</p>
|
||||
</div>
|
||||
) : (
|
||||
<div style={{ maxHeight: 500, overflowY: "auto" }}>
|
||||
{ratioTaskForm.selectedDatasets.map((datasetId) => {
|
||||
const dataset = datasets.find(
|
||||
(d) => d.id === datasetId
|
||||
);
|
||||
const config = ratioTaskForm.ratioConfigs.find(
|
||||
(c) => c.source === datasetId
|
||||
);
|
||||
const currentQuantity = config?.quantity || 0;
|
||||
if (!dataset) return null;
|
||||
return (
|
||||
<Card key={datasetId} size="small" className="mb-2">
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="font-medium text-sm">
|
||||
{dataset.name}
|
||||
</span>
|
||||
<Badge color="gray">
|
||||
{dataset.records.toLocaleString()}条
|
||||
</Badge>
|
||||
</div>
|
||||
<div className="text-xs text-gray-500">
|
||||
{config?.percentage || 0}%
|
||||
</div>
|
||||
</div>
|
||||
{ratioTaskForm.ratioType === "dataset" ? (
|
||||
<div>
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<span className="text-xs">数量:</span>
|
||||
<Input
|
||||
type="number"
|
||||
value={currentQuantity}
|
||||
onChange={(e) =>
|
||||
updateRatioConfig(
|
||||
datasetId,
|
||||
Number(e.target.value)
|
||||
)
|
||||
}
|
||||
style={{ width: 80 }}
|
||||
min={0}
|
||||
max={ratioTaskForm.totalTargetCount}
|
||||
/>
|
||||
<span className="text-xs text-gray-500">
|
||||
条
|
||||
</span>
|
||||
</div>
|
||||
<Progress
|
||||
percent={Math.round(
|
||||
(currentQuantity /
|
||||
ratioTaskForm.totalTargetCount) *
|
||||
100
|
||||
)}
|
||||
size="small"
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div>
|
||||
{dataset.labels?.map((label, index) => {
|
||||
const labelConfig =
|
||||
ratioTaskForm.ratioConfigs.find(
|
||||
(c) =>
|
||||
c.source === `${datasetId}_${label}`
|
||||
);
|
||||
const labelQuantity =
|
||||
labelConfig?.quantity || 0;
|
||||
return (
|
||||
<div
|
||||
key={index}
|
||||
className="flex items-center gap-2 mb-2"
|
||||
>
|
||||
<Badge color="gray">{label}</Badge>
|
||||
<Input
|
||||
type="number"
|
||||
value={labelQuantity}
|
||||
onChange={(e) =>
|
||||
updateRatioConfig(
|
||||
`${datasetId}_${label}`,
|
||||
Number(e.target.value)
|
||||
)
|
||||
}
|
||||
style={{ width: 70 }}
|
||||
min={0}
|
||||
/>
|
||||
<span className="text-xs text-gray-500">
|
||||
条
|
||||
</span>
|
||||
<Progress
|
||||
percent={Math.round(
|
||||
(labelQuantity /
|
||||
ratioTaskForm.totalTargetCount) *
|
||||
100
|
||||
)}
|
||||
size="small"
|
||||
style={{ width: 80 }}
|
||||
/>
|
||||
<span className="text-xs text-gray-500 min-w-8">
|
||||
{Math.round(
|
||||
(labelQuantity /
|
||||
ratioTaskForm.totalTargetCount) *
|
||||
100
|
||||
)}
|
||||
%
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</Card>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{/* 配比预览 */}
|
||||
{ratioTaskForm.ratioConfigs.length > 0 && (
|
||||
<div className="mb-4">
|
||||
<span className="text-sm font-medium">配比预览</span>
|
||||
<div className="p-3 bg-gray-50 rounded-lg">
|
||||
<div className="grid grid-cols-2 gap-4 text-sm">
|
||||
<div>
|
||||
<span className="text-gray-500">总配比数量:</span>
|
||||
<span className="ml-2 font-medium">
|
||||
{ratioTaskForm.ratioConfigs
|
||||
.reduce((sum, config) => sum + config.quantity, 0)
|
||||
.toLocaleString()}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-gray-500">目标数量:</span>
|
||||
<span className="ml-2 font-medium">
|
||||
{ratioTaskForm.totalTargetCount.toLocaleString()}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-gray-500">配比项目:</span>
|
||||
<span className="ml-2 font-medium">
|
||||
{ratioTaskForm.ratioConfigs.length}个
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-gray-500">预计时间:</span>
|
||||
<span className="ml-2 font-medium">约 20 分钟</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex items-center justify-between p-3 border rounded-lg mb-4">
|
||||
<div>
|
||||
<span className="text-sm font-medium">创建后自动开始</span>
|
||||
<div className="text-xs text-gray-500 mt-1">
|
||||
任务创建完成后立即开始执行
|
||||
</div>
|
||||
</div>
|
||||
<Form.Item name="autoStart" valuePropName="checked" noStyle>
|
||||
<Switch
|
||||
checked={ratioTaskForm.autoStart}
|
||||
onChange={(checked) =>
|
||||
setRatioTaskForm({
|
||||
...ratioTaskForm,
|
||||
autoStart: checked,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</Form.Item>
|
||||
</div>
|
||||
<Divider />
|
||||
<div className="flex justify-end gap-2">
|
||||
<Button
|
||||
onClick={() => navigate("/data/synthesis/ratio-task")}
|
||||
>
|
||||
取消
|
||||
</Button>
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={handleCreateRatioTask}
|
||||
disabled={
|
||||
!ratioTaskForm.name ||
|
||||
ratioTaskForm.ratioConfigs.length === 0
|
||||
}
|
||||
>
|
||||
<Play className="w-4 h-4 mr-2" />
|
||||
创建任务
|
||||
</Button>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</Form>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
382
frontend/src/pages/RatioTask/RatioTask.tsx
Normal file
382
frontend/src/pages/RatioTask/RatioTask.tsx
Normal file
@@ -0,0 +1,382 @@
|
||||
import { useState } from "react";
|
||||
import {
|
||||
Button,
|
||||
Card,
|
||||
Input,
|
||||
Select,
|
||||
Badge,
|
||||
Progress,
|
||||
Table,
|
||||
Alert,
|
||||
} from "antd";
|
||||
import {
|
||||
Plus,
|
||||
Eye,
|
||||
Clock,
|
||||
Play,
|
||||
CheckCircle,
|
||||
AlertCircle,
|
||||
Pause,
|
||||
Download as DownloadIcon,
|
||||
BarChart3,
|
||||
} from "lucide-react";
|
||||
import type { RatioTask } from "@/pages/RatioTask/ratio";
|
||||
import { mockRatioTasks } from "@/mock/ratio";
|
||||
import { useNavigate } from "react-router";
|
||||
import CardView from "@/components/CardView";
|
||||
import { SearchControls } from "@/components/SearchControls";
|
||||
import DevelopmentInProgress from "@/components/DevelopmentInProgress";
|
||||
|
||||
export default function RatioTasksPage() {
|
||||
return <DevelopmentInProgress />;
|
||||
const navigate = useNavigate();
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
const [filterStatus, setFilterStatus] = useState("all");
|
||||
const [filterType, setFilterType] = useState("all");
|
||||
const [sortBy, setSortBy] = useState("createdAt");
|
||||
const [sortOrder, setSortOrder] = useState<"asc" | "desc">("desc");
|
||||
const [viewMode, setViewMode] = useState<"card" | "list">("card");
|
||||
|
||||
const [tasks, setTasks] = useState<RatioTask[]>(mockRatioTasks);
|
||||
|
||||
// 过滤和排序任务
|
||||
const filteredAndSortedTasks = tasks
|
||||
.filter((task) => {
|
||||
const matchesSearch = task.name
|
||||
.toLowerCase()
|
||||
.includes(searchQuery.toLowerCase());
|
||||
const matchesStatus =
|
||||
filterStatus === "all" || task.status === filterStatus;
|
||||
const matchesType = filterType === "all" || task.ratioType === filterType;
|
||||
return matchesSearch && matchesStatus && matchesType;
|
||||
})
|
||||
.sort((a, b) => {
|
||||
let aValue: any, bValue: any;
|
||||
|
||||
switch (sortBy) {
|
||||
case "name":
|
||||
aValue = a.name.toLowerCase();
|
||||
bValue = b.name.toLowerCase();
|
||||
break;
|
||||
case "targetCount":
|
||||
aValue = a.targetCount;
|
||||
bValue = b.targetCount;
|
||||
break;
|
||||
case "generatedCount":
|
||||
aValue = a.generatedCount;
|
||||
bValue = b.generatedCount;
|
||||
break;
|
||||
case "progress":
|
||||
aValue = a.progress;
|
||||
bValue = b.progress;
|
||||
break;
|
||||
case "createdAt":
|
||||
default:
|
||||
aValue = new Date(a.createdAt).getTime();
|
||||
bValue = new Date(b.createdAt).getTime();
|
||||
break;
|
||||
}
|
||||
|
||||
if (sortOrder === "asc") {
|
||||
return aValue > bValue ? 1 : -1;
|
||||
} else {
|
||||
return aValue < bValue ? 1 : -1;
|
||||
}
|
||||
});
|
||||
|
||||
const getStatusBadge = (status: string) => {
|
||||
const statusConfig = {
|
||||
pending: {
|
||||
label: "等待中",
|
||||
color: "#f09e10ff",
|
||||
icon: <Clock className="w-4 h-4 inline mr-1" />,
|
||||
},
|
||||
running: {
|
||||
label: "运行中",
|
||||
color: "#007bff",
|
||||
icon: <Play className="w-4 h-4 inline mr-1" />,
|
||||
},
|
||||
completed: {
|
||||
label: "已完成",
|
||||
color: "#28a745",
|
||||
icon: <CheckCircle className="w-4 h-4 inline mr-1" />,
|
||||
},
|
||||
failed: {
|
||||
label: "失败",
|
||||
color: "#dc3545",
|
||||
icon: <AlertCircle className="w-4 h-4 inline mr-1" />,
|
||||
},
|
||||
paused: {
|
||||
label: "已暂停",
|
||||
color: "#6c757d",
|
||||
icon: <Pause className="w-4 h-4 inline mr-1" />,
|
||||
},
|
||||
};
|
||||
return (
|
||||
statusConfig[status as keyof typeof statusConfig] || statusConfig.pending
|
||||
);
|
||||
};
|
||||
|
||||
const handleTaskAction = (taskId: number, action: string) => {
|
||||
setTasks((prev) =>
|
||||
prev.map((task) => {
|
||||
if (task.id === taskId) {
|
||||
switch (action) {
|
||||
case "pause":
|
||||
return { ...task, status: "paused" as const };
|
||||
case "resume":
|
||||
return { ...task, status: "running" as const };
|
||||
case "stop":
|
||||
return {
|
||||
...task,
|
||||
status: "failed" as const,
|
||||
progress: task.progress,
|
||||
};
|
||||
default:
|
||||
return task;
|
||||
}
|
||||
}
|
||||
return task;
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: "任务名称",
|
||||
dataIndex: "name",
|
||||
key: "name",
|
||||
},
|
||||
{
|
||||
title: "状态",
|
||||
dataIndex: "status",
|
||||
key: "status",
|
||||
},
|
||||
{
|
||||
title: "配比方式",
|
||||
dataIndex: "ratioType",
|
||||
key: "ratioType",
|
||||
},
|
||||
{
|
||||
title: "进度",
|
||||
dataIndex: "progress",
|
||||
key: "progress",
|
||||
},
|
||||
{
|
||||
title: "目标数量",
|
||||
dataIndex: "targetCount",
|
||||
key: "targetCount",
|
||||
},
|
||||
{
|
||||
title: "已生成",
|
||||
dataIndex: "generatedCount",
|
||||
key: "generatedCount",
|
||||
},
|
||||
{
|
||||
title: "数据源",
|
||||
dataIndex: "sourceDatasets",
|
||||
key: "sourceDatasets",
|
||||
},
|
||||
{
|
||||
title: "创建时间",
|
||||
dataIndex: "createdAt",
|
||||
key: "createdAt",
|
||||
},
|
||||
{
|
||||
title: "操作",
|
||||
key: "actions",
|
||||
render: (_: any, task: RatioTask) => (
|
||||
<div className="flex items-center gap-1 justify-end">
|
||||
{task.status === "running" && (
|
||||
<Button
|
||||
type="link"
|
||||
size="small"
|
||||
onClick={() => handleTaskAction(task.id, "pause")}
|
||||
>
|
||||
停止
|
||||
</Button>
|
||||
)}
|
||||
{task.status === "paused" && (
|
||||
<Button
|
||||
size="small"
|
||||
type="link"
|
||||
onClick={() => handleTaskAction(task.id, "resume")}
|
||||
>
|
||||
开始
|
||||
</Button>
|
||||
)}
|
||||
<Button type="link" size="small">
|
||||
下载
|
||||
</Button>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
const renderTableView = () => (
|
||||
<Card>
|
||||
<Table
|
||||
columns={columns}
|
||||
dataSource={filteredAndSortedTasks}
|
||||
rowKey="id"
|
||||
scroll={{ x: "max-content" }}
|
||||
locale={{
|
||||
emptyText: (
|
||||
<div className="text-center py-8">
|
||||
<BarChart3 className="w-12 h-12 text-gray-400 mx-auto mb-4" />
|
||||
<h3 className="text-lg font-medium text-gray-900 mb-2">
|
||||
暂无配比任务
|
||||
</h3>
|
||||
<p className="text-gray-500 mb-4">
|
||||
{searchQuery || filterStatus !== "all" || filterType !== "all"
|
||||
? "没有找到匹配的任务"
|
||||
: "开始创建您的第一个配比任务"}
|
||||
</p>
|
||||
{!searchQuery &&
|
||||
filterStatus === "all" &&
|
||||
filterType === "all" && (
|
||||
<Button
|
||||
onClick={() =>
|
||||
navigate("/data/synthesis/ratio-task/create")
|
||||
}
|
||||
type="primary"
|
||||
>
|
||||
<Plus className="w-4 h-4 mr-2" />
|
||||
创建配比任务
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</Card>
|
||||
);
|
||||
const renderCardView = () => (
|
||||
<CardView
|
||||
data={filteredAndSortedTasks.map((task) => ({
|
||||
...task,
|
||||
description:
|
||||
task.ratioType === "dataset" ? "按数据集配比" : "按标签配比",
|
||||
icon: <BarChart3 className="w-6 h-6" />,
|
||||
iconColor:
|
||||
task.ratioType === "dataset" ? "bg-blue-100" : "bg-green-100",
|
||||
statistics: [
|
||||
{
|
||||
label: "目标数量",
|
||||
value: task.targetCount.toLocaleString(),
|
||||
},
|
||||
{
|
||||
label: "已生成",
|
||||
value: task.generatedCount.toLocaleString(),
|
||||
},
|
||||
{
|
||||
label: "进度",
|
||||
value: `${Math.round(task.progress)}%`,
|
||||
},
|
||||
],
|
||||
status: getStatusBadge(task.status),
|
||||
}))}
|
||||
operations={[
|
||||
{
|
||||
key: "view",
|
||||
label: "查看",
|
||||
onClick: (item) => navigate(`/data/synthesis/ratio-task/${item.id}`),
|
||||
},
|
||||
{
|
||||
key: "download",
|
||||
label: "下载",
|
||||
onClick: (item) => console.log("下载", item.name),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
);
|
||||
|
||||
// 搜索、筛选和视图控制相关
|
||||
const searchFilters = [
|
||||
{
|
||||
key: "status",
|
||||
label: "状态筛选",
|
||||
options: [
|
||||
{ label: "全部状态", value: "all" },
|
||||
{ label: "等待中", value: "pending" },
|
||||
{ label: "运行中", value: "running" },
|
||||
{ label: "已完成", value: "completed" },
|
||||
{ label: "失败", value: "failed" },
|
||||
{ label: "已暂停", value: "paused" },
|
||||
],
|
||||
},
|
||||
{
|
||||
key: "type",
|
||||
label: "类型筛选",
|
||||
options: [
|
||||
{ label: "全部类型", value: "all" },
|
||||
{ label: "按数据集", value: "dataset" },
|
||||
{ label: "按标签", value: "label" },
|
||||
],
|
||||
},
|
||||
{
|
||||
key: "sortBy",
|
||||
label: "排序方式",
|
||||
options: [
|
||||
{ label: "创建时间", value: "createdAt" },
|
||||
{ label: "任务名称", value: "name" },
|
||||
{ label: "目标数量", value: "targetCount" },
|
||||
{ label: "已生成", value: "generatedCount" },
|
||||
{ label: "进度", value: "progress" },
|
||||
],
|
||||
},
|
||||
{
|
||||
key: "sortOrder",
|
||||
label: "排序顺序",
|
||||
options: [
|
||||
{ label: "升序", value: "asc" },
|
||||
{ label: "降序", value: "desc" },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
// 处理 SearchControls 的筛选变化
|
||||
const handleSearchControlsFiltersChange = (
|
||||
filters: Record<string, string[]>
|
||||
) => {
|
||||
setFilterStatus(filters.status?.[0] || "all");
|
||||
setFilterType(filters.type?.[0] || "all");
|
||||
setSortBy(filters.sortBy?.[0] || "createdAt");
|
||||
setSortOrder((filters.sortOrder?.[0] as "asc" | "desc") || "desc");
|
||||
};
|
||||
|
||||
// 处理视图切换
|
||||
const handleViewModeChange = (mode: "card" | "list") => {
|
||||
setViewMode(mode === "card" ? "card" : "list");
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="">
|
||||
<div className="flex items-center justify-between">
|
||||
<h2 className="text-xl font-bold">配比任务</h2>
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={() => navigate("/data/synthesis/ratio-task/create")}
|
||||
icon={<Plus className="w-4 h-4" />}
|
||||
>
|
||||
创建配比任务
|
||||
</Button>
|
||||
</div>
|
||||
<>
|
||||
{/* 搜索、筛选和视图控制 */}
|
||||
<SearchControls
|
||||
searchTerm={searchQuery}
|
||||
onSearchChange={setSearchQuery}
|
||||
searchPlaceholder="搜索任务名称"
|
||||
filters={searchFilters}
|
||||
onFiltersChange={handleSearchControlsFiltersChange}
|
||||
viewMode={viewMode === "card" ? "card" : "list"}
|
||||
onViewModeChange={handleViewModeChange}
|
||||
showViewToggle={true}
|
||||
/>
|
||||
{/* 任务列表 */}
|
||||
{viewMode === "list" ? renderTableView() : renderCardView()}
|
||||
</>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
24
frontend/src/pages/RatioTask/ratio.d.ts
vendored
Normal file
24
frontend/src/pages/RatioTask/ratio.d.ts
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
export interface RatioTask {
|
||||
id: number
|
||||
name: string
|
||||
status: "pending" | "running" | "completed" | "failed" | "paused"
|
||||
progress: number
|
||||
sourceDatasets: string[]
|
||||
targetCount: number
|
||||
generatedCount: number
|
||||
createdAt: string
|
||||
ratioType: "dataset" | "label"
|
||||
estimatedTime?: string
|
||||
quality?: number
|
||||
errorMessage?: string
|
||||
ratioConfigs: RatioConfig[]
|
||||
}
|
||||
|
||||
export interface RatioConfig {
|
||||
id: string
|
||||
name: string
|
||||
type: "dataset" | "label"
|
||||
quantity: number
|
||||
percentage: number
|
||||
source: string
|
||||
}
|
||||
Reference in New Issue
Block a user