You've already forked DataMate
- 移除了未使用的 DatasetType 导入 - 删除了 TEXT_DATASET_TYPE_PREFIX 常量定义 - 移除了 isTextDataset 工具函数 - 直接设置 excludeDerivedFiles 参数为 true,简化查询逻辑
387 lines
13 KiB
TypeScript
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;
|
|
}
|