EasyExcel模板填充以及填充多个sheet

一 需求:

有一个需求是根据不同维度去查询数据然后汇总,最后一行数据为合计数据,并且总计那行要合并单元格

二 思路

因为有7个维度,不想去写7个查询,然后分析之后发现只有汇总条件是可变的,其它数据一样

然后创建一个视图 在那个视图里汇总数据,后续就算改变字段啥的也方便

以下是**部分关键代码**

```java

String nowDate = DateUtil.format(new Date(), "yyyy-MM-dd");

String paramDate = DateUtil.format(vo.getDate(), "yyyy-MM-dd");

int date = DateUtil.compare(DateUtil.parseDate(nowDate), DateUtil.parseDate(paramDate));

String sql=null;

if (vo.getStatisticDim().equals(DimensionEnum.UNITDIM.name())){

//voList=baseMapper.queryStatisticByDim(vo);

sql="t.construct_unit";

}else if (vo.getStatisticDim().equals(DimensionEnum.PROMANAGEDIM.name())){

vo.setConstructUnit(null);

sql="t.discipline";

//voList = baseMapper.queryByDimAndDiscipline(vo);

}else if (vo.getStatisticDim().equals(DimensionEnum.UNITPROMANAGERDIM.name())){

sql="t.construct_unit,t.project_implementation_manager_primary";

//voList = baseMapper.queryByUNITPROMANAGERDIM(vo);

}else if (vo.getStatisticDim().equals(DimensionEnum.UNITCITYCONSTRUCTDIM.name())){

sql="t.construct_unit,t.district,t.construction_unit";

}else if (vo.getStatisticDim().equals(DimensionEnum.PROVINCEPROMANAGERDIM.name())){

vo.setConstructUnit(null);

sql="t.project_management_manager_primary";

}else if (vo.getStatisticDim().equals(DimensionEnum.UNITCONSTRUCTDIM.name())){

sql="t.construct_unit,t.construction_unit";

}else if (vo.getStatisticDim().equals(DimensionEnum.UNITPROCONSTRUCTDIM.name())){

sql="t.construct_unit,t.construction_unit,t.project_name";

}

if (date<0){

// // TODO: 2024/3/23 查历史数据

}else {

voList = baseMapper.queryByDimAndUnit(vo,sql);

}

```

**xml**

```java

select rownum as index ,a.* from (

select

${sql},

sum(case when t.completion_report_completion_time is not null then 1 else 0 end) as videoFinishScale,

sum(case when t.completion_report_completion_time is not null and sysdate-t.completion_report_completion_time

<![CDATA[ <= ]]> 10 and (t.acceptance_opinion <![CDATA[<>]]> '通过' or t.acceptance_opinion is null) then 1 else 0

end) as videoTenunfinishScale,

sum(case when t.completion_report_completion_time is not null and sysdate-t.completion_report_completion_time

<![CDATA[ <= ]]> 20 and (t.acceptance_opinion <![CDATA[<>]]> '通过' or t.acceptance_opinion is null) then 1 else 0

end) as videoTwnunfinishScale,

sum(case when t.completion_report_completion_time is not null and sysdate-t.completion_report_completion_time

<![CDATA[ <= ]]> 30 and (t.acceptance_opinion <![CDATA[<>]]> '通过' or t.acceptance_opinion is null) then 1 else 0

end) as videoThiunfinishScale,

sum(case when t.completion_report_completion_time is not null and sysdate-t.completion_report_completion_time >

30 and (t.acceptance_opinion <![CDATA[<>]]> '通过' or t.acceptance_opinion is null) then 1 else 0 end) as

videoUnfinishScale,

(sum(case when t.completion_report_completion_time is not null and sysdate-t.completion_report_completion_time >

30 and (t.acceptance_opinion <![CDATA[<>]]> '通过' or t.acceptance_opinion is null) then 1 else 0 end) /

decode(sum(case when t.completion_report_completion_time is not null then 1 else 0 end),0,1,sum(case when

t.completion_report_completion_time is not null then 1 else 0 end))) * 100 as videoUnfinishRate,

sum(case when t.completion_report_completion_time is not null and sysdate-t.completion_report_completion_time >

30 and t.acceptance_opinion = '通过' then 1 else 0 end) as videoUnfinishCount,

sum(case when t.completion_report_completion_time is not null and t.acceptance_opinion = '通过' then 1 else 0 end)

as videofinishCount,

(sum(case when t.completion_report_completion_time is not null and t.acceptance_opinion = '通过' then 1 else 0

end) / decode(sum(case when t.completion_report_completion_time is not null then 1 else 0 end),0,1,sum(case when

t.completion_report_completion_time is not null then 1 else 0 end))) * 100 as videoRate,

sum(case when t.acceptance_opinion = '通过' then 1 else 0 end) as videoCount,

sum(case when t.acceptance_opinion = '通过' and (t.acceptance_failed_times = 0 or t.acceptance_failed_times is

null) then 1 else 0 end) as videoPass

from video_statistic_view t

where 1=1<if test="vo.constructUnit != null and vo.constructUnit !='' ">and t.construct_unit in

<foreach collection="vo.constructUnit" item="unit" open="(" separator="," close=")">

#{unit}

</foreach>

</if>

<if test="vo.projectImplementationManagerPrimary != null and vo.projectImplementationManagerPrimary != ''">

and t.project_implementation_manager_primary like concat('%', #{vo.projectImplementationManagerPrimary},'%')

</if>

<if test="vo.district != null and vo.district != ''">

and t.district like concat('%', #{vo.district},'%')

</if>

<if test="vo.constructionUnit != null and vo.constructionUnit != ''">

and t.construction_unit like concat('%', #{vo.constructionUnit},'%')

</if>

<if test="vo.projectManagementManagerPrimary != null and vo.projectManagementManagerPrimary != ''">

and t.project_management_manager_primary like concat('%', #{vo.projectManagementManagerPrimary},'%')

</if>

<if test="vo.projectName != null and vo.projectName != ''">

and t.project_name like concat('%', #{vo.projectName},'%')

</if>

group by ${ sql}

) a order by index

```

