iT邦幫忙

6

[前端/JavaScript] 實作匯出excel下載按鈕的超好用套件:ExcelJS(下)- 用React匯出excel (export excel)

  • 分享至 

  • xImage
  •  

有關於ExcelJS這個套件的教學與說明,請先看我的上一篇文章:
[前端/ES6] 實作匯出excel下載按鈕的超好用套件:ExcelJS(上)- 基礎介紹

這一篇呢,我們著重於在react中使用這個套件
以及要來發揮我們react的優點,把這個按鈕包裝成一個通用的 UI component

會特別寫這篇的原因也主要是我自己一開始在查 「react export excel」、「react 匯出excel」
找到的文件我覺得不夠詳盡,而且沒有一個現成的、包裝好的UI可以使用
好啦,廢話不多說,先來做一個最基礎的範例吧!

React + ExcelJS

讓我們把上次的onClick function和react直接做一個簡單的結合如下

  • react + 事件定義 (js版本)
    線上 codesandbox Demo: 簡易react + onClick範例

    import React from "react";
    import ExcelJs from "exceljs";
    
    function ExportExcelButton (){
      function onClick(){
        const workbook = new ExcelJs.Workbook(); // 創建試算表檔案
        const sheet = workbook.addWorksheet('工作表範例1'); //在檔案中新增工作表 參數放自訂名稱
    
    		sheet.addTable({ // 在工作表裡面指定位置、格式並用columsn與rows屬性填寫內容
    	    name: 'table名稱',  // 表格內看不到的,算是key值,讓你之後想要針對這個table去做額外設定的時候,可以指定到這個table
    	    ref: 'A1', // 從A1開始
    	    columns: [{name:'名字'},{name:'年齡'},{name:'電話'}],
    	    rows: [['小明','20','0987654321'],['小美','23','0912345678']]
    		});
    
        // 表格裡面的資料都填寫完成之後,訂出下載的callback function
    		// 異步的等待他處理完之後,創建url與連結,觸發下載
    	  workbook.xlsx.writeBuffer().then((content) => {
    		const link = document.createElement("a");
    	    const blobData = new Blob([content], {
    	      type: "application/vnd.ms-excel;charset=utf-8;"
    	    });
    	    link.download = '測試的試算表.xlsx';
    	    link.href = URL.createObjectURL(blobData);
    	    link.click();
    	  });
    	}
      return (
          <button onClick={onClick}> 下載excel </button>
        )
    };
    

    你會發現其實這個就是把那些上次的function直接複製過來
    然後render 一個button ,按下後會觸發

    一樣是按下之後,就會得到這樣的檔案

    成品

