You've already forked DataMate
feat(DataManagement): 添加文件预览功能支持多种文件类型
- 实现文本、图片、视频、音频文件的预览功能 - 添加预览模态框支持不同文件类型的展示 - 集成文件类型检测和预览内容加载逻辑 - 添加预览加载状态和错误处理机制 - 实现大文件内容截断和滚动预览功能 - 添加预览窗口关闭和资源清理功能
This commit is contained in:
@@ -2,7 +2,7 @@ import type {
|
||||
Dataset,
|
||||
DatasetFile,
|
||||
} from "@/pages/DataManagement/dataset.model";
|
||||
import { App } from "antd";
|
||||
import { App } from "antd";
|
||||
import { useState } from "react";
|
||||
import {
|
||||
deleteDatasetFileUsingDelete,
|
||||
@@ -13,11 +13,17 @@ import {
|
||||
downloadDirectoryUsingGet,
|
||||
deleteDirectoryUsingDelete,
|
||||
} from "../dataset.api";
|
||||
import { useParams } from "react-router";
|
||||
import { useParams } from "react-router";
|
||||
|
||||
const MAX_PREVIEW_LENGTH = 50000;
|
||||
const TEXT_FILE_EXTENSIONS = [".json", ".jsonl", ".txt", ".csv", ".tsv", ".xml", ".md", ".yaml", ".yml"];
|
||||
const IMAGE_FILE_EXTENSIONS = [".jpg", ".jpeg", ".png", ".gif", ".bmp", ".webp", ".svg"];
|
||||
const VIDEO_FILE_EXTENSIONS = [".mp4", ".webm", ".ogg", ".mov", ".avi"];
|
||||
const AUDIO_FILE_EXTENSIONS = [".mp3", ".wav", ".ogg", ".aac", ".flac", ".m4a"];
|
||||
|
||||
export function useFilesOperation(dataset: Dataset) {
|
||||
const { message } = App.useApp();
|
||||
const { id } = useParams(); // 获取动态路由参数
|
||||
export function useFilesOperation(dataset: Dataset) {
|
||||
const { message } = App.useApp();
|
||||
const { id } = useParams(); // 获取动态路由参数
|
||||
|
||||
// 文件相关状态
|
||||
const [fileList, setFileList] = useState<DatasetFile[]>([]);
|
||||
@@ -30,9 +36,12 @@ export function useFilesOperation(dataset: Dataset) {
|
||||
}>({ current: 1, pageSize: 10, total: 0, prefix: '' });
|
||||
|
||||
// 文件预览相关状态
|
||||
const [previewVisible, setPreviewVisible] = useState(false);
|
||||
const [previewContent, setPreviewContent] = useState("");
|
||||
const [previewFileName, setPreviewFileName] = useState("");
|
||||
const [previewVisible, setPreviewVisible] = useState(false);
|
||||
const [previewContent, setPreviewContent] = useState("");
|
||||
const [previewFileName, setPreviewFileName] = useState("");
|
||||
const [previewFileType, setPreviewFileType] = useState<"text" | "image" | "video" | "audio">("text");
|
||||
const [previewMediaUrl, setPreviewMediaUrl] = useState("");
|
||||
const [previewLoading, setPreviewLoading] = useState(false);
|
||||
|
||||
const fetchFiles = async (
|
||||
prefix?: string,
|
||||
@@ -90,17 +99,80 @@ export function useFilesOperation(dataset: Dataset) {
|
||||
setSelectedFiles([]); // 清空选中状态
|
||||
};
|
||||
|
||||
const handleShowFile = (file: DatasetFile) => async () => {
|
||||
// 请求文件内容并弹窗预览
|
||||
try {
|
||||
const res = await fetch(`/api/datasets/${dataset.id}/file/${file.id}`);
|
||||
const data = await res.text();
|
||||
setPreviewFileName(file.fileName);
|
||||
setPreviewContent(data);
|
||||
setPreviewVisible(true);
|
||||
} catch {
|
||||
message.error({ content: "文件预览失败" });
|
||||
const resolvePreviewFileType = (fileName?: string) => {
|
||||
const lowerName = (fileName || "").toLowerCase();
|
||||
if (TEXT_FILE_EXTENSIONS.some((ext) => lowerName.endsWith(ext))) {
|
||||
return "text";
|
||||
}
|
||||
if (IMAGE_FILE_EXTENSIONS.some((ext) => lowerName.endsWith(ext))) {
|
||||
return "image";
|
||||
}
|
||||
if (VIDEO_FILE_EXTENSIONS.some((ext) => lowerName.endsWith(ext))) {
|
||||
return "video";
|
||||
}
|
||||
if (AUDIO_FILE_EXTENSIONS.some((ext) => lowerName.endsWith(ext))) {
|
||||
return "audio";
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const handlePreviewFile = async (file: DatasetFile) => {
|
||||
const datasetId = dataset?.id || id;
|
||||
if (!datasetId) {
|
||||
message.warning({ content: "数据集未就绪" });
|
||||
return;
|
||||
}
|
||||
if (file?.id?.startsWith("directory-")) {
|
||||
return;
|
||||
}
|
||||
|
||||
const fileType = resolvePreviewFileType(file?.fileName);
|
||||
if (!fileType) {
|
||||
message.warning({ content: "不支持预览该文件类型" });
|
||||
return;
|
||||
}
|
||||
|
||||
const fileUrl = `/api/data-management/datasets/${datasetId}/files/${file.id}/download`;
|
||||
setPreviewFileName(file.fileName);
|
||||
setPreviewFileType(fileType);
|
||||
setPreviewContent("");
|
||||
setPreviewMediaUrl("");
|
||||
|
||||
if (fileType === "text") {
|
||||
setPreviewLoading(true);
|
||||
try {
|
||||
const response = await fetch(fileUrl);
|
||||
if (!response.ok) {
|
||||
throw new Error("下载失败");
|
||||
}
|
||||
const text = await response.text();
|
||||
if (text.length > MAX_PREVIEW_LENGTH) {
|
||||
setPreviewContent(
|
||||
`${text.slice(0, MAX_PREVIEW_LENGTH)}\n\n... (内容过长,仅显示前 ${MAX_PREVIEW_LENGTH} 字符)`
|
||||
);
|
||||
} else {
|
||||
setPreviewContent(text);
|
||||
}
|
||||
setPreviewVisible(true);
|
||||
} catch (error) {
|
||||
console.error("Preview file content error:", error);
|
||||
message.error({ content: "获取文件内容失败" });
|
||||
} finally {
|
||||
setPreviewLoading(false);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
setPreviewMediaUrl(fileUrl);
|
||||
setPreviewVisible(true);
|
||||
};
|
||||
|
||||
const closePreview = () => {
|
||||
setPreviewVisible(false);
|
||||
setPreviewContent("");
|
||||
setPreviewMediaUrl("");
|
||||
setPreviewFileName("");
|
||||
setPreviewFileType("text");
|
||||
};
|
||||
|
||||
const handleDeleteFile = async (file: DatasetFile) => {
|
||||
@@ -139,19 +211,20 @@ export function useFilesOperation(dataset: Dataset) {
|
||||
setSelectedFiles,
|
||||
pagination,
|
||||
setPagination,
|
||||
previewVisible,
|
||||
setPreviewVisible,
|
||||
previewContent,
|
||||
previewFileName,
|
||||
setPreviewContent,
|
||||
setPreviewFileName,
|
||||
fetchFiles,
|
||||
setFileList,
|
||||
handleBatchDeleteFiles,
|
||||
handleDownloadFile,
|
||||
handleShowFile,
|
||||
handleDeleteFile,
|
||||
handleBatchExport,
|
||||
previewVisible,
|
||||
previewContent,
|
||||
previewFileName,
|
||||
previewFileType,
|
||||
previewMediaUrl,
|
||||
previewLoading,
|
||||
closePreview,
|
||||
fetchFiles,
|
||||
setFileList,
|
||||
handleBatchDeleteFiles,
|
||||
handleDownloadFile,
|
||||
handlePreviewFile,
|
||||
handleDeleteFile,
|
||||
handleBatchExport,
|
||||
handleCreateDirectory: async (directoryName: string) => {
|
||||
const currentPrefix = pagination.prefix || "";
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user