一個普通的晚上,普通的我聽着普通disco回到普通的家,不普通的老婆讓我做一件普通的事情:導數據
因爲種種原因,只能在前端通過控制檯腳本導數據,而且有這幾種類型的數據:
1. 查詢列表接口,並導出一個 Excel 表格;
2. 查詢列表接口,分別將每一行數據導出一個文本文件;
3. 查詢列表接口,基於每一行數據的 ID 查詢詳情接口,再將詳情數據導出爲文本文件。
爲了晚上不用睡地板,我趕緊抄起鍵盤一頓操作
一、接口請求
由於是通過控制檯腳本請求,所以只能用原生 JavaScript 編寫,也沒辦法引入 npm 庫
好在不用擔心兼容性問題,於是 fetch 成爲發起請求的首選方案
首先是 GET 請求,比較簡單,簡單配一下請求頭就行:
function GET(url) {
return fetch(url, {
method: "GET",
headers: new Headers({
'Accept': 'application/json, text/plain, */*',
'Content-Type': 'application/x-www-form-urlencoded',
}),
credentials: 'include',
})
.then(response => response.json())
.then(json => new Promise((res) => {
res(json.data)
})
)
}
POST 請求會稍微複雜一點,需要根據 Content-Type 調整請求體 body
如果 Content-Type 接受 application/json 就還好,直接通過 JSON.stringify 處理即可
而對於 application/x-www-form-urlencoded 類型,通常是通過 qs.stringify 處理
但由於不能使用 npm 倉庫,只好手寫一個 stringify 函數
function POST(url, data) { return fetch(url, { method: 'POST', headers: new Headers({ Accept: 'application/json, text/plain, */*', 'Content-Type': 'application/x-www-form-urlencoded', }), credentials: 'include', body: stringify(data), }) .then((response) => response.json()) .then( (json) => new Promise((res) => { res(json.data); }), ); } function stringify(data) { return Object.keys(data) .map((k) => `${k}=${data[k]}`) .join('&'); }
二、文件導出
和服務端不同,前端應用本身不具備讀寫文件系統的能力,只能通過特定的頁面元素調用瀏覽器 API 實現
比如讀取文件只能通過主動觸發 <input type="file"> 實現,寫入文件只能通過 <a href="" download="" /> 觸發下載功能
// 通過 <a/> 下載文件,content 可能是一段富文本
function writeFile(fileName, content) {
const a = document.createElement('a');
const div = document.createElement('div');
div.innerHTML = content;
// 通過 innerText 過濾掉富文本中的 html 標籤,得到純文本
const text = div.innerText || '';
const blob = new Blob([text], { type: 'text/plain' });
a.download = fileName;
a.href = URL.createObjectURL(blob);
a.click();
}
在這次的導出需求中,會同時創建多個下載任務。首次觸發時,谷歌瀏覽器會提示“是否允許下載多個文件”,選擇“允許”就好
然而即便允許多文件下載,瀏覽器最多同時創建 10 個下載任務,這個問題可以通過異步創建下載任務的方式解決
// 就是有點費回車鍵
三、導出表格
準備就緒,首先處理第一種需求:查詢列表接口,並導出一個 Excel 表格
在正常的工作環境下,可以使用 js-xlsx 生成 .xlsx文件。現在一切從簡,可以用一個替代方案:製表符 tab
如果我們複製下圖這樣的表格
然後粘貼到記事本或者任何文本編輯器,會得到這樣的結果
反過來,如果我們生成這樣的文本,再複製粘貼到 Excel 中,也能得到對應的表格
於是就有了這樣的代碼:
function exportTable() {
const HEADER = ['序號', '名稱', '描述', '備註'];
fetchList().then(({ list }) => {
const temp = [renderRow(HEADER)];
list.forEach((x) => {
const row = renderRow([x.key1, x.key2, x.key3, x.key4]);
temp.push(row);
});
writeFile('表格', temp.join('\n'));
});
}
function fetchList() {
return get('/api/list');
}
function renderTxt(v) {
return v || '-';
}
function renderRow(arr) {
return arr.map((x) => renderTxt(x)).join(' ');
}
四、導出詳情
除了導出表格以外,還有兩種情況:
1. 查詢列表接口,分別將每一行數據導出一個文本文件;
2. 查詢列表接口,基於每一行數據的 ID 查詢詳情接口,再將詳情數據導出爲文本文件。
這兩個情況的處理思路很類似,只是第二種情況需要再請求一次接口
但有個細節需要注意:列表的數據很多,會同時創建多個下載任務,需要通過延時的方式繞開瀏覽器的下載任務上限
function exportDetail() {
fetchList().then(({ list }) => {
list.forEach((item, index) => {
fetchDetail(item.id).then((detail) => {
setTimeout(() => {
// 通過 setTimeout 異步創建下載任務,繞過瀏覽器的下載上限
writeFile(item.title, renderContent(detail))
}, index * 500);
});
});
});
}
function fetchList() {
return get('/api/list');
}
function fetchDetail(id) {
return get(`/api/detail/${id}`);
}
// 排版
function renderContent(detail) {
const { title, desc, content } = detail || {};
return `<h1>${title}\n</h1><div>${desc}\n</div>${content}`;
}
終於避免睡地板的慘劇,收工~