Compare commits

..

No commits in common. "feature/v0/main" and "main" have entirely different histories.

8 changed files with 120 additions and 224 deletions

View File

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

View File

@ -23,21 +23,18 @@ export const dataGenerationApi = {
return request.post(`${baseUrl}`, params); return request.post(`${baseUrl}`, params);
}, },
stopTask: (params: { stopTask: (params: {
task_id: number | string; task_id: number;
}) => { }) => {
return request.post(`${baseUrl}/${params.task_id}/stop`); return request.post(`${baseUrl}/${params.task_id}/stop`);
}, },
deleteTask: (params: { deleteTask: (params: {
task_id: number | string; task_id: number;
}) => { }) => {
return request.delete(`${baseUrl}/${params.task_id}`); return request.delete(`${baseUrl}/${params.task_id}`);
}, },
getLogs: (params: { getLogs: (params: {
task_id: number | string; task_id: number;
}) => { }) => {
return request.get(`${baseUrl}/${params.task_id}/logs`); 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: { createDataset: (params: {
name: string; name: string;
description?: string; description: string;
}) => { }) => {
return request.post(`${baseUrl}`, params); return request.post(`${baseUrl}`, params);
}, },

View File

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

View File

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

View File

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

View File

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

View File

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