介绍
关于前端实现导出excel的方法,我目前觉得不错的就是xlsx这个库和exceljs
如果你只是需要简简单单的导出一个excel就完事了,没什么其他需求,那么我推荐使用xlsx这个库,这个应该是sheetJS,如果你想个性化你导出的excel文档,比如excel文档里面的间距和表头多数据合并等等,那么可以继续看我下面关于exceljs的踩坑之旅
xlsx这个配合xlsx-style好像也是可以自定义样式的,vue的相关文档可以百度搜索到的。
下面的方法适用于React,Vue3,Vue2都支持, Angular
基础表格实现
npm install exceljs file-saver
// 代码逻辑
import ExcelJS from 'exceljs'
// 注意如果你是Angular 导入 ExcelJS 可能报警告,可以 import { 'xxx' } from exceljs 导入你需要的元素
import { saveAs } from 'file-saver'
// 这里我就是保持数据的一致性
const _pushSameList = (len: number, text: string) => {
let item = []
for (let i = 0; i < len; i++) {
if (i === 0) item.push(text)
else item.push('占位符')
}
return item
}
/**
* 导出基本的表格 excel
* @param {Array} data 二维数组数据 [value, value]
* @param {string} filename 导出的excel文件名
* @param {string} title 表格标题名称 可为空
* @param {string} desc 表格底部的描述信息 可为空
*/
export const outBaseExcel = (data: Array<string[]>, filename: string, title?: string, desc?: string) => {
const workbook = new ExcelJS.Workbook() // 生成工作簿
const worksheet = workbook.addWorksheet('Sheet') // 就是sheet 可以是多个。
// 比如我生成5个sheet 就 使用 workbook.addWorksheet('name') 可以循环的
// 这个需求是对于那些需要一个xlsx文件里面包括多个表格的同学
worksheet.properties.defaultColWidth = 25 // 设置excel文档的每一个单元格的宽度
worksheet.properties.defaultRowHeight = 30 // 设置excel文档的每一个单元格的高度
let copyData = JSON.parse(JSON.stringify(data)) // 深拷贝数据,避免污染数据,建议自己实现深拷贝还是而不是使用JSON.parse这种方法
let len = copyData[0].length // 如果你需要合并表头需要知道它的长度
if (title) {
copyData.unshift(_pushSameList(len, title))
}
if (desc) {
copyData.push(_pushSameList(len, desc))
}
// data(copydata) 数据格式为二维数组 [['老师', '时间', '数量'], ['张老师', '2022/10/01', '23']]
let rows: any[] = worksheet.addRows(copyData)
rows.forEach((row: any, key: number) => {
if (title && key === 0) {
let arr = row._cells
let start = arr[0].address, end = arr[arr.length - 1].address
worksheet.mergeCells(start, end)
worksheet.getCell(start, end).value = title
}
if (desc && key === copyData.length - 1) {
let arr = row._cells
let start = arr[0].address, end = arr[arr.length - 1].address
worksheet.mergeCells(start, end)
worksheet.getCell(start, end).value = desc
}
// 关于参数小伙伴们可以去exceljs官网查看,有中文的文档
row.font = {
size: 12,
name: '微软雅黑'
}
row.height = 30
row.alignment = { vertical: 'middle', horizontal: 'center', wrapText: true }
})
const item = await workbook.xlsx.writeBuffer()
// 如果需要批量导出excel同时压缩到一个文件夹里面就需要返回一个blob流,如果不需要就最近使用saveAs导出
if (zip) {
const blob = new Blob([item], {type: ''});
// zip.zips.file(`${filename}.xlsx`, blob);
return blob
} else {
saveAs(new Blob([item], { type: 'application/octet-stream' }), `${filename}.xlsx`)
}
}
上面代码可以实现基本的表格导出导出的样子是这样,如果你需要标题就传入title不需要就不给,desc就是底部的合并需要就传不需要就不传
多级表头合并实现
数据类型是截图里面这样
// 这样的数据我也是无语,后端处理返回的,只能按照后端老大哥的来,tBody我不需要处理,我只需要处理tHeader生成一样的跟tBody然后合并同名的就可以实现了
/**
* 多级复杂表格头
* @param data { tHeader: any[], tBody: [[]] } 数据
* @param fileName 文件名
* @param title 标题
* @param desc 底部描述内容
* @param zip 开启返回blob流
* @returns
*/
export const outMulitLevel = async (params: Mulit) => {
let { data: { tHeader, tBody }, title, desc, filename, zip } = params
const workbook = new ExcelJS.Workbook()
const worksheet = workbook.addWorksheet('Sheet')
worksheet.properties.defaultColWidth = 25 // 设置excel文档的每一个单元格的宽度
worksheet.properties.defaultRowHeight = 30 // 设置excel文档的每一个单元格的高度
let firstCol = [], secondCol = [], top = []
for (let item of tHeader) {
firstCol.push({
name: item.name,
len: item.zb.length
})
item.zb.map((el: { id: number, name: string }) => {
top.push(item.name)
secondCol.push(el.name)
})
}
secondCol.unshift('占位符')
top.unshift('占位符')
let arr = tBody
let all = [...[top], ...[secondCol], ...arr]
if (title) { all.unshift(_pushSameList(all[0].length, title)) }
if (desc) { all.push(_pushSameList(all[0].length, desc))}
console.warn('所有的', all)
let rows: any = worksheet.addRows(all)
let nums = title ? 1 : 0
for (let [idx, row] of rows.entries()) {
row.font = {
size: 12,
name: '微软雅黑'
}
row.height = 30
row.alignment = { vertical: 'middle', horizontal: 'center', wrapText: true }
if (idx === 0 && title) continue
if (idx === nums) {
console.log(row._cells)
worksheet.mergeCells(`A${nums + 1}:A${nums + 2}`)
worksheet.getCell(`A${nums + 1}:A${nums + 2}`).value = '班级名称'
let b: any[] = []
firstCol.map((el: any) => {
// console.error('el.name')
let vals = row?._cells.filter((v: any) => v.value === el.name)
// console.log(vals[0])
worksheet.mergeCells(`${vals[0].address}:${vals[vals.length - 1].address}`)
worksheet.getCell(`${vals[0].address}:${vals[vals.length - 1].address}`).value = el.name
b.push([vals[0].address, vals[vals.length - 1].address])
})
}
if (idx === all.length - 1 && desc) continue
}
if (title) {
let start = '', end = ''
worksheet.getRow(1).eachCell((cell, number) => {
if (number === 1) start = cell.address
if (number === all[0].length) end = cell.address
// console.warn('第一行的', cell, number)
cell.alignment = { vertical: 'middle', horizontal: 'left' }
})
worksheet.mergeCells(start, end)
worksheet.getCell(start, end).value = title
}
if (desc) {
let start = '', end = ''
// console.error('数组长度', all[0].length)
worksheet.lastRow?.eachCell((cell, number) => {
if (number === 1) start = cell.address
if (number === all[0].length) end = cell.address
cell.alignment = { vertical: 'middle', horizontal: 'left' }
})
worksheet.mergeCells(start, end)
worksheet.getCell(start, end).value = desc
}
const item = await workbook.xlsx.writeBuffer()
if (zip) {
const blob = new Blob([item], { type: '' });
return blob
} else {
saveAs(new Blob([item], { type: 'application/octet-stream' }), `${filename}.xlsx`)
}
}
踩坑过程
使用exceljs会碰到问题 worksheet.properties.defaultRowHeight 这个属性可能会失效,所以我解决办法就是去循环每一个单元格然后去设置了高度,exceljs提供了专门的table方法,但我使用的时候并没有满足我的需求,所以我使用的worksheet.addRows('数据')方法去生成
其他的介绍,其实小伙伴们可以去看看exceljs文档,中文文档还是比较友好,我使用的可能并不是特别好。我整体就是把这个excel文档看成了一个一个单元格,所以我就是去按照那个格式去填充数据,然后导出。我感觉就是这样的,合并,单元格背景色,图片导到excel里面都是支持的,方法都是一样的,不过图片个人感觉还是比较丑~
压缩文件导出
原谅我的词穷,总体来说就是我需要导8个表格,生成8个excel文档,同时把这8个,放到一个文件夹里面然后把文件夹搞成压缩包下载。如果你有这种类似需求,可以看看下面的代码
npm install jszip
// 数据类型说明
// arr 二维数组
// filename 文件名
// title 是否有标题
// desc 是否有底部描述
// zip 返回blob 默认不开启
// type 导出表格类型, 如果你批量导出的表格不一样,就需要type去控制了
const downloadAll = () => {
let downloadExcel = [
{ arr:[['数据','数据','数据数据']], filename: '文件名1', title: '', desc: '' },
{ arr:[['数据','数据','数据数据']], filename: '文件名2', title: '', desc: '' },
{ arr:[['数据','数据','数据数据']], filename: '文件名3', title: '', desc: '' },
{ arr:[['数据','数据','数据数据']], filename: '文件名4', title: '', desc: '' },
{ arr:[['数据','数据','数据数据']], filename: '文件名5', title: '', desc: '' },
{ arr:[['数据','数据','数据数据']], filename: '文件名6', title: '', desc: '' },
]
import * as JsZip from 'jszip'
let zip = new JsZip()
const promises = downloadExcel.map(async param => await this.handleEacheFile(zip, param))
await Promise.all(promises)
zip.generateAsync({ type: "blob" }).then(blob => {
saveAs(blob, `${文件名称}.zip`)
})
}
const handleEacheFile = (zip:JsZip, param: any) => {
// 使用对应的生成表格方法,获取参数里面的数据,文件名,是否有标题,等参数
let { arr, filename, title, desc } = param || {}
let item = { data: arr, filename, title, desc, zip: true }
let blob = outBaseExcel(item)
// 这里去根据type控制,如果都是一样的表格就不需要控制
// let blob: any = null
// blob = outBaseExcel(item) // outBaseExcel 方法已经实现, 传入参数就可返回blob流
zip.file(`${fileName}.xlsx`, blob)
}
如果不是老大要求要导出的excel好看一点,那xlsx完全满足我的需求,直接通过dom的id去导出,简直是不要太方便
总结
因为我的代码大部分是我工作导出的表格样式总结的,但总体效果还是不错,如果有其他问题或者是更方便快捷的方法,欢迎小伙伴们评论区交流讨论。
如果碰到其他表格样式导出问题,可以评论留言,非常乐意探讨实现。基本上需求都是支持的,还有就是关于方法的封装问题了,在代码里面其实也是可以看见有很多共同的地方!
谢谢观看!记录exceljs导出excel文档方法,后续优化文章。
国庆节放假完,回来的第一天,真难受哦 ~ 🥱