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页数据无法一次性导出,这里是先转成流最后一起输出

五 反思

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

相关推荐
wainyz5 分钟前
Java NIO操作
java·开发语言·nio
工业3D_大熊11 分钟前
【虚拟仿真】CEETRON SDK在船舶流体与结构仿真中的应用解读
java·python·科技·信息可视化·c#·制造·虚拟现实
lzb_kkk20 分钟前
【JavaEE】JUC的常见类
java·开发语言·java-ee
爬山算法43 分钟前
Maven(28)如何使用Maven进行依赖解析?
java·maven
2401_857439691 小时前
SpringBoot框架在资产管理中的应用
java·spring boot·后端
怀旧6661 小时前
spring boot 项目配置https服务
java·spring boot·后端·学习·个人开发·1024程序员节
李老头探索1 小时前
Java面试之Java中实现多线程有几种方法
java·开发语言·面试
芒果披萨1 小时前
Filter和Listener
java·filter
qq_4924484461 小时前
Java实现App自动化(Appium Demo)
java
阿华的代码王国1 小时前
【SpringMVC】——Cookie和Session机制
java·后端·spring·cookie·session·会话