三 easyexcel导出

3.1 根据维度导出

之前用过注解的方式导出,考虑到这次比较复杂一点就用模版填充的方式导出 ,刚做需求后面也很可能会改动,到时候也简单一点

```java

// 根据维度查出数据

List<VideoStatisticVO> voList1 = this.getVideoStatisticsByDim(vo, new ArrayList<>());

List<VideoStatisticVO> voList=this.getVideoStatisticCount(voList1);

String excelName=null;

int num=1;

if (vo.getStatisticDim().equals(DimensionEnum.UNITDIM.name())){

excelName="建设单位维度汇总";

}else if (vo.getStatisticDim().equals(DimensionEnum.PROMANAGEDIM.name())){

excelName="项目管理专业维度汇总";

}else if (vo.getStatisticDim().equals(DimensionEnum.UNITPROMANAGERDIM.name())){

excelName="建设单位项目经理维度汇总";

num = 2;

}else if (vo.getStatisticDim().equals(DimensionEnum.UNITCITYCONSTRUCTDIM.name())){

excelName="建设单位区县、施工单位维度汇总";

num = 3;

}else if (vo.getStatisticDim().equals(DimensionEnum.PROVINCEPROMANAGERDIM.name())){

excelName="省管项目经理汇总";

}else if (vo.getStatisticDim().equals(DimensionEnum.UNITCONSTRUCTDIM.name())){

excelName="建设单位、施工单位汇总";

num = 2;

}else if (vo.getStatisticDim().equals(DimensionEnum.UNITPROCONSTRUCTDIM.name())){

excelName="建设单位、项目、施工单位汇总";

num = 3;

}

try {

ClassPathResource classPathResource = null;

classPathResource = new ClassPathResource("/template/"+excelName+".xlsx");

response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");

response.setHeader("content-Type", "application/vnd.ms-excel");

response.setCharacterEncoding("utf-8");

// 这里URLEncoder.encode可以防止中文乱码 当然和easyexcel没有关系

String fileName = URLEncoder

.encode(excelName + "-" + DateUtil.format(DateUtil.date(), DatePattern.PURE_DATE_PATTERN), "UTF-8")

.replaceAll("\\+", "%20");

response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");

Map<String, Object> templateParamMap = new HashMap<>();

LpsRemarkDicItem dicItem = new LpsRemarkDicItem();

dicItem.setCode(vo.getStatisticDim());

List<LpsRemarkDicItem> dicItems = dicItemService.selectListByCode(dicItem);

StringBuilder builder = new StringBuilder();

if (dicItems !=null && dicItems.size()>0){

for (int i = 0; i < dicItems.size(); i++) {

builder.append(i+1).append(". ").append(dicItems.get(i).getDetail()).append("\r\n");

}

}

ExcelFillCellMergePrevColUtils cellMergePrevColUtils = new ExcelFillCellMergePrevColUtils();

// 合并总计行

cellMergePrevColUtils.add(voList.size()+2 ,0,num);

// 创建ExcelWriterBuilder

ExcelWriterBuilder excelWriterBuilder = EasyExcel.write(response.getOutputStream())

.withTemplate(classPathResource.getInputStream());

ExcelWriter excelWriter = excelWriterBuilder.autoCloseStream(Boolean.FALSE)

// 列合并

.registerWriteHandler(cellMergePrevColUtils).build();

WriteSheet writeSheet1 = EasyExcel.writerSheet().build();

/**

* 模板注意 用{} 来表示你要用的变量 如果本来就有"{","}" 特殊字符 用"\{","\}"代替

* {} 代表普通变量 {.} 代表是list的变量

* WriteSheet writeSheet1 = EasyExcel.writerSheet(sheetName).build();容易报空指针异常

* 获取不到数据,可以选择不写或者升级版本3.0.1以上

*/

// 填写配置,forceNewRow true表示自动创建一行,后面的数据后移

FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build();

excelWriter.fill(voList,fillConfig,writeSheet1);

templateParamMap.put("title","QIP视频验收推进进展("+excelName+")");

templateParamMap.put("remark",builder.toString());

//填写数据

excelWriter.fill(templateParamMap,writeSheet1);

// 关闭填写

excelWriter.finish();

}catch (Exception e){}

```

