From 066a8064d0a8f371a065630f62f5a58953dc3f4c Mon Sep 17 00:00:00 2001 From: MoeexT Date: Wed, 11 Mar 2026 11:35:47 +0800 Subject: [PATCH 1/2] :zap: instant download file --- frontend/src/utils/request.ts | 131 ++++++++++++++++++---------------- 1 file changed, 71 insertions(+), 60 deletions(-) diff --git a/frontend/src/utils/request.ts b/frontend/src/utils/request.ts index 5e5aff21..74371466 100644 --- a/frontend/src/utils/request.ts +++ b/frontend/src/utils/request.ts @@ -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 @@ -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; + // 对于下载,使用原生 标签 + token 参数 + if (action === "download") { + // 获取 token 并添加到 URL 参数中 + const token = this.getAuthToken(); + const downloadParams = token ? { ...params, token } : params; + const fullURL = this.buildURL(url, downloadParams); + + // 创建隐藏的 标签触发下载 + 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 {}; } /** From c90d0bdb702d6b8024f0058d3f23f71da07a9e98 Mon Sep 17 00:00:00 2001 From: MoeexT Date: Wed, 11 Mar 2026 15:17:30 +0800 Subject: [PATCH 2/2] :zap: use lucide icons --- .../DataManagement/Detail/DatasetDetail.tsx | 33 ++++++++----------- 1 file changed, 13 insertions(+), 20 deletions(-) diff --git a/frontend/src/pages/DataManagement/Detail/DatasetDetail.tsx b/frontend/src/pages/DataManagement/Detail/DatasetDetail.tsx index ea18d74e..82c89951 100644 --- a/frontend/src/pages/DataManagement/Detail/DatasetDetail.tsx +++ b/frontend/src/pages/DataManagement/Detail/DatasetDetail.tsx @@ -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"; @@ -140,7 +140,7 @@ export default function DatasetDetail() { { key: "detail", label: t("dataManagement.actions.detail"), - icon: , + icon: , onClick: () => { setShowDetailDrawer(true); }, @@ -148,7 +148,7 @@ export default function DatasetDetail() { { key: "edit", label: t("dataManagement.actions.edit"), - icon: , + icon: , onClick: () => { setShowEditDialog(true); }, @@ -157,26 +157,19 @@ export default function DatasetDetail() { { key: "upload", label: t("dataManagement.actions.importData"), - icon: , + icon: , onClick: () => setShowUploadDialog(true), }, { key: "export", label: t("dataManagement.actions.export"), - icon: , - // isDropdown: true, - // items: [ - // { key: "alpaca", label: "Alpaca 格式", icon: }, - // { key: "jsonl", label: "JSONL 格式", icon: }, - // { key: "csv", label: "CSV 格式", icon: }, - // { key: "coco", label: "COCO 格式", icon: }, - // ], + icon: , onClick: () => handleDownload(), }, { key: "refresh", label: t("dataManagement.actions.refresh"), - icon: , + icon: , onClick: handleRefresh, }, { @@ -190,7 +183,7 @@ export default function DatasetDetail() { cancelText: t("dataManagement.confirm.deleteCancel"), okType: "danger", }, - icon: , + icon: , onClick: handleDeleteDataset, }, ];