JavaScript 瀏覽器端產生 csv 檔案

撰寫一個簡單的物件,在瀏覽器端處理資料並且下載 csv 檔案的筆記。

JavaScript 瀏覽器端產生 csv 檔案

近日碰到一個需求,要在瀏覽器端用 js 產生 csv 檔案,讓使用者下載,如果是用 server 端產生 csv 檔案,解決這類的需求套件多到數不清,各個語言與 framework 的最佳解早都有了答案。可是在瀏覽器這邊 js 貌似都沒有看到有什麼成熟的套件可以用,猜測不外乎就是瀏覽器與使用者硬體上的限制,導致這類需求都被導向後端解,所以並沒有太多開源的專案。

解決方案

翻了一下 stackoverflow,幾乎都是用連結下載處理,就是往 <a href="xxxx">下載</a>href 內塞字串處理。
如此一來問題的難度就是在資料處理上而已了。

資料格式

我的資料格式是陣列當中乘載著一堆物件,大概如下的資料

[
  {
    id: 123,
    name: 'foo'
  },
  // ...
]

這樣的資料格式,所以我就寫了一個物件來處理。

程式碼

在開發上我沒有考慮太多,只是純粹想要解決問題,所以在擴充或是命名上沒有花太多時間。

class CSVGenerateService {
  constructor(data) {
    this.data = data;
    this.header = this.getHeaderMap();
    this.body = this.getChangeKeys(data);
    this.csvString = this.convertToCSV(this.body);
    this._filename = "";
  }

  getChangeKeys(rows) {
    return rows.map(row => {
      for (const [key, value] of Object.entries(row)) {
        if (this.header[key] == null) {
          row[key] = value;
        } else {
          row[this.header[key]] = value;
          delete row[key];
        }
      }
      return row;
    });
  }

  getHeaderMap() {
    return {
      sid: "系統編號",
      purchase_list_sid: "訂單編號",
      u_num: "會員編號",
      u_acc_name: "銀行戶名",
      u_acc_num: "銀行帳號",
    };
  }

  convertToCSV(arr) {
    const array = [Object.keys(arr[0])].concat(arr);

    return array.map(obj => Object.values(obj).toString()).join("\n");
  }

  download() {
    const csvContent = "data:text/csv;charset=utf-8,\ufeff" + this.csvString;
    const encodedUri = encodeURI(csvContent);
    const link = document.createElement("a");
    link.setAttribute("href", encodedUri);
    link.setAttribute("download", `${this._filename}.csv`);

    document.body.appendChild(link);
    link.click();
  }

  get filename() {
    return this._filename;
  }

  set filename(name) {
    this._filename = name;
  }
}

export default CSVGenerateService;

幾本上就是針對資料轉字串的處理,仔細看的話其實 getHeaderMap 這個方法,可以改成物件外面傳入翻譯,畢竟是給使用者看的,他們並不會知道每個資料欄位代表什麼意思,所以特別用這種方式處理。

getChangeKeys 就是處理把 object keys 抽換成中文,如果找不到翻譯,保留原本的 key。

最終 download 把字串資料,塞進 href 內並且下載 data:text/csv;charset=utf-8,\ufeff\ufeff 為了解決 excel 打開會亂碼的問題,所以務必要加上。

使用

const service = new CSVGenerateService(data);
service.filename = 'whatever'
service.download();

小結

一個很土炮的方式,可是很有用。
只是這種方式,如果資料量大到一個程度,會超過 href 乘載的上限,只限於小量資料,如果資料量太大還是要朝向著後端解的方式前進,既然都會寫 js 的話,那用 node 處理也不會是一個大問題就是了。