Skip to content

Commit

Permalink
feat: added file download examples (#4853)
Browse files Browse the repository at this point in the history
  • Loading branch information
anncwb authored Nov 10, 2024
1 parent 90dc00b commit a3d0d2e
Show file tree
Hide file tree
Showing 7 changed files with 250 additions and 2 deletions.
160 changes: 160 additions & 0 deletions packages/@core/base/shared/src/utils/download.ts
Original file line number Diff line number Diff line change
@@ -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 对象

Check warning on line 67 in packages/@core/base/shared/src/utils/download.ts

View workflow job for this annotation

GitHub Actions / Lint (ubuntu-latest)

Missing @param "blob.fileName"

Check warning on line 67 in packages/@core/base/shared/src/utils/download.ts

View workflow job for this annotation

GitHub Actions / Lint (ubuntu-latest)

Missing @param "blob.source"

Check warning on line 67 in packages/@core/base/shared/src/utils/download.ts

View workflow job for this annotation

GitHub Actions / Lint (windows-latest)

Missing @param "blob.fileName"

Check warning on line 67 in packages/@core/base/shared/src/utils/download.ts

View workflow job for this annotation

GitHub Actions / Lint (windows-latest)

Missing @param "blob.source"
* @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 数据

Check warning on line 84 in packages/@core/base/shared/src/utils/download.ts

View workflow job for this annotation

GitHub Actions / Lint (ubuntu-latest)

Missing @param "data.fileName"

Check warning on line 84 in packages/@core/base/shared/src/utils/download.ts

View workflow job for this annotation

GitHub Actions / Lint (ubuntu-latest)

Missing @param "data.source"

Check warning on line 84 in packages/@core/base/shared/src/utils/download.ts

View workflow job for this annotation

GitHub Actions / Lint (windows-latest)

Missing @param "data.fileName"

Check warning on line 84 in packages/@core/base/shared/src/utils/download.ts

View workflow job for this annotation

GitHub Actions / Lint (windows-latest)

Missing @param "data.source"
* @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;
}
1 change: 1 addition & 0 deletions packages/@core/base/shared/src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
3 changes: 2 additions & 1 deletion playground/src/locales/langs/en-US/demos.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
3 changes: 2 additions & 1 deletion playground/src/locales/langs/zh-CN/demos.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@
"fullScreen": "全屏",
"clipboard": "剪贴板",
"menuWithQuery": "带参菜单",
"openInNewWindow": "新窗口打开"
"openInNewWindow": "新窗口打开",
"fileDownload": "文件下载"
},
"breadcrumb": {
"navigation": "面包屑导航",
Expand Down
10 changes: 10 additions & 0 deletions playground/src/router/routes/modules/demos.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down

Large diffs are not rendered by default.

74 changes: 74 additions & 0 deletions playground/src/views/demos/features/file-download/index.vue
Original file line number Diff line number Diff line change
@@ -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>

0 comments on commit a3d0d2e

Please sign in to comment.