公共Hooks封装报表导出useExportExcel实现详解

作者:JasonSubmara 时间:2024-04-28 09:21:36 

写在前面

对于经常需要开发企业管理后台的前端开发来说,必不可少的需要使用表格对于数据进行操作,在对于现有项目进行代码优化时,封装一些公共的Hooks.

本篇文章为useExportExcel.js

基于个人项目环境进行封装的Hooks,仅以本文介绍封装Hooks思想心得,故相关代码可能不适用他人

项目环境

Vue3.x + Ant Design Vue3.x + Vite3.x

对于企业管理后台最大的作用来说,用以管理企业内各种数据状况,同时,基于实际业务过程中,客户每逢年终(中)时都有大型汇报的需求,因此,数据报表形式的文档产出必不可少,本文则基于该常见需求场景进行封装的Hooks -- 导出数据报表

封装思考:报表数据来源

  • 后端接口返回数据

后端返回二进制Blob文件,前端利用Blob进行下载,即参考系列文章公共Hooks封装之文件下载useDownloadFile的方式。

  • 前端导出界面数据

前端导出界面数据的方式在企业管理后台中占比相对较少,一般用以数据量较少的特殊情况,以自己项目举例则是 用户在导入Excel时部分数据失败后,展示的失败数据报表及失败原因统计的表格,使用前端导出数据的方式进行导出。

封装分解:前端生成报表

接收options配置对象,包括data(源数据)、key(用来生成表格的行数据唯一标识)、title(表格标题)、fileName(导出文件名称)

// 通过数组数据前端导出excel
const exportByArray = options => {
 if (!options.data || !options.key || !options.title || !options.fileName)
 return new Error('缺少必需参数');
 const arr = options.data.map(v =>
   options.key.map(j => {
     return v[j];
   }),
 );
 arr.unshift(options.title);
 const ws = utils.aoa_to_sheet(arr);
 const colWidth = arr.map(row =>
   row.map(val => {
     if (val == null) {
       return { wch: 10 };
     } else if (val.toString().charCodeAt(0) > 255) {
       return { wch: val.toString().length * 2 };
     } else {
       return { wch: val.toString().length };
     }
   }),
 );
 const result = colWidth[0];
 for (let i = 1; i < colWidth.length; i++) {
   for (let j = 0; j < colWidth[i].length; j++) {
     if (result[j]['wch'] < colWidth[i][j]['wch']) {
       result[j]['wch'] = colWidth[i][j]['wch'];
     }
   }
 }
 ws['!cols'] = result;
 const wb = utils.book_new();
 utils.book_append_sheet(wb, ws, options.fileName);
 writeFile(wb, options.fileName + '.xlsx');
};

前端生成报表方法Sheet.js

前端生成报表方法中用到的utils.aoa_to_sheetutils.book_newutils.book_append_sheetwriteFile,都来源于 SheetJS。

Step1: 项目安装依赖yarn add xlsx

Step2: 在Hooks文件中引入 import { utils, writeFile } from 'xlsx'

Step3: 参考官方API,完善Hooks中前端导出方法 SheetJS - Utility Functions

  • utils.aoa_to_sheet

将一个二维数组转成sheet,会自动处理number、string、boolean、boolean、date 等类型数据

  • utils.table_to_sheet

将一个table的dom直接转成sheet,会自动识别colspan和rowspan并将其转成对应的单元格合并

  • utils.json_to_sheet

将一个由对象key-value组成的数组转成sheet,可以设置header

这三种方法都是SheetJS的导出方法,存在差异,考虑实际数据,最后选择的是utils.aoa_to_sheet,其余方法可以在官方文档中找到对应的示例

以上是一个完整的导出报表流程
utils.book_new => 创建一个工作簿 utils.aoa_to_sheet => 源数据转成工作表 utils.book_append_sheet => 将工作表插入到工作簿中 writeFile => 调用下载

封装分解:后端接口返回数据导出优化

因为需要请求后端接口导出,即下载返回的二进制文件,依旧考虑用户体验设计,增加二次确认弹窗,并从store里拿接口必须的token

// 打开导出文件确认弹窗
const exportByResBlob = options => {
 Modal.confirm({
   title: options.title ? options.title : '导出确认',
   content: options.content ? options.content : '确认导出报表吗?',
   onOk() {
     downloadFile(options);
     return Promise.resolve();
   },
 });
};

useExportExcel.js完整代码

