Talk is cheap. Show me your code

关于帮老婆在前端导数据这件小事

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

 


终于避免睡地板的惨剧,收工~

posted @ 2022-04-22 09:25  Wise.Wrong  阅读(117)  评论(0编辑  收藏  举报