download.ts 4.1 KB

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