Hutool操作excel相较于easyexcel更加的简单灵活,适合数据量小、格式不太复杂的excel操作
此excel导出格式较为复杂,较多的合并单元格。首先定义具体类,采用list集合封装,调用合并方法进行合并单元格即可。
导入依赖
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.7.19</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>4.0.0</version>
</dependency>
//导出excel
// 通过工具类创建writer
ExcelWriter writer = ExcelUtil.getWriter();
int number = HutoolExcelUtil.setHeaderAlias(writer, DataTbbTableVO.class);
// 默认的,未添加alias的属性也会写出,如果想只写出加了别名的字段,可以调用此方法排除之
writer.setOnlyAlias(true);
//标题字体样式
Font font = HutoolExcelUtil.createFont(writer, true, false, "仿宋_GB2312", 22);
//合并单元格后的标题行,使用默认标题样式
CellStyle cellStyle = HutoolExcelUtil.createCellStyle(writer, font, true, VerticalAlignment.CENTER, HorizontalAlignment.CENTER);
writer.merge(0, 0, 0, number - 1, dataTbbInfo.getTitle(), cellStyle);
//跳过当前行 这里我也不知道为什么要调用,不调用内容表头不会显示
writer.passCurrentRow();
//写入标题
writer.writeHeadRow(Arrays.asList(""));
//全局字体默认样式
Font font1 = HutoolExcelUtil.createFont(writer, false, false, "仿宋_GB2312", 12);
//设置全局样式
StyleSet styleSet = HutoolExcelUtil.setBaseGlobalStyle(writer, font1);
//设置标题样式
HutoolExcelUtil.createHeadCellStyle(styleSet, font1, HorizontalAlignment.CENTER, VerticalAlignment.CENTER);
//手动设置列宽
HutoolExcelUtil.setSizeColumn(writer, Arrays.asList(5, 12, 16, 15, 14, 15, 30, 15, 15, 12), number - 1);
//合并单元格
Map<String, Object> merge = this.merge(dataTbbInfo.getDataTbbTableVOS(), writer);
//设置筛选下拉框
HutoolExcelUtil.setFilter(writer, "A2:D" + merge.get("index"));
//一次性写出内容,使用默认样式,强制输出标题
writer.write((List<List<Object>>) merge.get("result"), true);
//下载文件
HutoolExcelUtil.downloadExcel(response, dataTbbInfo.getTitle(), writer);
/**
* @author z
* @Description 导出excel工具类
* @date 2022/11/21 18:21
*/
public class HutoolExcelUtil {
/**
* 方法描述: 全局基础样式设置
* 默认 全局水平居中+垂直居中
* 默认 自动换行
* 默认单元格边框颜色为黑色,细线条
* 默认背景颜色为白色
*
* @param writer writer
* @param font 字体样式
* @return cn.hutool.poi.excel.StyleSet
*/
public static StyleSet setBaseGlobalStyle(ExcelWriter writer, Font font) {
//全局样式设置
StyleSet styleSet = writer.getStyleSet();
//设置全局文本居中
styleSet.setAlign(HorizontalAlignment.CENTER, VerticalAlignment.CENTER);
//设置全局字体样式
styleSet.setFont(font, true);
//设置背景颜色 第二个参数表示是否将样式应用到头部
styleSet.setBackgroundColor(IndexedColors.WHITE, true);
//设置自动换行 当文本长于单元格宽度是否换行
styleSet.setWrapText();
// 设置全局边框样式
styleSet.setBorder(BorderStyle.THIN, IndexedColors.BLACK);
return styleSet;
}
/**
* 方法描述: 设置标题的基础样式
*
* @param styleSet StyleSet
* @param font 字体样式
* @param horizontalAlignment 水平排列方式
* @param verticalAlignment 垂直排列方式
* @return org.apache.poi.ss.usermodel.CellStyle
*/
public static CellStyle createHeadCellStyle(StyleSet styleSet, Font font,
HorizontalAlignment horizontalAlignment,
VerticalAlignment verticalAlignment) {
CellStyle headCellStyle = styleSet.getHeadCellStyle();
headCellStyle.setAlignment(horizontalAlignment);
headCellStyle.setVerticalAlignment(verticalAlignment);
headCellStyle.setFont(font);
return headCellStyle;
}
/**
* 方法描述: 设置基础字体样式字体 这里保留最基础的样式使用
*
* @param bold 是否粗体
* @param fontName 字体名称
* @param fontSize 字体大小
* @return org.apache.poi.ss.usermodel.Font
*/
public static Font createFont(ExcelWriter writer, boolean bold, boolean italic, String fontName, int fontSize) {
Font font = writer.getWorkbook().createFont();
//设置字体名称
font.setFontName(fontName);
//设置是否斜体
font.setItalic(italic);
//设置字体大小 以磅为单位
font.setFontHeightInPoints((short) fontSize);
//设置是否加粗
font.setBold(bold);
return font;
}
/**
* 方法描述: 设置行或单元格基本样式
*
* @param writer writer
* @param font 字体样式
* @param verticalAlignment 垂直居中
* @param horizontalAlignment 水平居中
* @return void
*/
public static CellStyle createCellStyle(ExcelWriter writer, Font font, boolean wrapText,
VerticalAlignment verticalAlignment,
HorizontalAlignment horizontalAlignment) {
CellStyle cellStyle = writer.getWorkbook().createCellStyle();
cellStyle.setVerticalAlignment(verticalAlignment);
cellStyle.setAlignment(horizontalAlignment);
cellStyle.setWrapText(wrapText);
cellStyle.setFont(font);
return cellStyle;
}
/**
* 方法描述: 设置边框样式
*
* @param cellStyle 样式对象
* @param bottom 下边框
* @param left 左边框
* @param right 右边框
* @param top 上边框
* @return void
*/
public static void setBorderStyle(CellStyle cellStyle, BorderStyle bottom, BorderStyle left, BorderStyle right,
BorderStyle top) {
cellStyle.setBorderBottom(bottom);
cellStyle.setBorderLeft(left);
cellStyle.setBorderRight(right);
cellStyle.setBorderTop(top);
}
/**
* 方法描述: 获取对象需要导出的列和别名 这里按字段顺序来(可以在自定义注解上添加属性标识字段顺序,重写方法)
* 在需要导出的字段上贴上注解 这里是@ExcelProperty,也可以用自定义注解
* 注解需要有value 标识别名 order 标识字段顺序
*
* @param clazz 对象类型
* @return int 导出的字段个数
*/
public static int setHeaderAlias(ExcelWriter writer, ExcelReader reader, Class<?> clazz) {
//需要导出的字段数
TreeMap<Integer, Map<String, String>> headerAliasMap = new TreeMap<>();
//判断类中字段类型和是否有别名注释
HutoolExcelUtil.getResult(headerAliasMap, clazz);
LinkedHashMap<String, String> linkedMap = new LinkedHashMap<>();
for (Map.Entry<Integer, Map<String, String>> entry : headerAliasMap.entrySet()) {
linkedMap.putAll(entry.getValue());
}
Optional.ofNullable(writer).ifPresent(w -> w.setHeaderAlias(linkedMap));
Optional.ofNullable(reader).ifPresent(r -> r.setHeaderAlias(linkedMap));
return linkedMap.size();
}
/**
* 判断类中字段类型和是否有别名注释
*
* @param headerAliasMap map集合
* @param clazz 类
*/
private static void getResult(TreeMap<Integer, Map<String, String>> headerAliasMap, Class<?> clazz) {
Field[] fields = clazz.getDeclaredFields();
Arrays.stream(fields).forEach(f -> {
//获取字段类型
Class<?> type = f.getType();
//判断字段类型是否是list
if (type.equals(List.class)) {
//获取集合的泛型对象
Type genericType = f.getGenericType();
if (genericType instanceof ParameterizedType) {
ParameterizedType pt = (ParameterizedType) genericType;
Class<?> aClass = (Class<?>) pt.getActualTypeArguments()[0];
//递归调用
HutoolExcelUtil.getResult(headerAliasMap, aClass);
}
}
//判断是否有注解
if (f.isAnnotationPresent(ExcelProperty.class)) {
ExcelProperty annotation = f.getAnnotation(ExcelProperty.class);
//别名
String[] value = annotation.value();
int order = annotation.order();
Map<String, String> alisMap = new HashMap<>();
String fieldName = f.getName();
//字段名
String headerAlias = value[0];
alisMap.put(fieldName, headerAlias);
headerAliasMap.put(order, alisMap);
}
});
}
/**
* 方法描述: 自适应宽度(中文支持)
*
* @param sheet 页
* @param size 因为for循环从0开始,size值为 列数-1
* @return void
*/
public static void setSizeAutoColumn(Sheet sheet, int size) {
for (int columnNum = 0; columnNum <= size; columnNum++) {
int columnWidth = sheet.getColumnWidth(columnNum) / 256;
for (int rowNum = 0; rowNum <= sheet.getLastRowNum(); rowNum++) {
Row currentRow;
//当前行未被使用过
if (sheet.getRow(rowNum) == null) {
currentRow = sheet.createRow(rowNum);
} else {
currentRow = sheet.getRow(rowNum);
}
if (currentRow.getCell(columnNum) != null) {
Cell currentCell = currentRow.getCell(columnNum);
if (currentCell.getCellType() == CellType.STRING) {
int length = currentCell.getStringCellValue().getBytes().length;
if (columnWidth < length) {
columnWidth = length;
}
}
}
}
sheet.setColumnWidth(columnNum, columnWidth * 256);
}
}
/**
* 方法描述: 手动宽度(中文支持)
*/
public static void setSizeColumn(ExcelWriter excelWriter, List<Integer> list, int size) {
for (int columnNum = 0; columnNum <= size; columnNum++) {
excelWriter.setColumnWidth(columnNum, list.get(columnNum));
}
}
/**
* 方法描述: excel 导出下载
*
* @param response 响应
* @param fileName 文件名
* @param writer writer
* @return void
*/
public static void downloadExcel(HttpServletResponse response, String fileName, ExcelWriter writer) {
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8");
// xxx.xlsx是弹出下载对话框的文件名
ServletOutputStream out = null;
try {
// 设置请求头属性
fileName = URLEncoder.encode(fileName, "UTF-8");
response.setHeader("Content-Disposition",
"attachment;filename=" + new String((fileName + ".xlsx").getBytes(), StandardCharsets.ISO_8859_1));
out = response.getOutputStream();
// 写出到文件
writer.flush(out, true);
// 关闭writer,释放内存
writer.close();
// 此处记得关闭输出Servlet流
IoUtil.close(out);
} catch (IOException e) {
throw new BusinessException("导出失败");
}
}
/**
* 方法描述: 最简单的导出,直接将数据放到excel
* 导入的数据最好是List<map>格式数据,主要原因是List<Object> ,
* 对象有字段是LocalDate或者是LocalDateTime类型的数据,
* 时间格式化可能达不到想要的效果
*
* @param data 对象集合 两种
* @param globalFont 全局字体样式
* @param clazz 导出的对应class类型
* @return void
*/
public static ExcelWriter exportBaseExcel(ExcelWriter writer, List<?> data, Font globalFont, Class clazz) {
Sheet sheet = writer.getSheet();
writer.setStyleSet(setBaseGlobalStyle(writer, globalFont));
int columnSize = setHeaderAlias(writer, clazz);
writer.setOnlyAlias(true);
writer.write(data);
HutoolExcelUtil.setSizeAutoColumn(sheet, columnSize);
return writer;
}
/**
* 设置下拉列表框
*
* @param writer ExcelWriter
* @param str 内容
* @param firstRow 第一行
* @param lastRow 第二行
* @param firstCol 第一列
* @param lastCol 第二列
*/
public static void setSelect(ExcelWriter writer, String[] str, int firstRow, int lastRow, int firstCol, int lastCol) {
CellRangeAddressList addressList = new CellRangeAddressList(firstRow, lastRow, firstCol, lastCol);
DataValidationHelper helper = writer.getSheet().getDataValidationHelper();
DataValidationConstraint constraint = helper.createExplicitListConstraint(str);
DataValidation dataValidation = helper.createValidation(constraint, addressList);
writer.addValidationData(dataValidation);
}
/**
* 设置筛选功能
*
* @param writer ExcelWriter
* @param str 要筛选的坐标 例如:A2:D6
*/
public static void setFilter(ExcelWriter writer, String str) {
Sheet sheet = writer.getSheet();
CellRangeAddress c = CellRangeAddress.valueOf(str);
sheet.setAutoFilter(c);
}
}
//注解类
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface ExcelProperty {
String[] value();
int order();
}
/**
合并单元格方法
*/
private Map<String, Object> merge(List<DataTbbTableVO> dataTbbTableVOS, ExcelWriter writer) {
Map<String, Object> map = new HashMap<>(2);
//有个字段要合并的就定义多少
int index1 = 2;
int index2 = 2;
// 后面要用到的临时对象
List<List<Object>> rows = new LinkedList<>();
List<Object> rowA;
//按照序号合并单元格
Map<Integer, List<DataTbbTableVO>> reservoirNameMaps = dataTbbTableVOS.stream()
.collect(Collectors.groupingBy(DataTbbTableVO::getNumber, LinkedHashMap::new, Collectors.toList()));
for (Map.Entry<Integer, List<DataTbbTableVO>> reservoirNameEntry : reservoirNameMaps.entrySet()) {
List<DataTbbTableVO> reservoirNameValue = reservoirNameEntry.getValue();
//根据数据条数设置合并单元格信息
if (reservoirNameValue.size() == 1) {//一条数据不合并
index1 = index1 + reservoirNameValue.size();
} else {
//规则编写
writer.merge(index1, index1 + reservoirNameValue.size() - 1, 0, 0, null, true);
writer.merge(index1, index1 + reservoirNameValue.size() - 1, 1, 1, null, true);
index1 = index1 + reservoirNameValue.size();
//按照二级指标合并单元格
Map<String, List<DataTbbTableVO>> reservoirScaleMaps = reservoirNameValue.stream()
.collect(Collectors.groupingBy(DataTbbTableVO::getSecondLevel, LinkedHashMap::new, Collectors.toList()));
for (Map.Entry<String, List<DataTbbTableVO>> reservoirScaleEntry : reservoirScaleMaps.entrySet()) {
List<DataTbbTableVO> reservoirScaleValue = reservoirScaleEntry.getValue();
//根据数据条数设置合并单元格信息
if (reservoirScaleValue.size() == 1) {//一条数据不合并
index2 = index2 + reservoirScaleValue.size();
} else {
//规则编写
writer.merge(index2, index2 + reservoirScaleValue.size() - 1, 2, 2, null, true);
writer.merge(index2, index2 + reservoirScaleValue.size() - 1, 3, 3, null, true);
writer.merge(index2, index2 + reservoirScaleValue.size() - 1, 4, 4, null, true);
writer.merge(index2, index2 + reservoirScaleValue.size() - 1, 5, 5, null, true);
index2 = index2 + reservoirScaleValue.size();
}
}
}
//保存数据
for (DataTbbTableVO dataTbbTableVO : reservoirNameValue) {
rowA = CollUtil.newArrayList(
dataTbbTableVO.getNumber(),
dataTbbTableVO.getFirstLevel(),
dataTbbTableVO.getSecondLevel(),
dataTbbTableVO.getSecondLevelData(),
dataTbbTableVO.getSecondLevelRank(),
dataTbbTableVO.getSecondBest(),
dataTbbTableVO.getThirdLevel(),
dataTbbTableVO.getThirdLevelData(),
dataTbbTableVO.getThirdLevelRank(),
dataTbbTableVO.getThirdBest()
);
rows.add(rowA);
}
}
map.put("index", index2);
map.put("result", rows);
return map;
}