??最近项目中遇到一个需要解析Excel并在线展示的功能,excel的内容不是固定的,是需要事先通过专用的工具生成xml模板导入到系统中,然后系统解析上报的excel文件时就可以根据模板来解析内容。
??其中生成的xml模板保存了针对每一个单元格的一些属性,所以excel解析不是难事,关键是页面展示excel内容的时候有个合并单元格的需求,这个着实让我苦恼了几天,上下班的路上都在思考这个问题,后来终于想到一种方案,这里记录一下。
??因为excel内容不是固定的,所以解析与展示数据时,我都是以单元格为单位的,所以这里我的实体类对象是:
public class TemplateItem {
private Long id;
private String col;
private String colSpan;
private String row;
private String rowSpan;
private String contentEnum;
private String content;
}
??返回给前端展示的时候,要么返回每一个单元格,这样直接前端通过<table></table> 标签或者Bootstrap Table 插件画出即可;要么只返回有数据的单元格,需要前端计算出单元格具体的展示位置。
??既然是后端开发(虽然前端也是自己写),那我就把计算的逻辑放后端了,而且对比了一下,感觉前端计算好像也不是太方便。然后问题就来了,存在合并单元格的情况时我怎么计算每个单元格的具体位置。首先明确的是,前端直接采用<table></table> 标签来实现,虽然两种都实现了,但是Bootstrap Table 在合并单元格的时候似乎不是很好用,当初采用只是为了样式美观😓,<table></table> 标签则可直接通过<td> 的rowspan、colspan 属性来控制合并的行数和列数。
------------------------------------------------ 重点来了 ------------------------------------------------ ??使用<table></table> 来画excel的内容,也就是我返回是数据格式得是一个二维数组,因为<table></table> 标签是使用<tr>、<td> 来画表格的,这样我数组里每一项放那个单元格对象即可。
??在组装二维数组的时候,一开始我总是想着在没有数据的位置拼上空的单元格对象,但是感觉计算好复杂,因为合并多行多列的情况都有,这样计算的时候要考虑很多。后来一想,干脆我反过来做,因为已经知道一共有多少行多少列了,我先按照一张空白excel的样子,先画出每一个单元格(先组装一个空的二维数组),然后根据已有的数据项信息,删除掉被合并的单元格(删除被合并的数组项),这样不就能很容易得出来想要的二维数组了吗。
上代码:
两个实体类Template 和TemplateItem ,其中Template 存储了这一套模板的属性信息,TemplateItem 存储了模板里每一个单元格的属性信息。 Template 对象:
package com.example.demo.excel;
import lombok.Getter;
import lombok.Setter;
import java.io.Serializable;
import java.util.Date;
import java.util.List;
@Getter
@Setter
public class Template implements Serializable {
private Long id;
private String templateName;
private String rowCount;
private String colCount;
private Integer headCount;
private String remark;
private Boolean isDel;
private Long createBy;
private Date createTime;
private Long updateBy;
private Date updateTime;
private List<TemplateItem> items;
private static final long serialVersionUID = 1L;
}
TemplateItem 对象:
package com.example.demo.excel;
import lombok.Getter;
import lombok.Setter;
import java.io.Serializable;
@Getter
@Setter
public class TemplateItem implements Serializable {
private Long id;
private Long templateId;
private String col;
private String colSpan;
private String row;
private String rowSpan;
private String contentEnum;
private String content;
private String expstr;
private String isUnitConver;
private String eleUnit;
private String cssClass;
private String formatString;
private String isReadonly;
private static final long serialVersionUID = 1L;
}
业务方法: 其中二维数组我是用guava 的Table 来代替,这个使用起来比较方便。
package com.example.demo.excel;
import com.google.common.collect.HashBasedTable;
import com.google.common.collect.Table;
import org.springframework.stereotype.Service;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@Service
public class TemplateService {
public List<List<TemplateItem>> getFormData() {
Template template = new Template();
List<TemplateItem> items = template.getItems();
HashBasedTable<Integer, Integer, TemplateItem> dataTable = HashBasedTable.create();
items.stream().forEach(item -> {
Integer row = Integer.valueOf(item.getRow());
Integer col = Integer.valueOf(item.getCol());
dataTable.put(row, col, item);
});
Integer rowCount = Integer.valueOf(template.getRowCount());
Integer colCount = Integer.valueOf(template.getColCount());
List<List<TemplateItem>> dataList = fillTableCell(rowCount, colCount, dataTable);
return dataList;
}
private List<List<TemplateItem>> fillTableCell(int rowCount, int colCount,
Table<Integer, Integer, ? extends TemplateItem> dataTable) {
HashBasedTable<Integer, Integer, TemplateItem> fullTable = HashBasedTable.create();
for (int i = 1; i <= rowCount; i++) {
for (int j = 1; j <= colCount; j++) {
fullTable.put(i, j, new TemplateItem());
}
}
List<List<? extends TemplateItem>> dataList = dataTable.rowMap().entrySet().stream()
.sorted(Comparator.comparing(Map.Entry::getKey))
.map(entry -> entry.getValue().entrySet().stream()
.sorted(Comparator.comparing(Map.Entry::getKey))
.map(Map.Entry::getValue)
.collect(Collectors.toList()))
.collect(Collectors.toList());
for (List<? extends TemplateItem> rowData : dataList) {
for (TemplateItem templateItem : rowData) {
Integer row = Integer.valueOf(templateItem.getRow());
Integer col = Integer.valueOf(templateItem.getCol());
Integer rowSpan = Integer.valueOf(templateItem.getRowSpan());
Integer colSpan = Integer.valueOf(templateItem.getColSpan());
fullTable.put(row, col, templateItem);
if (rowSpan == 1 && colSpan == 1) {
continue;
}
if (rowSpan > 1 && colSpan == 1) {
for (int i = 1; i < rowSpan; i++) {
fullTable.remove(row + i, col);
}
continue;
}
if (rowSpan == 1 && colSpan > 1) {
for (int i = 1; i < colSpan; i++) {
fullTable.remove(row, col + i);
}
continue;
}
if (rowSpan > 1 && colSpan > 1) {
for (int i = 0; i < rowSpan; i++) {
for (int j = 0; j < colSpan; j++) {
if (i == 0 && j == 0) {
continue;
}
fullTable.remove(row + i, col + j);
}
}
continue;
}
}
}
return fullTable.rowMap().entrySet().stream()
.map(rowEntry -> rowEntry.getValue().entrySet().stream()
.map(colEntry -> colEntry.getValue())
.collect(Collectors.toList()))
.collect(Collectors.toList());
}
}
这样返回的集合在前端直接遍历画出每一行<tr> 每一个<td> 即可。
|