feat: added file download examples (#4853)
parent
90dc00b168
commit
a3d0d2ed34
|
@ -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;
|
||||
}
|
|
@ -2,6 +2,7 @@ export * from './cn';
|
|||
export * from './date';
|
||||
export * from './diff';
|
||||
export * from './dom';
|
||||
export * from './download';
|
||||
export * from './inference';
|
||||
export * from './letter';
|
||||
export * from './merge';
|
||||
|
|
|
@ -49,7 +49,8 @@
|
|||
"fullScreen": "FullScreen",
|
||||
"clipboard": "Clipboard",
|
||||
"menuWithQuery": "Menu With Query",
|
||||
"openInNewWindow": "Open in New Window"
|
||||
"openInNewWindow": "Open in New Window",
|
||||
"fileDownload": "File Download"
|
||||
},
|
||||
"breadcrumb": {
|
||||
"navigation": "Breadcrumb Navigation",
|
||||
|
|
|
@ -49,7 +49,8 @@
|
|||
"fullScreen": "全屏",
|
||||
"clipboard": "剪贴板",
|
||||
"menuWithQuery": "带参菜单",
|
||||
"openInNewWindow": "新窗口打开"
|
||||
"openInNewWindow": "新窗口打开",
|
||||
"fileDownload": "文件下载"
|
||||
},
|
||||
"breadcrumb": {
|
||||
"navigation": "面包屑导航",
|
||||
|
|
|
@ -177,6 +177,16 @@ const routes: RouteRecordRaw[] = [
|
|||
title: $t('demos.features.fullScreen'),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'FileDownloadDemo',
|
||||
path: '/demos/features/file-download',
|
||||
component: () =>
|
||||
import('#/views/demos/features/file-download/index.vue'),
|
||||
meta: {
|
||||
icon: 'lucide:hard-drive-download',
|
||||
title: $t('demos.features.fileDownload'),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'ClipboardDemo',
|
||||
path: '/demos/features/clipboard',
|
||||
|
|
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