import { openWindow } from './window'; interface DownloadOptions { fileName?: string; source: T; target?: string; } const DEFAULT_FILENAME = 'downloaded_file'; /** * 通过 URL 下载文件,支持跨域 * @throws {Error} - 当下载失败时抛出错误 */ export async function downloadFileFromUrl({ fileName, source, target = '_blank', }: DownloadOptions): Promise { if (!source || typeof source !== 'string') { throw new Error('Invalid URL.'); } const isChrome = window.navigator.userAgent.toLowerCase().includes('chrome'); const isSafari = window.navigator.userAgent.toLowerCase().includes('safari'); if (/iP/.test(window.navigator.userAgent)) { console.error('Your browser does not support download!'); return; } if (isChrome || isSafari) { triggerDownload(source, resolveFileName(source, fileName)); } if (!source.includes('?')) { source += '?download'; } openWindow(source, { target }); } /** * 通过 Base64 下载文件 */ export function downloadFileFromBase64({ fileName, source }: DownloadOptions) { if (!source || typeof source !== 'string') { throw new Error('Invalid Base64 data.'); } const resolvedFileName = fileName || DEFAULT_FILENAME; triggerDownload(source, resolvedFileName); } /** * 通过图片 URL 下载图片文件 */ export async function downloadFileFromImageUrl({ fileName, source, }: DownloadOptions) { const base64 = await urlToBase64(source); downloadFileFromBase64({ fileName, source: base64 }); } /** * 通过 Blob 下载文件 * @param blob - 文件的 Blob 对象 * @param fileName - 可选,下载的文件名称 */ export function downloadFileFromBlob({ fileName = DEFAULT_FILENAME, source, }: DownloadOptions): void { if (!(source instanceof Blob)) { throw new TypeError('Invalid Blob data.'); } const url = URL.createObjectURL(source); triggerDownload(url, fileName); } /** * 下载文件,支持 Blob、字符串和其他 BlobPart 类型 * @param data - 文件的 BlobPart 数据 * @param fileName - 下载的文件名称 */ export function downloadFileFromBlobPart({ fileName = DEFAULT_FILENAME, source, }: DownloadOptions): void { // 如果 data 不是 Blob,则转换为 Blob const blob = source instanceof Blob ? source : new Blob([source], { type: 'application/octet-stream' }); // 创建对象 URL 并触发下载 const url = URL.createObjectURL(blob); triggerDownload(url, fileName); } /** * img url to base64 * @param url */ export function urlToBase64(url: string, mineType?: string): Promise { return new Promise((resolve, reject) => { let canvas = document.createElement('CANVAS') as HTMLCanvasElement | null; const ctx = canvas?.getContext('2d'); const img = new Image(); img.crossOrigin = ''; img.addEventListener('load', () => { if (!canvas || !ctx) { return reject(new Error('Failed to create canvas.')); } canvas.height = img.height; canvas.width = img.width; ctx.drawImage(img, 0, 0); const dataURL = canvas.toDataURL(mineType || 'image/png'); canvas = null; resolve(dataURL); }); img.src = url; }); } /** * 通用下载触发函数 * @param href - 文件下载的 URL * @param fileName - 下载文件的名称,如果未提供则自动识别 * @param revokeDelay - 清理 URL 的延迟时间 (毫秒) */ export function triggerDownload( href: string, fileName: string | undefined, revokeDelay: number = 100, ): void { const defaultFileName = 'downloaded_file'; const finalFileName = fileName || defaultFileName; const link = document.createElement('a'); link.href = href; link.download = finalFileName; link.style.display = 'none'; if (link.download === undefined) { link.setAttribute('target', '_blank'); } document.body.append(link); link.click(); link.remove(); // 清理临时 URL 以释放内存 setTimeout(() => URL.revokeObjectURL(href), revokeDelay); } function resolveFileName(url: string, fileName?: string): string { return fileName || url.slice(url.lastIndexOf('/') + 1) || DEFAULT_FILENAME; }