公共Hooks封装之报表导出useExportExcel
写在前面
对于经常需要开发企业管理后台的前端开发来说,必不可少的需要使用表格对于数据进行操作,在对于现有项目进行代码优化时,封装一些公共的 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_sheet
,utils.book_new
,utils.book_append_sheet
,writeFile
,都来源于 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,
};
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南