合并后的样子:
使用EasyExcel利用**模板填充的方式,以一个单元格为最小单位,把数据全部查出来**,然后将数据处理成一行一行的形式进行填充,碰到相同的数据,就进行合并单元格。
有一部分表头数据的字段没有落表,在实际数据库中都属于一个字段,例如下图:光学、电学、声学实际上都属于category
,而不是optics
、electricity
、acoustics
。
可以使用map
的进行对数据进行处理和存储,处理后的样子:
一般都是固定表头,然后填充数据,相当于一维的。因为表头是动态的,所以第二部分数据相当于二维的,需要将表头和表格数据分别进行填充。
EasyExcel的填充方式是通过模板进行填充导出的,那我们可以导出两次,第一次用/resources/template
下的模板文件将Excel导出成流,接着以第一次导出的Excel流,作为第二次导出的模板,最后再导出需要的Excel表格。
模板:
第一次导出:
第二次导出:
以这种方法,不仅仅是导出两次,还可以导出多次,以此处理更加复杂的表格。
参考官方的文章合并单元格和文章EasyExcel-合并单元格-默念x,然后根据需要自定义合并策略Strategy
并且继承于AbstractMergeStrategy
,计算出需要合并单元格数量的列表,然后利用CellRangeAddress
进行单元格合并。
CellRangeAddress cellRangeAddress = new CellRangeAddress(起始行,结尾行,起始列,结尾列);
sheet.addMergedRegionUnsafe(cellRangeAddress);
使用合并策略的方式:
ExcelWriter screenTemplateExcelWriter = EasyExcel
.write(templateOut) // 导出最终临时文件
.withTemplate(templateFileName) // 使用的模板
.registerWriteHandler(new XXXStrategy(需要的参数)) // 自定义单元格合并策略
.build();
总体样貌,模板名称为screenTemplate.xlsx
,工作表名称为sheet0
。
拉长单元格,查看具体变量。
@Service
public class ScreenServiceImpl implements ScreenService {
@Override
public void export(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) {
// HttpServletResponse消息头参数设置
String filename = "exportFile.xlsx";
httpServletResponse.addHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + filename);
httpServletResponse.setContentType("application/octet-stream;charset=UTF-8");
httpServletResponse.addHeader("Pragma", "no-cache");
httpServletResponse.addHeader("Cache-Control", "no-cache");
// 通过ClassPathResource获取/resources/template下的模板文件
ClassPathResource classPathResource = new ClassPathResource("template/screenTemplate.xlsx");
// 这里用try-with-resource
try (
// 获取模板文件
InputStream screenParamTemplateFileName = classPathResource.getInputStream();
OutputStream screenOut = httpServletResponse.getOutputStream();
BufferedOutputStream screenBos = new BufferedOutputStream(screenOut);
) {
// --------------------------------基本配置--------------------------------
// 设置内容的策略
WriteCellStyle contentWriteCellStyle = new WriteCellStyle();
// 设置内容水平居中对齐
contentWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
// 设置内容垂直居中对齐
contentWriteCellStyle.setVerticalAlignment(VerticalAlignment.CENTER);
// 自动换行
contentWriteCellStyle.setWrapped(true);
// 设置字体样式和大小
WriteFont contentWriteFont = new WriteFont();
contentWriteFont.setFontHeightInPoints((short) 12);
contentWriteFont.setFontName("微软雅黑");
contentWriteCellStyle.setWriteFont(contentWriteFont);
// 配置横向填充
FillConfig fillConfig = FillConfig.builder().direction(WriteDirectionEnum.HORIZONTAL).build();
// sheet名称
WriteSheet writeSheet = EasyExcel.writerSheet("sheet0").build();
// --------------------------------基本配置--------------------------------
// ---------------------模拟获取第一部分的表格数据、表头参数---------------------
List<ScreenGatherDTO> screenGatherDTOList = new ArrayList<>();
// 构造5个产品数据
for (int i = 1; i <= 5; i++) {
// 每份数据乘以4,为了合并单元格做准备
for (int j = 0; j < 4; j++) {
ScreenGatherDTO screenGatherDTO = new ScreenGatherDTO();
screenGatherDTO.setScreenSize(String.valueOf(i * 10));
screenGatherDTO.setSupplier("厂商" + i);
screenGatherDTO.setPartMode("型号" + i);
screenGatherDTO.setResolution("1080P");
screenGatherDTO.setRefreshRate("60Hz");
screenGatherDTO.setPanel("IPS");
screenGatherDTOList.add(screenGatherDTO);
}
}
if (CollectionUtils.isNotEmpty(screenGatherDTOList)) {
for (int i = 0; i < screenGatherDTOList.size(); i++) {
// 在屏规格末尾加上表格模板参数
screenGatherDTOList.get(i).setValueTemplateParam("{screenValueTemplateParam" + i + ".value}");
}
}
// 填充第一个表头的单元格
ScreenValueExcelDTO screenValueExcelDTO = new ScreenValueExcelDTO();
List<ScreenValueExcelDTO> screenValueExcelDTOList = new ArrayList<>();
screenValueExcelDTO.setValue("产品测试");
screenValueExcelDTOList.add(screenValueExcelDTO);
// 在屏规格末尾加上表头模板参数
List<ScreenValueExcelDTO> screenTableExcelDTOList = new ArrayList<>();
for (int i = 0; i < 4; i++) {
ScreenValueExcelDTO screenTableExcelDTO = new ScreenValueExcelDTO();
switch (i) {
case 0:
screenTableExcelDTO.setValue("{screenTableExcelDTOList.modelName}");
break;
case 1:
screenTableExcelDTO.setValue("{screenTableExcelDTOList.testItemCategory}");
break;
case 2:
screenTableExcelDTO.setValue("{screenTableExcelDTOList.testItemName}");
break;
case 3:
screenTableExcelDTO.setValue("{screenTableExcelDTOList.subTestItemName}");
break;
default:
break;
}
screenTableExcelDTOList.add(screenTableExcelDTO);
}
// ---------------------模拟获取第一部分的表格数据、表头参数---------------------
// --------------------------------第一次导出--------------------------------
ExcelWriter screenTemplateExcelWriter = EasyExcel
.write(screenBos) // 导出临时文件,使用的是BufferedOutputStream
.withTemplate(screenParamTemplateFileName) // 使用的模板
.registerWriteHandler(new ScreenValueMergeStrategy(screenGatherDTOList, 1, 6, 5)) // 自定义单元格合并策略
.registerWriteHandler(new HorizontalCellStyleStrategy(null, contentWriteCellStyle)) // 只配置内容策略,头部为null
.build();
// 填充屏规格表格数据
screenTemplateExcelWriter.fill(new FillWrapper("screenGatherDTOList", screenGatherDTOList), writeSheet);
// 填充第一个表头的单元格
screenTemplateExcelWriter.fill(new FillWrapper("screenValueExcelDTOList", screenValueExcelDTOList), writeSheet);
// 填充表头模板参数
screenTemplateExcelWriter.fill(new FillWrapper("screenTableExcelDTOList", screenTableExcelDTOList), writeSheet);
screenTemplateExcelWriter.finish();
// --------------------------------第一次导出--------------------------------
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
public class ScreenValueMergeStrategy extends AbstractMergeStrategy {
/**
* 分组,每几行合并一次
*/
private List<Integer> exportFieldGroupCountList;
/**
* 合并的目标开始列索引
*/
private Integer targetBeginColIndex;
/**
* 合并的目标结束列索引
*/
private Integer targetEndColIndex;
/**
* 需要开始合并单元格的首行索引
*/
private Integer firstRowIndex;
public ScreenValueMergeStrategy() {
}
/**
* @param exportDataList 待合并目标行的值
* @param targetBeginColIndex 合并的目标开始列索引
* @param targetEndColIndex 合并的目标结束列索引
* @param firstRowIndex 需要开始合并单元格的首行索引
*/
public ScreenValueMergeStrategy(List<ScreenGatherDTO> exportDataList, Integer targetBeginColIndex, Integer targetEndColIndex, Integer firstRowIndex) {
this.exportFieldGroupCountList = getGroupCountList(exportDataList);
this.targetBeginColIndex = targetBeginColIndex;
this.targetEndColIndex = targetEndColIndex;
this.firstRowIndex = firstRowIndex;
}
@Override
protected void merge(Sheet sheet, Cell cell, Head head, Integer relativeRowIndex) {
if (cell.getRowIndex() == this.firstRowIndex && cell.getColumnIndex() >= targetBeginColIndex - 1 && cell.getColumnIndex() <= targetEndColIndex - 1) {
int rowCount = this.firstRowIndex;
for (Integer count : exportFieldGroupCountList) {
if (count == 1) {
rowCount += count;
continue;
}
// 合并单元格
CellRangeAddress cellRangeAddress;
for (int i = 0; i < targetEndColIndex - targetBeginColIndex + 1; i++) {
cellRangeAddress = new CellRangeAddress(rowCount - 1, rowCount + count - 2, i, i);
sheet.addMergedRegionUnsafe(cellRangeAddress);
}
rowCount += count;
}
}
}
/**
* 该方法将目标列根据值是否相同连续可合并,存储可合并的行数
*
* @param exportDataList
* @return
*/
private List<Integer> getGroupCountList(List<ScreenGatherDTO> exportDataList) {
if (CollectionUtils.isEmpty(exportDataList)) {
return new ArrayList<>();
}
List<Integer> groupCountList = new ArrayList<>();
int count = 1;
for (int i = 1; i < exportDataList.size(); i++) {
boolean equals = exportDataList.get(i).getPartMode().equals(exportDataList.get(i - 1).getPartMode());
if (equals) {
count++;
} else {
groupCountList.add(count);
count = 1;
}
}
// 处理完最后一条后
groupCountList.add(count);
return groupCountList;
}
}
未合并的效果。
合并后的效果。
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。