- excel导出工具类
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.support.ExcelTypeEnum;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.alibaba.excel.write.metadata.style.WriteCellStyle;
import com.alibaba.excel.write.style.HorizontalCellStyleStrategy;
import org.apache.poi.ss.usermodel.HorizontalAlignment;
import org.apache.poi.ss.usermodel.VerticalAlignment;
import javax.servlet.http.HttpServletResponse;
import java.io.OutputStream;
import java.net.URLEncoder;
import java.util.List;
/**
* Excel相关处理
* @author dmw
*/
public class ExcelUtil {
/**
* 单个sheet页导出
*/
public static void writeExcel(HttpServletResponse response, List<?> data, String fileName, String sheetName, Class<?> clazz) throws Exception {
EasyExcel.write(getOutputStream(fileName, response), clazz)
.excelType(ExcelTypeEnum.XLSX).sheet(sheetName)
.relativeHeadRowIndex(1)
.registerWriteHandler(new CustomTitleWriteHandler(clazz,fileName))
.registerWriteHandler(getCellStyle())
.doWrite(data);
}
/**
* 合并导出
*/
public static void writeExcelCustomMerge(HttpServletResponse response, List<?> data, String fileName, String sheetName, Class<?> clazz) throws Exception {
EasyExcel.write(getOutputStream(fileName, response), clazz)
.excelType(ExcelTypeEnum.XLSX)
.sheet(sheetName)
//表格标题占位
.relativeHeadRowIndex(1)
.registerWriteHandler(getCellStyle())
.head(clazz)
//自定义表格标题处理
.registerWriteHandler(new CustomTitleWriteHandler(clazz,fileName))
//自定义表格合并处理
.registerWriteHandler(new CustomMergeStrategy(clazz))
.doWrite(data);
}
/**
* 多个sheet页导出
*/
public static void writeExcels(HttpServletResponse response,List<List<?>> data, String fileName, Class<?> clazz) throws Exception{
ExcelWriter excelWriter = null;
try{
excelWriter = EasyExcel.write(getOutputStream(fileName, response), clazz).excelType(ExcelTypeEnum.XLSX).registerWriteHandler(getCellStyle()).build();
for(int i=0;i<data.size();i++){
WriteSheet writeSheet = EasyExcel.writerSheet(i, "sheet"+i).build();
excelWriter.write(data.get(i), writeSheet);
}
} finally {
if(excelWriter != null){
excelWriter.finish();
}
}
}
/**
* 设置请求
*/
private static OutputStream getOutputStream(String fileName, HttpServletResponse response) throws Exception {
fileName = URLEncoder.encode(fileName +"."+ ExcelTypeEnum.XLSX, "UTF-8");
// 告诉浏览器用什么软件可以打开此文件
response.setHeader("content-Type", "application/vnd.ms-excel");
response.setCharacterEncoding("utf8");
response.setHeader("content-disposition", "attachment;filename="+fileName+";"+"filename*=utf-8''"+fileName);
return response.getOutputStream();
}
/**
* 设置样式
*/
private static HorizontalCellStyleStrategy getCellStyle(){
//表头样式
WriteCellStyle headWriteCellStyle = new WriteCellStyle();
//设置表头居中对齐
headWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
//内容样式
WriteCellStyle contentWriteCellStyle = new WriteCellStyle();
//设置内容垂直居中对齐
contentWriteCellStyle.setVerticalAlignment(VerticalAlignment.CENTER);
//设置内容水平居中对齐
contentWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
return new HorizontalCellStyleStrategy(headWriteCellStyle,contentWriteCellStyle);
}
}
- 自定义表格标题处理
import com.alibaba.excel.write.handler.SheetWriteHandler;
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
import com.alibaba.excel.write.metadata.holder.WriteWorkbookHolder;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddress;
import java.lang.reflect.Field;
/**
* @description: 表格标题处理
* @author: dmw
* @date: 2023/3/2 17:00
**/
public class CustomTitleWriteHandler implements SheetWriteHandler {
/**
* 标题
*/
private final String fileName;
/**
* DTO数据类型
*/
private final Class<?> elementType;
public CustomTitleWriteHandler(Class<?> elementType,String fileName) {
this.fileName = fileName;
this.elementType = elementType;
}
@Override
public void afterSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {
// 获取clazz所有的属性
Field[] fields = this.elementType.getDeclaredFields();
Workbook workbook = writeWorkbookHolder.getWorkbook();
Sheet sheet = workbook.getSheetAt(0);
Row row1 = sheet.createRow(0);
row1.setHeight((short) 800);
Cell cell = row1.createCell(0);
//设置标题
cell.setCellValue(fileName);
CellStyle cellStyle = workbook.createCellStyle();
cellStyle.setVerticalAlignment(VerticalAlignment.CENTER);
cellStyle.setAlignment(HorizontalAlignment.CENTER);
Font font = workbook.createFont();
font.setBold(true);
font.setFontHeight((short) 400);
font.setFontName("宋体");
cellStyle.setFont(font);
cell.setCellStyle(cellStyle);
sheet.addMergedRegionUnsafe(new CellRangeAddress(0, 0, 0, fields.length-1));
}
}
4 . 自定义表格合并注解
import java.lang.annotation.*;
/**
* 自定义注解,用于判断是否需要合并以及合并的主键
* @author dmw
*/
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface CustomMerge {
/**
* 是否是主键,即该字段相同的行合并
*/
boolean isPk() default false;
/**
* 是否需要合并单元格
*/
boolean needMerge() default false;
}
3.2 表格合并策略
package com.springcloud.aj.mall.common.util.easyexcel;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.write.handler.RowWriteHandler;
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
import com.alibaba.excel.write.metadata.holder.WriteTableHolder;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.util.CellRangeAddress;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
/**
* 自定义单元格合并策略
* @author dmw
*/
@Slf4j
public class CustomMergeStrategy implements RowWriteHandler {
/**
* 主键下标
*/
private Integer pkIndex;
/**
* 需要合并的列的下标集合
*/
private final List<Integer> needMergeColumnIndex = new ArrayList<>();
/**
* DTO数据类型
*/
private final Class<?> elementType;
public CustomMergeStrategy(Class<?> elementType) {
this.elementType = elementType;
}
@Override
public void beforeRowCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Integer integer, Integer integer1, Boolean aBoolean) {
}
@Override
public void afterRowCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Row row, Integer integer, Boolean aBoolean) {
}
@Override
public void afterRowDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Row row, Integer relativeRowIndex, Boolean isHead) {
// 如果是标题,则直接返回
if (isHead) {
return;
}
// 获取当前sheet
Sheet sheet = writeSheetHolder.getSheet();
if (null == pkIndex) {
this.lazyInit(writeSheetHolder);
}
// 不能和标题合并,只能数据行之间合并
int rowNum = row.getRowNum();
if (rowNum <= 1) {
return;
}
// 获取上一行数据
Row lastRow = sheet.getRow(rowNum - 1);
// 将本行和上一行是同一类型的数据(通过主键字段进行判断),则需要合并
if (lastRow.getCell(pkIndex).getStringCellValue().equalsIgnoreCase(row.getCell(pkIndex).getStringCellValue())) {
boolean isMerged = false;
for (Integer needMerIndex : needMergeColumnIndex) {
List<CellRangeAddress> mergeRegions = sheet.getMergedRegions();
for (int i = 0; i < mergeRegions.size(); i++) {
CellRangeAddress cellRangeAddress = mergeRegions.get(i);
// 若上一个单元格已经被合并,则先移出原有的合并单元,再重新添加合并单元
if (cellRangeAddress.isInRange(rowNum - 1, needMerIndex)) {
log.info("已合并移除:{}",i);
sheet.removeMergedRegion(i);
cellRangeAddress.setLastRow(rowNum);
sheet.addMergedRegionUnsafe(cellRangeAddress);
isMerged = true;
}
}
// 若上一个单元格未被合并,则新增合并单元
if (!isMerged) {
CellRangeAddress cellRangeAddress = new CellRangeAddress(rowNum - 1, rowNum, needMerIndex, needMerIndex);
sheet.addMergedRegionUnsafe(cellRangeAddress);
}
}
}
}
/**
* 初始化主键下标和需要合并字段的下标
*/
private void lazyInit(WriteSheetHolder writeSheetHolder) {
// 获取当前sheet
Sheet sheet = writeSheetHolder.getSheet();
// 获取标题行
Row titleRow = sheet.getRow(1);
// 获取DTO所有的属性
Field[] fields = this.elementType.getDeclaredFields();
// 遍历所有的字段,因为是基于DTO的字段来构建excel,所以字段数 >= excel的列数
for (Field theField : fields) {
// 获取@ExcelProperty注解,用于获取该字段对应在excel中的列的下标
ExcelProperty easyExcelAnn = theField.getAnnotation(ExcelProperty.class);
// 为空,则表示该字段不需要导入到excel,直接处理下一个字段
if (null == easyExcelAnn) {
continue;
}
// 获取自定义的注解,用于合并单元格
CustomMerge customMerge = theField.getAnnotation(CustomMerge.class);
// 没有@CustomMerge注解的默认不合并
if (null == customMerge) {
continue;
}
for (int index = 0; index < fields.length; index++) {
Cell theCell = titleRow.getCell(index);
// 当配置为不需要导出时,返回的为null,这里作一下判断,防止NPE
if (null == theCell) {
continue;
}
// 将字段和excel的表头匹配上
if (easyExcelAnn.value()[0].equalsIgnoreCase(theCell.getStringCellValue())) {
if (customMerge.isPk()) {
pkIndex = index;
}
if (customMerge.needMerge()) {
needMergeColumnIndex.add(index);
}
}
}
}
// 没有指定主键,则异常
if (null == this.pkIndex) {
throw new IllegalStateException("使用@CustomMerge注解必须指定主键");
}
}
}
- 表格实体vo
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.write.style.ColumnWidth;
import com.springcloud.aj.mall.common.util.RebateStatusConverter;
import com.springcloud.aj.mall.common.util.easyexcel.CustomMerge;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.math.BigDecimal;
@Data
public class FinanceRebateExportVo {
@ColumnWidth(20)
@ExcelProperty("订单编号")
@CustomMerge(needMerge = true,isPk = true)
private String orderCode;
//......其他字段
@ColumnWidth(15)
@CustomMerge(needMerge = true)
@ExcelProperty(value = "实际返利金额" )
private BigDecimal realRebateAmount;
- 实际使用
@Override
public void financeRebateExport(HttpServletResponse response,FinanceRebateForm form) {
String sheetName = "返利报表";
if (Objects.nonNull(form.getBeginTime()) && Objects.nonNull(form.getEndTime())){
String begin = form.getBeginTime().substring(0, 10);
String end = form.getEndTime().substring(0, 10);
sheetName=sheetName+"("+begin+"至"+end+")";
}
List<FinanceRebateVo> rebateList = ajOrderMapper.getFinanceRebateList(form);
// 转换成execl 对象输出
List<FinanceRebateExportVo> list = rebateList.stream().map(record -> {
FinanceRebateExportVo excelVO = new FinanceRebateExportVo();
BeanUtils.copyProperties(record, excelVO);
return excelVO;
}).collect(Collectors.toList());
try {
ExcelUtil.writeExcelCustomMerge(response,list, sheetName, sheetName, FinanceRebateExportVo.class);
} catch (Exception e) {
e.printStackTrace();
}
}
- 导出效果如下
end…
1.已参考并修复导出合并问题 easyExcel导入导出
2.添加自定义表格标题
感谢