vue前端文件下載實現(xiàn)過程
網(wǎng)上介紹了很多前端文件下載的方式,這里就只給出一種完整的方式:前端將后端返回的文件流轉(zhuǎn)化為blob對象,搭配FileReader實現(xiàn)文件下載。
1、前端vue代碼
前端的主要邏輯是,點擊下載按鈕,通過post請求把 下載路徑傳遞給后端,下面是點擊click以后的執(zhí)行函數(shù)downloadAppendix。
downloadAppendix(fileUrl){
let params = {};
params['pathName'] = fileUrl;
//第一個參數(shù)是請求方式,第二個參數(shù)是后端接口的路徑,第三個參數(shù)是請求攜帶的數(shù)據(jù)(Object類型)
downloadFile('post','/api/download/file',params,null)
},其中downloadFile函數(shù)的定義,我單獨放在了downloadUtil.js中,下面的代碼可以直接照搬。
function downloadEvt(url, fileName ) {
const el = document.createElement('a');
el.style.display = 'none';
el.setAttribute('target', '_blank');
/**
* download的屬性是HTML5新增的屬性
* href屬性的地址必須是非跨域的地址,如果引用的是第三方的網(wǎng)站或者說是前后端分離的項目(調(diào)用后臺的接口),這時download就會不起作用。
* 此時,如果是下載瀏覽器無法解析的文件,例如.exe,.xlsx..那么瀏覽器會自動下載,但是如果使用瀏覽器可以解析的文件,比如.txt,.png,.pdf....瀏覽器就會采取預(yù)覽模式
* 所以,對于.txt,.png,.pdf等的預(yù)覽功能我們就可以直接不設(shè)置download屬性(前提是后端響應(yīng)頭的Content-Type: application/octet-stream,如果為application/pdf瀏覽器則會判斷文件為 pdf ,自動執(zhí)行預(yù)覽的策略)
*/
fileName && el.setAttribute('download', fileName);
el.href = url;
console.log(el);
document.body.appendChild(el);
el.click();
document.body.removeChild(el);
};
// 根據(jù)header里的contenteType轉(zhuǎn)換請求參數(shù)
function transformRequestData(contentType, requestData) {
requestData = requestData || {};
if (contentType.includes('application/x-www-form-urlencoded')) {
// formData格式:key1=value1&key2=value2,方式二:qs.stringify(requestData, {arrayFormat: 'brackets'}) -- {arrayFormat: 'brackets'}是對于數(shù)組參數(shù)的處理
let str = '';
for (const key in requestData) {
if (Object.prototype.hasOwnProperty.call(requestData, key)) {
str += `${key}=${requestData[key]}&`;
}
}
return encodeURI(str.slice(0, str.length - 1));
} else if (contentType.includes('multipart/form-data')) {
const formData = new FormData();
for (const key in requestData) {
const files = requestData[key];
// 判斷是否是文件流
const isFile = files ? files.constructor === FileList || (files.constructor === Array && files[0].constructor === File) : false;
if (isFile) {
for (let i = 0; i < files.length; i++) {
formData.append(key, files[i]);
}
} else {
formData.append(key, files);
}
}
return formData;
}
// json字符串{key: value}
return Object.keys(requestData).length ? JSON.stringify(requestData) : '';
}
/**
* ajax實現(xiàn)文件下載、獲取文件下載進(jìn)度
* @param {String} method - 請求方法get/post
* @param {String} url
* @param {Object} [params] - 請求參數(shù),{name: '文件下載'}
* @param {Object} [config] - 方法配置
*/
export function downloadFile(method = 'get', url, params, config){
const _method = method.toUpperCase();
const _config = Object.assign({
contentType: _method === 'GET' ? 'application/x-www-form-urlencoded' : 'application/json', // 請求頭類型
fileName: '未知文件', // 下載文件名(必填,若為空,下載下來都是txt格式)
async: true, // 請求是否異步-true異步、false同步
token: 'token' // 用戶token
}, config);
const queryParams = transformRequestData(_config.contentType, params);
const _url = `${url}${_method === 'GET' && queryParams ? '?' + queryParams : ''}`;
const ajax = new XMLHttpRequest();
ajax.open(_method, _url, _config.async);
ajax.setRequestHeader('Authorization', _config.token);
ajax.setRequestHeader('Content-Type', _config.contentType);
// responseType若不設(shè)置,會導(dǎo)致下載的文件可能打不開
ajax.responseType = 'blob';
// 獲取文件下載進(jìn)度
ajax.addEventListener('progress', (progress) => {
const percentage = ((progress.loaded / progress.total) * 100).toFixed(2);
const msg = `下載進(jìn)度 ${percentage}%...`;
// console.log(msg);
});
ajax.onload = function () {
if (this.status === 200 || this.status === 304) {
// 通過FileReader去判斷接口返回是json還是文件流
const fileReader = new FileReader();
fileReader.onloadend = (e) => {
if (this.getResponseHeader('content-type').includes('application/json')) {
const result = JSON.parse(fileReader.result || '{message: 服務(wù)器出現(xiàn)問題,請聯(lián)系管理員}');
alert(result.message);
} else {
// 兩種解碼方式,區(qū)別自行百度: decodeURIComponent/decodeURI(主要獲取后綴名,否則低版本瀏覽器會一律識別為txt,導(dǎo)致下載下來的都是txt)
let _fileName = this.getResponseHeader('content-disposition').split('filename=')[1];
_fileName = decodeURI(encodeURI(_fileName));
/**
* Blob.type一個字符串,表明該 Blob 對象所包含數(shù)據(jù)的 MIME 類型。如果類型未知,則該值為空字符串。
* 對于pdf:type為application/pdf 同時 a標(biāo)簽 不設(shè)置download屬性, 可以直接預(yù)覽
*/
const blob = new Blob([this.response]);
const href = URL.createObjectURL(blob);
downloadEvt(href,_fileName);
// 釋放一個之前已經(jīng)存在的、通過調(diào)用 URL.createObjectURL() 創(chuàng)建的 URL 對象
URL.revokeObjectURL(href);
}
};
// 調(diào)用readAsText讀取文件,少了readAsText將不會觸發(fā)onloadend事件
fileReader.readAsText(this.response);
} else {
alert('服務(wù)器出現(xiàn)問題,請聯(lián)系管理員');
}
};
// send(string): string:僅用于 POST 請求
ajax.send(queryParams);
}2、后端springboot代碼
這里有個奇怪的現(xiàn)象是,我使用其他名字作為下載的文件的名字,文件下載以后的格式全都是txt格式,即便原本是word格式,下載以后也會變成txt格式,但是使用pathName變量就不會。
package com.example.demo.controller;
import com.alibaba.fastjson.JSONObject;
import com.example.demo.utils.GlobalValueUtil;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.InputStream;
@RestController
@RequestMapping("/download")
public class DownloadFileController {
@PostMapping("/file")
public void downloadFile(@RequestBody String params, HttpServletResponse response) {
//逐一取出前端發(fā)來的數(shù)據(jù)
String pathName = JSONObject.parseObject(params).getString("pathName");
//拼接成完整的路徑
String fileUrl = GlobalValueUtil.rootPath + pathName;
ServletOutputStream out =null;
ByteArrayOutputStream baos = null;
try {
//通過輸入流獲取文件數(shù)據(jù)
InputStream inStream = new FileInputStream(fileUrl);
byte[] buffer = new byte[1024];
int len;
baos = new ByteArrayOutputStream();
while ((len=inStream.read(buffer))!=-1){
baos.write(buffer,0,len);
}
//修改下載文件的名字
response.addHeader("Content-Disposition", "attachment;filename=" + pathName);
response.addHeader("Content-Length", "" + baos.size());
// response.setHeader("filename", pathName);
response.setContentType("application/octet-stream");
out = response.getOutputStream();
out.write(baos.toByteArray());
baos.flush();
baos.close();
out.flush();
out.close();
}catch (Exception e){
e.printStackTrace();
}
// return ReadFileUtil.readFromIOStream(fileUrl);
}
}
測試結(jié)果如下,能夠下載word格式和txt格式的文件,但是文件名無法顯示中文

總結(jié)
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
使用vue實現(xiàn)簡單鍵盤的示例(支持移動端和pc端)
這篇文章主要介紹了使用vue實現(xiàn)簡單鍵盤的示例(支持移動端和pc端),小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-12-12
Vue3結(jié)合TypeScript項目開發(fā)實戰(zhàn)記錄
聽說有的公司已經(jīng)開始用vue3了 趕緊打開B站學(xué)一下,下面這篇文章主要給大家介紹了關(guān)于Vue3結(jié)合TypeScript項目開發(fā)實戰(zhàn)的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),需要的朋友可以參考下2021-09-09
vue-admin-template模板添加tagsview的實現(xiàn)
本文主要介紹了vue-admin-template模板添加tagsview的實現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-04-04
vue+element+oss實現(xiàn)前端分片上傳和斷點續(xù)傳
這篇文章主要介紹了vue+element+oss實現(xiàn)前端分片上傳和斷點續(xù)傳,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-03-03

