JavaScript 瀏覽器端產生 csv 檔案
撰寫一個簡單的物件,在瀏覽器端處理資料並且下載 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 處理也不會是一個大問題就是了。