![建设单位汇总](https://img-blog.csdnimg.cn/direct/ff5482c4ecf742dcac689038de47054d.png)

![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/4c921172600846c5a30a4421b06e3233.png)

![**最终效果**](https://img-blog.csdnimg.cn/direct/bb1481dfb745411696e31701a0d1dcf4.png)

3.2 不同数据导入到不同sheet中

这里也是采用模版导入,这里也犯了很多错误,下面会统一总结

```java

InputStream templateStream = classPathResource.getInputStream();

// 1 设置响应格式

response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");

response.setHeader("content-Type", "application/vnd.ms-excel");

response.setCharacterEncoding("utf-8");

// 这里URLEncoder.encode可以防止中文乱码 当然和easyexcel没有关系

String fileName = URLEncoder

.encode("视频验收汇总清单" + "-" + DateUtil.format(DateUtil.date(), DatePattern.PURE_DATE_PATTERN), "UTF-8")

.replaceAll("\\+", "%20");

response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");

// 2 创建一个XSSFWorkbook对象

XSSFWorkbook workbook = new XSSFWorkbook(templateStream);

//写到流里

ByteArrayOutputStream bos1 = new ByteArrayOutputStream();

workbook.write(bos1);

byte[] bArray1 = bos1.toByteArray();

InputStream is1 = new ByteArrayInputStream(bArray1);

ExcelWriter excelWriter = EasyExcel.write(response.getOutputStream())

.withTemplate(is1).build();

FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build();

// 循环处理每个sheet页

for (int i = 0; i < workbook.getNumberOfSheets()-2; i++) {

XSSFSheet sheet = workbook.getSheetAt(i);

String sheetName = sheet.getSheetName();

String byExcelName = DimensionEnum.getNameByStatus(sheetName);

vo.setStatisticDim(byExcelName);

// 查询数据

// 处理数据填充等操作

List<VideoStatisticVO> voList1 = this.getVideoStatisticsByDim(vo, new ArrayList<>());

List<VideoStatisticVO> voList = this.getVideoStatisticCount(voList1);

Map<String, Object> templateParamMap = new HashMap<>();

LpsRemarkDicItem dicItem = new LpsRemarkDicItem();

dicItem.setCode(vo.getStatisticDim());

List<LpsRemarkDicItem> dicItems = dicItemService.selectListByCode(dicItem);

StringBuilder builder = new StringBuilder();

if (dicItems !=null && dicItems.size()>0){

for (int k = 0; k < dicItems.size(); k++) {

builder.append(k+1).append(". ").append(dicItems.get(k).getDetail()).append("\r\n");

}

}

ExcelFillCellMergePrevColUtils cellMergePrevColUtils = new ExcelFillCellMergePrevColUtils();

// 合并总计行

int num =1;

if (byExcelName.equals(DimensionEnum.UNITPROMANAGERDIM.name()) || byExcelName.equals(DimensionEnum.UNITCONSTRUCTDIM.name()) ){

num = 2 ;

}else if(byExcelName.equals(DimensionEnum.UNITCITYCONSTRUCTDIM.name()) || byExcelName.equals(DimensionEnum.UNITPROCONSTRUCTDIM.name())) {

num = 3 ;

}

cellMergePrevColUtils.add(voList.size()+2 ,0,num);

// 创建基于模板的WriteSheet对象

WriteSheet writeSheet = EasyExcel.writerSheet(i).registerWriteHandler(cellMergePrevColUtils).build();

// 填充数据到指定sheet页

templateParamMap.put("title","QIP视频验收推进进展("+sheetName+")");

templateParamMap.put("remark",builder.toString());

//填写数据

excelWriter.fill(voList, fillConfig, writeSheet);

excelWriter.fill(templateParamMap,writeSheet);

}

// 处理第8 ,9sheet页数据

WriteSheet writeSheet8= EasyExcel.writerSheet(workbook.getSheetName(7)).build();

WriteSheet writeSheet9= EasyExcel.writerSheet(workbook.getSheetName(8)).build();

List<VideoStatisticDTO> sheet8List = this.selectData(vo);

excelWriter.fill(sheet8List,fillConfig,writeSheet8);

excelWriter.fill(sheet8List,fillConfig,writeSheet9);

//关闭流

excelWriter.finish();

bos1.close();

is1.close();

```

然后是列合并的工具类

```java

//列合并工具类

public class ExcelFillCellMergePrevColUtils implements CellWriteHandler {

private static final String KEY ="%s-%s";

//所有的合并信息都存在了这个map里面

Map<String, Integer> mergeInfo = new HashMap<>();

public ExcelFillCellMergePrevColUtils() {

}

@Override

public void beforeCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Row row, Head head, Integer integer, Integer integer1, Boolean aBoolean) {

}

@Override

public void afterCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Cell cell, Head head, Integer integer, Boolean aBoolean) {

}

@Override

public void afterCellDataConverted(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, CellData cellData, Cell cell, Head head, Integer integer, Boolean aBoolean) {

}

@Override

public void afterCellDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, List<CellData> list, Cell cell, Head head, Integer integer, Boolean aBoolean) {

//当前行

int curRowIndex = cell.getRowIndex();

//当前列

int curColIndex = cell.getColumnIndex();

Integer num = mergeInfo.get(String.format(KEY, curRowIndex, curColIndex));

if(null != num){

// 合并最后一行 ,列

mergeWithPrevCol(writeSheetHolder, cell, curRowIndex, curColIndex,num);

}

}

public void mergeWithPrevCol(WriteSheetHolder writeSheetHolder, Cell cell, int curRowIndex, int curColIndex, int num) {

Sheet sheet = writeSheetHolder.getSheet();

CellRangeAddress cellRangeAddress = new CellRangeAddress(curRowIndex, curRowIndex, curColIndex, curColIndex + num);

sheet.addMergedRegion(cellRangeAddress);

}

//num从第几列开始增加多少列,(6,2,7)代表的意思就是第6行的第2列至第2+7也就是9列开始合并

public void add (int curRowIndex, int curColIndex , int num){

mergeInfo.put(String.format(KEY, curRowIndex, curColIndex),num);

}

}

```

四 总结错误

这里遇到了几个问题,总结一下

1 以流的形式输出文件,controller层返回格式为void

2 WriteSheet writeSheet1 = EasyExcel.writerSheet(sheetName).build();以这样方式写时报错,报dofill方法哪里空指针异常 没找到问题,可能是当时idea缓存,也有可能是版本低于3.0.0

3 分多个sheet页导出时忘记绑定模版报fill方法无法绑定模版

```java

ExcelWriter excelWriter = EasyExcel.write(response.getOutputStream())

.withTemplate(is1).build();

.withTemplate(is1)忘记写了

```

4 多个sheet页数据无法一次性导出,这里是先转成流最后一起输出

五 反思

在模版填充这里其实还可以动态表头模版导出数据的,这样就不用创建那么多模版了,后续需要优化,然后代码写法问题

相关推荐
守护者17012 分钟前
JAVA学习-练习试用Java实现“一个词频统计工具 :读取文本文件,统计并输出每个单词的频率”
java·学习
bing_15823 分钟前
Spring Boot 中ConditionalOnClass、ConditionalOnMissingBean 注解详解
java·spring boot·后端
ergdfhgerty25 分钟前
斐讯N1部署Armbian与CasaOS实现远程存储管理
java·docker
勤奋的知更鸟38 分钟前
Java性能测试工具列举
java·开发语言·测试工具
三目君42 分钟前
SpringMVC异步处理Servlet
java·spring·servlet·tomcat·mvc
用户05956611920942 分钟前
Java 基础篇必背综合知识点总结包含新技术应用及实操指南
java·后端
fie888943 分钟前
Spring MVC扩展与SSM框架整合
java·spring·mvc
不太可爱的叶某人1 小时前
【学习笔记】深入理解Java虚拟机学习笔记——第3章 垃圾收集器与内存分配策略
java·笔记·学习
YuTaoShao1 小时前
Java八股文——JVM「类加载篇」
java·开发语言·jvm
爱玩电脑的L1 小时前
javaee初阶-多线程
java·开发语言·jvm