Compare commits

...

1 Commits

Author SHA1 Message Date
yue02.sun
1331552cfc 提交代码 2025-08-05 15:30:03 +08:00
8 changed files with 225 additions and 121 deletions

View File

@ -15,6 +15,7 @@
"axios": "^1.11.0",
"i18next": "^25.3.2",
"i18next-browser-languagedetector": "^8.2.0",
"lodash": "^4.17.21",
"react": "^19.1.0",
"react-dom": "^19.1.0",
"react-i18next": "^15.6.0",
@ -23,6 +24,7 @@
},
"devDependencies": {
"@eslint/js": "^9.30.1",
"@types/lodash": "^4.17.20",
"@types/node": "^24.0.14",
"@types/react": "^19.1.8",
"@types/react-dom": "^19.1.6",

View File

@ -23,18 +23,21 @@ export const dataGenerationApi = {
return request.post(`${baseUrl}`, params);
},
stopTask: (params: {
task_id: number;
task_id: number | string;
}) => {
return request.post(`${baseUrl}/${params.task_id}/stop`);
},
deleteTask: (params: {
task_id: number;
task_id: number | string;
}) => {
return request.delete(`${baseUrl}/${params.task_id}`);
},
getLogs: (params: {
task_id: number;
task_id: number | string;
}) => {
return request.get(`${baseUrl}/${params.task_id}/logs`);
},
getOptions: () => {
return request.get(`${baseUrl}/options`);
},
};

View File

@ -13,7 +13,7 @@ export const datasetApi = {
},
createDataset: (params: {
name: string;
description: string;
description?: string;
}) => {
return request.post(`${baseUrl}`, params);
},

View File