import { onBeforeUnmount } from 'vue';
import { utils, writeFile } from 'xlsx';
import { stringify } from 'qs';
import { Modal } from 'ant-design-vue';
import { useUserStore } from '@/store/userStore';
export function useExportExcel() {
 const userStore = useUserStore();
 let xhr = null;
 let downloading = false; // 限制同一文件同时触发多次下载
 onBeforeUnmount(() => {
   xhr && xhr.abort();
 });
 // 打开导出文件确认弹窗
 const exportByResBlob = options => {
   Modal.confirm({
     title: options.title ? options.title : '导出确认',
     content: options.content ? options.content : '确认导出报表吗?',
     onOk() {
       downloadFile(options);
       return Promise.resolve();
     },
   });
 };
 // 通过请求后端接口文件流导出excel
 const downloadFile = options => {
   try {
     if (downloading || !options.url || !options.fileName)
     return new Error('缺少必需参数');
     downloading = true;
     const paramsStr = stringify(options.params || {});
     xhr = new XMLHttpRequest();
     xhr.responseType = 'blob';
     if (paramsStr) {
       xhr.open('get', `${options.url}?${paramsStr}`, true);
     } else {
       xhr.open('get', options.url, true);
     }
     xhr.setRequestHeader('token', userStore.userToken);
     xhr.onloadend = function (e) {
       if (e.target.status === 200) {
         const aElement = document.createElement('a');
         const blob = e.target.response;
         const url = window.URL.createObjectURL(blob);
         aElement.style.display = 'none';
         aElement.href = url;
         aElement.download = `${options.fileName}.xlsx`;
         document.body.appendChild(aElement);
         aElement.click();
         if (window.URL) {
           window.URL.revokeObjectURL(blob);
         } else {
           window.webkitURL.revokeObjectURL(blob);
         }
         document.body.removeChild(aElement);
         downloading = false;
       }
     };
     xhr.send();
   } catch (e) {
     console.error(e);
     downloading = false;
     Modal.error({
       title: '提示',
       content: '导出发生异常,请重试',
     });
   }
 };
 // 通过数组数据前端导出excel
 const exportByArray = options => {
   if (!options.data || !options.key || !options.title || !options.fileName) return new Error('缺少必需参数');
     const arr = options.data.map(v =>
       options.key.map(j => {
         return v[j];
       }),
     );
     arr.unshift(options.title);
     const ws = utils.aoa_to_sheet(arr);
     const colWidth = arr.map(row =>
     row.map(val => {
       if (val == null) {
         return { wch: 10 };
       } else if (val.toString().charCodeAt(0) > 255) {
         return { wch: val.toString().length * 2 };
       } else {
         return { wch: val.toString().length };
       }
     }),
   );
   const result = colWidth[0];
   for (let i = 1; i < colWidth.length; i++) {
     for (let j = 0; j < colWidth[i].length; j++) {
       if (result[j]['wch'] < colWidth[i][j]['wch']) {
         result[j]['wch'] = colWidth[i][j]['wch'];
       }
     }
   }
   ws['!cols'] = result;
   const wb = utils.book_new();
   utils.book_append_sheet(wb, ws, options.fileName);
   writeFile(wb, options.fileName + '.xlsx');
 };
 return {
   exportByResBlob,
   exportByArray,
 };
}

来源:https://juejin.cn/post/7170948983219552270

标签:Hooks,封装,useExportExcel,报表导出
0
投稿

猜你喜欢

  • javascript实现圣旨卷轴展开效果(代码分享)

    2024-04-10 11:03:29
  • vue基础之面包屑和标签tag详解

    2024-05-28 15:42:43
  • Python全角与半角之间相互转换的方法总结

    2023-12-25 03:50:50
  • Python 切分数组实例解析

    2022-04-15 02:45:05
  • python格式化字符串实例总结

    2023-09-01 04:36:11
  • DHTML实例解析:用HTC统一定制表单样式

    2007-11-04 18:48:00
  • Python爬取百度翻译实现中英互译功能

    2023-04-29 08:32:26
  • 使用go求幂的几种方法小结

    2023-09-23 05:07:45
  • 解析如何用SQL语句在指定字段前面插入新的字段

    2024-01-24 12:18:36
  • Python采集某评论区内容的实现示例

    2023-02-28 15:07:10
  • 卷积神经网络经典模型及其改进点学习汇总

    2023-07-22 22:15:19
  • mysql drop database删除数据库命令实例讲解

    2024-01-18 19:40:01
  • spring boot 不连接数据库启动的解决

    2024-01-18 06:38:54
  • Django RBAC权限管理设计过程详解

    2021-09-02 03:57:07
  • MySQL数据库 Load Data 多种用法

    2024-01-22 09:54:50
  • SQL Server数据库内存会不断增加的问题分析

    2009-01-08 15:46:00
  • Python使用PyCrypto实现AES加密功能示例

    2022-09-18 13:23:57
  • win2008 R2 WEB环境配置之MYSQL 5.6.22安装版安装配置方法

    2024-01-25 10:25:17
  • 基于python实现cdn日志文件导入mysql进行分析

    2024-01-29 07:37:55
  • python glom模块的使用简介

    2021-08-21 10:22:02
  • asp之家 网络编程 m.aspxhome.com