最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

vue前端文件下載實(shí)現(xiàn)過(guò)程

 更新時(shí)間:2025年12月18日 08:37:48   作者:仲夏夜之夢(mèng)~  
文章介紹了前端通過(guò)將后端返回的文件流轉(zhuǎn)化為blob對(duì)象,并使用FileReader實(shí)現(xiàn)文件下載的完整方式,文章詳細(xì)描述了前端Vue代碼和后端Spring Boot代碼,指出了后端使用不同變量下載文件時(shí)文件格式和名稱(chēng)的問(wèn)題

網(wǎng)上介紹了很多前端文件下載的方式,這里就只給出一種完整的方式:前端將后端返回的文件流轉(zhuǎn)化為blob對(duì)象,搭配FileReader實(shí)現(xiàn)文件下載。

1、前端vue代碼

前端的主要邏輯是,點(diǎn)擊下載按鈕,通過(guò)post請(qǐng)求把 下載路徑傳遞給后端,下面是點(diǎn)擊click以后的執(zhí)行函數(shù)downloadAppendix。

downloadAppendix(fileUrl){
      let params = {};
      params['pathName'] = fileUrl;
      //第一個(gè)參數(shù)是請(qǐng)求方式,第二個(gè)參數(shù)是后端接口的路徑,第三個(gè)參數(shù)是請(qǐng)求攜帶的數(shù)據(jù)(Object類(lèi)型)
      downloadFile('post','/api/download/file',params,null)
},

其中downloadFile函數(shù)的定義,我單獨(dú)放在了downloadUtil.js中,下面的代碼可以直接照搬。

function downloadEvt(url, fileName ) {
    const el = document.createElement('a');
    el.style.display = 'none';
    el.setAttribute('target', '_blank');
    /**
     * download的屬性是HTML5新增的屬性
     * href屬性的地址必須是非跨域的地址,如果引用的是第三方的網(wǎng)站或者說(shuō)是前后端分離的項(xiàng)目(調(diào)用后臺(tái)的接口),這時(shí)download就會(huì)不起作用。
     * 此時(shí),如果是下載瀏覽器無(wú)法解析的文件,例如.exe,.xlsx..那么瀏覽器會(huì)自動(dòng)下載,但是如果使用瀏覽器可以解析的文件,比如.txt,.png,.pdf....瀏覽器就會(huì)采取預(yù)覽模式
     * 所以,對(duì)于.txt,.png,.pdf等的預(yù)覽功能我們就可以直接不設(shè)置download屬性(前提是后端響應(yīng)頭的Content-Type: application/octet-stream,如果為application/pdf瀏覽器則會(huì)判斷文件為 pdf ,自動(dòng)執(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)換請(qǐng)求參數(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'}是對(duì)于數(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實(shí)現(xiàn)文件下載、獲取文件下載進(jìn)度
 * @param {String} method - 請(qǐng)求方法get/post
 * @param {String} url
 * @param {Object} [params] - 請(qǐng)求參數(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',  // 請(qǐng)求頭類(lèi)型
        fileName: '未知文件',                                        // 下載文件名(必填,若為空,下載下來(lái)都是txt格式)
        async: true,                                                // 請(qǐng)求是否異步-true異步、false同步
        token: 'token'                                              // 用戶(hù)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è)置,會(huì)導(dǎo)致下載的文件可能打不開(kāi)
    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) {
            // 通過(guò)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)問(wèn)題,請(qǐng)聯(lián)系管理員}');
                    alert(result.message);
                } else {
                    // 兩種解碼方式,區(qū)別自行百度: decodeURIComponent/decodeURI(主要獲取后綴名,否則低版本瀏覽器會(huì)一律識(shí)別為txt,導(dǎo)致下載下來(lái)的都是txt)
                    let _fileName = this.getResponseHeader('content-disposition').split('filename=')[1];
                    _fileName = decodeURI(encodeURI(_fileName));
                    /**
                     * Blob.type一個(gè)字符串,表明該 Blob 對(duì)象所包含數(shù)據(jù)的 MIME 類(lèi)型。如果類(lèi)型未知,則該值為空字符串。
                     * 對(duì)于pdf:type為application/pdf  同時(shí) a標(biāo)簽 不設(shè)置download屬性, 可以直接預(yù)覽
                     */
                    const blob = new Blob([this.response]);

                    const href = URL.createObjectURL(blob);
                    downloadEvt(href,_fileName);
                    // 釋放一個(gè)之前已經(jīng)存在的、通過(guò)調(diào)用 URL.createObjectURL() 創(chuàng)建的 URL 對(duì)象
                    URL.revokeObjectURL(href);
                }
            };
            // 調(diào)用readAsText讀取文件,少了readAsText將不會(huì)觸發(fā)onloadend事件
            fileReader.readAsText(this.response);
        } else {
            alert('服務(wù)器出現(xiàn)問(wèn)題,請(qǐng)聯(lián)系管理員');
        }
    };
    // send(string): string:僅用于 POST 請(qǐng)求
    ajax.send(queryParams);
}

2、后端springboot代碼