@ -1,3 +1,4 @@
import { datasetApi } from "@/api/dataset";
import {
Button,
Divider,
@ -5,6 +6,7 @@ import {
Form,
Input,
InputNumber,
message,
Modal,
Select,
Space,
@ -12,11 +14,18 @@ import {
} from "antd";
import React, { useEffect } from "react";
// 生成简短的UUID
const generateShortUUID = () => {
return Math.random().toString(36).substring(2, 8);
};
interface CreateTaskModalProps {
visible: boolean;
onCancel: () => void;
onConfirm: (values: any) => void; // 修改onConfirm的类型以接收表单值
datasetList: { id: number; name: string; description: string }[] | undefined;
taskTypeOptions?: { value: string; label: string }[];
onDatasetCreate?: () => void;
}
const CreateTaskModal: React.FC<CreateTaskModalProps> = ({
@ -24,11 +33,45 @@ const CreateTaskModal: React.FC<CreateTaskModalProps> = ({
onCancel,
onConfirm,
datasetList,
taskTypeOptions,
onDatasetCreate
}) => {
const [form] = Form.useForm();
const [datasetName, setDatasetName] = React.useState("");
const [loading, setLoading] = React.useState(false);
// 监听表单字段变化,自动生成数据集名称
useEffect(() => {
const simulator = form.getFieldValue('simulator');
const taskType = form.getFieldValue('task_type');
const camera = form.getFieldValue('camera');
if (simulator && taskType && camera) {
const uuid = generateShortUUID();
const generatedName = `${simulator}-${taskType}-${camera}-${uuid}`;
setDatasetName(generatedName);
}
}, [form]);
// 监听表单字段变化
const handleFormChange = () => {
const taskType = form.getFieldValue('task_type');
const camera = form.getFieldValue('camera');
if (taskType && camera) {
const uuid = generateShortUUID();
const generatedName = `${taskType}-${camera}-${uuid}`;
setDatasetName(generatedName);
}
};
const simulationOptions = [
{ value: "robotwin", label: "robotwin" },
];
const cameraTypeOptions = [
{ value: "D435", label: "D435" },
{ value: "D515", label: "D515" },
];
const handleOk = async () => {
try {
const values = await form.validateFields();
@ -41,7 +84,18 @@ const CreateTaskModal: React.FC<CreateTaskModalProps> = ({
const handleCreateDataset = async () => {
setLoading(true);
try {
await datasetApi.createDataset({
name: datasetName,
});
message.success("创建数据集成功");
setLoading(false);
setDatasetName("");
onDatasetCreate?.();
} catch (error) {
setLoading(false);
message.error("创建数据集失败");
}
};
return (
@ -50,21 +104,16 @@ const CreateTaskModal: React.FC<CreateTaskModalProps> = ({
closable={false}
open={visible}
onCancel={onCancel}
onOk={handleOk} // 修改为handleOk
onOk={handleOk}
destroyOnHidden
>
<Form form={form} layout="vertical">
<Form.Item label="任务名称" name="task_name">
<Input placeholder="请输入任务名称(可选)" />
</Form.Item>
<Form form={form} layout="vertical" onValuesChange={handleFormChange}>
<Form.Item
label="仿真器"
name="simulator"
rules={[{ required: true, message: "请选择仿真器!" }]}
>
<Select placeholder="请选择仿真器">
<Select.Option value="robotwin">robotwin</Select.Option>
{/* 可以根据实际情况添加更多仿真器选项 */}
<Select placeholder="请选择仿真器" options={simulationOptions}>
</Select>
</Form.Item>
<Form.Item
@ -72,11 +121,7 @@ const CreateTaskModal: React.FC<CreateTaskModalProps> = ({
name="task_type"
rules={[{ required: true, message: "请选择任务类型!" }]}
>
<Select placeholder="请选择任务类型">
<Select.Option value="beat_block_hammer">
beat_block_hammer
</Select.Option>
{/* 可以根据实际情况添加更多任务类型选项 */}
<Select placeholder="请选择任务类型" options={taskTypeOptions}>
</Select>
</Form.Item>
<Form.Item
@ -84,9 +129,7 @@ const CreateTaskModal: React.FC<CreateTaskModalProps> = ({
name="camera"
rules={[{ required: true, message: "请选择相机类型!" }]}
>
<Select placeholder="请选择相机类型">
<Select.Option value="D435">D435</Select.Option>
{/* 可以根据实际情况添加更多相机类型选项 */}
<Select placeholder="请选择相机类型" options={cameraTypeOptions}>
</Select>
</Form.Item>
<Form.Item
@ -101,6 +144,9 @@ const CreateTaskModal: React.FC<CreateTaskModalProps> = ({
placeholder="请输入轨迹条数 (1-10000)"
/>
</Form.Item>
<Form.Item label="任务名称" name="task_name">
<Input placeholder="请输入任务名称(可选)" />
</Form.Item>
<Form.Item
label="数据集"
name="dataset_id"

View File

@ -1,5 +1,5 @@
import ColLayout from "@/components/ColLayout";
import { Button, Flex, Pagination, Table } from "antd";
import { Button, Flex, message, Pagination, Popconfirm, Table } from "antd";
import { createStyles } from "antd-style";
import ButtonGroup from "antd/es/button/button-group";
import Search from "antd/es/input/Search";
@ -9,6 +9,8 @@ import { datasetApi } from "@/api/dataset";
import useToken from "antd/es/theme/useToken";
import { dataGenerationApi } from "@/api/data-generation";
import { preciseInterval } from "@/utils";
import dayjs from "dayjs";
import { debounce } from "lodash";
// 假设的任务类型接口,如果已存在请忽略
interface TaskItem {
@ -17,7 +19,7 @@ interface TaskItem {
type: string;
create_time: string;
update_time: string;
// 其他可能的字段
id: number | string;
}
interface DataGenerationPageProps {}
@ -37,6 +39,10 @@ const DataGenerationPage: React.FC<DataGenerationPageProps> = () => {
const [, token] = useToken();
const PollyRef = useRef<any>(null);
const refreshTime = 5000;
const [taskTypeOptions, setTaskTypeOptions] = useState<
{ value: string; label: string }[]
>([]);
const [searchKeyword, setSearchKeyword] = useState<string>("");
const HeaderSlot = (
<div>
@ -66,7 +72,7 @@ const DataGenerationPage: React.FC<DataGenerationPageProps> = () => {
const columns = [
{
title: "任务名称",
dataIndex: "name",
dataIndex: "task_name",
key: "name",
},
{
@ -76,78 +82,126 @@ const DataGenerationPage: React.FC<DataGenerationPageProps> = () => {
},
{
title: "任务类型",
dataIndex: "type",
dataIndex: "task_type",
key: "type",
},
{
title: "任务创建时间",
dataIndex: "create_time",
key: "create_time",
dataIndex: "created_at",
key: "created_at",
render: (text: string) => dayjs(text).format("YYYY-MM-DD HH:mm:ss"),
},
{
title: "任务更新时间",
dataIndex: "update_time",
key: "update_time",
dataIndex: "updated_at",
key: "updated_at",
render: (text: string) => dayjs(text).format("YYYY-MM-DD HH:mm:ss"),
},
{
title: "操作",
key: "action",
render: (_: any, record: TaskItem) => (
<ButtonGroup>
<span>
{record.status === "running" ? ( // 假设 "running" 表示执行中
<>
<Button type="primary"></Button>
<Flex gap={16}>
<Popconfirm
title="确定停止吗?"
onConfirm={() => onOperate?.("stop", record)}
>
<Button type="primary" danger>
</Button>
</Popconfirm>
<Button type="primary"></Button>
</>
</Flex>
) : (
<>
<Button type="primary"></Button>
<Flex gap={16}>
<Button type="primary" danger></Button>
<Button type="primary"></Button>
</>
</Flex>
)}
</ButtonGroup>
</span>
),
},
];
const getDatasetList = async (
page: number,
pageSize: number,
) => {
const { data } = await datasetApi.getDatasetList({
current_page: page,
page_size: pageSize,
});
setDatasetList(data.list);
const onOperate = async (type: "stop" | "delete", record: TaskItem) => {
try {
if (type === "stop") {
await dataGenerationApi.stopTask({ task_id: record.id });
} else if (type === "delete") {
await dataGenerationApi.deleteTask({ task_id: record.id });
}
setSearchKeyword('');
setPage(1);
getTaskList(1, pageSize, '');
message.success("操作成功");
} catch (error) {
console.error(error);
}
};
const getTaskList = async (page: number, pageSize: number, keyword?: string) => {
const getDatasetList = async () => {
const { data } = await datasetApi.getDatasetList({
current_page: 1,
page_size: 100,
});
setDatasetList(data.items);
};
const getTaskList = async (
page: number,
pageSize: number,
keyword?: string
) => {
const { data } = await dataGenerationApi.getTaskList({
current_page: page,
page_size: pageSize,
search_keyword: keyword
search_keyword: keyword,
});
setDataSource(data.items);
setTotal(data.total);
};
const getOptions = async () => {
const { data } = await dataGenerationApi.getOptions();
setTaskTypeOptions(
data.options.robotwin.task_types.map((item: any) => ({
value: item,
label: item,
}))
);
};
const clearPolling = () => {
PollyRef.current?.cancel();
PollyRef.current = null;
};
const startPolling = () => {
const startPolling = (page: number, pageSize: number, keyword?: string) => {
PollyRef.current = preciseInterval(() => {
getTaskList(page, pageSize);
getTaskList(page, pageSize, keyword);
}, refreshTime);
};
useEffect(() => {
clearPolling();
getDatasetList(1, 10000)
startPolling();
getDatasetList();
getOptions();
}, []);
const debounceStartPolling = debounce(
(page: number, pageSize: number, keyword?: string) => {
clearPolling();
startPolling(page, pageSize, keyword);
},
500
);
useEffect(() => {
debounceStartPolling(page, pageSize, searchKeyword);
}, [page, pageSize, searchKeyword]);
return (
<>
<ColLayout HeaderSlot={HeaderSlot} FooterSlot={FooterSlot}>
@ -155,7 +209,10 @@ const DataGenerationPage: React.FC<DataGenerationPageProps> = () => {
<Flex justify="space-between" align="center" className="operate">
<Search
placeholder="请输入任务名称模糊搜索"
onSearch={(value) => getTaskList(page, pageSize, value)}
onSearch={(value) => {
setSearchKeyword(value);
getTaskList(page, pageSize, value);
}}
style={{ width: "300px" }}
/>
<ButtonGroup>
@ -176,6 +233,8 @@ const DataGenerationPage: React.FC<DataGenerationPageProps> = () => {
onCancel={() => setCreateTaskModalVisible(false)}
onConfirm={() => setCreateTaskModalVisible(false)}
datasetList={datasetList}
taskTypeOptions={taskTypeOptions}
onDatasetCreate={() => getDatasetList()}
></CreateTaskModal>
)}
</>

View File

@ -5,6 +5,7 @@ import useToken from "antd/es/theme/useToken";
import React, { useEffect, useRef, useState } from "react";
import DatasetForm, { DatasetFormRef } from "./DatasetForm";
import { Dataset } from "@/types/dataset";
import dayjs from "dayjs";
interface DatasetCardProps {
DatasetDetail: Dataset;
@ -27,9 +28,12 @@ const DatasetCard: React.FC<DatasetCardProps> = ({
className="dataset_card_header"
justify="space-between"
align="center"
wrap={false}
>
<span>{DatasetDetail.name}</span>
<span>{DatasetDetail.data_count} / </span>
<Tooltip title={DatasetDetail.name}>
<span className="ellipsis" style={{flex: 1}}>{DatasetDetail.name}</span>
</Tooltip>
<span style={{ maxWidth: '180px' }}>{`${DatasetDetail.data_count} / 条 数据`}</span>
</Flex>
<div className="dataset_card_content">
<div className="dataset_card_content_description">
@ -45,7 +49,7 @@ const DatasetCard: React.FC<DatasetCardProps> = ({
</div>
<div className="dataset_card_content_description">
<p className="description"></p>
<p className="ellipsis">{DatasetDetail.created_at}</p>
<p className="ellipsis">{dayjs(DatasetDetail.created_at).format('YYYY-MM-DD HH:mm:ss')}</p>
</div>
</div>
<Flex
@ -114,6 +118,7 @@ const useStyles = createStyles(({ token }) => {
color: token.colorText,
width: "100%",
marginBottom: "16px",
overflow: "hidden",
},
"& .dataset_card_content": {
"& .dataset_card_content_description": {

View File

@ -1,7 +1,7 @@
import ColLayout from "@/components/ColLayout";
import React, { useEffect, useRef, useState } from "react";
import React, { Fragment, useEffect, useRef, useState } from "react";
import DatasetCard from "./components/DatasetCard";
import { Form, Input, message, Pagination, Spin } from "antd";
import { Empty, Flex, Form, Input, message, Pagination, Spin } from "antd";
import { datasetApi } from "@/api/dataset";
import { preciseInterval } from "@/utils";
import useToken from "antd/es/theme/useToken";
@ -22,21 +22,7 @@ const DatasetPage: React.FC<DatasetPageProps> = (props: DatasetPageProps) => {
const refreshTime = 5000;
const [searchKeyword, setSearchKeyword] = useState("");
const [operateFormVisible, setOperateFormVisible] = useState(false);
const [datasetList, setDatasetList] = useState<Dataset[]>([
{
id: 1,
name: "数据集1",
description: "数据集1描述",
task_count: 1,
simulator: "simulator1",
task: "task1",
camera: "camera1",
storage_time: "2024-01-01",
created_at: "2024-01-01",
updated_at: "2024-01-01",
data_count: 1,
},
]);
const [datasetList, setDatasetList] = useState<Dataset[]>([]);
const [loading, setLoading] = useState(false);
const HeaderSlot = (
@ -65,7 +51,7 @@ const DatasetPage: React.FC<DatasetPageProps> = (props: DatasetPageProps) => {
getDatasetList(newPage, newPageSize);
PollyRef.current = preciseInterval(() => {
getDatasetList(newPage, newPageSize);
}, refreshTime);
}, 5000);
}}
showSizeChanger
></Pagination>
@ -79,28 +65,14 @@ const DatasetPage: React.FC<DatasetPageProps> = (props: DatasetPageProps) => {
) => {
try {
setLoading(true);
// const { data } = await datasetApi.getDatasetList({
// current_page: page,
// page_size: pageSize,
// search_keyword: name,
// });
// setTotal(data.total);
setDatasetList([
{
id: 1,
name: "数据集1",
description: "数据集1描述",
task_count: 1,
simulator: "simulator1",
task: "task1",
camera: "camera1",
storage_time: "2024-01-01",
created_at: "2024-01-01",
updated_at: "2024-01-01",
data_count: 1,
},
]);
const { data } = await datasetApi.getDatasetList({
current_page: page,
page_size: pageSize,
search_keyword: name,
});
setTotal(data.total);
setLoading(false);
setDatasetList(data?.items || []);
} catch (error) {
message.error("获取数据集列表失败");
setLoading(false);
@ -161,7 +133,7 @@ const DatasetPage: React.FC<DatasetPageProps> = (props: DatasetPageProps) => {
getDatasetList(page, pageSize);
PollyRef.current = preciseInterval(() => {
getDatasetList(page, pageSize);
}, refreshTime);
}, 5000);
return () => {
clearPolling();
};
@ -169,18 +141,24 @@ const DatasetPage: React.FC<DatasetPageProps> = (props: DatasetPageProps) => {
return (
<ColLayout HeaderSlot={HeaderSlot} FooterSlot={FooterSlot}>
{loading ? (
<Spin />
) : (
<div
className={styles.container}
onClick={() => setOperateFormVisible(false)}
>
<Search className="search" placeholder="请输入数据集名称" onChange={(e) => {
<Search
className="search"
placeholder="请输入数据集名称"
onChange={(e) => {
setSearchKeyword(e.target.value);
getDatasetList(page, pageSize, e.target.value)
}} />
getDatasetList(page, pageSize, e.target.value);
}}
/>
{datasetList.length === 0 ? (
<Empty />
) : (
<Flex wrap gap={16}>
{datasetList.map((item) => (
<Fragment key={item.id}>
<DatasetCard
key={item.id}
DatasetDetail={item}
@ -188,9 +166,11 @@ const DatasetPage: React.FC<DatasetPageProps> = (props: DatasetPageProps) => {
handleOperate(type, dataset as Dataset)
}
></DatasetCard>
</Fragment>
))}
</div>
</Flex>
)}
</div>
</ColLayout>
);
};
@ -203,7 +183,7 @@ const useStyles = createStyles(({ token }) => {
"& .search": {
width: "300px",
marginBottom: token.margin,
}
},
},
};
});

View File

@ -10,4 +10,13 @@ export default defineConfig({
"@": join(__dirname, "./src"),
},
},
server: {
proxy: {
"/api": {
target: "http://120.48.128.161:30030",
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, ""),
},
},
},
})