You've already forked DataMate
feat: Enhance DatasetDetail component with delete functionality and improved download handling
feat: Add automatic data refresh and improved user feedback in DatasetManagementPage fix: Update dataset API to streamline download functionality and improve error handling
This commit is contained in:
@@ -4,7 +4,7 @@ import { ArrowLeft } from "lucide-react";
|
||||
import { Button, Form, App } from "antd";
|
||||
import { Link, useNavigate } from "react-router";
|
||||
import { createDatasetUsingPost } from "../dataset.api";
|
||||
import { DatasetType, DataSource } from "../dataset.model";
|
||||
import { DatasetType } from "../dataset.model";
|
||||
import BasicInformation from "./components/BasicInformation";
|
||||
|
||||
export default function DatasetCreate() {
|
||||
@@ -69,7 +69,11 @@ export default function DatasetCreate() {
|
||||
</div>
|
||||
<div className="flex gap-2 justify-end p-6 border-top">
|
||||
<Button onClick={() => navigate("/data/management")}>取消</Button>
|
||||
<Button type="primary" onClick={handleSubmit}>
|
||||
<Button
|
||||
type="primary"
|
||||
disabled={!newDataset.name || !newDataset.datasetType}
|
||||
onClick={handleSubmit}
|
||||
>
|
||||
确定
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -10,11 +10,12 @@ import {
|
||||
import DetailHeader from "@/components/DetailHeader";
|
||||
import { mapDataset, datasetTypeMap } from "../dataset.const";
|
||||
import type { Dataset } from "@/pages/DataManagement/dataset.model";
|
||||
import { Link, useParams } from "react-router";
|
||||
import { useFilesOperation } from "../hooks";
|
||||
import { Link, useNavigate, useParams } from "react-router";
|
||||
import { useFilesOperation } from "./useFilesOperation";
|
||||
import {
|
||||
createDatasetTagUsingPost,
|
||||
downloadFile,
|
||||
deleteDatasetByIdUsingDelete,
|
||||
downloadDatasetUsingGet,
|
||||
queryDatasetByIdUsingGet,
|
||||
queryDatasetTagsUsingGet,
|
||||
updateDatasetByIdUsingPut,
|
||||
@@ -43,6 +44,7 @@ const tabList = [
|
||||
|
||||
export default function DatasetDetail() {
|
||||
const { id } = useParams(); // 获取动态路由参数
|
||||
const navigate = useNavigate();
|
||||
const [activeTab, setActiveTab] = useState("overview");
|
||||
const { message } = App.useApp();
|
||||
const [showEditDialog, setShowEditDialog] = useState(false);
|
||||
@@ -79,10 +81,16 @@ export default function DatasetDetail() {
|
||||
};
|
||||
|
||||
const handleDownload = async () => {
|
||||
await downloadFile(dataset.id);
|
||||
await downloadDatasetUsingGet(dataset.id);
|
||||
message.success("文件下载成功");
|
||||
};
|
||||
|
||||
const handleDeleteDataset = async () => {
|
||||
await deleteDatasetByIdUsingDelete(dataset.id);
|
||||
navigate("/data/management");
|
||||
message.success("数据集删除成功");
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const refreshDataset = () => {
|
||||
fetchDataset();
|
||||
@@ -166,11 +174,16 @@ export default function DatasetDetail() {
|
||||
key: "delete",
|
||||
label: "删除",
|
||||
danger: true,
|
||||
icon: <DeleteOutlined />,
|
||||
onClick: () => {
|
||||
console.log("delete dataset");
|
||||
confirm: {
|
||||
title: "确认删除该数据集?",
|
||||
description: "删除后该数据集将无法恢复,请谨慎操作。",
|
||||
okText: "删除",
|
||||
cancelText: "取消",
|
||||
okType: "danger",
|
||||
},
|
||||
}
|
||||
icon: <DeleteOutlined />,
|
||||
onClick: handleDeleteDataset,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,10 +1,21 @@
|
||||
import { Select, Input, Form, Radio, Modal, Button } from "antd";
|
||||
import {
|
||||
Select,
|
||||
Input,
|
||||
Form,
|
||||
Radio,
|
||||
Modal,
|
||||
Button,
|
||||
App,
|
||||
UploadFile,
|
||||
} from "antd";
|
||||
import { InboxOutlined } from "@ant-design/icons";
|
||||
import { dataSourceOptions } from "../../dataset.const";
|
||||
import { Dataset, DataSource } from "../../dataset.model";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { queryTasksUsingGet } from "@/pages/DataCollection/collection.apis";
|
||||
import { useImportFile } from "../../hooks";
|
||||
import { updateDatasetByIdUsingPut } from "../../dataset.api";
|
||||
import { sliceFile } from "@/utils/file.util";
|
||||
import Dragger from "antd/es/upload/Dragger";
|
||||
|
||||
export default function ImportConfiguration({
|
||||
data,
|
||||
@@ -15,16 +26,52 @@ export default function ImportConfiguration({
|
||||
data?: Dataset;
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
onRefresh?: () => void;
|
||||
onRefresh?: (showMessage?: boolean) => void;
|
||||
}) {
|
||||
const { message } = App.useApp();
|
||||
const [form] = Form.useForm();
|
||||
const [collectionOptions, setCollectionOptions] = useState([]);
|
||||
const [importConfig, setImportConfig] = useState<any>({
|
||||
source: DataSource.UPLOAD,
|
||||
});
|
||||
const { importFileRender, handleUpload } = useImportFile();
|
||||
|
||||
// 获取归集任务列表
|
||||
const [fileList, setFileList] = useState<UploadFile[]>([]);
|
||||
const fileSliceList = useMemo(() => {
|
||||
const sliceList = fileList.map((file) => {
|
||||
const slices = sliceFile(file);
|
||||
return { originFile: file, slices, name: file.name, size: file.size };
|
||||
});
|
||||
return sliceList;
|
||||
}, [fileList]);
|
||||
|
||||
// 本地上传文件相关逻辑
|
||||
|
||||
const resetFiles = () => {
|
||||
setFileList([]);
|
||||
};
|
||||
|
||||
const handleUpload = async (dataset: Dataset) => {
|
||||
const formData = new FormData();
|
||||
fileList.forEach((file) => {
|
||||
formData.append("file", file);
|
||||
});
|
||||
window.dispatchEvent(
|
||||
new CustomEvent("upload:dataset", {
|
||||
detail: { dataset, files: fileSliceList },
|
||||
})
|
||||
);
|
||||
resetFiles();
|
||||
};
|
||||
|
||||
const handleBeforeUpload = (_, files: UploadFile[]) => {
|
||||
setFileList([...fileList, ...files]);
|
||||
return false;
|
||||
};
|
||||
|
||||
const handleRemoveFile = (file: UploadFile) => {
|
||||
setFileList((prev) => prev.filter((f) => f.uid !== file.uid));
|
||||
};
|
||||
|
||||
const fetchCollectionTasks = async () => {
|
||||
try {
|
||||
const res = await queryTasksUsingGet({ page: 0, size: 100 });
|
||||
@@ -40,6 +87,8 @@ export default function ImportConfiguration({
|
||||
|
||||
const resetState = () => {
|
||||
form.resetFields();
|
||||
setFileList([]);
|
||||
form.setFieldsValue({ files: null });
|
||||
setImportConfig({ source: DataSource.UPLOAD });
|
||||
};
|
||||
|
||||
@@ -51,13 +100,16 @@ export default function ImportConfiguration({
|
||||
...importConfig,
|
||||
});
|
||||
}
|
||||
resetState();
|
||||
onRefresh?.();
|
||||
message.success("数据已更新");
|
||||
onRefresh?.(false);
|
||||
onClose();
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (open) fetchCollectionTasks();
|
||||
if (open) {
|
||||
resetState();
|
||||
fetchCollectionTasks();
|
||||
}
|
||||
}, [open]);
|
||||
|
||||
return (
|
||||
@@ -65,12 +117,19 @@ export default function ImportConfiguration({
|
||||
title="导入数据"
|
||||
open={open}
|
||||
width={600}
|
||||
onCancel={onClose}
|
||||
onCancel={() => {
|
||||
onClose();
|
||||
resetState();
|
||||
}}
|
||||
maskClosable={false}
|
||||
footer={
|
||||
<>
|
||||
<Button onClick={onClose}>取消</Button>
|
||||
<Button type="primary" onClick={handleImportData}>
|
||||
<Button
|
||||
type="primary"
|
||||
disabled={!fileList?.length && !importConfig.dataSource}
|
||||
onClick={handleImportData}
|
||||
>
|
||||
确定
|
||||
</Button>
|
||||
</>
|
||||
@@ -132,6 +191,7 @@ export default function ImportConfiguration({
|
||||
</Form.Item>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* obs import */}
|
||||
{importConfig?.source === DataSource.OBS && (
|
||||
<div className="grid grid-cols-2 gap-3 p-4 bg-blue-50 rounded-lg">
|
||||
@@ -185,7 +245,18 @@ export default function ImportConfiguration({
|
||||
},
|
||||
]}
|
||||
>
|
||||
{importFileRender()}
|
||||
<Dragger
|
||||
className="w-full"
|
||||
onRemove={handleRemoveFile}
|
||||
beforeUpload={handleBeforeUpload}
|
||||
multiple
|
||||
>
|
||||
<p className="ant-upload-drag-icon">
|
||||
<InboxOutlined />
|
||||
</p>
|
||||
<p className="ant-upload-text">本地文件上传</p>
|
||||
<p className="ant-upload-hint">拖拽文件到此处或点击选择文件</p>
|
||||
</Dragger>
|
||||
</Form.Item>
|
||||
)}
|
||||
|
||||
|
||||
@@ -82,11 +82,6 @@ export default function Overview({ dataset, filesOperation }) {
|
||||
label: "更新时间",
|
||||
children: dataset.updatedAt,
|
||||
},
|
||||
{
|
||||
key: "dataSource",
|
||||
label: "数据源",
|
||||
children: dataset.dataSource || "未知",
|
||||
},
|
||||
{
|
||||
key: "description",
|
||||
label: "描述",
|
||||
|
||||
@@ -6,7 +6,7 @@ import { App } from "antd";
|
||||
import { useState } from "react";
|
||||
import {
|
||||
deleteDatasetFileUsingDelete,
|
||||
downloadFile,
|
||||
downloadFileByIdUsingGet,
|
||||
exportDatasetUsingPost,
|
||||
queryDatasetFilesUsingGet,
|
||||
} from "../dataset.api";
|
||||
@@ -51,7 +51,7 @@ export function useFilesOperation(dataset: Dataset) {
|
||||
const handleDownloadFile = async (file: DatasetFile) => {
|
||||
console.log("批量下载文件:", selectedFiles);
|
||||
// 实际导出逻辑
|
||||
await downloadFile(dataset.id, file.id, file.fileName);
|
||||
await downloadFileByIdUsingGet(dataset.id, file.id, file.fileName);
|
||||
// 假设导出成功
|
||||
message.success({
|
||||
content: `已导出 1 个文件`,
|
||||
@@ -119,7 +119,13 @@ export default function DatasetManagementPage() {
|
||||
fetchData,
|
||||
setSearchParams,
|
||||
handleFiltersChange,
|
||||
} = useFetchData(queryDatasetsUsingGet, mapDataset);
|
||||
} = useFetchData<Dataset>(
|
||||
queryDatasetsUsingGet,
|
||||
mapDataset,
|
||||
30000, // 30秒轮询间隔
|
||||
true, // 自动刷新
|
||||
[fetchStatistics] // 额外的轮询函数
|
||||
);
|
||||
|
||||
const handleDownloadDataset = async (dataset: Dataset) => {
|
||||
await downloadDatasetUsingGet(dataset.id, dataset.name);
|
||||
@@ -138,9 +144,12 @@ export default function DatasetManagementPage() {
|
||||
setShowUploadDialog(true);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchStatistics();
|
||||
}, []);
|
||||
const handleRefresh = async (showMessage = true) => {
|
||||
await fetchData();
|
||||
if (showMessage) {
|
||||
message.success("数据已刷新");
|
||||
}
|
||||
};
|
||||
|
||||
const operations = [
|
||||
{
|
||||
@@ -173,6 +182,13 @@ export default function DatasetManagementPage() {
|
||||
key: "delete",
|
||||
label: "删除",
|
||||
danger: true,
|
||||
confirm: {
|
||||
title: "确认删除该数据集?",
|
||||
description: "删除后该数据集将无法恢复,请谨慎操作。",
|
||||
okText: "删除",
|
||||
cancelText: "取消",
|
||||
okType: "danger",
|
||||
},
|
||||
icon: <DeleteOutlined />,
|
||||
onClick: (item: Dataset) => handleDeleteDataset(item.id),
|
||||
},
|
||||
@@ -306,7 +322,7 @@ export default function DatasetManagementPage() {
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between">
|
||||
<h1 className="text-xl font-bold">数据管理</h1>
|
||||
<div className="flex gap-2">
|
||||
<div className="flex gap-2 items-center">
|
||||
{/* tasks */}
|
||||
<TagManager
|
||||
onCreate={createDatasetTagUsingPost}
|
||||
@@ -351,7 +367,7 @@ export default function DatasetManagementPage() {
|
||||
viewMode={viewMode}
|
||||
onViewModeChange={setViewMode}
|
||||
showViewToggle
|
||||
onReload={fetchData}
|
||||
onReload={handleRefresh}
|
||||
/>
|
||||
{viewMode === "card" ? renderCardView() : renderListView()}
|
||||
<EditDataset
|
||||
@@ -361,7 +377,7 @@ export default function DatasetManagementPage() {
|
||||
setCurrentDataset(null);
|
||||
setEditDatasetOpen(false);
|
||||
}}
|
||||
onRefresh={fetchData}
|
||||
onRefresh={handleRefresh}
|
||||
/>
|
||||
<ImportConfiguration
|
||||
data={currentDataset}
|
||||
@@ -370,7 +386,7 @@ export default function DatasetManagementPage() {
|
||||
setCurrentDataset(null);
|
||||
setShowUploadDialog(false);
|
||||
}}
|
||||
onRefresh={fetchData}
|
||||
onRefresh={handleRefresh}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { get, post, put, del, download, upload } from "@/utils/request";
|
||||
import { get, post, put, del, download } from "@/utils/request";
|
||||
|
||||
// 数据集统计接口
|
||||
export function getDatasetStatisticsUsingGet() {
|
||||
@@ -35,15 +35,8 @@ export function deleteDatasetByIdUsingDelete(id: string | number) {
|
||||
}
|
||||
|
||||
// 下载数据集
|
||||
export function downloadDatasetUsingGet(
|
||||
id: string | number,
|
||||
filename?: string
|
||||
) {
|
||||
return download(
|
||||
`/api/data-management/datasets/${id}/download`,
|
||||
null,
|
||||
filename
|
||||
);
|
||||
export function downloadDatasetUsingGet(id: string | number) {
|
||||
return download(`/api/data-management/datasets/${id}/files/download`);
|
||||
}
|
||||
|
||||
// 验证数据集
|
||||
@@ -61,8 +54,16 @@ export function uploadDatasetFileUsingPost(id: string | number, data: any) {
|
||||
return post(`/api/data-management/datasets/${id}/files`, data);
|
||||
}
|
||||
|
||||
export function downloadFile(id: string | number) {
|
||||
return download(`/api/data-management/datasets/${id}/files/download`);
|
||||
export function downloadFileByIdUsingGet(
|
||||
id: string | number,
|
||||
fileId: string | number,
|
||||
fileName: string
|
||||
) {
|
||||
return download(
|
||||
`/api/data-management/datasets/${id}/files/${fileId}/download`,
|
||||
null,
|
||||
fileName
|
||||
);
|
||||
}
|
||||
|
||||
// 删除数据集文件
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
CloseCircleOutlined,
|
||||
FileOutlined,
|
||||
} from "@ant-design/icons";
|
||||
import { AnyObject } from "antd/es/_util/type";
|
||||
import {
|
||||
FileImage,
|
||||
FileText,
|
||||
@@ -194,7 +195,7 @@ export const dataSourceMap: Record<string, { label: string; value: string }> = {
|
||||
|
||||
export const dataSourceOptions = Object.values(dataSourceMap);
|
||||
|
||||
export function mapDataset(dataset: Dataset) {
|
||||
export function mapDataset(dataset: AnyObject): Dataset {
|
||||
const { icon: IconComponent, iconColor } =
|
||||
datasetTypeMap[dataset?.datasetType] || {};
|
||||
return {
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
export { useFilesOperation } from "./useFilesOperation";
|
||||
export { useImportFile } from "./useImportFile";
|
||||
@@ -1,61 +0,0 @@
|
||||
import { Upload, type UploadFile } from "antd";
|
||||
import { InboxOutlined } from "@ant-design/icons";
|
||||
import { useMemo, useState } from "react";
|
||||
import type { Dataset } from "@/pages/DataManagement/dataset.model";
|
||||
import { sliceFile } from "@/utils/file.util";
|
||||
|
||||
const { Dragger } = Upload;
|
||||
|
||||
export const useImportFile = () => {
|
||||
const [fileList, setFileList] = useState<UploadFile[]>([]);
|
||||
const fileSliceList = useMemo(() => {
|
||||
const sliceList = fileList.map((file) => {
|
||||
const slices = sliceFile(file);
|
||||
return { originFile: file, slices, name: file.name, size: file.size };
|
||||
});
|
||||
return sliceList;
|
||||
}, [fileList]);
|
||||
|
||||
const resetFiles = () => {
|
||||
setFileList([]);
|
||||
};
|
||||
|
||||
const handleUpload = async (dataset: Dataset) => {
|
||||
const formData = new FormData();
|
||||
fileList.forEach((file) => {
|
||||
formData.append("file", file);
|
||||
});
|
||||
window.dispatchEvent(
|
||||
new CustomEvent("upload:dataset", {
|
||||
detail: { dataset, files: fileSliceList },
|
||||
})
|
||||
);
|
||||
resetFiles();
|
||||
};
|
||||
|
||||
const handleBeforeUpload = (_, files: UploadFile[]) => {
|
||||
setFileList([...fileList, ...files]);
|
||||
return false;
|
||||
};
|
||||
|
||||
const handleRemoveFile = (file: UploadFile) => {
|
||||
setFileList((prev) => prev.filter((f) => f.uid !== file.uid));
|
||||
};
|
||||
|
||||
const importFileRender = () => (
|
||||
<Dragger
|
||||
className="w-full"
|
||||
onRemove={handleRemoveFile}
|
||||
beforeUpload={handleBeforeUpload}
|
||||
multiple
|
||||
>
|
||||
<p className="ant-upload-drag-icon">
|
||||
<InboxOutlined />
|
||||
</p>
|
||||
<p className="ant-upload-text">本地文件上传</p>
|
||||
<p className="ant-upload-hint">拖拽文件到此处或点击选择文件</p>
|
||||
</Dragger>
|
||||
);
|
||||
|
||||
return { fileList, resetFiles, handleUpload, importFileRender };
|
||||
};
|
||||
Reference in New Issue
Block a user