這里有個(gè)奇怪的現(xiàn)象是,我使用其他名字作為下載的文件的名字,文件下載以后的格式全都是txt格式,即便原本是word格式,下載以后也會(huì)變成txt格式,但是使用pathName變量就不會(huì)。

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ā)來(lái)的數(shù)據(jù)
        String pathName = JSONObject.parseObject(params).getString("pathName");

        //拼接成完整的路徑
        String fileUrl = GlobalValueUtil.rootPath + pathName;

        ServletOutputStream out =null;
        ByteArrayOutputStream baos = null;
        try {
            //通過(guò)輸入流獲取文件數(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);
    }
}

測(cè)試結(jié)果如下,能夠下載word格式和txt格式的文件,但是文件名無(wú)法顯示中文

總結(jié)

以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。

相關(guān)文章

  • vue封裝圖片滑塊驗(yàn)證組件的方法

    vue封裝圖片滑塊驗(yàn)證組件的方法

    這篇文章主要為大家詳細(xì)介紹了vue封裝圖片滑塊驗(yàn)證組件,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-03-03
  • 使用vue實(shí)現(xiàn)簡(jiǎn)單鍵盤(pán)的示例(支持移動(dòng)端和pc端)

    使用vue實(shí)現(xiàn)簡(jiǎn)單鍵盤(pán)的示例(支持移動(dòng)端和pc端)

    這篇文章主要介紹了使用vue實(shí)現(xiàn)簡(jiǎn)單鍵盤(pán)的示例(支持移動(dòng)端和pc端),小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2017-12-12
  • 基于vite2+vue3制作個(gè)招財(cái)貓游戲

    基于vite2+vue3制作個(gè)招財(cái)貓游戲

    端午將至,大家都開(kāi)始吃粽子了么?本文將用vite2與vue3開(kāi)發(fā)出一個(gè)招財(cái)貓小游戲,在圖案不停滾動(dòng)的同時(shí)選出可以轉(zhuǎn)出不同的素材最終得到粽子獎(jiǎng)勵(lì),康康你能用多少次才會(huì)轉(zhuǎn)出自己喜愛(ài)口味的粽子吧
    2022-05-05
  • vue使用npm發(fā)布自己的公網(wǎng)包

    vue使用npm發(fā)布自己的公網(wǎng)包

    本文主要介紹了vue使用npm發(fā)布自己的公網(wǎng)包,通過(guò)創(chuàng)建一個(gè)簡(jiǎn)單的npm包,本文詳細(xì)闡述了從創(chuàng)建到發(fā)布的整個(gè)過(guò)程,具有一定的參考價(jià)值,感興趣的可以了解一下
    2023-08-08
  • Vue3結(jié)合TypeScript項(xiàng)目開(kāi)發(fā)實(shí)戰(zhàn)記錄

    Vue3結(jié)合TypeScript項(xiàng)目開(kāi)發(fā)實(shí)戰(zhàn)記錄

    聽(tīng)說(shuō)有的公司已經(jīng)開(kāi)始用vue3了 趕緊打開(kāi)B站學(xué)一下,下面這篇文章主要給大家介紹了關(guān)于Vue3結(jié)合TypeScript項(xiàng)目開(kāi)發(fā)實(shí)戰(zhàn)的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2021-09-09
  • 最基礎(chǔ)的vue.js雙向綁定操作

    最基礎(chǔ)的vue.js雙向綁定操作

    這篇文章主要為大家詳細(xì)介紹了最基礎(chǔ)的vue.js雙向綁定操作,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-08-08
  • 淺談vue-cli 3.0.x 初體驗(yàn)

    淺談vue-cli 3.0.x 初體驗(yàn)

    這篇文章主要介紹了淺談vue-cli 3.0.x 初體驗(yàn),小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2018-04-04
  • vue使用once修飾符,使事件只能觸發(fā)一次問(wèn)題

    vue使用once修飾符,使事件只能觸發(fā)一次問(wèn)題

    這篇文章主要介紹了vue使用once修飾符,使事件只能觸發(fā)一次問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-05-05
  • vue-admin-template模板添加tagsview的實(shí)現(xiàn)

    vue-admin-template模板添加tagsview的實(shí)現(xiàn)

    本文主要介紹了vue-admin-template模板添加tagsview的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2022-04-04
  • vue+element+oss實(shí)現(xiàn)前端分片上傳和斷點(diǎn)續(xù)傳

    vue+element+oss實(shí)現(xiàn)前端分片上傳和斷點(diǎn)續(xù)傳

    這篇文章主要介紹了vue+element+oss實(shí)現(xiàn)前端分片上傳和斷點(diǎn)續(xù)傳,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2021-03-03

最新評(píng)論

平凉市| 宿迁市| 铜川市| 邓州市| 梁山县| 辽阳市| 丁青县| 全州县| 长海县| 松江区| 文化| 自治县| 鄂托克前旗| 柯坪县| 潮安县| 山东| 汉沽区| 巴彦县| 临朐县| 霍邱县| 汉源县| 凤凰县| 商丘市| 广平县| 花莲县| 综艺| 鄂托克前旗| 溧水县| 乌恰县| 淮南市| 福清市| 彰化县| 凤庆县| 易门县| 射洪县| 常宁市| 苏尼特左旗| 仪陇县| 天气| 乌兰浩特市| 弥渡县|