前言
过去有很多次文件下载的功能,但是都没有记录下来,这次有空就把文件下载的功能从0写一遍,于是就有了这篇文章。 我会从简到难的方式去实现下载功能。从直接下载字符串到简单请求下载文件,最终通过后端返回的文件名来实现动态下载文件。
简单下载
常见的文件下载就是office系列格式文件了,然后再就是txt格式的文件,前端下载通过a标签来实现下载的,其实也是调用浏览器内置功能。
那么把文件格式的mimeType列举一下: 这个篇文章中更全:百度文库
const mimeTypeMapping = { xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', xls: 'application/vnd.ms-excel', docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', doc: 'application/msword', pdf: 'application/pdf', default: 'text/plain' // 默认的文件下载方式};
浏览器内置的下载功能,虽然知道有新出的api,但是我记得得谷歌浏览器97版本及之后才能使用,于是我用了兼容性最好的方式,a标签 + URL.createObjectURL + blob。
const link = document.createElement('a');const urlObject = window.URL || window.webkitURL || window;link.href = urlObject.createObjectURL(new Blob([data], { type: mimeTypeMapping[fileType]}));link.download = fileName;document.body.appendChild(link);
它其实通过创建对象字符串,赋值为a标签的href属性,然后通过超链接下载协议实现文件的下载。
完整代码如下:
// 下载文件export function downloadFile(data, fileName = '未知文件', callback) { const mimeTypeMapping = { xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', xls: 'application/vnd.ms-excel', docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', doc: 'application/msword', pdf: 'application/pdf', default: 'text/plain' // 默认的文件下载方式 }; const fileType = fileName.split('.')[1] || 'default'; const link = document.createElement('a'); const urlObject = window.URL || window.webkitURL || window; link.href = urlObject.createObjectURL( new Blob([data], { type: mimeTypeMapping[fileType] }) ); link.download = fileName; document.body.appendChild(link); link.click(); if (typeof callback === 'function') { callback('success'); } document.body.removeChild(link);}
通过请求下载文件
通过请求下载文件,需要将请求头部的responseType设置为 arrayBuffer 或者 blob。 其它的就是简单的基本操作了。这里我用到的是umi-request,axios或者jquery都是类似的逻辑。
import request from 'umi-request';// 简单下载,超级简单,只能下载前端已知的文件类型的文件,不支持后端动态设置类型。export async function simpleDownload(url, data = {}, method = 'get', fileName) { const options = { responseType: 'arrayBuffer' // blob }; let requestMethod; if (method.toLocaleLowerCase() === 'get') { requestMethod = request.get(url, { params: data, ...options, }); } else { requestMethod = request.post(url, { data, ...options }); } return new Promise((resolve, reject) => { requestMethod .then(res => { const blob = new Blob([res]); downloadFile(blob, fileName, resolve); }) .catch(reject); });}
根据后端返回的文件名及类型来动态下载文件
也是通过请求来下载文件,只是会根据后端返回的响应头部的信息content-disposition来下载文件,这时候你能够知道文件类型及文件名称,而不是前端写死的。
import React from 'react';import { message } from 'antd';import request from 'umi-request';// 发请求下载文件,复杂一点,支持后端动态设置文件类型export async function download(url, data = {}, method = 'get') { const options = { responseType: 'arrayBuffer' // blob }; let requestMethod; if (method.toLocaleLowerCase() === 'get') { requestMethod = request.get(url, { prefix, timeout, ...options }); } else { requestMethod = request.post(url, { data, prefix, timeout, ...options }); } return new Promise((resolve, reject) => { requestMethod .then(response => { // 状态码不对,报错 if (response.status < 200 || response.status >= 300) { const errorData = JSON.parse(String.fromCharCode.apply(null, new Uint8Array(response.data))); message.error(errorData.message); return reject('error'); } // 文件名提取不出来,报错 const disposition = response.headers?.['content-disposition']; const matchGroup = /filename=(.*);?/.exec(`${disposition}`); let fileName = matchGroup && window.decodeURI(matchGroup[0]); if (['', null, undefined].includes(fileName)) { if (response.headers.msg) { message.error(response.headers.msg); } return reject('error'); } // 去掉字符串两边的 " 符号 fileName = fileName.replace(/^\"?([\s\S]{0,})\"?$/, "$1") downloadFile(response.data, fileName, resolve); }) .catch(() => { resolve('error'); }); });}
总结
这些都是比较常见的下载文件的方式,其实也没啥难度,只不过你稍不注意,可能就会踩坑了,比如我从写到测试,就花了近三个小时。 有时你觉得很简单的东西,兴许也会变成很费时费力的东西,所以习惯记录,不然脑子是不会把发生的每一件事每一个细节都给记的死死的,时间久了,就容易忘记,然后就会出现很多小问题。
原文:https://juejin.cn/post/7098511096197152776