committed by
GitHub
7 changed files with 250 additions and 2 deletions
@ -0,0 +1,160 @@ |
|||||
|
import { openWindow } from './window'; |
||||
|
|
||||
|
interface DownloadOptions<T = string> { |
||||
|
fileName?: string; |
||||
|
source: T; |
||||
|
target?: string; |
||||
|
} |
||||
|
|
||||
|
const DEFAULT_FILENAME = 'downloaded_file'; |
||||
|
|
||||
|
/** |
||||
|
* 通过 URL 下载文件,支持跨域 |
||||
|
* @throws {Error} - 当下载失败时抛出错误 |
||||
|
*/ |
||||
|
export async function downloadFileFromUrl({ |
||||
|
fileName, |
||||
|
source, |
||||
|
target = '_blank', |
||||
|
}: DownloadOptions): Promise<void> { |
||||
|
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<Blob>): 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<BlobPart>): 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<string> { |
||||
|
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; |
||||
|
} |
||||
File diff suppressed because one or more lines are too long
@ -0,0 +1,74 @@ |
|||||
|
<script setup lang="ts"> |
||||
|
import { Page } from '@vben/common-ui'; |
||||
|
import { |
||||
|
downloadFileFromBase64, |
||||
|
downloadFileFromBlobPart, |
||||
|
downloadFileFromImageUrl, |
||||
|
downloadFileFromUrl, |
||||
|
} from '@vben/utils'; |
||||
|
|
||||
|
import { Button, Card } from 'ant-design-vue'; |
||||
|
|
||||
|
import imageBase64 from './base64'; |
||||
|
</script> |
||||
|
|
||||
|
<template> |
||||
|
<Page title="文件下载示例"> |
||||
|
<Card title="根据文件地址下载文件"> |
||||
|
<Button |
||||
|
type="primary" |
||||
|
@click=" |
||||
|
downloadFileFromUrl({ |
||||
|
source: |
||||
|
'https://codeload.github.com/vbenjs/vue-vben-admin-doc/zip/main', |
||||
|
target: '_self', |
||||
|
}) |
||||
|
" |
||||
|
> |
||||
|
Download File |
||||
|
</Button> |
||||
|
</Card> |
||||
|
|
||||
|
<Card class="my-5" title="根据地址下载图片"> |
||||
|
<Button |
||||
|
type="primary" |
||||
|
@click=" |
||||
|
downloadFileFromImageUrl({ |
||||
|
source: |
||||
|
'https://unpkg.com/@vbenjs/static-source@0.1.7/source/logo-v1.webp', |
||||
|
fileName: 'vben-logo.png', |
||||
|
}) |
||||
|
" |
||||
|
> |
||||
|
Download File |
||||
|
</Button> |
||||
|
</Card> |
||||
|
|
||||
|
<Card class="my-5" title="base64流下载"> |
||||
|
<Button |
||||
|
type="primary" |
||||
|
@click=" |
||||
|
downloadFileFromBase64({ |
||||
|
source: imageBase64, |
||||
|
fileName: 'image.png', |
||||
|
}) |
||||
|
" |
||||
|
> |
||||
|
Download Image |
||||
|
</Button> |
||||
|
</Card> |
||||
|
<Card class="my-5" title="文本下载"> |
||||
|
<Button |
||||
|
type="primary" |
||||
|
@click=" |
||||
|
downloadFileFromBlobPart({ |
||||
|
source: 'text content', |
||||
|
fileName: 'test.txt', |
||||
|
}) |
||||
|
" |
||||
|
> |
||||
|
Download TxT |
||||
|
</Button> |
||||
|
</Card> |
||||
|
</Page> |
||||
|
</template> |
||||
Loading…
Reference in new issue