Files
DataMate/frontend/src/pages/DataManagement/Detail/useFilesOperation.ts
Jerry Yan 0b8fe34586 refactor(DataManagement): 简化文件操作逻辑并移除文本数据集类型检查
- 移除了未使用的 DatasetType 导入
- 删除了 TEXT_DATASET_TYPE_PREFIX 常量定义
- 移除了 isTextDataset 工具函数
- 直接设置 excludeDerivedFiles 参数为 true,简化查询逻辑
2026-02-01 23:13:09 +08:00

387 lines
13 KiB
TypeScript

import type {
Dataset,
DatasetFile,
} from "@/pages/DataManagement/dataset.model";
import { App } from "antd";
import { useCallback, useEffect, useRef, useState } from "react";
import {
PREVIEW_TEXT_MAX_LENGTH,
resolvePreviewFileType,
truncatePreviewText,
type PreviewFileType,
} from "@/utils/filePreview";
import {
deleteDatasetFileUsingDelete,
downloadFileByIdUsingGet,
exportDatasetUsingPost,
queryDatasetFilesUsingGet,
createDatasetDirectoryUsingPost,
downloadDirectoryUsingGet,
deleteDirectoryUsingDelete,
queryDatasetFilePreviewStatusUsingGet,
convertDatasetFilePreviewUsingPost,
} from "../dataset.api";
import { useParams } from "react-router";
const OFFICE_FILE_EXTENSIONS = [".doc", ".docx"];
const OFFICE_PREVIEW_POLL_INTERVAL = 2000;
const OFFICE_PREVIEW_POLL_MAX_TIMES = 60;
type OfficePreviewStatus = "UNSET" | "PENDING" | "PROCESSING" | "READY" | "FAILED";
const isOfficeFileName = (fileName?: string) => {
const lowerName = (fileName || "").toLowerCase();
return OFFICE_FILE_EXTENSIONS.some((ext) => lowerName.endsWith(ext));
};
const normalizeOfficePreviewStatus = (status?: string): OfficePreviewStatus => {
if (!status) {
return "UNSET";
}
const upper = status.toUpperCase();
if (upper === "PENDING" || upper === "PROCESSING" || upper === "READY" || upper === "FAILED") {
return upper as OfficePreviewStatus;
}
return "UNSET";
};
export function useFilesOperation(dataset: Dataset) {
const { message } = App.useApp();
const { id } = useParams(); // 获取动态路由参数
// 文件相关状态
const [fileList, setFileList] = useState<DatasetFile[]>([]);
const [selectedFiles, setSelectedFiles] = useState<string[]>([]);
const [pagination, setPagination] = useState<{
current: number;
pageSize: number;
total: number;
prefix?: string;
}>({ current: 1, pageSize: 10, total: 0, prefix: '' });
// 文件预览相关状态
const [previewVisible, setPreviewVisible] = useState(false);
const [previewContent, setPreviewContent] = useState("");
const [previewFileName, setPreviewFileName] = useState("");
const [previewFileType, setPreviewFileType] = useState<PreviewFileType>("text");
const [previewMediaUrl, setPreviewMediaUrl] = useState("");
const [previewLoading, setPreviewLoading] = useState(false);
const [officePreviewStatus, setOfficePreviewStatus] = useState<OfficePreviewStatus | null>(null);
const [officePreviewError, setOfficePreviewError] = useState("");
const officePreviewPollingRef = useRef<number | null>(null);
const officePreviewFileRef = useRef<string | null>(null);
const clearOfficePreviewPolling = useCallback(() => {
if (officePreviewPollingRef.current) {
window.clearTimeout(officePreviewPollingRef.current);
officePreviewPollingRef.current = null;
}
}, []);
useEffect(() => {
return () => {
clearOfficePreviewPolling();
};
}, [clearOfficePreviewPolling]);
const fetchFiles = async (
prefix?: string,
current?: number,
pageSize?: number
) => {
// 如果明确传了 prefix(包括空字符串),使用传入的值;否则使用当前 pagination.prefix
const targetPrefix = prefix !== undefined ? prefix : (pagination.prefix || '');
const params: DatasetFilesQueryParams = {
page: current !== undefined ? current : pagination.current,
size: pageSize !== undefined ? pageSize : pagination.pageSize,
isWithDirectory: true,
prefix: targetPrefix,
excludeDerivedFiles: true,
};
const { data } = await queryDatasetFilesUsingGet(id!, params);
setFileList(data.content || []);
// Update pagination with current prefix
setPagination(prev => ({
...prev,
current: params.page,
pageSize: params.size,
prefix: targetPrefix,
total: data.totalElements || 0,
}));
};
const handleBatchDeleteFiles = () => {
if (selectedFiles.length === 0) {
message.warning({ content: "请先选择要删除的文件" });
return;
}
// 执行批量删除逻辑
selectedFiles.forEach(async (fileId) => {
await fetch(`/api/datasets/${dataset.id}/files/${fileId}`, {
method: "DELETE",
});
});
fetchFiles(); // 刷新文件列表
setSelectedFiles([]); // 清空选中状态
message.success({
content: `已删除 ${selectedFiles.length} 个文件`,
});
};
const handleDownloadFile = async (file: DatasetFile) => {
// 实际导出逻辑
await downloadFileByIdUsingGet(dataset.id, file.id, file.fileName);
// 假设导出成功
message.success({
content: `已导出 1 个文件`,
});
setSelectedFiles([]); // 清空选中状态
};
const handlePreviewFile = async (file: DatasetFile) => {
const datasetId = dataset?.id || id;
if (!datasetId) {
message.warning({ content: "数据集未就绪" });
return;
}
if (file?.id?.startsWith("directory-")) {
return;
}
const previewUrl = `/api/data-management/datasets/${datasetId}/files/${file.id}/preview`;
setPreviewFileName(file.fileName);
setPreviewContent("");
setPreviewMediaUrl("");
if (isOfficeFileName(file?.fileName)) {
setPreviewFileType("pdf");
setPreviewVisible(true);
setPreviewLoading(true);
setOfficePreviewStatus("PROCESSING");
setOfficePreviewError("");
officePreviewFileRef.current = file.id;
try {
const { data: statusData } = await queryDatasetFilePreviewStatusUsingGet(datasetId, file.id);
const currentStatus = normalizeOfficePreviewStatus(statusData?.status);
if (currentStatus === "READY") {
setPreviewMediaUrl(previewUrl);
setOfficePreviewStatus("READY");
setPreviewLoading(false);
return;
}
if (currentStatus === "PROCESSING") {
pollOfficePreviewStatus(datasetId, file.id, 0);
return;
}
const { data } = await convertDatasetFilePreviewUsingPost(datasetId, file.id);
const status = normalizeOfficePreviewStatus(data?.status);
if (status === "READY") {
setPreviewMediaUrl(previewUrl);
setOfficePreviewStatus("READY");
} else if (status === "FAILED") {
setOfficePreviewStatus("FAILED");
setOfficePreviewError(data?.previewError || "转换失败,请稍后重试");
} else {
setOfficePreviewStatus("PROCESSING");
pollOfficePreviewStatus(datasetId, file.id, 0);
return;
}
} catch (error) {
console.error("触发预览转换失败", error);
message.error({ content: "触发预览转换失败" });
setOfficePreviewStatus("FAILED");
setOfficePreviewError("触发预览转换失败");
} finally {
setPreviewLoading(false);
}
return;
}
const fileType = resolvePreviewFileType(file?.fileName);
if (!fileType) {
message.warning({ content: "不支持预览该文件类型" });
return;
}
setPreviewFileType(fileType);
if (fileType === "text") {
setPreviewLoading(true);
try {
const response = await fetch(previewUrl);
if (!response.ok) {
throw new Error("下载失败");
}
const text = await response.text();
setPreviewContent(truncatePreviewText(text, PREVIEW_TEXT_MAX_LENGTH));
setPreviewVisible(true);
} catch (error) {
console.error("Preview file content error:", error);
message.error({ content: "获取文件内容失败" });
} finally {
setPreviewLoading(false);
}
return;
}
setPreviewMediaUrl(previewUrl);
setPreviewVisible(true);
};
const closePreview = () => {
clearOfficePreviewPolling();
officePreviewFileRef.current = null;
setPreviewVisible(false);
setPreviewContent("");
setPreviewMediaUrl("");
setPreviewFileName("");
setPreviewFileType("text");
setOfficePreviewStatus(null);
setOfficePreviewError("");
};
const pollOfficePreviewStatus = useCallback(
async (datasetId: string, fileId: string, attempt: number) => {
clearOfficePreviewPolling();
officePreviewPollingRef.current = window.setTimeout(async () => {
if (officePreviewFileRef.current !== fileId) {
return;
}
try {
const { data } = await queryDatasetFilePreviewStatusUsingGet(datasetId, fileId);
const status = normalizeOfficePreviewStatus(data?.status);
if (status === "READY") {
setPreviewMediaUrl(`/api/data-management/datasets/${datasetId}/files/${fileId}/preview`);
setOfficePreviewStatus("READY");
setOfficePreviewError("");
setPreviewLoading(false);
return;
}
if (status === "FAILED") {
setOfficePreviewStatus("FAILED");
setOfficePreviewError(data?.previewError || "转换失败,请稍后重试");
setPreviewLoading(false);
return;
}
if (attempt >= OFFICE_PREVIEW_POLL_MAX_TIMES - 1) {
setOfficePreviewStatus("FAILED");
setOfficePreviewError("转换超时,请稍后重试");
setPreviewLoading(false);
return;
}
pollOfficePreviewStatus(datasetId, fileId, attempt + 1);
} catch (error) {
console.error("轮询预览状态失败", error);
if (attempt >= OFFICE_PREVIEW_POLL_MAX_TIMES - 1) {
setOfficePreviewStatus("FAILED");
setOfficePreviewError("转换超时,请稍后重试");
setPreviewLoading(false);
return;
}
pollOfficePreviewStatus(datasetId, fileId, attempt + 1);
}
}, OFFICE_PREVIEW_POLL_INTERVAL);
},
[clearOfficePreviewPolling]
);
const handleDeleteFile = async (file: DatasetFile) => {
try {
await deleteDatasetFileUsingDelete(dataset.id, file.id);
fetchFiles(); // 刷新文件列表
message.success({ content: `文件 ${file.fileName} 已删除` });
} catch {
message.error({ content: `文件 ${file.fileName} 删除失败` });
}
};
const handleBatchExport = () => {
if (selectedFiles.length === 0) {
message.warning({ content: "请先选择要导出的文件" });
return;
}
// 执行批量导出逻辑
exportDatasetUsingPost(dataset.id, { fileIds: selectedFiles })
.then(() => {
message.success({
content: `已导出 ${selectedFiles.length} 个文件`,
});
setSelectedFiles([]); // 清空选中状态
})
.catch(() => {
message.error({
content: "导出失败,请稍后再试",
});
});
};
return {
fileList,
selectedFiles,
setSelectedFiles,
pagination,
setPagination,
previewVisible,
previewContent,
previewFileName,
previewFileType,
previewMediaUrl,
previewLoading,
officePreviewStatus,
officePreviewError,
closePreview,
fetchFiles,
setFileList,
handleBatchDeleteFiles,
handleDownloadFile,
handlePreviewFile,
handleDeleteFile,
handleBatchExport,
handleCreateDirectory: async (directoryName: string) => {
const currentPrefix = pagination.prefix || "";
try {
await createDatasetDirectoryUsingPost(dataset.id, {
parentPrefix: currentPrefix,
directoryName,
});
// 创建成功后刷新当前目录,重置到第一页
await fetchFiles(currentPrefix, 1, pagination.pageSize);
message.success({ content: `文件夹 ${directoryName} 创建成功` });
} catch (caught) {
message.error({ content: `文件夹 ${directoryName} 创建失败` });
throw caught;
}
},
handleDownloadDirectory: async (directoryPath: string, directoryName: string) => {
try {
await downloadDirectoryUsingGet(dataset.id, directoryPath);
message.success({ content: `文件夹 ${directoryName} 下载成功` });
} catch {
message.error({ content: `文件夹 ${directoryName} 下载失败` });
}
},
handleDeleteDirectory: async (directoryPath: string, directoryName: string) => {
try {
await deleteDirectoryUsingDelete(dataset.id, directoryPath);
// 删除成功后刷新当前目录
const currentPrefix = pagination.prefix || "";
await fetchFiles(currentPrefix, 1, pagination.pageSize);
message.success({ content: `文件夹 ${directoryName} 已删除` });
} catch {
message.error({ content: `文件夹 ${directoryName} 删除失败` });
}
},
};
}
interface DatasetFilesQueryParams {
page: number;
size: number;
isWithDirectory: boolean;
prefix: string;
excludeDerivedFiles?: boolean;
}