原生JS實現后端文件流導出Excel(附Node后端代碼)


原生JS實現后端文件流導出Excel(附Node后端代碼)


導出文件一般是這兩種方式:第一種是后端返回一個路徑然后前端直接跳轉下載。第二種也是本文使用的方式則是后端返回文件流,前端下載。第二種一般是ajax操作,所以還有可能后端返回的是json格式的錯誤消息,這些都需要前端做相應的處理。

現在前端技術越來越成熟,這個操作完全可以直接用原生的js實現,這次直接拋開JQuery/axios等ajax庫,來學習下原生js如何實現該操作。(學習原生js,雖然可能用不到。但是能加強你對現成的基於原生js封裝的庫的理解,所以是很有必要的)

下文將依次介紹:

  • 后端效果
  • XMLHttpRequest實現文件下載
  • 處理后端的返回結果
  • Fetch實現文件下載
  • Node.js后端導出代碼
  • demo演示

注意:本文很多代碼都用了ES6的新特性,有ES5需求的可以用babel轉一下。

后端效果

先看看后端的效果是啥樣的,我們再寫前端代碼。
先來看看模擬數據,一個很簡單的人員列表,數據是這樣的:
后端數據
為了方便測試api我沒有限制Request Method,所以我們可以直接在瀏覽器看效果。
根據名稱搜索,沒有數據時返回一個提示的json:
后端json提示
搜索名稱中有'小'字的數據,可以下載Excel,excel中的數據是查詢結果:
查詢導出
導出的Excel
再看看后端的接口信息,中文部分被瀏覽器自動轉碼了:
后端接口信息
看完后端的效果,咱們可以開始寫前端代碼。

XMLHttpRequest實現文件下載

先上代碼:

        //XMLHttpRequest實現文件下載
        function exportExcelByXhr(name = '') {
            var xhr = new XMLHttpRequest();
            xhr.timeout = 3000;
            xhr.ontimeout = (event) => alert("請求超時!");
            xhr.responseType = 'blob';
            xhr.open('GET', baseURL + name, true);
            xhr.onreadystatechange = function () {
                if (xhr.readyState == XMLHttpRequest.DONE && xhr.status == 200) {
                    //下載操作:文件流在xhr.response中
                    handleBlob(xhr.response);
                }
            }
            xhr.send(null);//請求數據
        }

流程也很簡單,其實就是:
創建一個XMLHttpRequest -> 設置類型為blob -> 傳入查詢參數請求接口 -> 回調函數中處理響應結果
好像挺簡單的,沒啥好說的了。
handleBlob是我自己封裝的js函數,對后端數據的處理(包括下載Excel,以及json的處理)在這個里面。

處理后端的返回結果

想要處理后端的返回結果,我們得先知道xhr.response是個啥。先打印出來看看吧
console.log(xhr.response);
json的情況:
json的response
文件流的情況:
excel的response
好家伙,既然已經知道了是啥玩意,我們就可以直接判斷然后做相應的處理了。
只需要判斷type是不是json,是json就轉json,不是就轉文件就行。
上代碼:

        //處理后端返回的Blob
        function handleBlob(responseData) {
            if (responseData.type === "application/json") {
                const reader = new FileReader();
                reader.onload = function () {
                    const { msg } = JSON.parse(reader.result);
                    alert(`后端消息:${msg}`);
                }
                reader.readAsText(responseData, 'utf-8');
            } else {
                // 如果后端響應頭中沒有指定 MIME Type , 就需要前端轉一下
                // const blob = new Blob([responseData], {
                //     type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
                // });
                openDownloadFile(responseData, '列表');
            }
        }

目前代碼實現:如果是文件就下載,不是就彈出消息。
下載文件我專門搞了個openDownloadFile方法,里面沒有ES6的語法,有需要的可以直接拿去用。
Blob對象和url都可以導出。

        /**
         * 下載文件
         * @param {any} urlObj 要下載的地址 或者 Blob對象
         * @param {any} saveName 文件保存的名字 可選參數
         */
        function openDownloadFile(urlObj, saveName) {
            var openDownload = function (href, fileName) {
                var downloadElement = document.createElement('a');
                downloadElement.target = "_blank";
                downloadElement.href = href;
                downloadElement.download = fileName || ""; // HTML5新增的屬性,指定保存文件名,可以不要后綴
                document.body.appendChild(downloadElement);
                downloadElement.click(); // 點擊下載
                document.body.removeChild(downloadElement); // 下載完成移除元素
            }

            if (typeof urlObj == 'object' && urlObj instanceof Blob) {
                if (window.navigator && window.navigator.msSaveOrOpenBlob) {
                    //針對於IE瀏覽器的處理, 因部分IE瀏覽器不支持createObjectURL
                    window.navigator.msSaveOrOpenBlob(urlObj, saveName);
                } else {
                    var url = window.URL.createObjectURL(urlObj); // 創建blob地址
                    openDownload(url, saveName);
                    window.URL.revokeObjectURL(url); // 釋放掉blob對象創建的地址
                }
            } else {
                openDownload(urlObj, saveName);
            }
        }

