Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 13 additions & 20 deletions frontend/src/pages/DataManagement/Detail/DatasetDetail.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { useEffect, useMemo, useState } from "react";
import { Breadcrumb, App, Tabs, Drawer, Descriptions } from "antd";
import {
ReloadOutlined,
DownloadOutlined,
UploadOutlined,
EditOutlined,
DeleteOutlined,
InfoCircleOutlined,
} from "@ant-design/icons";
Info,
Edit,
Upload,
Download,
RefreshCw,
Trash2,
} from "lucide-react";
import DetailHeader from "@/components/DetailHeader";
import { getDatasetTypeMap, mapDataset } from "../dataset.const";
import type { Dataset } from "@/pages/DataManagement/dataset.model";
Expand Down Expand Up @@ -140,15 +140,15 @@ export default function DatasetDetail() {
{
key: "detail",
label: t("dataManagement.actions.detail"),
icon: <InfoCircleOutlined />,
icon: <Info className="w-4 h-4" />,
onClick: () => {
setShowDetailDrawer(true);
},
},
{
key: "edit",
label: t("dataManagement.actions.edit"),
icon: <EditOutlined />,
icon: <Edit className="w-4 h-4" />,
onClick: () => {
setShowEditDialog(true);
},
Expand All @@ -157,26 +157,19 @@ export default function DatasetDetail() {
{
key: "upload",
label: t("dataManagement.actions.importData"),
icon: <UploadOutlined />,
icon: <Upload className="w-4 h-4" />,
onClick: () => setShowUploadDialog(true),
},
{
key: "export",
label: t("dataManagement.actions.export"),
icon: <DownloadOutlined />,
// isDropdown: true,
// items: [
// { key: "alpaca", label: "Alpaca 格式", icon: <FileTextOutlined /> },
// { key: "jsonl", label: "JSONL 格式", icon: <DatabaseOutlined /> },
// { key: "csv", label: "CSV 格式", icon: <FileTextOutlined /> },
// { key: "coco", label: "COCO 格式", icon: <FileImageOutlined /> },
// ],
icon: <Download className="w-4 h-4" />,
onClick: () => handleDownload(),
},
{
key: "refresh",
label: t("dataManagement.actions.refresh"),
icon: <ReloadOutlined />,
icon: <RefreshCw className="w-4 h-4" />,
onClick: handleRefresh,
},
{
Expand All @@ -190,7 +183,7 @@ export default function DatasetDetail() {
cancelText: t("dataManagement.confirm.deleteCancel"),
okType: "danger",
},
icon: <DeleteOutlined />,
icon: <Trash2 className="w-4 h-4" />,
onClick: handleDeleteDataset,
},
];
Expand Down
131 changes: 71 additions & 60 deletions frontend/src/utils/request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,46 @@ class Request {
return this.request(fullURL, config);
}

/**
* 从 Content-Disposition 头中解析文件名
*/
parseContentDisposition(contentDisposition) {
if (!contentDisposition) return null;

// 匹配 filename="..." 或 filename=...
const filenameRegex = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/;
const matches = filenameRegex.exec(contentDisposition);

if (matches && matches[1]) {
let fileName = matches[1].replace(/['"]/g, '');
// 处理 URL 编码的文件名(如中文文件名)
try {
fileName = decodeURIComponent(fileName);
} catch {
// 如果解码失败,使用原始文件名
}
return fileName;
}

return null;
}

/**
* 获取当前认证 token
*/
getAuthToken() {
const session = localStorage.getItem("session");
if (session) {
try {
const sessionData = JSON.parse(session);
return sessionData.token;
} catch {
return null;
}
}
return null;
}

/**
* 下载文件
* @param {string} url - 请求URL
Expand All @@ -397,85 +437,56 @@ class Request {
* @param {object} options - 额外的fetch选项,包括showLoading, onDownloadProgress
*/
async download(url, params = null, filename = "", action = "download", options = {}) {
const fullURL = this.buildURL(url, params);

const config = {
method: "GET",
responseType: "blob",
...options,
};

// 执行请求拦截器
const processedConfig = await this.executeRequestInterceptors(config);

let blob;
let name = filename;

// 如果需要下载进度监听,使用XMLHttpRequest
if (config.onDownloadProgress) {
const xhrResponse = await this.createXHRWithProgress(
fullURL,
{ ...processedConfig, responseType: "blob" },
null,
config.onDownloadProgress
);

if (xhrResponse.status < 200 || xhrResponse.status >= 300) {
throw new Error(`HTTP error! status: ${xhrResponse.status}`);
}
// 对于预览,使用 fetch + blob 方式
if (action === "preview") {
const fullURL = this.buildURL(url, params);
const config = {
method: "GET",
...options,
};

blob = xhrResponse.xhr.response;
name =
name ||
xhrResponse.headers.get("Content-Disposition")?.split("filename=")[1] ||
"download";
} else {
// 使用fetch
const processedConfig = await this.executeRequestInterceptors(config);
const response = await fetch(fullURL, processedConfig);

// 文件下载不需要执行响应拦截器(因为响应是二进制数据,不是JSON)
// 文件预览不需要执行响应拦截器(因为响应是二进制数据,不是JSON)
// 直接检查响应状态
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}

blob = await response.blob();
name =
name ||
response.headers.get("Content-Disposition")?.split("filename=")[1] ||
`download_${Date.now()}`;
}

if (action === "download") {
// 创建下载链接
const downloadUrl = window.URL.createObjectURL(blob);
const link = document.createElement("a");
link.href = downloadUrl;
link.download = filename ?? name;

// 添加到DOM并触发下载
document.body.appendChild(link);
link.click();
document.body.removeChild(link);

// 清理URL对象
window.URL.revokeObjectURL(downloadUrl);
} else if (action === "preview") {
// 预览逻辑 - 返回Blob URL和相关信息
const blob = await response.blob();
const blobUrl = window.URL.createObjectURL(blob);
const name = filename || this.parseContentDisposition(response.headers.get("Content-Disposition")) || `download_${Date.now()}`;

// 可以返回更多信息用于预览
return {
blob,
blobUrl,
filename: name,
size: blob.size,
// 自动清理的钩子
revoke: () => window.URL.revokeObjectURL(blobUrl)
};
}

return blob;
// 对于下载,使用原生 <a> 标签 + token 参数
if (action === "download") {
// 获取 token 并添加到 URL 参数中
const token = this.getAuthToken();
const downloadParams = token ? { ...params, token } : params;
const fullURL = this.buildURL(url, downloadParams);

// 创建隐藏的 <a> 标签触发下载
const link = document.createElement("a");
link.href = fullURL;
link.download = filename || `download_${Date.now()}`;
link.style.display = "none";
document.body.appendChild(link);
link.click();
document.body.removeChild(link);

return { success: true };
}

return {};
}

/**
Expand Down
Loading