上传
  1. form input 上传
    设置form的action为后端页面,enctype=“multipart/form-data”,type=“post”
<form action='uploadFile.php' enctype="multipart/form-data" type='post'>
  <input type='file'>
  <input type='hidden' name='userid'>
  <input type='hidden' name='signature'>
  <button>提交</button>
</form>

form表单提交数据后会自动跳转到action指定的页面,为了禁止页面跳转,可以在页面中新建一个空的ifame,比如name=‘upload’,然后设置form的target=“Uploader”,form有一个target的属性,规定在何处打开action,这样form提交数据后就会仍停留在当前页。代码如下:

form action='uploadFile.php' enctype="multipart/form-data" type='post'  target="uploader1">
<input type='file'>
<button>提交</button>
</form>

<ifrmae name='upload' id='uploader1'></iframe>

这样写的另一个好处是,可以知道什么时候上传完成并接收到后端的回调结果。比如上面这个例子,文件数据发送到了 ‘uploadFile.php’,假设该页面处理完数据后返回了一个地址,该地址会被写入到之前的iframe中。所以在ifame的onload函数触发时,也就是上传完成后,可以在iframe中读取到后端返回的数据。

var iframe = document.getElementById('upload1'); iframe.onload = function () {
var doc = window.frames['uploader1'].document; var pre =
doc.getElementsByTagName('pre'); var obj = JSON.parse(pre[0].innerHTML); }

使用这种方法时需要注意,iframe有跨域限制,创建出来的iframe的地址如果和当前页面地址不同源,会报错。这种情况下,建议大家在iframe的onload函数中,再次向后端请求一个接口获取文件地址,而不是直接去iframe里读取。或者返回这样的数据

<script type="text/javascript">
window.top.window[callback](data)
</script>

callback是和前端约定好的名字,上传完成后触发该函数并返回后端数据。


  1. js formData

https://developer.mozilla.org/zh-CN/docs/Web/API/FormData/append

const data = new FormData();
data.append('file', file, file.name); // 表单 key, 表单 value, 传给服务器 fileName

axios.post({ // 上传接口
  url,
  data,
  params: {
    
  }
})

下载图片

前端下载图片分为两种情况同源图片下载非同源图片下载同源图片下载方案

  1. 通过 a 标签自带 download 属性实现下载:<a href="./img/logo.png" download="logo.png"></a>
  2. 通过 js 创建 a 标签并触发 click
const imgDownload = (url, fileName) => {
	const link = document.createElement('a');
	link.href = url;
	link.download = fileName;
	link.click()
};
imgDownload('./img/logo.png', 'logo.png');

非同源图片下载方案

  1. 将图片转化为blob或者base64
// 转为 blob
function urlToBlobAndDownload(url){
	//实例化一个img对象
	const img = new Image();
	//设置img的图片路径
	img.src = url;
	//设置跨域解决
	img.setAttribute('crossOrigin', 'Anonymous');
	//img加载完后处理
	img.onload = function() {
		//创建一个canvas对象
        const canvas = document.createElement('canvas')
        //把图片的宽度设为canves的宽度
        canvas.width = img.width
        //把图片的高度设为canves的高度
        canvas.height = img.height
        //创建一个2d画布
        const ctx = canvas.getContext('2d')
        // 将img中的内容画到画布上
        ctx.drawImage(img, 0, 0, canvas.width, canvas.height)
        // 将画布内容转换为Blob
        canvas.toBlob((blob) => {
            // blob转为同源url
            let blobUrl = window.URL.createObjectURL(blob)
            // 创建a链接
            const a = document.createElement('a')
            a.href = blobUrl
            a.download = ''
            // 触发a链接点击事件,浏览器开始下载文件
            a.click()
        })
    }
}

// 转为 base64
function urlToBase64AndDownload(url){
	//实例化一个img对象
	const img = new Image();
	//设置img的图片路径
	img.src = url;
	//设置跨域解决
	img.setAttribute('crossOrigin', 'Anonymous');
	//img加载完后处理
	img.onload = function() {
		//创建一个canvas对象
        const canvas = document.createElement('canvas')
        //把图片的宽度设为canves的宽度
        canvas.width = img.width
        //把图片的高度设为canves的高度
        canvas.height = img.height
        //创建一个2d画布
        const ctx = canvas.getContext('2d')
        // 将img中的内容画到画布上
        ctx.drawImage(img, 0, 0, canvas.width, canvas.height)
        // 将画布内容转换为base64
        let base64 = canvas.toDataURL()
        // 创建a链接
        const a = document.createElement('a')
        a.href = base64
        a.download = ''
        // 触发a链接点击事件,浏览器开始下载文件
        a.click()
    }
}

坑:以上两种方案生成的图片格式大部分为 png 格式正常,但是 gif 的下载就为一帧,下面这种方案可以实现 gif 的下载,不改变格式

  1. 通过axios请求来实现图片下载
function downloadByAxios(url,name){
	axios({
		//设置图片路径
		url:url,
		//设置请求方法为get请求
		method:'get',
		//设置相应类型为blob
		responseType: 'blob'
	}).then(
		//得到的是一个blob对象
		res => {
			let url = window.URL.createObjectURL(res.data)
            const a = document.createElement('a');
             a.href = url
             a.download = name
             a.click()
		}
	)
}

坑:如果是请求oss上的图片的话,会有一个跨域的提示,解决方案是去oss服务控制台设置跨域规则


下载接口返回的二进制流

在前端下载中,一般使用 a 标签下载,但有时候要通过后端接口,比如文件资源放在一个有网络限制的非公网环境,也就是说工网环境通过 url 不能请求到资源
这时候一般后端会帮你读这个文件,然后接口返回二进制流给前端下载

二进制流大概长这样

ÿØÿàJFIFÿÛC
&/"$&81;:7165=EXK=ATB56MiNT[^cdc<Jltl`sXac_ÿÛC--_?6?__________________________________________________ÿÀk "ÿÄÿÄH!1A"QRaq‘234STr’#¡±B¢Á$5Cbs‚Ñ%Dðcƒ“ñUádÿÄÿÄ'!1AQ"2Baq#ÿÚ?÷çKªu„;gáÄ«²’ãÔ¸„ÜÜó^ÖynQßÁ‡+ºËžç—õXÌã̬+t@tÜ@9BùørÏ/oVzÆoJÍÎçŒ×êG´ÙفVÙV3€"[]oQ8àî/uÛö§våû—~”.îÑYɶ·S2fÃÜЬ®0=ý&[¾?ËÕ2òqøsœÃg\®gp$®“‹ïbè¯ÍTª{]”4XŽ+>OÆok†|®´„Hö›µä~’¨ÉfIéu®rÚ2[+Hãu›,röÞ~9c¸ˆÛöùúPDDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DEÊ.ÇýW
wžk‹<f)maÉ|ï×al•éý=ÕÒ5nŠá’’4²¨¬C3Næ‘rx¯‡S',¶i¼u1‡³½®¦©Œx0ë‹Þʳ_JÒ•×yf§”‡9®ºôóülۏý"ó'0ò*û]€KAì±sÁ„I~oŽªÃ*XnÖô’xs“äòco¥‡A—5ºNà:bã+³¥,õ!Å‚"N]n£šQ-ŽP5?“¦£^,l½¢[3Ö3Ä-/­•ŠXŒ²ƒn‹OçñãrÊHïÔí×·D"u,¯¿:‘óDEA§ÿ8!²
ÒHe1ÆÐH$•›––&K(oSØgš^§°Ï59ÓI‘CzžÃ<Òõ=–y§?ðÒdQyì3Í?¼öæœÿÃIQCzžÃ<ÖoSØgšsÿ
%E
ê{óKÔöæœÿÃI‘EzžË<Òõ=†y§?ðÒTQyì³Í?¼öæœÿÃIQEýç°Ï5†Hó!ŽF€m}
Ó™¤Èˆ´âTrí³Ç‚ñ(¦X̦ªË¥‡‹ô_¢ÇáßýE}ù|mþîj‡ëéþ‰øúÿE}þOþöj‡¯ôOÃÿÖ¯¢Éã?{?µÃ¿×ú'áßëýô²Ëã?{?µ&áͤí¦FØÀkF‹¢.˜xpÃÔg,òËÝu`DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DD=±ÿ(R•=±ÿ(\ò÷7©{£§‘íâÖ’˜Ô-\R0Ä^á–ÙOU×r¼E1:tì¼Ìm©¨’¢’(ŽÀ–ôƒüBÚ/CW‰1´sÔMΡö,-µáª¿YZcª‚ž'09Ç3órh\¦MWZØ šJYó8m!ØÇn<ôRÏEqÊa'M²FàGpàZž¾ªYè#ÏG¦â5ʺÕ1ÔÄ$ˆÜ0{×%“LÚUñHZØÜá	Âä­CFÚŠ8^%’ƹïˆå.6惦±}.¹£

有两种做法

通过 Blob

如果后端返回 Blob 时,有两处坑。文件名下载后打不开 文件名可以前端写死,但最好用后端返的 Content-disposition 这个在 network 内可以看到,但是前端不能直接获取。
需要后端设置 Access-Control-Expose-Headers: Content-disposition 使得浏览器将该字段暴露给前端

Eg:

请求参数添加 responseType: 'blob'

axios({
  method: 'post',
  responseType: 'blob', // 必须设置否则下载结束后打不开
  data
}).then(({ data: ByteStream, headers }) => {
  /* 准备工作
  	 通过 content-disposition 获取文件名,需要后端设置
  	 通过 content-type 获取 new Blob 参数, 指定文件 MIME 类型
  */
  let { 'content-disposition': FileName, 'content-type': type } = headers;
  FileName = decodeURIComponent(FileName.replace(/.*filename=/, '')); // decodeURIComponent 解码出文件名 replace看具体后端返什么
  type = type.replace(';charset=utf-8', ''); // 同上
  
  /* 创建 blob 进行下载 */
  const blob = new Blob([ByteStream], { type });
  if ('download' in document.createElement('a')) {
    const elink = document.createElement('a');
    elink.download = FileName;
    elink.href = URL.createObjectURL(blob);
    elink.style.display = 'none';
    document.body.appendChild(elink);
    elink.click();
    URL.revokeObjectURL(elink.href);
    document.body.removeChild(elink);
  } else {
    navigator.msSaveBlob(blob, FileName); // 兼容 IE10 +
  }
});

  1. 通过 window.open
    这个办法很简单,但前提是后端接口是 get 请求,post 没测试过,但 post 好像不能直接通过 open 打开
    如果不是 get 请和后端商量(打一架)
const { fileId, fileName } = file;
open('/api_jur/file/downloadSingle?fileId=' + fileId); // vue-cli 环境, /api_jur 是后端 ip+port, 脚手架配了对应的反向代理

a download

只能用 http 协议,本地打开不管用
href 不能写完整的 http 网络地址,只能写相对服务器的地址

<a
  href='https://www.baidu.com/img/flexible/logo/pc/result@2.png'
  download='logo'
>
  只能新标签打开 不能下载
</a>
<a href='logo.png' download='logo'>
  正常下载
</a>