前言
最近工作中接到一个需求,是将页面中的表格数据给整合成Excel文件给用户下载,这个问题最开始的想法是后端直接生成一个文件流就好了,但是他要求要有文件的格式和部分文本的样式。但是后端觉得麻烦,就丢给可怜又无助的前端来做。 本来先想着就直接使用sheetJS提供的js-xlsx来实现就好了,可是发现无法对表格进行样式渲染。(其实是可以实现的,不过要使用js-xlsx的Pro版本,这个版本是要钱的,作为一个贫穷公司的贫穷前端码农来说这个是可以的。没有啥是手写不能解决的。如果真的有,那就和产品硬钢换需求)而我就找到一个xlsx-style的开源库。 这个库其实是js-xlsx的一个分支,它可以对导出的Excel文件进行编辑样式,但是目前为止,还是不能编辑单元格的高度。不过这也不影响我们使用,毕竟还是可以编辑单元格的宽度,字体的样式,填充颜色,边框等等样式,还是可以使用的哦~
安装
因为我们开发的环境是electron+vue的,本身就自带node,所以就不用担心node相关的库我们使用不了。
yarn
yarn add xlsx-style
In the browser:
<script lang="javascript" src="dist/xlsx.core.min.js"></script>
With bower:
bower install js-xlsx-style#beta
导出Excel文件说明
单元格对象(cell-Object)
cellObject = {c:C,r:R}
其中c
代表列号,r
代表行号,单元格B5
,用对象表示就是{c:1,r:5}
。如果要是想表示单元格的范围,则可以使用{s:S,e:E}
,其中s表示第一个单元格即start,e表示最后一个单元格即end,这里面的s和e都是cellObject对象;;例如要表示A1:B5
则可以用对象就是{s:{c:0,r:0},e:{c:1,r:4}}
工作表格对象(sheet-object)
sheetObject = {
A1: {
v: '单元格',
t: 's',
s: {
font: {},
fill: {},
numFmt: {},
alignment: {},
border: {}
}
}
}
V: 表示单元格的值;
t:表示单元格值的类型,b:表示Boolean布尔值,n表示number数组,e表示error错误信息,s表示string字符串,d:表示date日期
s: 表示单元格的样式,后面会重点讲这个
当然单元格的属性肯定不会只有这些,但是对于一般的简单导出,这些其实就已经足够了,剩下的属性其实我们用的也不多
ok,看完这些基础信息配置后我们看看如何将一个数组给变成Excel文件的,只需要三步:打开冰箱,把大象放进冰箱,关上冰箱
// 表格中展示的原始数据
const tableArray = [
{
name: '张三',
age: 14,
gender: '男'
},
{
name: '李四',
age: 23,
gender: '女'
}
]
const title = ['姓名','年龄','性别']
/**
* 转换数据的函数
* tableArray 需要转换的数据
* title 表格展示的title
* excludeKey 不需要导出的数据
* backgroundRed 背景颜色为红色
*/
const ProcessingData = ({tableArray,title,excludeKey,backgroundRed}) => {
if(!tableArray?.length) {
return null
}
const excelTable = [title]
// 获取转换的的数组
const sheetData = tableArray.map((item, index) => {
// lodash 方法自己按需引用
const copyItem = cloneDeep(item)
if (excludeKey) {
delete copyItem[excludeKey]
}
const colArray = Object.values(copyItem)
colArray.unshift(index + 1)
return colArray
})
sheetData.unshift(...excelTable)
const sheet = {}
sheetData.forEach((item, index) => {
item.forEach((sheetItem, key) => {
const itemIndex = `${EnLetter[key]}${index + 1}`
const s = { alignment: { vertical: 'center', horizontal: 'center', wrapText: true } }
if (backgroundRed && backgroundRed.length && index === 1) {
if (backgroundRed.some(item => item === key)) {
// 注意设置颜色的时候要是ARGB格式
s.fill = { fgColor: { rgb: 'FFFF0023' } }
s.font = { color: { rgb: 'FFFFFFFF' } }
s.alignment.vertical = 'left'
}
}
sheet[itemIndex] = { v: sheetItem, t: 's', s }
})
})
const sheetKeys = Object.keys(sheet)
const ref = `${sheetKeys[0]}:${sheetKeys.pop()}`
sheet['!ref'] = ref
return { sheet, sheetData }
}
const base64ToBlob = s => {
const bstr = atob(s)
let n = bstr.length
const u8Arr = new Uint8Array(n)
while (n--) {
u8Arr[n] = bstr.charCodeAt(n)
}
return new Blob([u8Arr], { type: 'xlsx' })
}
/**
* 转换为文件
* @param {Object} params
* @param {Array} params.tableArray 表格中展示的数据
* @param {Boolean} params.autoSerial 是否为自动序号
* @param {String} params.fileName 文件名
* @param {Array} params.title 文件表格展示的title
* @param {Boolean} params.autoWidth 是否为自适应宽度
* @param {Array} params.description 文件的描述
* @param {Array} params.backgroundRed 背景标红的序号一般指title
*/
const JSON2ExcelFile = ({
tableArray,
title,
autoWidth = true,
errorToRed = true,
description,
backgroundRed,
excludeKey
}) => {
const { sheet, sheetData: excelArray } = ProcessingData({
tableArray,
title,
description,
excludeKey,
errorToRed,
backgroundRed
})
// 合并单元格描述的
sheet['!merges'] = [
{
s: {
c: 0,
r: 0
},
e: {
c: 7,
r: 0
}
}
]
// 设置自动高度
if (autoWidth) {
/* 设置worksheet每列的最大宽度 */
excelArray.shift()
const colWidth = excelArray.map(row =>
row.map(val => {
/* 先判断是否为null/undefined */
if (val == null) {
return {
wch: 20
}
}
if (val.toString().charCodeAt(0) > 255) {
/* 再判断是否为中文 */
return {
wch: val.toString().length * 2 > 20 ? val.toString().length * 2 : 10
}
}
return {
wch: val.toString().length > 20 ? val.toString().length * 2 : 20
}
})
)
// /* 以第一行为初始值 */
const result = colWidth[0]
for (let i = 1; i < colWidth.length; i += 1) {
for (let j = 0; j < colWidth[i].length; j += 1) {
if (result[j].wch < colWidth[i][j].wch) {
result[j].wch = colWidth[i][j].wch
}
}
}
sheet['!cols'] = result
}
const workbook = { Sheets: { 员工信息表: sheet }, SheetNames: ['员工信息表'] }
const bouts = XLSXStyle.write(workbook, { type: 'base64', bookType: 'xlsx', cellStyles: true })
const file = base64ToBlob(bouts)
return file
}
/**
* 将错误信息导出为Excel表格,用于下载使用
* @param {Object} params
* @param {Array} params.tableArray 表格中展示的数据
* @param {String} params.fileName 文件名
* @param {Array} params.title 文件表格展示的title
* @param {Boolean} params.autoWidth 是否为自适应宽度
* @param {Array} params.description 文件的描述
* @param {Array} params.backgroundRed 背景标红的序号
*/
const ExcelFile = ({
tableArray,
filename,
title,
autoWidth = true,
errorToRed = true,
description,
backgroundRed,
excludeKey
}) => {
const file = JSON2ExcelFile({
tableArray,
autoWidth,
title,
errorToRed,
description,
backgroundRed,
excludeKey
})
const homeDir = homedir()
const options = {
title: '保存Excel',
defaultPath: `${homeDir}\\downloads\\${filename}.xlsx`,
filters: [{ name: 'excel', extensions: ['xlsx'] }]
}
remote.dialog.showSaveDialog(options).then(({ filePath: filename }) => {
if (!filename) return
const reader = new FileReader()
reader.readAsArrayBuffer(file)
reader.onload = () => {
const buffer = Buffer.from(reader.result)
fs.writeFile(filename, buffer, {}, err => {
if (err) {
Message({
showClose: true,
message: '下载失败',
type: 'error'
})
throw err
}
const vm = new Vue()
const h = vm.$createElement
const fileName = h('p', basename(filename))
const notifier = Notification.success({
title: '下载完成',
duration: 0,
dangerouslyUseHTMLString: true,
message: h('div', [
fileName,
h('span', {
style: {
display: 'inline-block',
color: '#409eff',
cursor: 'pointer',
margin: '10px 0 0'
},
domProps: {
innerHTML: '在文件夹中显示'
},
on: {
click: () => {
fs.access(filename, err => {
if (err) {
vm.$message.error({
message: '该文件不存在',
showClose: true
})
} else {
remote.shell.showItemInFolder(filename)
}
notifier.close()
})
}
}
})
])
})
})
}
})
}
前端实现导出Excel文件,基本的实现逻辑是在现将展示的table的数据转换成Excel能够识别的json数据,将这些数据转换成文件需要的blob数据。将这个blob数据写成文件;
@startuml
start
:获取需要导出的数据;
:将数据转换为Excel能识别的json数据;
:将json转化为blob数据;
:将blob文件写成文件;
:导出文件;
stop
@enduml
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!