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


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

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

  • 相关阅读:
    PO、POJO、BO、DTO、VO区别与总结
    redis缓存策略
    redis key/value 出现乱码\xAC\xED\x00\x05t\x00\x05
    Maven中的GroupID和ArtifactID和name指的是什么?
    为什么不应该加班?
    IDEA 激活
    redis使用规范
    python 练习题 172. 阶乘后的零
    3.28 Niagara ROLLTHE
    3.31 ROLLTHE
  • 原文地址:https://www.cnblogs.com/wisewrong/p/16094626.html
Copyright © 2020-2023  润新知