diff --git a/package.json b/package.json index 70367c8..12e1142 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/api/data-generation/index.ts b/src/api/data-generation/index.ts index 2cd9c35..60be992 100644 --- a/src/api/data-generation/index.ts +++ b/src/api/data-generation/index.ts @@ -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`); + }, }; diff --git a/src/api/dataset/index.ts b/src/api/dataset/index.ts index 264ebef..a0e614a 100644 --- a/src/api/dataset/index.ts +++ b/src/api/dataset/index.ts @@ -13,7 +13,7 @@ export const datasetApi = { }, createDataset: (params: { name: string; - description: string; + description?: string; }) => { return request.post(`${baseUrl}`, params); }, diff --git a/src/views/data-generation/components/CreateTaskModal.tsx b/src/views/data-generation/components/CreateTaskModal.tsx index 77cc32f..bfcc19f 100644 --- a/src/views/data-generation/components/CreateTaskModal.tsx +++ b/src/views/data-generation/components/CreateTaskModal.tsx @@ -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 = ({ @@ -24,11 +33,45 @@ const CreateTaskModal: React.FC = ({ 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 = ({ 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 = ({ closable={false} open={visible} onCancel={onCancel} - onOk={handleOk} // 修改为handleOk + onOk={handleOk} destroyOnHidden > -
- - - + - = ({ name="task_type" rules={[{ required: true, message: "请选择任务类型!" }]} > - = ({ name="camera" rules={[{ required: true, message: "请选择相机类型!" }]} > - = ({ placeholder="请输入轨迹条数 (1-10000)" /> + + + = () => { const [, token] = useToken(); const PollyRef = useRef(null); const refreshTime = 5000; + const [taskTypeOptions, setTaskTypeOptions] = useState< + { value: string; label: string }[] + >([]); + const [searchKeyword, setSearchKeyword] = useState(""); const HeaderSlot = (
@@ -66,7 +72,7 @@ const DataGenerationPage: React.FC = () => { const columns = [ { title: "任务名称", - dataIndex: "name", + dataIndex: "task_name", key: "name", }, { @@ -76,78 +82,126 @@ const DataGenerationPage: React.FC = () => { }, { 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) => ( - + {record.status === "running" ? ( // 假设 "running" 表示执行中 - <> - + + onOperate?.("stop", record)} + > + + - + ) : ( - <> - + + - + )} - + ), }, ]; - 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 ( <> @@ -155,7 +209,10 @@ const DataGenerationPage: React.FC = () => { getTaskList(page, pageSize, value)} + onSearch={(value) => { + setSearchKeyword(value); + getTaskList(page, pageSize, value); + }} style={{ width: "300px" }} /> @@ -176,6 +233,8 @@ const DataGenerationPage: React.FC = () => { onCancel={() => setCreateTaskModalVisible(false)} onConfirm={() => setCreateTaskModalVisible(false)} datasetList={datasetList} + taskTypeOptions={taskTypeOptions} + onDatasetCreate={() => getDatasetList()} > )} diff --git a/src/views/dataset/components/DatasetCard.tsx b/src/views/dataset/components/DatasetCard.tsx index 0b96572..00511ce 100644 --- a/src/views/dataset/components/DatasetCard.tsx +++ b/src/views/dataset/components/DatasetCard.tsx @@ -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 = ({ className="dataset_card_header" justify="space-between" align="center" + wrap={false} > - {DatasetDetail.name} - {DatasetDetail.data_count} / 条 数据 + + {DatasetDetail.name} + + {`${DatasetDetail.data_count} / 条 数据`}
@@ -45,7 +49,7 @@ const DatasetCard: React.FC = ({

创建时间:

-

{DatasetDetail.created_at}

+

{dayjs(DatasetDetail.created_at).format('YYYY-MM-DD HH:mm:ss')}

{ color: token.colorText, width: "100%", marginBottom: "16px", + overflow: "hidden", }, "& .dataset_card_content": { "& .dataset_card_content_description": { diff --git a/src/views/dataset/index.tsx b/src/views/dataset/index.tsx index d0d93a9..e8b223d 100644 --- a/src/views/dataset/index.tsx +++ b/src/views/dataset/index.tsx @@ -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 = (props: DatasetPageProps) => { const refreshTime = 5000; const [searchKeyword, setSearchKeyword] = useState(""); const [operateFormVisible, setOperateFormVisible] = useState(false); - const [datasetList, setDatasetList] = useState([ - { - 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([]); const [loading, setLoading] = useState(false); const HeaderSlot = ( @@ -65,7 +51,7 @@ const DatasetPage: React.FC = (props: DatasetPageProps) => { getDatasetList(newPage, newPageSize); PollyRef.current = preciseInterval(() => { getDatasetList(newPage, newPageSize); - }, refreshTime); + }, 5000); }} showSizeChanger > @@ -79,28 +65,14 @@ const DatasetPage: React.FC = (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 = (props: DatasetPageProps) => { getDatasetList(page, pageSize); PollyRef.current = preciseInterval(() => { getDatasetList(page, pageSize); - }, refreshTime); + }, 5000); return () => { clearPolling(); }; @@ -169,28 +141,36 @@ const DatasetPage: React.FC = (props: DatasetPageProps) => { return ( - {loading ? ( - - ) : ( -
setOperateFormVisible(false)} - > - { +
setOperateFormVisible(false)} + > + { setSearchKeyword(e.target.value); - getDatasetList(page, pageSize, e.target.value) - }} /> - {datasetList.map((item) => ( - - handleOperate(type, dataset as Dataset) - } - > - ))} -
- )} + getDatasetList(page, pageSize, e.target.value); + }} + /> + {datasetList.length === 0 ? ( + + ) : ( + + {datasetList.map((item) => ( + + + handleOperate(type, dataset as Dataset) + } + > + + ))} + + )} +
); }; @@ -200,10 +180,10 @@ export default DatasetPage; const useStyles = createStyles(({ token }) => { return { container: { - "& .search": { - width: "300px", - marginBottom: token.margin, - } + "& .search": { + width: "300px", + marginBottom: token.margin, + }, }, }; }); diff --git a/vite.config.ts b/vite.config.ts index c0c6dab..386b57c 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -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/, ""), + }, + }, + }, })