Fetch實現文件下載

Fetch是2017年甚至更早出來的一個可以代替XMLHttpRequest做ajax請求的方式。雖然好像出來好幾年了,但是IE所有版本都是不支持的,其它瀏覽器在2017年之后發布的版本應該可以用。同時Fetch不屬於ECMAScript的范疇,它屬於Web API
直接上代碼,也挺簡單的:

        //Fetch實現文件下載
        function exportExcelByFetch(name = '') {
            fetch(baseURL + name, {
                method: 'GET', // or 'PUT'
                body: null, // data can be `string` or {object}!
                headers: new Headers({
                    'responseType': 'blob'
                })
            })
                .then(response => response.blob())
                .then(data => {
                    handleBlob(data);
                });
        }

Node.js后端導出代碼

搞了這么久前端咱們來看看后端怎么實現的吧。
代碼:

const Koa = require('koa');
const XLSX = require('xlsx');
const cors = require('koa2-cors');

const app = new Koa();

const titles = ['姓名', '年齡', '性別'];
const data = [
  { name: '陳小瓦', age: 18, sex: '男' },
  { name: '李飛', age: 33, sex: '男' },
  { name: '林妹妹', age: 22, sex: '女' },
  { name: '小明', age: 10, sex: '男' },
  { name: '小紅', age: 9, sex: '男' },
  { name: '小軍', age: 11, sex: '男' },
  { name: '小麗', age: 18, sex: '女' },
  { name: '甲', age: 45, sex: '男' },
  { name: '乙', age: 21, sex: '男' },
  { name: '丁', age: 55, sex: '女' },
  { name: '武丑', age: 100, sex: '男' },
  { name: '子牛', age: 101, sex: '男' },
  { name: '鹽虎', age: 102, sex: '男' },
  { name: '峰兒', age: 26, sex: '男' },
  { name: '坤雞', age: 22, sex: '男' },
];

app.use(cors());

app.use((ctx, next) => {
  if (ctx.path === '/') {
    next();
    return;
  }
  //不是根目錄訪問全部返回404
  ctx.status = 404;
});

app.use((ctx, next) => {
  const name = ctx.query["name"];
  //獲取查詢條件查詢
  const queryData = data.filter(p => p.name.includes(name));

  let result, mimeType, filename;

  if (queryData.length > 0) {

    const xlsData = [titles];
    for (const item of queryData) {
      let d = [item.name, item.age, item.sex];
      xlsData.push(d);
    }
    //json轉excel
    let sheet = XLSX.utils.aoa_to_sheet(xlsData);
    //excel轉node文件流
    result = sheetToBuffer(sheet);
    mimeType = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';
    filename = `attachment;filename=${encodeURIComponent('人員列表')}.xlsx`;
  } else {
    result = { msg: '無數據可導出' };
    mimeType = "application/json; charset=utf-8";
  }

  ctx.body = result;
  ctx.set("Content-Type", mimeType);
  if (filename) {
    ctx.set("Content-Disposition", filename);
  }
  ctx.status = 200;
});

app.listen(3001);

/**
 * 獲取excel文件流
 */
function sheetToBuffer(sheet, sheetName) {
  sheetName = sheetName || 'sheet1';
  let workbook = {
    SheetNames: [sheetName],
    Sheets: {}
  };
  workbook.Sheets[sheetName] = sheet;
  // 生成excel的配置項
  let wopts = {
    bookType: 'xlsx', // 要生成的文件類型
    type: 'buffer'
  };
  let wbout = XLSX.write(workbook, wopts);
  return wbout;
}

說下流程:
利用filter和includes篩選出符合條件的數據 -> 把excel表頭和數據拼在一起 -> js數組對象轉excel -> excel轉流 -> 最后返回文件流並且設置響應頭

demo演示

放了個文本框和按鈕,點擊按鈕可以導出:
excel的response
本文全部源碼都在這個demo里,可自行下載使用:
https://gitee.com/cluyun/excelexportdemo


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM