需求說明:
后端接口返回附件列表(含URL),這些附件存在阿里雲OSS上。一期需求是實現對列出的附件進行點擊下載,也就是每次只下載點擊的附件(PDF | 圖片),使用一段時間后制定二期需求時,增加批量下載功能,需要打包在一起。附言:本文所寫都是純前端實現,后端接口只需要返回路徑即可。
單獨下載圖片:
直接使用<a>標簽,會存在這樣的問題:同源鏈接點擊后直接在瀏覽器打開,通過在<a>標簽內指定download屬性可以避免,但是非同源鏈接卻不行。所以通過Canvas + JS方式實現點擊圖片直接下載,這里要解決這么幾個問題:1、非同源及跨域問題;2、Canvas圖片下載限制問題。同源問題很好解決,只需要一行代碼:
image.setAttribute('crossOrigin', 'anonymous')
為了解決Canvas圖片像素大小的限制,轉為blod流數據,下面附上代碼:
/** * @param {Number} imgsrc 圖片路徑 * @param {Number} name 自定義圖片名稱 * 直接使用canvas下載有圖片像素大小的限制,所以轉成blod流下載 **/ downloadIamge (imgsrc, name) { // 下載圖片地址和圖片名 let image = new Image() // 解決跨域 Canvas 污染問題 image.setAttribute('crossOrigin', 'anonymous') let fun = this.dataURLtoBlob image.onload = function () { let canvas = document.createElement('canvas') // 創建Canvas canvas.width = image.width canvas.height = image.height let context = canvas.getContext('2d') context.drawImage(image, 0, 0, image.width, image.height) // Canvas畫圖 let url = canvas.toDataURL({format: 'png', multiplier: 4}) // 通過toDataURL()方法將圖像轉為url var blob = fun(url) var objurl = URL.createObjectURL(blob) // 轉成blod let a = document.createElement('a') // 生成一個a元素 let event = new MouseEvent('click') // 創建一個單擊事件 a.download = name || "'photo" // 設置圖片名稱 a.href = objurl // 將生成的URL設置為a.href屬性 a.dispatchEvent(event) // 觸發a的單擊事件 } image.src = imgsrc + '?=' + Math.random() // 加入隨機數 解決跨域不能下載問題 }, /** * @param {string} dataurl 圖像地址 * 轉blod流 */ dataURLtoBlob (dataurl) { let arr = dataurl.split(',') let mime = arr[0].match(/:(.*?);/)[1] let bstr = atob(arr[1]) let n = bstr.length let u8arr = new Uint8Array(n) while (n--) { u8arr[n] = bstr.charCodeAt(n) } return new Blob([u8arr], {type: mime}) }
在實現的時候,如果這樣指定image的src屬性:
image.src = imgsrc
在運行之后,發現並沒有解決非同源圖片直接打開的問題,所以在下載地址后加了隨機數
image.src = imgsrc + '?=' + Math.random()
打包批量下載:
這里使用了其他的包:JSZip(用於文件打包)和 file-saver(用於下載),因為是在Vue項目中使用,直接在項目中下載
npm install jszip
npm install file-saver
在需要使用的文件中引入:
import JSZip from "jszip";
import FileSaver from "file-saver";
下載主方法代碼如下:
// <button @click="down">批量下載</button> async down() { try { let zip = new JSZip(); for (let i = 0; i < this.arr.length; i++) { let lst = this.arr[i].split("."); let fileType = lst[lst.length - 1]; if (fileType.toLocaleUpperCase() === "PDF") { await this.getFile(this.arr[i]).then(pdf => { zip.file("HelloPDF.pdf", pdf, { binary: true }); }); } else { await this.getBase64Image(this.arr[i]).then(res => { zip.file("HelloIMG" + i + "." + fileType, res, { base64: true }); }); } } this.downImg(zip); } catch (err) { console.log("err", err); } },
這里使用了async/await來進行文件異步操作,確保zip填充操作執行完之后,才執行zip的下載。
downImg(zip) { zip .generateAsync({ type: "blob" }) .then(content => { let fileName = "批量下載.zip"; FileSaver.saveAs(content, fileName); }); }, //****傳入圖片鏈接,返回base64數據 getBase64Image(url) { return new Promise((resolve, reject) => { var base64 = ""; var img = new Image(); img.setAttribute("crossOrigin", "Anonymous"); img.onload = () => { base64 = this.image2Base64(img); resolve(base64.split(",")[1]); }; img.onerror = () => reject("加載失敗");
// 這里可能會有跨域失敗的問題,解決方案同上,url + 隨機數 img.src = url; }); }, image2Base64(img) { var canvas = document.createElement("canvas"); canvas.width = img.width; canvas.height = img.height; var ctx = canvas.getContext("2d"); ctx.drawImage(img, 0, 0, img.width, img.height); var dataURL = canvas.toDataURL("image/png"); return dataURL; }, //****傳入文件鏈接,返回arraybuffer數據 getFile(url) { return new Promise((resolve, reject) => {
// 這里的$http是Vue里的axios this.$http({ method: "get", url, responseType: "arraybuffer" }) .then(data => { resolve(data.data); }) .catch(error => { reject("PDF加載失敗:" + error); }); }); },
以上,實現方式記錄。
