diff --git a/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/application/DatasetFileApplicationService.java b/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/application/DatasetFileApplicationService.java index 05f2cbe7c..1253e6d7d 100644 --- a/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/application/DatasetFileApplicationService.java +++ b/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/application/DatasetFileApplicationService.java @@ -226,34 +226,50 @@ public DatasetFile getDatasetFile(Dataset dataset, String fileId, String prefix) /** * 删除文件 + * 使用悲观锁防止并发删除导致 fileCount 计算不准确 */ @Transactional public void deleteDatasetFile(String datasetId, String fileId, String prefix) { - Dataset dataset = datasetRepository.getById(datasetId); + // 使用悲观锁获取 dataset,确保并发安全 + Dataset dataset = datasetRepository.getByIdWithLock(datasetId); DatasetFile file = getDatasetFile(dataset, fileId, prefix); - dataset.setFiles(new ArrayList<>(Collections.singleton(file))); + + // 先删除数据库记录 datasetFileRepository.removeById(fileId); - if (CommonUtils.isUUID(fileId)) { - dataset.removeFile(file); - } - datasetRepository.updateById(dataset); - // 删除文件时,上传到数据集中的文件会同时删除数据库中的记录和文件系统中的文件,归集过来的文件仅删除数据库中的记录 + + // 删除物理文件(仅删除在数据集路径下的文件) if (file.getFilePath().startsWith(dataset.getPath())) { try { Path filePath = validateAndResolvePath(file.getFilePath(), dataset.getPath()); Files.deleteIfExists(filePath); } catch (IOException ex) { + log.error("删除物理文件失败: {}", file.getFilePath(), ex); throw BusinessException.of(SystemErrorCode.FILE_SYSTEM_ERROR); } } + + // 重新计算文件统计信息(确保数据准确) + List remainingFiles = datasetFileRepository.findAllByDatasetId(datasetId); + dataset.setFileCount((long) remainingFiles.size()); + dataset.setSizeBytes(remainingFiles.stream() + .mapToLong(f -> f.getFileSize() != null ? f.getFileSize().longValue() : 0L) + .sum()); + dataset.setUpdatedAt(LocalDateTime.now()); + + datasetRepository.updateById(dataset); + + log.info("删除文件成功 - datasetId: {}, fileId: {}, fileName: {}, fileCount: {}, sizeBytes: {}", + datasetId, fileId, file.getFileName(), dataset.getFileCount(), dataset.getSizeBytes()); } /** * 批量删除文件 + * 使用悲观锁防止并发删除导致 fileCount 计算不准确 */ @Transactional public void batchDeleteFiles(String datasetId, BatchDeleteFilesRequest request) { - Dataset dataset = datasetRepository.getById(datasetId); + // 使用悲观锁获取 dataset,确保并发安全 + Dataset dataset = datasetRepository.getByIdWithLock(datasetId); if (dataset == null) { throw BusinessException.of(DataManagementErrorCode.DATASET_NOT_FOUND); } @@ -266,6 +282,7 @@ public void batchDeleteFiles(String datasetId, BatchDeleteFilesRequest request) List filesToDelete = new ArrayList<>(); List failedFileIds = new ArrayList<>(); + // 先删除数据库记录 for (String fileId : fileIds) { try { DatasetFile file = getDatasetFile(dataset, fileId, request.getPrefix()); @@ -277,20 +294,8 @@ public void batchDeleteFiles(String datasetId, BatchDeleteFilesRequest request) } } - // 更新数据集(避免 ConcurrentModificationException) - List datasetFiles = dataset.getFiles(); - if (datasetFiles != null) { - // 创建一个新的列表来存储要保留的文件 - List remainingFiles = new ArrayList<>(datasetFiles); - // 移除要删除的文件 - remainingFiles.removeAll(filesToDelete); - dataset.setFiles(remainingFiles); - } - datasetRepository.updateById(dataset); - - // 删除文件系统中的文件 + // 删除物理文件(仅删除在数据集路径下的文件) for (DatasetFile file : filesToDelete) { - // 上传到数据集中的文件会同时删除数据库中的记录和文件系统中的文件,归集过来的文件仅删除数据库中的记录 if (file.getFilePath().startsWith(dataset.getPath())) { try { Path filePath = validateAndResolvePath(file.getFilePath(), dataset.getPath()); @@ -303,6 +308,19 @@ public void batchDeleteFiles(String datasetId, BatchDeleteFilesRequest request) } } + // 重新计算文件统计信息(确保数据准确) + List remainingFiles = datasetFileRepository.findAllByDatasetId(datasetId); + dataset.setFileCount((long) remainingFiles.size()); + dataset.setSizeBytes(remainingFiles.stream() + .mapToLong(f -> f.getFileSize() != null ? f.getFileSize().longValue() : 0L) + .sum()); + dataset.setUpdatedAt(LocalDateTime.now()); + + datasetRepository.updateById(dataset); + + log.info("批量删除文件成功 - datasetId: {}, successCount: {}, failedCount: {}, fileCount: {}, sizeBytes: {}", + datasetId, filesToDelete.size(), failedFileIds.size(), dataset.getFileCount(), dataset.getSizeBytes()); + // 如果有失败的文件,记录日志但不抛出异常 if (!failedFileIds.isEmpty()) { log.warn("Failed to delete {} files out of {}", failedFileIds.size(), fileIds.size()); @@ -643,7 +661,8 @@ private void zipDirectory(Path sourceDir, Path basePath, ZipArchiveOutputStream */ @Transactional public void deleteDirectory(String datasetId, String prefix) { - Dataset dataset = datasetRepository.getById(datasetId); + // 使用悲观锁获取 dataset,防止并发删除导致 fileCount 不准确 + Dataset dataset = datasetRepository.getByIdWithLock(datasetId); if (dataset == null) { throw BusinessException.of(DataManagementErrorCode.DATASET_NOT_FOUND); } @@ -694,6 +713,10 @@ public void deleteDirectory(String datasetId, String prefix) { }) .collect(Collectors.toList()); + log.info("删除目录开始 - datasetId: {}, prefix: {}, fileCount: {}, filesToDelete: {}", + datasetId, prefix, dataset.getFileCount(), filesToDelete.size()); + + // 删除数据库记录 for (DatasetFile file : filesToDelete) { datasetFileRepository.removeById(file.getId()); } @@ -702,20 +725,22 @@ public void deleteDirectory(String datasetId, String prefix) { try { deleteDirectoryRecursively(normalized); } catch (IOException e) { - log.error("Failed to delete directory {} for dataset {}", normalized, datasetId, e); + log.error("删除目录失败: datasetId={}, prefix={}", datasetId, prefix, e); throw BusinessException.of(SystemErrorCode.FILE_SYSTEM_ERROR); } - // 更新数据集(避免 ConcurrentModificationException,先获取文件列表再删除) - List datasetFiles = dataset.getFiles(); - if (datasetFiles != null) { - // 创建一个新的列表来存储要保留的文件 - List remainingFiles = new ArrayList<>(datasetFiles); - // 移除要删除的文件 - remainingFiles.removeAll(filesToDelete); - dataset.setFiles(remainingFiles); - } + // 重新计算文件统计信息(确保数据准确) + List remainingFiles = datasetFileRepository.findAllByDatasetId(datasetId); + dataset.setFileCount((long) remainingFiles.size()); + dataset.setSizeBytes(remainingFiles.stream() + .mapToLong(f -> f.getFileSize() != null ? f.getFileSize().longValue() : 0L) + .sum()); + dataset.setUpdatedAt(LocalDateTime.now()); + datasetRepository.updateById(dataset); + + log.info("删除目录成功 - datasetId: {}, prefix: {}, deletedCount: {}, fileCount: {}, sizeBytes: {}", + datasetId, prefix, filesToDelete.size(), dataset.getFileCount(), dataset.getSizeBytes()); } /** diff --git a/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/infrastructure/persistence/mapper/DatasetMapper.java b/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/infrastructure/persistence/mapper/DatasetMapper.java index 445051187..6da91ceac 100644 --- a/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/infrastructure/persistence/mapper/DatasetMapper.java +++ b/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/infrastructure/persistence/mapper/DatasetMapper.java @@ -5,6 +5,7 @@ import com.datamate.datamanagement.interfaces.dto.AllDatasetStatisticsResponse; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Select; import org.apache.ibatis.session.RowBounds; import java.util.List; @@ -12,6 +13,12 @@ @Mapper public interface DatasetMapper extends BaseMapper { Dataset findById(@Param("id") String id); + + /** + * 使用悲观锁查询数据集(FOR UPDATE) + */ + @Select("SELECT * FROM t_dm_datasets WHERE id = #{id} FOR UPDATE") + Dataset findByIdWithLock(@Param("id") String id); Dataset findByName(@Param("name") String name); List findByStatus(@Param("status") String status); List findByCreatedBy(@Param("createdBy") String createdBy, RowBounds rowBounds); diff --git a/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/infrastructure/persistence/repository/DatasetRepository.java b/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/infrastructure/persistence/repository/DatasetRepository.java index b25716124..8be0904f6 100644 --- a/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/infrastructure/persistence/repository/DatasetRepository.java +++ b/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/infrastructure/persistence/repository/DatasetRepository.java @@ -19,6 +19,11 @@ public interface DatasetRepository extends IRepository { Dataset findByName(String name); + /** + * 使用悲观锁获取数据集(用于更新操作,防止并发冲突) + */ + Dataset getByIdWithLock(String id); + List findByCriteria(String type, String status, String keyword, List tagList, RowBounds bounds); long countByCriteria(String type, String status, String keyword, List tagList); diff --git a/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/infrastructure/persistence/repository/impl/DatasetRepositoryImpl.java b/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/infrastructure/persistence/repository/impl/DatasetRepositoryImpl.java index 8bb6b0174..c75bc39a1 100644 --- a/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/infrastructure/persistence/repository/impl/DatasetRepositoryImpl.java +++ b/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/infrastructure/persistence/repository/impl/DatasetRepositoryImpl.java @@ -31,6 +31,11 @@ public Dataset findByName(String name) { return datasetMapper.selectOne(new LambdaQueryWrapper().eq(Dataset::getName, name)); } + @Override + public Dataset getByIdWithLock(String id) { + return datasetMapper.findByIdWithLock(id); + } + @Override public List findByCriteria(String type, String status, String keyword, List tagList, RowBounds bounds) { diff --git a/frontend/src/i18n/locales/en/common.json b/frontend/src/i18n/locales/en/common.json index 0010260cc..39ff6c1ab 100644 --- a/frontend/src/i18n/locales/en/common.json +++ b/frontend/src/i18n/locales/en/common.json @@ -94,8 +94,8 @@ "updateSuccess": "Task updated successfully", "updateFailed": "Failed to update task", "deleteSuccess": "Task deleted", - "deleteConfirm": "Are you sure you want to delete this task? This action cannot be undone.", - "deleteConfirmMessage": "Are you sure you want to delete \"{{itemName}}\"? This action cannot be undone.", + "deleteTitle": "Confirm delete collection task", + "deleteDesc": "Are you sure you want to delete \"{{itemName}}\"? This action cannot be undone.", "confirmDelete": "Delete", "cancel": "Cancel" }, @@ -375,10 +375,14 @@ "fileDownloadSuccess": "File downloaded successfully" }, "confirm": { - "deleteDatasetTitle": "Confirm delete this dataset?", + "deleteDatasetTitle": "Confirm delete dataset", "deleteDatasetDesc": "Are you sure you want to delete \"{{itemName}}\"? This action cannot be undone.", - "deleteFolderTitle": "Confirm delete folder?", - "deleteFolderDesc": "Deleting folder \"{{name}}\" will remove all files and subfolders. This action cannot be undone.", + "deleteFileTitle": "Confirm delete file", + "deleteFileDesc": "Are you sure you want to delete \"{{itemName}}\"? This action cannot be undone.", + "deleteFolderTitle": "Confirm delete folder", + "deleteFolderDesc": "Are you sure you want to delete \"{{itemName}}\" and all its contents? This action cannot be undone.", + "batchDeleteTitle": "Confirm batch delete", + "batchDeleteDesc": "Are you sure you want to delete the selected {{count}} item(s)? This action cannot be undone.", "deleteConfirm": "Delete", "deleteCancel": "Cancel" }, @@ -664,7 +668,7 @@ "deleteFailed": "Task deletion failed, please try again later" }, "confirm": { - "deleteTitle": "Confirm delete this task?", + "deleteTitle": "Confirm delete ratio task", "deleteDesc": "Are you sure you want to delete \"{{itemName}}\"? This action cannot be undone.", "okText": "Delete", "cancelText": "Cancel" @@ -750,8 +754,8 @@ "delete": "Delete" }, "confirm": { - "deleteTitle": "Confirm delete this ratio task?", - "deleteDesc": "This task cannot be recovered after deletion. Please proceed with caution.", + "deleteTitle": "Confirm delete ratio task", + "deleteDesc": "Are you sure you want to delete \"{{itemName}}\"? This action cannot be undone.", "okText": "Delete", "cancelText": "Cancel" }, @@ -1446,7 +1450,12 @@ "taskDeleted": "Task deleted successfully", "taskDetailFailed": "Failed to fetch task details", "taskPaused": "Task paused", - "taskStarted": "Task started" + "taskStarted": "Task started", + "deleteFailed": "Failed to delete task, please try again later" + }, + "confirm": { + "deleteTitle": "Confirm delete data processing task", + "deleteDesc": "Are you sure you want to delete \"{{itemName}}\"? This action cannot be undone." } }, "template": { @@ -2270,8 +2279,8 @@ "cot": "Chain of Thought Generation" }, "confirm": { - "deleteTitle": "Confirm delete this task?", - "deleteContent": "Task name: {{name}}", + "deleteTitle": "Confirm delete synthesis task", + "deleteDesc": "Are you sure you want to delete \"{{name}}\"? This action cannot be undone.", "archiveTitle": "Confirm archive this synthesis task?", "archiveContent": "Task name: {{name}}" }, @@ -2567,9 +2576,9 @@ "delete": "Delete Task" }, "confirm": { - "deleteTitle": "Confirm delete this synthesis task?", - "deleteDescription": "This task cannot be recovered after deletion, please proceed with caution.", - "okText": "Confirm Delete", + "deleteTitle": "Confirm delete synthesis task", + "deleteDescription": "Are you sure you want to delete \"{{name}}\"? This action cannot be undone.", + "okText": "Delete", "cancelText": "Cancel" }, "messages": { diff --git a/frontend/src/i18n/locales/zh/common.json b/frontend/src/i18n/locales/zh/common.json index b9826e634..1d048c64f 100644 --- a/frontend/src/i18n/locales/zh/common.json +++ b/frontend/src/i18n/locales/zh/common.json @@ -94,8 +94,8 @@ "updateSuccess": "任务更新成功", "updateFailed": "任务更新失败", "deleteSuccess": "任务已删除", - "deleteConfirm": "确定要删除该任务吗?此操作不可撤销。", - "deleteConfirmMessage": "确定要删除「{{itemName}}」吗?删除后将无法恢复。", + "deleteTitle": "确认删除归集任务", + "deleteDesc": "删除「{{itemName}}」后将无法恢复,确定要执行此操作吗?", "confirmDelete": "删除", "cancel": "取消" }, @@ -375,10 +375,14 @@ "fileDownloadSuccess": "文件下载成功" }, "confirm": { - "deleteDatasetTitle": "确认删除该数据集?", - "deleteDatasetDesc": "确定要删除「{{itemName}}」吗?删除后将无法恢复。", - "deleteFolderTitle": "确认删除文件夹?", - "deleteFolderDesc": "删除文件夹 \"{{name}}\" 将同时删除其中的所有文件和子文件夹,此操作不可恢复。", + "deleteDatasetTitle": "确认删除数据集", + "deleteDatasetDesc": "删除「{{itemName}}」后将无法恢复,确定要执行此操作吗?", + "deleteFileTitle": "确认删除文件", + "deleteFileDesc": "删除「{{itemName}}」后将无法恢复,确定要执行此操作吗?", + "deleteFolderTitle": "确认删除文件夹", + "deleteFolderDesc": "删除「{{itemName}}」及其所有内容后将无法恢复,确定要执行此操作吗?", + "batchDeleteTitle": "确认批量删除", + "batchDeleteDesc": "删除选中的 {{count}} 个项目后将无法恢复,确定要执行此操作吗?", "deleteConfirm": "删除", "deleteCancel": "取消" }, @@ -664,8 +668,8 @@ "deleteFailed": "任务删除失败,请稍后重试" }, "confirm": { - "deleteTitle": "确认删除该任务?", - "deleteDesc": "确定要删除「{{itemName}}」吗?删除后将无法恢复。", + "deleteTitle": "确认删除配比任务", + "deleteDesc": "删除「{{itemName}}」后将无法恢复,确定要执行此操作吗?", "okText": "删除", "cancelText": "取消" }, @@ -750,8 +754,8 @@ "delete": "删除" }, "confirm": { - "deleteTitle": "确认删除该配比任务?", - "deleteDesc": "删除后该任务将无法恢复,请谨慎操作。", + "deleteTitle": "确认删除配比任务", + "deleteDesc": "删除「{{itemName}}」后将无法恢复,确定要执行此操作吗?", "okText": "删除", "cancelText": "取消" }, @@ -1446,7 +1450,12 @@ "taskDeleted": "任务删除成功", "taskDetailFailed": "获取任务详情失败", "taskPaused": "任务已暂停", - "taskStarted": "任务已启动" + "taskStarted": "任务已启动", + "deleteFailed": "任务删除失败,请稍后重试" + }, + "confirm": { + "deleteTitle": "确认删除数据处理任务", + "deleteDesc": "删除「{{itemName}}」后将无法恢复,确定要执行此操作吗?" } }, "template": { @@ -2270,8 +2279,8 @@ "cot": "链式推理生成" }, "confirm": { - "deleteTitle": "确认删除任务?", - "deleteContent": "任务名:{{name}}", + "deleteTitle": "确认删除合成任务", + "deleteDesc": "删除「{{name}}」后将无法恢复,确定要执行此操作吗?", "archiveTitle": "确认归档该合成任务?", "archiveContent": "任务名:{{name}}" }, @@ -2567,9 +2576,9 @@ "delete": "删除任务" }, "confirm": { - "deleteTitle": "确认删除该合成任务?", - "deleteDescription": "删除后将无法恢复,请谨慎操作。", - "okText": "确认删除", + "deleteTitle": "确认删除合成任务", + "deleteDescription": "删除「{{name}}」后将无法恢复,确定要执行此操作吗?", + "okText": "删除", "cancelText": "取消" }, "messages": { diff --git a/frontend/src/pages/DataAnnotation/Home/DataAnnotation.tsx b/frontend/src/pages/DataAnnotation/Home/DataAnnotation.tsx index 88dbbcf10..2d5035925 100644 --- a/frontend/src/pages/DataAnnotation/Home/DataAnnotation.tsx +++ b/frontend/src/pages/DataAnnotation/Home/DataAnnotation.tsx @@ -1,4 +1,5 @@ import { useState, useEffect } from "react"; +import { useNavigate } from "react-router"; import { Card, Button, Table, message, Modal, Tabs, Tag, Progress, Tooltip, Dropdown } from "antd"; import { PlusOutlined, @@ -38,6 +39,7 @@ import { useTranslation } from "react-i18next"; export default function DataAnnotation() { const { t } = useTranslation(); + const navigate = useNavigate(); // return ; const [activeTab, setActiveTab] = useState("tasks"); const [viewMode, setViewMode] = useState<"list" | "card">("list"); @@ -564,6 +566,24 @@ export default function DataAnnotation() { dataIndex: "datasetName", key: "datasetName", width: 180, + render: (text: string, record: any) => { + if (!text || text === "-") return "-"; + const datasetId = record.datasetId || record.dataset_id; + if (!datasetId) return text; + + return ( + { + e.preventDefault(); + e.stopPropagation(); + navigate(`/data/management/detail/${datasetId}`); + }} + style={{ cursor: 'pointer', color: '#1890ff' }} + > + {text} + + ); + }, }, { title: t('dataAnnotation.home.columns.model'), diff --git a/frontend/src/pages/DataCleansing/Home/components/TaskList.tsx b/frontend/src/pages/DataCleansing/Home/components/TaskList.tsx index a0ca8913a..b7895b2ee 100644 --- a/frontend/src/pages/DataCleansing/Home/components/TaskList.tsx +++ b/frontend/src/pages/DataCleansing/Home/components/TaskList.tsx @@ -1,5 +1,5 @@ import { useState, useEffect } from "react"; -import { Table, Progress, Badge, Button, Tooltip, Card, App } from "antd"; +import { Table, Progress, Badge, Button, Tooltip, Card, App, Modal } from "antd"; import { PlayCircleOutlined, PauseCircleOutlined, @@ -27,6 +27,18 @@ export default function TaskList() { const navigate = useNavigate(); const { message } = App.useApp(); const [viewMode, setViewMode] = useState<"card" | "list">("card"); + + // 删除确认弹窗状态 + const [deleteModal, setDeleteModal] = useState<{ + visible: boolean; + taskId: string; + taskName: string; + }>({ + visible: false, + taskId: "", + taskName: "", + }); + const filterOptions = [ { key: "status", @@ -59,9 +71,26 @@ export default function TaskList() { }; const deleteTask = async (item: CleansingTask) => { - await deleteCleaningTaskByIdUsingDelete(item.id); - message.success(t("dataCleansing.task.messages.taskDeleted")); - fetchData(); + setDeleteModal({ + visible: true, + taskId: item.id, + taskName: item.name, + }); + }; + + const handleConfirmDelete = async () => { + try { + await deleteCleaningTaskByIdUsingDelete(deleteModal.taskId); + message.success(t("dataCleansing.task.messages.taskDeleted")); + setDeleteModal({ visible: false, taskId: "", taskName: "" }); + fetchData(); + } catch (error) { + message.error(t("dataCleansing.task.messages.deleteFailed")); + } + }; + + const handleCancelDelete = () => { + setDeleteModal({ visible: false, taskId: "", taskName: "" }); }; useEffect(() => { @@ -311,6 +340,19 @@ export default function TaskList() { /> )} + + {/* 删除确认弹窗 */} + +

{t("dataCleansing.task.confirm.deleteDesc", { itemName: deleteModal.taskName })}

+
); } diff --git a/frontend/src/pages/DataCollection/Home/TaskManagement.tsx b/frontend/src/pages/DataCollection/Home/TaskManagement.tsx index 6df90b294..5e9a89b5a 100644 --- a/frontend/src/pages/DataCollection/Home/TaskManagement.tsx +++ b/frontend/src/pages/DataCollection/Home/TaskManagement.tsx @@ -347,8 +347,8 @@ export default function TaskManagement() { {/* 删除确认弹窗 */} ({} as Dataset); - const filesOperation = useFilesOperation(dataset); + + // 定义 fetchDataset,必须在 useFilesOperation 之前定义 + const fetchDataset = async () => { + const { data } = await queryDatasetByIdUsingGet(id as unknown as number); + setDataset(mapDataset(data, t)); + }; + + const filesOperation = useFilesOperation(dataset, fetchDataset); const [showUploadDialog, setShowUploadDialog] = useState(false); const navigateItems = useMemo( @@ -54,10 +62,6 @@ export default function DatasetDetail() { ], [dataset, t] ); - const fetchDataset = async () => { - const { data } = await queryDatasetByIdUsingGet(id as unknown as number); - setDataset(mapDataset(data, t)); - }; useEffect(() => { fetchDataset(); @@ -176,15 +180,8 @@ export default function DatasetDetail() { key: "delete", label: t("dataManagement.actions.delete"), danger: true, - confirm: { - title: t("dataManagement.confirm.deleteDatasetTitle"), - description: t("dataManagement.confirm.deleteDatasetDesc"), - okText: t("dataManagement.confirm.deleteConfirm"), - cancelText: t("dataManagement.confirm.deleteCancel"), - okType: "danger", - }, icon: , - onClick: handleDeleteDataset, + onClick: () => setShowDeleteModal(true), }, ]; @@ -333,6 +330,22 @@ export default function DatasetDetail() { ]} /> + + {/* 删除数据集确认弹窗 */} + { + setShowDeleteModal(false); + await handleDeleteDataset(); + }} + onCancel={() => setShowDeleteModal(false)} + okText={t("dataManagement.confirm.deleteConfirm")} + cancelText={t("dataManagement.confirm.deleteCancel")} + okButtonProps={{ danger: true }} + > +

{t("dataManagement.confirm.deleteDatasetDesc", { itemName: dataset.name || t("dataManagement.detail.title") })}

+
); } diff --git a/frontend/src/pages/DataManagement/Detail/components/Overview.tsx b/frontend/src/pages/DataManagement/Detail/components/Overview.tsx index f4a525336..ab140c164 100644 --- a/frontend/src/pages/DataManagement/Detail/components/Overview.tsx +++ b/frontend/src/pages/DataManagement/Detail/components/Overview.tsx @@ -15,8 +15,9 @@ export default function Overview({ dataset, filesOperation, fetchDataset }) { // 删除确认弹窗状态 const [deleteModal, setDeleteModal] = useState<{ visible: boolean; - type: "file" | "directory"; + type: "file" | "directory" | "batch"; fileName: string; + count?: number; handler: () => void | Promise; }>({ visible: false, @@ -25,6 +26,15 @@ export default function Overview({ dataset, filesOperation, fetchDataset }) { handler: () => {}, }); + // 批量删除确认弹窗状态 + const [batchDeleteModal, setBatchDeleteModal] = useState<{ + visible: boolean; + count: number; + }>({ + visible: false, + count: 0, + }); + const { fileList, pagination, @@ -87,6 +97,24 @@ export default function Overview({ dataset, filesOperation, fetchDataset }) { setDeleteModal({ visible: false, type: "file", fileName: "", handler: () => {} }); }; + // 显示批量删除确认弹窗 + const showBatchDeleteConfirm = () => { + if (selectedFiles.length === 0) { + message.warning({ content: "请先选择要删除的文件" }); + return; + } + setBatchDeleteModal({ + visible: true, + count: selectedFiles.length, + }); + }; + + // 执行批量删除 + const handleConfirmBatchDelete = async () => { + setBatchDeleteModal({ visible: false, count: 0 }); + await handleBatchDeleteFiles(); + }; + // 文件列表列定义 const columns = [ { @@ -472,7 +500,7 @@ export default function Overview({ dataset, filesOperation, fetchDataset }) { {t("dataManagement.actions.batchExport")}