包裝成一個通用的 Component

  • TS版本
    (markdown好像沒有支援TSX所以我只能先選typescript, 最後面return元件的語法顏色有點跑掉)

    import React, { CSSProperties } from "react";
    import ExcelJs from "exceljs";
    
    export type SheetData = {
    	sheetName: string,  // 工作表名稱
        thead: Array<string>,  // 欄位標題,例如:['姓名','年齡','電話']
        tbody: Array<Array<string>>, 
        // 內容,例如:[['小明','20','0987654321'],['小美','23','0912345678']]
        columnWidths?: Array<{number: number, width: number}> //用來指定欄寬的
    }
    
    interface ExportExcelButtonProps {
        fileName: string, // 檔案名稱
        sheetDatas:Array<SheetData> , // 要匯出的表格資料 
        disabled?: boolean, // 是不是要禁止按鈕動作
        buttonRef?: React.MutableRefObject<any>, // 外面用useRef傳進來
        style?: CSSProperties // 按鈕的style
    }
    
    export function ExportExcelButton (props: ExportExcelButtonProps){
      function onClick(){
        const workbook = new ExcelJs.Workbook();
        props.sheetDatas.forEach((sheetData: SheetData)=>{
            const sheet = workbook.addWorksheet(sheetData.sheetName);
            sheet.addTable({
                name: sheetData.sheetName,
                ref: `A1`, // 從A1開始
                headerRow: true,
                columns: sheetData.thead.map((s)=>{ return {name: s}}),
                rows: sheetData.tbody
            });
            if (sheetData.columnWidths) {
              sheetData.columnWidths.forEach((column)=>{
                  sheet.getColumn(column.number).width = column.width
              });
            }
        })
    
        // 表格裡面的資料都填寫完成之後,訂出下載的callback function
    		// 異步的等待他處理完之後,創建url與連結,觸發下載
        workbook.xlsx.writeBuffer().then((content: ExcelJs.Buffer) => {
          const link = document.createElement("a");
          const blob = new Blob([content], {
            type: "application/vnd.ms-excel;charset=utf-8;"
          });
          link.download = `${props.fileName}.xlsx`;
          link.href = URL.createObjectURL(blob);
          link.click();
        });
      };
    
      const style: CSSProperties = {
          borderRadius: '5px',
          ...props.style
      }
    
      return (
          <button
              ref={props.buttonRef}
              disabled={props.disabled}
              onClick={onClick}
              style={style}
          >
            匯出excel
          </button>
        )
    };
    
    export default ExportExcelButton;
    
  • JS版本

    import React from "react";
    import ExcelJs from "exceljs";
    
    export function ExportExcelButton (props){
      function onClick(){
        const workbook = new ExcelJs.Workbook();
        props.sheetDatas.forEach((sheetData)=>{
            const sheet = workbook.addWorksheet(sheetData.sheetName);
            sheet.addTable({
                name: sheetData.sheetName,
                ref: `A1`, // 從A1開始
                headerRow: true,
                columns: sheetData.thead.map((s)=>{ return {name: s}}),
                rows: sheetData.tbody
            });
            if (sheetData.columnWidths) {
              sheetData.columnWidths.forEach((column)=>{
                  sheet.getColumn(column.number).width = column.width
              });
            }
        })
    
        // 表格裡面的資料都填寫完成之後,訂出下載的callback function
    		// 異步的等待他處理完之後,創建url與連結,觸發下載
        workbook.xlsx.writeBuffer().then((content) => {
          const link = document.createElement("a");
          const blob = new Blob([content], {
            type: "application/vnd.ms-excel;charset=utf-8;"
          });
          link.download = `${props.fileName}.xlsx`;
          link.href = URL.createObjectURL(blob);
          link.click();
        });
      };
    
      const style = {
          borderRadius: '5px',
          ...props.style
      }
    
      return (
          <button
              ref={props.buttonRef}
              disabled={props.disabled}
              onClick={onClick}
              style={style}
          >
            匯出excel
          </button>
        )
    };
    
    export default ExportExcelButton;
    
  • 使用範例

    import ExportExcelButton from "./ExportExcelButton";
    
    function App(){
    	const downloadData = [
            { 
              sheetName: `工作表1`,
    	      thead: ['姓名','年齡','電話'],
    	      tbody: [['小明','20','0987654321'],['小美','23','0912345678']],
    	      columnWidths: [{number: 1, width:20},{number: 2, width:10},{number: 3, width:40}]
    		},
            { 
              sheetName: `工作表2`,
    	      thead: ['姓名','座號'],
    	      tbody: [['小明','1'],['小美','2']],
    	      columnWidths: [{number: 1, width:20}]
    		}
    	];
    
    	return (
    		<ExportExcelButton
    		  fileName={'測試的試算表'}
    		  sheetDatas={downloadData}
    		/>
    	)
    }
    
    

    如此一來,就可以將JSON格式的資料,轉成excel下載
    而且對component外來說,只要整理好格式,試算表裡面就能夠有好幾張工作表

    這邊也一樣提供以上的完整範例
    線上codesandbox demo: TS componentJS component

    也歡迎大家根據這個示範,去準備自己的UI庫

    踩到的坑

    我個人是使用react搭配 nx 這套toolchain 來進行開發的
    題外話提一下nx這套toolchain
    他有好用的環境建置,能做應用整合、共用LIB、方便搭配jest、e2e等優點
    創建應用之後會有設置production這個configurations,幫忙分出測試的打包配置與正式發佈的配置
    但事情就這麼發生了。

    • 錯誤訊息:webpack.WebpackError is not a constructor

      使用環境: 使用nx 這套toolchain 建置react專案

      問題發生: import 這個套件之後,就發現build專案時,只要選擇production 配置
      就會執行失敗,並噴出錯誤訊息: webpack.WebpackError is not a constructor

    解決方式:
    主要是引入該套件之後,會產生大量warning
    而預設設定在webpack.json中的production參數如下
    https://ithelp.ithome.com.tw/upload/images/20220324/201357504JaarSBcZk.png
    注意到budgets的部分,裡面的maximumWarning 設置了最大warning為2mb
    所以只要把這個數值拉高就可以了 (個人把他直接拉高到10mb)

    等等,就這樣?

    對。就這樣。我從錯誤訊息中根本看不出來是噴很多警告的關係,所以他花了我一小時在那邊爬文
    如果各位引入套件之後,有類似的情形,可以先找找你家的webpack.json
    把警告和錯誤訊息的上限拉高看看

結語

exceljs這個套件就介紹到這邊
本來有想要示範怎麼用react做一個可以異步取得資料的按鈕
不過想想感覺已經跟這個套件本身脫離關係了
比較像是遷就他 觸發 => 建立表格、塞資料、觸發下載
這個機制所必須做的行為,跟..react 還有promise比較有關

如果有人有留言敲碗的話,我再寫一篇好了
就這樣!希望文章都有幫助到大家~讓大家少踩一點坑
還有讓exceljs的文章多一點繁體字的介紹...

喜歡這篇文的話可以幫我點一個喜歡~ 我會很開心XDD (真是虛榮的女人

以上!


圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

0
zen1231
iT邦新手 5 級 ‧ 2022-12-06 17:13:33

您好:

在閱讀完文章及實作後,發現匯出rows: sheetData.tbody僅能匯出最後一筆,也有使用forEach,不知道是不是我漏了什麼?

謝謝

你要貼一下你的程式碼嗎?不確定你帶入的格式之類的

zen1231 iT邦新手 5 級 ‧ 2023-02-13 10:17:16 檢舉

不好意思,現在才看到回覆。已經有解決了,謝謝您

我要留言

立即登入留言