關於幫老婆在前端導數據這件小事

一個普通的晚上,普通的我聽着普通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}`;
}

 


終於避免睡地板的慘劇,收工~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章