背景:一般前端导出excel表格时,都是通过调取后端的接口去生成excel表格,但有时候的业务逻辑要求是前端自己导出excel表格。如果只是单纯地导出为excel表格的话,需要下载XSXL依赖,但有些时候客户要求导出的excel表格要有样式,更加直观。
分析:如果导出的excel要有样式,XSXL就不能满足需求了,就需要借用XSXL-js-style依赖了。此处是官方文档:xlsx-js-style - npm
实现:
1、首先需要下载依赖:
npm install xlsx-js-style
2、安装好后,新建一个js文件,在此文件中去设置样式即可。
export function saveFSDZJsonToExcel(data, name, merges) {
// data是点击导出按钮传递过来要导出的数据,name是导出的文件名,merges是合并的单元格
// 将数据源转成我们需要的二维数组,序号、姓名这些是表格的表头
const body = data.map(x => ([x['序号'], x['姓名'], x['编号'], x['日期'], x['时间'], x['类型'], x['内容'], x['岗位'], x['单资']]))
const header = [ // 这个是设置excel表格的标题,即在A1单元格的标题
[
{
v: `${name}`,
t: 's' // 指定标题的样式
},
],
// 这是对应body常量的表头
['序号', '姓名', '编号', '日期', '时间', '类型', '内容', '岗位', '单资']
]
body.unshift(...header);// 将定义好的表头添加到 body 中
const sheet = XLSXS.utils.aoa_to_sheet(body);// aoa_to_sheet 将二维数组转成 sheet
// 此处开始合并列,即相同名字的表格合并单元格
let includeB = []
let includesBnumber = []
for (let key in sheet) {
if (key.includes('B')) {// 第一列是A,代表序号;第二列是B,代表姓名
includeB.push(sheet[key])
}
}
includeB.forEach((e, i) => {
if (!(e.v.includes('姓名') || e.v.includes('共计'))) {
includesBnumber.push({
name: e.v,
number: i + 1 // 因为有标题,所以要从第2行开始
})
}
})
// 去重
let cancelTwice = () => {
let map = new Map();
for (let item of includesBnumber) {
if (!map.has(item.name)) {
map.set(item.name, item)
}
}
return [...map.values()];
}
let newArr = cancelTwice()
// 合并
let newMerges = []
merges.forEach((e, i) => {
if (i != 0) {
newMerges.push( // 合并:s代表合并的开始单元格,c是列,r是行,e是代表合并的结束单元格
{
s: {
c: e.s.c,
r: e.s.r + 1 //r加1是因为加了表格的标题
},
e: {
c: e.e.c,
r: e.e.r + 1
}
}
)
} else {
newMerges.push(e) // 标题合并
}
})
merges = newMerges
// 设置合并姓名列的单元格
newArr.forEach((e, i) => {
merges.push({ s: { r: e.number, c: 1 }, e: { r: newArr[i + 1]?.number ? newArr[i + 1].number - 2 : data.length - 1, c: 1 } })
})
sheet['!merges'] = merges; // 将merges添加到sheet中
// 设置列宽
const cols = [{ wch: 8 }, { wch: 15 }, { wch: 12 }, { wch: 12 }, { wch: 15 }, { wch: 12 }, { wch: 15 }, { wch: 10 }, { wch: 10 }];
sheet['!cols'] = cols; // 将cols添加到sheet中
const rows = [{ hpx: 20 }, { hpx: 16 }, { hpx: 18 }]
sheet['!rows'] = rows; // 将rows添加到sheet中,设置行高
Object.keys(sheet).forEach((key, i) => { // 设置单元格样式
if (key.indexOf('!') < 0) {
if (key == 'A1') {// 设置标题
sheet[key].s = {
alignment: {
horizontal: 'center',//水平居中
vertical: 'center',//竖直居中
},
font: {
bold: true,
sz: 14,
name: '宋体',
}
}
} else if (key == 'A2' || key == 'B2' || key == 'C2' || key == 'D2' || key == 'E2' || key == 'F2' || key == 'G2' || key == 'H2' || key == 'I2') { // 设置表头
sheet[key].s = {
fill: {
fgColor: { rgb: 'A6A6A6' },
},
border: {
top: { style: 'thin' },
left: { style: 'thin' },
bottom: { style: 'thin' },
right: { style: 'thin' }
},
font: {
bold: true,
sz: 12,
name: '宋体',
},
}
} else {
if (key.includes('A')) { // 序列所在列
if (typeof sheet[key].v == 'number') { // 列表列要求序号要居中,合计和总合计居左(应该是系统默认的,如果要设置居左或居右,也可以自己设置下alignment属性)
sheet[key].s = {
alignment: {
horizontal: 'center',
vertical: 'center'
},
border: {
top: { style: 'thin' },
left: { style: 'thin' },
bottom: { style: 'thin' },
right: { style: 'thin' }
}
}
} else {
sheet[key].s = {
border: {
top: { style: 'thin' },
left: { style: 'thin' },
bottom: { style: 'thin' },
right: { style: 'thin' }
}
}
}
} else {
sheet[key].s = {
border: {
top: { style: 'thin' },
left: { style: 'thin' },
bottom: { style: 'thin' },
right: { style: 'thin' }
},
alignment: {
horizontal: 'left',
vertical: 'center',
},
}
}
}
}
})
// 设置结尾的表格
let sheetBorder = sheet['!ref'].substring(4) // 因为导出的单元格行数不确定,所以拿到最后一行的数字
let endSheetBorder = ['C', 'D', 'E', 'F', 'G', 'H'] //因为最后一行的合并的列是一样的,所以这里可以直接将列的单元格序号写个定值
endSheetBorder.forEach(e => {
let addBorder = e + sheetBorder
sheet[addBorder] = {
v: '',
t: 's',
s: {
border: {
top: { style: 'thin' },
bottom: { style: 'thin' }
}
}
}
})
const workbook = XLSXS.utils.book_new();// 创建虚拟的 workbook
XLSXS.utils.book_append_sheet(workbook, sheet); // 向 workbook 中添加 sheet
XLSXS.writeFile(workbook, '统计' + '.xlsx'); // 导出 workbook
}