vue3 实现下载指令
// download.ts
/*** v-downLoad 下载文件* */
export type DownloadBindingType = DownloadFile | string | string[];interface ElType extends HTMLElement {$destroyDownLoad?: () => void;$downLoadCallback?: () => void;$downLoadValue?: DownloadBindingType;
}export default {beforeMount(el: ElType,{value,arg,}: DirectiveBinding<((value?: DownloadBindingType) => void) | DownloadBindingType>,) {if (value instanceof Function && arg === 'callback') {el.$downLoadCallback = value;} else {el.$downLoadValue = value as DownloadBindingType;}const handler = async () => {if (el.$downLoadCallback && el.$downLoadValue instanceof Function) {el.$downLoadCallback();return;}if (Array.isArray(el.$downLoadValue)) {for (const val of el.$downLoadValue) {await (await downloadFile(val))();}} else {await (await downloadFile(el.$downLoadValue as DownloadFile))();}if (el.$downLoadCallback) {el.$downLoadCallback();}};el.addEventListener('click', handler);el.$destroyDownLoad = () => el.removeEventListener('click', handler);},// 当指令绑定的dom元素发生变化时触发updated(el: ElType,{value,arg,}: DirectiveBinding<((value?: DownloadBindingType) => void) | DownloadBindingType>,) {// 重新绑定if (value instanceof Function && arg === 'callback') {el.$downLoadCallback = value;} else {el.$downLoadValue = value as DownloadBindingType;}},beforeUnmount(el: ElType) {// 移除事件处理器、地址对象/回调函数el.$destroyDownLoad?.();delete el.$downLoadValue;delete el.$downLoadCallback;},
};interface DownloadFile {blob?: Blob;url?: string;fileName?: string;
}async function downloadFile(file: DownloadFile | string) {return async () => {let blob: Blob;let fileName: string;if (typeof file === 'string') {// 处理URL字符串blob = await fetchBlob(file);fileName = getFileNameFromUrl(file);} else if (file.blob instanceof Blob) {// 处理Blob对象blob = file.blob;fileName = file.fileName || 'download';} else if (file.url) {// 处理包含URL的对象blob = await fetchBlob(file.url);fileName = file.fileName || getFileNameFromUrl(file.url);} else {console.error('Invalid file format');return false;}const url = URL.createObjectURL(blob);const a = document.createElement('a');a.href = url;a.download = fileName;document.body.append(a);a.click();a.remove();URL.revokeObjectURL(url);return true;};
}async function fetchBlob(url: string): Promise<Blob> {import.meta.env.PROD && (url = url.replace(/^http:/, 'https:'));const response = await fetch(url);return await response.blob();
}function getFileNameFromUrl(url: string): string {const parts = url.split('/');let fileName = parts[parts.length - 1];// 移除查询参数fileName = fileName?.split('?')[0];// 如果有扩展名,移除扩展名// 如果没有文件扩展名,添加.pngif (!fileName?.includes('.')) {fileName += '.png';}if (fileName?.includes('.') &&!fileName.endsWith('.mp3') &&!fileName.endsWith('.mp4')) {fileName = fileName.replace(/\.\w+$/, '.png');}if (fileName) {return fileName;}return '';
}
公共导出指令
// directive/index.ts
import downLoad from './download';
import type { App } from 'vue';// eslint-disable-next-line unicorn/no-anonymous-default-export
export default (app: App) => { app.directive('downLoad', downLoad);
};
注册指令
//main.ts
import App from './App.vue';
import directive from './directive';
const app = createApp(App);
// 注册自定义指令
directive(app);
使用指令进行下载
// 下载图片
<el-buttonclass="flex-1"size="large"type="primary"v-downLoad="{url: imgContent.image,fileName: `${imgContent.title}.png`,}"
>下载图片
</el-button>
// 下载视频
<div
v-downLoad="{url: item.video_url,fileName: `${item.createTime}.mp4`,
}"
>
<el-icon color="#556477" size="24px"><ep-Download />
</el-icon>
</div>