diff --git a/frontend/src/pages/DataManagement/Detail/components/Overview.tsx b/frontend/src/pages/DataManagement/Detail/components/Overview.tsx
index 801fa31..fb4a62b 100644
--- a/frontend/src/pages/DataManagement/Detail/components/Overview.tsx
+++ b/frontend/src/pages/DataManagement/Detail/components/Overview.tsx
@@ -1,4 +1,4 @@
-import { App, Button, Descriptions, DescriptionsProps, Modal, Table, Input } from "antd";
+import { App, Button, Descriptions, DescriptionsProps, Modal, Table, Input } from "antd";
import { formatBytes, formatDateTime } from "@/utils/unit";
import { Download, Trash2, Folder, File } from "lucide-react";
import { datasetTypeMap } from "../../dataset.const";
@@ -9,6 +9,15 @@ type DatasetFileRow = DatasetFile & {
fileCount?: number;
uploadTime?: string;
};
+
+const PREVIEW_MAX_HEIGHT = 500;
+const PREVIEW_MODAL_WIDTH = {
+ text: 800,
+ media: 700,
+};
+const PREVIEW_TEXT_FONT_SIZE = 12;
+const PREVIEW_TEXT_PADDING = 12;
+const PREVIEW_AUDIO_PADDING = 40;
export default function Overview({
dataset,
@@ -22,17 +31,21 @@ export default function Overview({
pagination,
selectedFiles,
previewVisible,
- previewFileName,
- previewContent,
- setPreviewVisible,
- handleDeleteFile,
- handleDownloadFile,
- handleBatchDeleteFiles,
- handleBatchExport,
- handleCreateDirectory,
- handleDownloadDirectory,
- handleDeleteDirectory,
- } = filesOperation;
+ previewFileName,
+ previewContent,
+ previewFileType,
+ previewMediaUrl,
+ previewLoading,
+ closePreview,
+ handleDeleteFile,
+ handleDownloadFile,
+ handleBatchDeleteFiles,
+ handleBatchExport,
+ handleCreateDirectory,
+ handleDownloadDirectory,
+ handleDeleteDirectory,
+ handlePreviewFile,
+ } = filesOperation;
// 基本信息
const items: DescriptionsProps["items"] = [
@@ -127,15 +140,16 @@ export default function Overview({
);
}
- return (
+ return (
- );
- },
+ );
+ },
},
{
title: "大小",
@@ -370,25 +384,63 @@ export default function Overview({
/>
- {/* 文件预览弹窗 */}
- setPreviewVisible(false)}
- footer={null}
- width={700}
- >
-
- {previewContent}
-
-
- >
- );
-}
+ {/* 文件预览弹窗 */}
+
+ 关闭
+ ,
+ ]}
+ width={previewFileType === "text" ? PREVIEW_MODAL_WIDTH.text : PREVIEW_MODAL_WIDTH.media}
+ >
+ {previewFileType === "text" && (
+
+ {previewContent}
+
+ )}
+ {previewFileType === "image" && (
+
+

+
+ )}
+ {previewFileType === "video" && (
+
+
+
+ )}
+ {previewFileType === "audio" && (
+
+ )}
+
+ >
+ );
+}
diff --git a/frontend/src/pages/DataManagement/Detail/useFilesOperation.ts b/frontend/src/pages/DataManagement/Detail/useFilesOperation.ts
index 4d316ce..f3c95cb 100644
--- a/frontend/src/pages/DataManagement/Detail/useFilesOperation.ts
+++ b/frontend/src/pages/DataManagement/Detail/useFilesOperation.ts
@@ -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([]);
@@ -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 {