download.ts 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
  1. import { openWindow } from './window';
  2. interface DownloadOptions<T = string> {
  3. fileName?: string;
  4. source: T;
  5. target?: string;
  6. }
  7. const DEFAULT_FILENAME = 'downloaded_file';
  8. /**
  9. * 通过 URL 下载文件,支持跨域
  10. * @throws {Error} - 当下载失败时抛出错误
  11. */
  12. export async function downloadFileFromUrl({
  13. fileName,
  14. source,
  15. target = '_blank',
  16. }: DownloadOptions): Promise<void> {
  17. if (!source || typeof source !== 'string') {
  18. throw new Error('Invalid URL.');
  19. }
  20. const isChrome = window.navigator.userAgent.toLowerCase().includes('chrome');
  21. const isSafari = window.navigator.userAgent.toLowerCase().includes('safari');
  22. if (/iP/.test(window.navigator.userAgent)) {
  23. console.error('Your browser does not support download!');
  24. return;
  25. }
  26. if (isChrome || isSafari) {
  27. triggerDownload(source, resolveFileName(source, fileName));
  28. return;
  29. }
  30. if (!source.includes('?')) {
  31. source += '?download';
  32. }
  33. openWindow(source, { target });
  34. }
  35. /**
  36. * 通过 Base64 下载文件
  37. */
  38. export function downloadFileFromBase64({ fileName, source }: DownloadOptions) {
  39. if (!source || typeof source !== 'string') {
  40. throw new Error('Invalid Base64 data.');
  41. }
  42. const resolvedFileName = fileName || DEFAULT_FILENAME;
  43. triggerDownload(source, resolvedFileName);
  44. }
  45. /**
  46. * 通过图片 URL 下载图片文件
  47. */
  48. export async function downloadFileFromImageUrl({
  49. fileName,
  50. source,
  51. }: DownloadOptions) {
  52. const base64 = await urlToBase64(source);
  53. downloadFileFromBase64({ fileName, source: base64 });
  54. }
  55. /**
  56. * 通过 Blob 下载文件
  57. */
  58. export function downloadFileFromBlob({
  59. fileName = DEFAULT_FILENAME,
  60. source,
  61. }: DownloadOptions<Blob>): void {
  62. if (!(source instanceof Blob)) {
  63. throw new TypeError('Invalid Blob data.');
  64. }
  65. const url = URL.createObjectURL(source);
  66. triggerDownload(url, fileName);
  67. }
  68. /**
  69. * 下载文件,支持 Blob、字符串和其他 BlobPart 类型
  70. */
  71. export function downloadFileFromBlobPart({
  72. fileName = DEFAULT_FILENAME,
  73. source,
  74. }: DownloadOptions<BlobPart>): void {
  75. // 如果 data 不是 Blob,则转换为 Blob
  76. const blob =
  77. source instanceof Blob
  78. ? source
  79. : new Blob([source], { type: 'application/octet-stream' });
  80. // 创建对象 URL 并触发下载
  81. const url = URL.createObjectURL(blob);
  82. triggerDownload(url, fileName);
  83. }
  84. /**
  85. * img url to base64
  86. * @param url
  87. */
  88. export function urlToBase64(url: string, mineType?: string): Promise<string> {
  89. return new Promise((resolve, reject) => {
  90. let canvas = document.createElement('CANVAS') as HTMLCanvasElement | null;
  91. const ctx = canvas?.getContext('2d');
  92. const img = new Image();
  93. img.crossOrigin = '';
  94. img.addEventListener('load', () => {
  95. if (!canvas || !ctx) {
  96. return reject(new Error('Failed to create canvas.'));
  97. }
  98. canvas.height = img.height;
  99. canvas.width = img.width;
  100. ctx.drawImage(img, 0, 0);
  101. const dataURL = canvas.toDataURL(mineType || 'image/png');
  102. canvas = null;
  103. resolve(dataURL);
  104. });
  105. img.src = url;
  106. });
  107. }
  108. /**
  109. * 通用下载触发函数
  110. * @param href - 文件下载的 URL
  111. * @param fileName - 下载文件的名称,如果未提供则自动识别
  112. * @param revokeDelay - 清理 URL 的延迟时间 (毫秒)
  113. */
  114. export function triggerDownload(
  115. href: string,
  116. fileName: string | undefined,
  117. revokeDelay: number = 100,
  118. ): void {
  119. const defaultFileName = 'downloaded_file';
  120. const finalFileName = fileName || defaultFileName;
  121. const link = document.createElement('a');
  122. link.href = href;
  123. link.download = finalFileName;
  124. link.style.display = 'none';
  125. if (link.download === undefined) {
  126. link.setAttribute('target', '_blank');
  127. }
  128. document.body.append(link);
  129. link.click();
  130. link.remove();
  131. // 清理临时 URL 以释放内存
  132. setTimeout(() => URL.revokeObjectURL(href), revokeDelay);
  133. }
  134. function resolveFileName(url: string, fileName?: string): string {
  135. return fileName || url.slice(url.lastIndexOf('/') + 1) || DEFAULT_FILENAME;
  136. }