最新在使用easypoi,使用注解导出和模板导出的方式,现在主要就模板导出合并单元格一些坑和解决方法。
首先我建议刚接触的同学看这篇文章,很详细,功能也比较全面,比较感谢这篇文章的原创作者,网站1:easyPOI基本用法 - 钟小嘿 - 博客园
然后模板导出一些坑,可以看这篇文章,作者写的很详细,目前模板导出存在的一些问题,网站2:springboot集成easypoi并使用其模板导出功能和遇到的坑_巴中第一皇子的博客-CSDN博客_springboot的poi
这几天因为最后一个问题捣鼓了好久,最后写了一个临时方案来处理:
我目前需要处理的报表样式如下:
公司 | 项目 | 姓名 | 有效工作日 | 合计 | 人数 |
寿险 | 社保平台 | 张三 | 12 | 12000.0 | 2 |
李四 | 13 | 12000.0 | 2 | ||
王五 | 14 | 12000.0 | 2 | ||
合计 | 4 | 120000 | 120000 | 240000 | 2 |
产险 | 公积金平台 | 赵六 | 13 | 12000.0 | 2 |
僧其 | 16 | 12000.0 | 2 | ||
合计 | 8 | 120000 | 120000 | 240000 | 2 |
上面是一个excel报表,上下分成两部分,分别是两个list(easypoi模板可以允许多list的)
这里的坑,可以看网站2中第四条中的说明,两种方法,即一个使用$fe , 一个使用fe (汗,第一次使用这个,我都没发现这两个不一样), 但是都存在缺陷,
这种多层循环,使用$fe会产生空白行, 使用fe会覆盖循环的数据以下的内容, 如我需求里有 “合计” 行,必定会覆盖。
尝试了很多次后,我的想法是,先把 以人员为维度的所有数据全部查出来,比如上面这个excel表,我查询的数据总共就是5条(这里分成两个list,上面一个3条,下面是2条),这时可能有人疑惑,但是“公司”和“项目” 我是要相同的合并在一起呀,不错,我先以“人员”为粒度查出所有,然后导出前对需要合并的列进行合并。
首先这里两个list都必须使用$fe,因为这样才不会有覆盖的情况产生。
下面是我的模板,分成zkr和bat两个map,里面各自有对应的list:
公司 | 项目 | 姓名 | 有效工作日 | 合计 | 人数 |
{{$fe:zkr.list t.companyName | t.projectName | t.name | t.effectDays | t.totalAmount | t.emplNum}} |
合计 | {{zkr.projectNum}} | 120000 | 120000 | 240000 | 2 |
{{$fe:bat.list t.companyName | t.projectName | t.name | t.effectDays | t.totalAmount | t.emplNum}} |
合计 | {{bat.projectNum}} | 120000 | 120000 | 240000 | 2 |
下面是我封装了下模板导出兼容合并单元格的方法,先贴网站1原始方法:
/**
* 根据模板生成excel后导出
*
* @param templatePath 模板路径
* @param map 数据集合
* @param fileName 文件名
* @param response
* @throws IOException
*/
public static void exportExcel(TemplateExportParams templatePath, Map map, String fileName, HttpServletResponse response) throws IOException {
Workbook workbook = ExcelExportUtil.exportExcel(templatePath, map);
downLoadExcel(fileName, response, workbook);
}
新兼容合并单元格方法:
/**
* @Description: 合并指定列单元格并导出(合并行)
* @Date: 2021/5/24 9:42
* @Param templatePath: 模板路径
* @Param map: 需要导出的数据map
* @Param fileName: 导出文件名称
* @Param response: response
* @Param sheetMergeParamList: sheet参数集合
* @return: void
* @Version: 1.0
**/
public static void exportMergeExcel(TemplateExportParams templatePath, Map map, String fileName, HttpServletResponse response, List sheetMergeParamList) throws IOException {
Workbook workbook = ExcelExportUtil.exportExcel(templatePath, map);
//合并单元格
mergeExcel(workbook,sheetMergeParamList);
downLoadExcel(fileName, response, workbook);
}
/**
* @Description: 合并单元格具体执行方法
* @Date: 2021/5/24 14:58
* @Param workbook: 工作薄
* @Param sheetMergeParamList: sheet集合
* @return: void
* @Version: 1.0
**/
private static void mergeExcel(Workbook workbook, List sheetMergeParamList){
for(SheetMergeParam sheetMergeParam : sheetMergeParamList){
Sheet sheet = workbook.getSheetAt(sheetMergeParam.getSheetIndex());
int lastRowNum = sheet.getLastRowNum();
int i;
for(i = 0;i < lastRowNum;i++){
//获取每行第一个单元格
if(null == sheet.getRow(i) || null == sheet.getRow(i).getCell(0)){
continue;
}
Cell cell = sheet.getRow(i).getCell(0);
if(sheetMergeParam.getIgnoreCellValues().contains(cell.getStringCellValue()) || StringUtils.isEmpty(cell.getStringCellValue()) ){
continue;
}
//定义合并终止行数
int endRowNum = 0;
for(int j = i + 1 ;j <= lastRowNum;j++){
Cell desColumn = sheet.getRow(i).getCell(sheetMergeParam.getDesColumnIndex());
Cell nextDesColumn = sheet.getRow(j).getCell(sheetMergeParam.getDesColumnIndex());
if(!desColumn.getStringCellValue().equals(nextDesColumn.getStringCellValue())){
//值不同,终止此层循环
break;
}
endRowNum ++;
}
//判断是否有合并项
if(endRowNum == 0){
continue;
}
//合并单元格操作
for(int z = 0; z < sheetMergeParam.getMergeColumnIndexs().length; z++){
//合并起始行,终止行,起始列,终止列
int firstRow = i;
int lastRow = i + endRowNum;
int firstCol = sheetMergeParam.getMergeColumnIndexs()[z];
int lastCol = sheetMergeParam.getMergeColumnIndexs()[z];
PoiMergeCellUtil.addMergedRegion(sheet,firstRow,lastRow,firstCol,lastCol);
}
//合并后行号下移
i = i + endRowNum;
}
}
}
下面是我的测试类:
@GetMapping("/testExport")
public void testExport(HttpServletResponse response) throws Exception{
test(response);
}
private void test(HttpServletResponse response) throws Exception{
//第一组map数据(内含list)
Map zkr = Maps.newHashMap();
zkr.put("projectNum",4);
ContractTest contractTestZkr = new ContractTest("寿险","社保平台",12000,2,"张三",12
);
ContractTest contractTestZkr1 = new ContractTest("寿险","社保平台",12000,2,"李四",13);
ContractTest contractTestZkr2 = new ContractTest("寿险","社保平台",12000,2,"王五",14);
zkr.put("list",Lists.newArrayList(contractTestZkr,contractTestZkr1,contractTestZkr2));
//第二组map数据(内含list)
Map bat = Maps.newHashMap();
bat.put("projectNum",8);
ContractTest contractTestBat = new ContractTest("产险","公积金平台",12000,2,"赵六",13);
ContractTest contractTestBat1 = new ContractTest("产险","公积金平台",12000,2,"僧其",16);
bat.put("list",Lists.newArrayList(contractTestBat,contractTestBat1));
Map map = Maps.newHashMap();
map.put("zkr",zkr);
map.put("bat",bat);
TemplateExportParams templatePath = new TemplateExportParams("template/当月结算考勤汇总测试.xlsx",true);
SheetMergeParam sheetMergeParam = new SheetMergeParam(0,0,new int[]{0,1},Lists.newArrayList("公司","合计"));
ExcelUtils.exportMergeExcel(templatePath,map,"当月结算考勤汇总测试daochu",response, Lists.newArrayList(sheetMergeParam));
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public class SheetMergeParam implements Serializable{
/***
* sheet下标
*/
private int sheetIndex;
/***
* 合并参考列列号
*/
private int desColumnIndex;
/***
* 合并单元格列列号数组
*/
private int[] mergeColumnIndexs;
/***
* 忽略行内容(如标题行,合计行等)
*/
private List ignoreCellValues;
}
这个是我的解决方案,如有不正确的地方,欢迎各位指正。
附加
关于导出的样式需要更改的,我贴一篇样式是实例:
/**
* @Description: 导出表格样式类
* @Date: 2021/4/22 18:02
* @Param null:
* @return: null
* @Version: 1.0
**/
public class ExcelStyleUtil implements IExcelExportStyler {
private static final short STRING_FORMAT = (short) BuiltinFormats.getBuiltinFormat("TEXT");
private static final short FONT_SIZE_TEN = 9;
private static final short FONT_SIZE_ELEVEN = 10;
private static final short FONT_SIZE_TWELVE = 10;
/**
* 大标题样式
*/
private CellStyle headerStyle;
/**
* 每列标题样式
*/
private CellStyle titleStyle;
/**
* 数据行样式
*/
private CellStyle styles;
public ExcelStyleUtil(Workbook workbook) {
this.init(workbook);
}
/**
* 初始化样式
*
* @param workbook
*/
private void init(Workbook workbook) {
this.headerStyle = initHeaderStyle(workbook);
this.titleStyle = initTitleStyle(workbook);
this.styles = initStyles(workbook);
}
/**
* 大标题样式
*
* @param color
* @return
*/
@Override
public CellStyle getHeaderStyle(short color) {
return headerStyle;
}
/**
* 每列标题样式
*
* @param color
* @return
*/
@Override
public CellStyle getTitleStyle(short color) {
return titleStyle;
}
/**
* 数据行样式
*
* @param parity 可以用来表示奇偶行
* @param entity 数据内容
* @return 样式
*/
@Override
public CellStyle getStyles(boolean parity, ExcelExportEntity entity) {
return styles;
}
/**
* 获取样式方法
*
* @param dataRow 数据行
* @param obj 对象
* @param data 数据
*/
@Override
public CellStyle getStyles(Cell cell, int dataRow, ExcelExportEntity entity, Object obj, Object data) {
return getStyles(true, entity);
}
/**
* 模板使用的样式设置
*/
@Override
public CellStyle getTemplateStyles(boolean isSingle, ExcelForEachParams excelForEachParams) {
return null;
}
/**
* 初始化--大标题样式
*
* @param workbook
* @return
*/
private CellStyle initHeaderStyle(Workbook workbook) {
CellStyle style = getBaseCellStyle(workbook);
style.setFont(getFont(workbook, FONT_SIZE_TWELVE, true));
return style;
}
/**
* 初始化--每列标题样式
*
* @param workbook
* @return
*/
private CellStyle initTitleStyle(Workbook workbook) {
CellStyle style = getBaseCellStyle(workbook);
style.setFont(getFont(workbook, FONT_SIZE_ELEVEN, false));
//背景色
style.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
style.setFillPattern(FillPatternType.SOLID_FOREGROUND);
return style;
}
/**
* 初始化--数据行样式
*
* @param workbook
* @return
*/
private CellStyle initStyles(Workbook workbook) {
CellStyle style = getBaseCellStyle(workbook);
style.setFont(getFont(workbook, FONT_SIZE_TEN, false));
style.setDataFormat(STRING_FORMAT);
return style;
}
/**
* 基础样式
*
* @return
*/
private CellStyle getBaseCellStyle(Workbook workbook) {
CellStyle style = workbook.createCellStyle();
//下边框
style.setBorderBottom(BorderStyle.THIN);
//左边框
style.setBorderLeft(BorderStyle.THIN);
//上边框
style.setBorderTop(BorderStyle.THIN);
//右边框
style.setBorderRight(BorderStyle.THIN);
//水平居中
style.setAlignment(HorizontalAlignment.CENTER);
//上下居中
style.setVerticalAlignment(VerticalAlignment.CENTER);
//设置自动换行
style.setWrapText(true);
return style;
}
/**
* 字体样式
*
* @param size 字体大小
* @param isBold 是否加粗
* @return
*/
private Font getFont(Workbook workbook, short size, boolean isBold) {
Font font = workbook.createFont();
//字体样式
font.setFontName("宋体");
//是否加粗
font.setBold(isBold);
//字体大小
font.setFontHeightInPoints(size);
return font;
}
}
此处模板导出使用地方:
/**
* excel 导出
*
* @param list 数据列表
* @param pojoClass pojo类型
* @param fileName 导出时的excel名称
* @param response
* @param exportParams 导出参数(标题、sheet名称、是否创建表头,表格类型)
*/
private static void defaultExport(List list, Class pojoClass, String fileName, HttpServletResponse response, ExportParams exportParams) throws IOException {
//改变原始样式
ExportParams exportParamsSelf = new ExportParams(exportParams.getTitle(), exportParams.getSheetName(), ExcelType.XSSF);
exportParamsSelf.setStyle(ExcelStyleUtil.class);
//把数据添加到excel表格中
Workbook workbook = ExcelExportUtil.exportExcel(exportParamsSelf, pojoClass, list);
downLoadExcel(fileName, response, workbook);
}
多sheet表普通导出使用地方:
public ResultDomain testExportMutiSheet(HttpServletResponse response) throws Exception{
Workbook workBook = null;
List exportList = Lists.newArrayList(new TestEntityVo("中二班","张三",12,"广东深圳"),
new TestEntityVo("中二班","李四",15,"湖北武汉"));
// 创建参数对象(用来设定excel得sheet得内容等信息)
ExportParams deptExportParams = new ExportParams();
// 设置sheet得名称
deptExportParams.setSheetName("员工表");
deptExportParams.setStyle(ExcelStyleUtil.class);
// 创建sheet1使用得map
Map deptExportMap = new HashMap<>();
// title的参数为ExportParams类型,目前仅仅在ExportParams中设置了sheetName
deptExportMap.put("title", deptExportParams);
// 模版导出对应得实体类型
deptExportMap.put("entity", TestEntityVo.class);
// sheet中要填充得数据
deptExportMap.put("data", exportList);
List emExportList = Lists.newArrayList(new TestGoodsVo("001","辣条",1),
new TestGoodsVo("002","方便面",2));
ExportParams empExportParams = new ExportParams();
empExportParams.setSheetName("货物表");
empExportParams.setStyle(ExcelStyleUtil.class);
// 创建sheet2使用得map
Map empExportMap = new HashMap<>();
empExportMap.put("title", empExportParams);
empExportMap.put("entity", TestGoodsVo.class);
empExportMap.put("data", emExportList);
// 将sheet1、sheet2、sheet3使用得map进行包装
List
下载:
/**
* excel下载
*
* @param fileName 下载时的文件名称
* @param response
* @param workbook excel数据
*/
private static void downLoadExcel(String fileName, HttpServletResponse response, Workbook workbook) throws IOException {
try {
response.setCharacterEncoding("UTF-8");
response.setHeader("content-Type", "application/vnd.ms-excel");
response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName + ".xlsx", "UTF-8"));
workbook.write(response.getOutputStream());
} catch (Exception e) {
throw new IOException(e.getMessage());
}
}