这篇文章是接着纯前端 导出Excel文件的 方法-CSDN博客(https://blog.csdn.net/qq_44327851/article/details/134045649),导出excel的基本步骤在这里就不再多叙述,本文主要是介绍导出excel的一些enhancement的实现方法和注意事项。
问题:即使给每个colunm设置了样式,比如给每个单元格设置了边框,但是实际导出文件的时候却发现仍然有一些单元格样式没生效,再仔细看会发现,样式没生效的单元格都是没有数据的。
解决:设置excel的样式是跟着数据的,既然是因为没有数据导致的样式没生效,那么我们就在数据生成的时候加多一种可能,让其生成一种不占位置,人为看不见的数据。对!那就是空数据,最常用的就是空字符串了""。(这里也是用数组数据模拟文件数据进行excel文件导出)
//要导出的数组数据
extract = this.row.map((item) => {
'Name': item.name || "",
'Sex': item.sex || ""
}
)
2.Merge 特定columns的单元格
这里以垂直merge单元格为例,也就是merge rows。需要导出的数组数据格式依然是:
# 创建数组数据
data = {
'A': [{'a':1, 'b':2, 'c':3},{'a':4, 'b':5}],
'B': [{'a':'d', 'b':'e', 'c':'c3'}, {'a':'d', 'b':'e'}],
'C': [{'a':'x', 'b':'y', 'c':'z'}, {'a':'w', 'b':'v'}]
}
垂直merge单元格的主要代码:
// 需要垂直合并的列的索引
const columnsToMerge = [0, 1, 2]; // 假设需要合并第0、1、2列
// 遍历需要合并的列
columnsToMerge.forEach(colIndex => {
// 确定要合并的单元格范围
const mergeRange = {
s: { r: 1, c: colIndex }, // 起始位置为第2行
e: { r: 10, c: colIndex } // 结束位置为第11行
};
// 应用垂直合并
worksheet['!merges'] = worksheet['!merges'] || [];
worksheet['!merges'].push(mergeRange);
});
实际的实现要复杂的多,需要多加几重处理,下面是完整代码:(只合并A列,也就是第0列)
<button id="exportBtn">Export Excel</button>
<script src="https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.16.9/xlsx.full.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/2.0.5/FileSaver.min.js"></script>
<script>
document.getElementById('exportBtn').addEventListener('click', function () {
var fileData = {
2014: [{ name: 'yanfeng', sex: '', height: 188, weigth: 100 }],
2015: [{ name: '', sex: 'female', height: 188, weigth: 100 }],
2016: [{ name: 'yanfeng', sex: 'female', height: 188, weigth: 100 }, { name: '小红', sex: 'female', height: 180, weigth: 90 }],
2017: [{ name: 'yanfeng', sex: 'female', height: 188, weigth: 110 }, { name: '小白', sex: 'female', height: 180, weigth: 91 }],
2018: [{ name: 'yanfeng', sex: 'female', height: 188, weigth: 120 }, { name: '小丽', sex: 'female', height: 180, weigth: 94 }],
2019: [
{ name: 'yanfeng', sex: 'female', height: 0, weigth: 0 },
{ name: 'yanfeng', sex: 'female', height: '', weigth: '' },
{ name: 'yanfengdai', sex: 'female', height: 188, weigth: 130 },
{ height: 188, weigth: 120 },
{ height: 188, weigth: 110 },
{ height: 188, weigth: 100 }
],
2020: [
{ name: 'yanfeng', sex: 'female', height: 167, weigth: 90 },
{ height: 188, weigth: 120 },
{ height: 188, weigth: 110 },
{ height: 188, weigth: 1000 }
]
}
const workbook = XLSX.utils.book_new();
const columns = [{ wch: 35 }, { wch: 35 }, { wch: 15 }, { wch: 25 }];
const mergeRange = [0, 1]//定义需要merge的columns 在这里定义的是合并0,1列,也就是A,B两列
const cellStyle = {
alignment: {
wrapText: true,//换行
horazital: 'center',//水平居中
vertical: 'center'//垂直居中
},
border: {
top: { style: 'thin' },
bottom: { style: 'thin' },
left: { style: 'thin' },
right: { style: 'thin' }
}
}
for (const key in fileData) {
if (fileData.hasOwnProperty(key)) {
const dataArray = fileData[key];
//首先要将你需要垂直合并单元格那列的数据进行排序,保证空的数据在前面
dataArray.sort((a, b) => {
if (!a.height && !a.weigth && b.height && b.weigth) {
return -1;
} else if (a.height && a.weigth && !b.height && !b.weigth) {
return 1;
} else {
return 0;
}
});
//然后将表格数据写进worksheet
const workSheet = XLSX.utils.json_to_sheet(dataArray);
//接着设置columns的长度,样式等
if (columns && columns.length) {
workSheet['!cols'] = columns;
}
Object.keys(workSheet).forEach(cell => {
if (cell !== '!ref') {
workSheet[cell].s = cellStyle
}
})
//开始垂直merge row的代码
//首先计算从哪行开始进行merge,即就是计算上面空数据的长度,空数据不merge,需要merge的在最后一行
let startLength = 1;
let isMergeAvaliable = false;
if (dataArray.length > 1) {
isMergeAvaliable = true;
let isAllHaveWAndHValue = 0;
dataArray.forEach(item => {
if (!item.weigth && !item.height) {
startLength++;
} else {
isAllHaveWAndHValue++;
}
})
//判断是否需要merge,如果该数组中的数据都是完整的,也就是数组中的每个数据都是有数值的,就不需要被merge,比如2016,2017,2018中的数据就不需要被merge
if (isAllHaveWAndHValue === dataArray.length) {
isMergeAvaliable = false;
}
}
if (isMergeAvaliable) {
mergeRange.forEach((mergeIndex) => {
const mergeConfig = { s: { r: startLength, c: mergeIndex }, e: { r: dataArray.length, c: mergeIndex } };
for (let R = mergeConfig.s.r; R < mergeConfig.e.r; ++R) {
for (let C = mergeConfig.s.c; C < mergeConfig.e.c; ++C) {
const cellAdress = XLSX.utils.encode_cell({ r: R, c: C });
const cell = workSheet[cellAdress];
if (cell) {//给有数据的row添加样式
if (!cell.s) cell.s = {};
cell.s = cellStyle;
} else {//保证没有数据的row增加空数据,添加样式
workSheet[cellAdress] = { s: cellStyle, v: '' }
}
}
}
workSheet['!merges'] = workSheet['!merges'] || [];
workSheet['!merges'].push(mergeConfig);
})
}
//生成sheet
XLSX.utils.book_append_sheet(workbook, workSheet, key);
}
}
//最后就是文件导出的代码了
const excelBuffer = XLSX.write(workbook, { bookType: 'xlsx', type: 'array' });
const excelBlob = new Blob([excelBuffer], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
saveAs(excelBlob, 'filename.xlsx');
})
</script>
注意:如果使用上述代码而未使用UI框架的话(Angular测试已通过,VUE, React待测试),上面代码实际运行的效果并没有样式,如果想直接在HTML中使用并带有样式导出,请将使用ExcelJS(<script src="https://cdnjs.cloudflare.com/ajax/libs/exceljs/4.3.1/exceljs.min.js"></script>)这样的库来处理Excel文件而非XLSX。
部分代码的解释:
workSheet['!merges'] = workSheet['!merge'] || [];
workSheet['!merges'].push(mergeConfig);这两句话是什么意思?
`worksheet['!merges'] = worksheet['!merges'] || [];`:这行代码使用了JavaScript中的逻辑或运算符`||`。它的作用是检查`worksheet['!merges']`是否已经存在,如果不存在(即为假值),则将一个空数组赋给`worksheet['!merges']`,否则保持不变。这样做是为了确保`worksheet['!merges']`属性一定是一个数组,以便我们可以向其中添加合并单元格的范围。
`worksheet['!merges'].push(mergeRange);`:一旦确保了`worksheet['!merges']`是一个数组,我们就可以使用`push`方法向数组中添加新的合并单元格范围`mergeRange`。这样就把新的合并范围添加到了`worksheet['!merges']`数组中。
这两句代码的目的是为了确保我们能够向`worksheet['!merges']`数组中添加新的合并单元格范围而不会出现错误。
后续:增加处理类似于下面2021的数据格式的主要代码
//数据格式
var fileData = {
2021: [
{ name: '小小', sex: 'female', height: 167, weigth: 90 },
{ height: 188, weigth: 120 },
{ height: 188, weigth: 110 },
{ height: 188, weigth: 1000 },
{ name: 'yanfeng', sex: 'female', height: 166, weigth: 80 },
{ height: 188, weigth: 120 },
{ height: 188, weigth: 110 },
{ height: 188, weigth: 1000 }
],
}
//处理方法
if (dataArray.length > 1) {
isMergeAvaliable = true;
let isAllHaveWAndHValue = 0;
dataArray.forEach((item, index) => {
if (!item.weigth && !item.height) {
startLength++;
} else if (item.weigth && item.height && item.name && item.sex) {
isAllHaveWAndHValue++;
}
})
//判断是否需要merge,如果该数组中的数据都是完整的,也就是数组中的每个数据都是有数值的,就不需要被merge,比如2016,2017,2018中的数据就不需要被merge
if (isAllHaveWAndHValue === dataArray.length) {
isMergeAvaliable = false;
}
//处理类似于2021的数据
merge = dataArray.map((element, index) => { if (element.name && element.sex && element.height && element.weigth && index > startLength) { return index; } }).filter(index => index !== undefined);
merge.push(dataArray.length);
}
if (isMergeAvaliable && (merge && merge.length)) {
let start = 0; end = 0;
merge.forEach((mergeLen, index) => {
if (index == 0) {
start = startLength;
end = mergeLen;
} else {
start = end + 1;
end = mergeLen;
}
mergeRange.forEach((mergeIndex) => {
let mergeConfig = {};
mergeConfig = { s: { r: start, c: mergeIndex }, e: { r: end, c: mergeIndex } };
for (let R = mergeConfig.s.r; R < mergeConfig.e.r; ++R) {
for (let C = mergeConfig.s.c; C < mergeConfig.e.c; ++C) {
const cellAdress = XLSX.utils.encode_cell({ r: R, c: C });
const cell = workSheet[cellAdress];
if (cell) {//给有数据的row添加样式
if (!cell.s) cell.s = {};
cell.s = cellStyle;
} else {//保证没有数据的row增加空数据,添加样式
workSheet[cellAdress] = { s: cellStyle, v: '' }
}
}
}
workSheet['!merges'] = workSheet['!merges'] || [];
workSheet['!merges'].push(mergeConfig);
})
})
}
3. sheet_add_aoa可以用来增加空行
比如XLSX.utils.sheet_add_aoa(ws, [], { origin: 'A2' }),这句话的意思
是向工作表workSheet中的'A2'位置插入一个空的二维数组。这个操作实际上相当于在Excel中,在'A2'位置插入了一个空行。