之前做了一个功能,在前端的呈现有根据订单合并单元格,但是导出Excel并没有做合并的功能,而且客户提出,导出的数字无法进行求和操作。
这两个问题都很简单,首先合并单元格,只需要在@Excel注解后面加上合并相关参数即可。
合并单元格:
@Excel(name = "数量", width = 15, mergeVertical = true, mergeRely = {1, 11})
private BigDecimal quantity;
其中,mergeVertical指的是垂直合并,设置为true即开启。mergeRely为合并依据,比如上述代码中设置的,为第2列和第12列相同时合并,第12列是本列,第一列可能为订单号,这里可以设置多列。如果不设置,则只要本列相同就合并。
设置数字格式:
@Excel(name = "数量", width = 15, type = 4)
private BigDecimal quantity;
这里type值1为字符串,4为数字。
其实poi本来0是数字,1是字符串(可以参考poi包下的CellType类),但AutoPoi做了个转化。
到这里似乎两个问题都解决了,其实会报错:
IllegalStateException: Cannot get a STRING value from a NUMERIC cell。
于是debug追源代码,发现合并单元格的时候直接是cell.getStringCellValue(),这时候单元格格式是数字就会报错了。
源代码如下(位于org.jeecgframework.poi.util.PoiMergeCellUtil大约93行):
if (row == null || row.getCell(index) == null) {
if (mergeDataMap.get(index) == null) {
continue;
}
if (mergeDataMap.get(index).getEndRow() == 0) {
mergeDataMap.get(index).setEndRow(i - 1);
}
} else {
text = row.getCell(index).getStringCellValue();
if (StringUtils.isNotEmpty(text)) {
hanlderMergeCells(index, i, text, mergeDataMap, sheet, row.getCell(index), mergeMap.get(index));
} else {
mergeCellOrContinue(index, mergeDataMap, sheet);
}
}
可以看到else中直接获取StringCellValue的方法。
那怎么办呢?
由于工期比较急,我只能用最简单粗暴的方法了,直接把调用链上的类都复制出来,大概有下面这几个:
org.jeecgframework.poi.excel.view.JeecgEntityExcelView
org.jeecgframework.poi.excel.export.base.ExcelExportBase
org.jeecgframework.poi.excel.export.ExcelExportServer
org.jeecgframework.poi.excel.ExcelExportUtil
org.jeecgframework.poi.util.PoiMergeCellUtil
复制到自己创建的目录下,然后开始修改。为了解决上面的问题,修改的主要代码为:
…
} else {
Cell cell = row.getCell(index);
try {
// 加try…catch,如果获取字符串类型值报错,则去获取数字类型值,然后转成字符串
text = cell.getStringCellValue();
} catch (Exception e) {
double numericCellValue = cell.getNumericCellValue();
text = String.valueOf(numericCellValue);
}
if (StringUtils.isNotEmpty(text)) {
hanlderMergeCells(index, i, text, mergeDataMap, sheet, cell, mergeMap.get(index));
} else {
mergeCellOrContinue(index, mergeDataMap, sheet);
}
}
改完之后,大功告成……
了吗?并没有,现在导出的Excel看起来单元格也合并了,单元格类型也成数字了,可是一求和发现,怎么这么大?
比如一共5行,前三行是25,后两行是20,合并单元格之后就是一个25一个20,求和应该是45,但实际结果却是115!
接下来就要解决这个问题。
还是捋代码,我们进入上面else中的hanlderMergeCells()
方法,点进去,看一下逻辑:
private static void hanlderMergeCells(Integer index, int rowNum, String text, Map<Integer, MergeEntity> mergeDataMap, Sheet sheet, Cell cell, int[] delys) {
if (mergeDataMap.containsKey(index)) {
if (checkIsEqualByCellContents(mergeDataMap.get(index), text, cell, delys, rowNum)) {
mergeDataMap.get(index).setEndRow(rowNum);
} else {
//update-begin-author:wangshuai date:20201118 for:一对多导出needMerge 子表数据对应数量小于2时报错 github#1840、gitee I1YH6B
try{
sheet.addMergedRegion(new CellRangeAddress(mergeDataMap.get(index).getStartRow(), mergeDataMap.get(index).getEndRow(), index, index));
}catch (IllegalArgumentException e){
LOGGER.error("合并单元格错误日志:"+e.getMessage());
e.fillInStackTrace();
}
//update-end-author:wangshuai date:20201118 for:一对多导出needMerge 子表数据对应数量小于2时报错 github#1840、gitee I1YH6B
mergeDataMap.put(index, createMergeEntity(text, rowNum, cell, delys));
}
} else {
mergeDataMap.put(index, createMergeEntity(text, rowNum, cell, delys));
}
}
这里可以看到,如果是合并格的第一行进来,会直接进else,否则会进入if,如果没有子表,就进入再里面一层的if,我们就在这里动手脚。
修改后的代码如下:
if (mergeDataMap.containsKey(index)) {
if (checkIsEqualByCellContents(mergeDataMap.get(index), text, cell, delys, rowNum)) {
try {
// 获取字符串类型值,如果不抛异常,则设置单元格值为空字符串
cell.getStringCellValue();
cell.setCellValue("");
} catch (Exception e) {
// 如果抛异常,则将单元格值设置为数字0
cell.setCellValue(0);
}
cell.setCellType(CellType.BLANK);
mergeDataMap.get(index).setEndRow(rowNum);
} else {
……
}
else {……}
核心思想就是将合并单元格除首行外的其他行值都设置为空,这样就是“真·合并”了。
这个改动的位置找了好久,一开始改的是
if (mergeDataMap.size() > 0) {
后面的代码,发现无效,才又把代码往前提,最终成功。
这次真的大功告成了。