一 框架及版本
- springboot: 2.1.3.RELEASE
- hutool工具
- easyExcel
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>2.1.4</version>
</dependency>
二 数据准备
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student {
@ExcelProperty(value = {"年级"}, index = 0)
private String grade;
@ExcelProperty(value = {"姓名"}, index = 1)
private String name;
@ExcelProperty(value = {"年龄"}, index = 2)
private Integer age;
@ExcelProperty(value = {"性别"}, index = 3)
private String sex;
@ExcelProperty(value = {"综合素质评价"}, index = 4)
@ColumnWidth(50)
private String evaluate;
@ExcelProperty(value = {"综合评分"}, index = 5)
@ColumnWidth(20)
private String score;
}
public class DataReady {
public static List<Student> listStudent() {
// 综合素质评价
List<String> evaluateList = evaluateList();
List<Student> students = new ArrayList<>();
students.add(new Student("三年一班", "小东", 12, "男", evaluateList.get(0), "优"));
students.add(new Student("三年一班", "小南", 12, "男", evaluateList.get(1), "优"));
students.add(new Student("三年二班", "小西", 13, "男",evaluateList.get(2), "优"));
students.add(new Student("三年二班", "小北", 12, "男",evaluateList.get(3), "优"));
students.add(new Student("三年二班", "小红", 12, "女",evaluateList.get(4), "优"));
students.add(new Student("三年三班", "小独", 12, "男",evaluateList.get(5), "优"));
return students;
}
private static List<String> evaluateList() {
List<String> strings = new ArrayList<>();
strings.add("你活泼乐观,自信心强,尊敬老师,是你的最大优点,作业能按时完成,有强烈的好奇心,可惜的是你上课管不住自己,不守纪律,老师提出问题不经过深思熟虑");
strings.add("该生在校期间学习态度端正,成绩优良");
strings.add("积极参与各项实践活动,全面的锻炼自己。");
strings.add("你是个天性好动的男孩。想到你,浮现在老师眼前的是你积极劳动、礼貌待人的身影。");
strings.add("热情活泼,全面发展,对于自己喜欢的工作,能投入极大的热情去做,哪怕再苦再累,也不计较。");
strings.add("好");
return strings;
}
}
三 基础下载
效果
EasyExcelController
@GetMapping("/download")
public void download(HttpServletResponse response) throws IOException {
service.download(response);
}
EasyExcelService
/**
* 基础下载
*/
void download(HttpServletResponse response) throws IOException;
EasyExcelServiceImpl
/**
* 基础下载
*/
@Override
public void download(HttpServletResponse response) throws IOException {
String message = "下载文件失败";
String filename = "学生信息.xlsx";
try {
// 设置响应头
response.addHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(filename, "utf-8"));
response.setContentType("application/octet-stream;charset=UTF-8"); // 类型
response.setCharacterEncoding("utf-8");
// 获取导出数据
List<Student> students = DataReady.listStudent();
// 数据导出
EasyExcel.write(response.getOutputStream(), Student.class)
.sheet("Sheet1").doWrite(students);
} catch (Exception e) {
e.printStackTrace();
// 重置response
response.reset();
response.setContentType("application/json");
response.setCharacterEncoding("utf-8");
response.getWriter().println(JsonUtil.writeValueAsString(message));
}
}
测试
浏览器访问: http://localhost:8060/easyExcel/download 下载Excel
四 单元格样式设置
目标
- 单元格字体居中
- 超出单元格部分,自适应高度显示
效果
自定义样式
public class ExcelUtils {
/**
* 单元格水平样式策略
*/
public static HorizontalCellStyleStrategy setHorizontalCellStyleStrategy(){
// 头部样式
WriteCellStyle headWriteCellStyle = new WriteCellStyle();
// 内容样式
WriteCellStyle contentWriteCellStyle = new WriteCellStyle();
contentWriteCellStyle.setWrapped(true); // 数据超出换行
contentWriteCellStyle.setVerticalAlignment(VerticalAlignment.CENTER); // 垂直居中
contentWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER); // 水平居中
// 这个策略是 头是头的样式 内容是内容的样式 其他的策略可以自己实现
return new HorizontalCellStyleStrategy(headWriteCellStyle, contentWriteCellStyle);
}
}
注册自定义样式
五 单元格行合并
目标
- 将同一年级的行进行合并
效果
1 合并统计
统计“指定列“ 的“哪几行”需要进行合并
使用Map集合进行统计
Map<String, List<RowRange>> merStrategyMap
- key: 需要合并的列
- value: 指定列中需要合并的行
每一个列有多个 rowRange, 每个rowRange表示这个列合并行的开始和结束的位置
RowRange
/**
* 用于记录 Excel某一列, 某一类型的
* 合并的开始和结束位置
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class RowRange {
/**
* 行合并开始位置
*/
private int start;
/**
* 行合并结束位置
*/
private int end;
}
EasyExcelServiceImpl
/**
* 添加合并策略
*/
private Map<String, List<RowRange>> addMerStrategy(List<Student> students) {
// key: 表示需要合并的列 value: 表示该列需要合并的行集合
Map<String, List<RowRange>> strategyMap = new HashMap<>();
// 前一条数据。 (用于判断是否需要合并)
Student preStudent = null;
for (int i = 0; i < students.size(); i++) {
Student student = students.get(i);
if (preStudent != null) {
// 年级合并
if (student.getGrade().equals(preStudent.getGrade())) {
fillStrategyMap(strategyMap, "0", i);
}
}
preStudent = student;
}
return strategyMap;
}
/**
* 计算需要合并的行
*
*/
public static void fillStrategyMap(Map<String, List<RowRange>> strategyMap, String key, int index) {
List<RowRange> rowRangeList = strategyMap.get(key) == null ? new ArrayList<>() : strategyMap.get(key);
// 判断是否新增分段
boolean flag = false;
for (RowRange dto : rowRangeList) {
//分段list中是否有end索引是上一行索引的,如果有,则索引+1
if (dto.getEnd() == index) {
dto.setEnd(index + 1);
flag = true;
}
}
//如果没有,则新增分段
if (!flag) {
rowRangeList.add(new RowRange(index, index + 1));
}
strategyMap.put(key, rowRangeList);
}
2 合并策略类
在上一步已经统计出了,哪些列的行需要进行合并, 具体的合并工作在这个类完成
/**
* 合并类
*/
public class MergeStrategy extends AbstractMergeStrategy {
// Map<列, 要进行合并的行>
private Map<String, List<RowRange>> strategyMap;
public MergeStrategy(Map<String, List<RowRange>> strategyMap) {
this.strategyMap = strategyMap;
}
/**
* 每一个单元格都会进入到该方法
* 第一个单元格进入到该方法时就进行合并操作,其余单元格进入到该方法时不进行合并操作
*/
@Override
protected void merge(Sheet sheet, Cell cell, Head head, Integer relativeRowIndex) {
int rowIndex = cell.getRowIndex();
int columnIndex1 = cell.getColumnIndex();
if (rowIndex == 1 && columnIndex1 == 0) {
for (Map.Entry<String, List<RowRange>> entry : strategyMap.entrySet()) {
// 哪一列的行需要进行合并
int columnIndex = Integer.parseInt(entry.getKey());
entry.getValue().forEach(rowRange -> {
// 对行进行合并(四个参数: 行开始 行结束 列开始 列结束)
sheet.addMergedRegionUnsafe(new CellRangeAddress(rowRange.getStart(), rowRange.getEnd(), columnIndex, columnIndex));
});
}
}
}
}
关于 if (rowIndex == 1 && columnIndex1 == 0) 的说明
3 注册合并策略
EasyExcelServiceImpl
六 行高设置
1 非动态设置行高
效果 行高发生了变化
在实体中添加注解即可设置
注意
对超出单元格部分的数据不会自适应高度显示了。这是正常的,因为设置了行高将单元格的高度固定了。
2 动态设置行高
效果: 行高发生变化且超出单元格部分自适应高度显示
思路: 某一行中,如果某个单元格数据超出了单元格则不设置该行的行高,则该行的行高会根据 “四 单元格样式设置” 中的配置进行自适应高度显示
行高样式策略类
/**
* 自定义行高
* 注意: 实体类上不可加 @HeadRowHeight(value = 30) 注解,不然此方法失效(会进入到SimpleRowHeightStyleStrategy)
*/
public class EvaSysRowHeightStyleStrategy extends AbstractRowHeightStyleStrategy {
private Map<Integer, Integer> map = new HashMap<>();
// 列数(用于判断当前行是否执行结束了)
private Integer cellNum;
// 行高
private static final Float height = 30f;
// 需要设置行高的限制值(如果数据长度 < heightLimit 则需要设置行高,否则不需要)
private static final Integer heightLimit = 40;
public EvaSysRowHeightStyleStrategy(int cellNum) {
this.cellNum = cellNum;
}
/**
* 设置标题的行高
*/
@Override
protected void setHeadColumnHeight(Row row, int i) {
row.setHeightInPoints((float)40);
}
@Override
protected void setContentColumnHeight(Row row, int i) {
row.forEach(cell -> {
// 设置行高和解决数据显示不完整问题
// 当前行号
int currentNum = cell.getRow().getRowNum();
// 当前数据的长度
Integer currentLength = getDataLength(cell);
// 当前单元格列数
int currentCellNum = cell.getColumnIndex()+1;
// 如果已经是最后一列,则判断是否需要设置行高
if (cellNum == currentCellNum) {
// 如果数据最大长度 <= 20 则设置行高
Integer max = map.get(currentNum);
max = max > currentLength? max: currentLength;
if(max <= heightLimit) {
cell.getRow().setHeightInPoints(height);
}
} else {
// 获取当前行最大的数据长度。因为可能是新的行,获取数据时可能为null,如果为null,我们就置max为0
Integer max = map.get(currentNum) == null? 0: map.get(currentNum);
// 比较获取最大的数据长度更新max的值
max = max > currentLength? max: currentLength;
map.put(currentNum, max);
}
});
}
/**
* 获取数据长度
*/
private Integer getDataLength(Cell cell) {
CellType type = cell.getCellTypeEnum();
String data;
switch (type) {
case STRING: data = cell.getStringCellValue(); break;
case FORMULA: data = cell.getCellFormula(); break;
case NUMERIC: data = String.valueOf(cell.getNumericCellValue()); break;
default: data = "";
}
return data.length();
}
}
注: 实体类中不可加 @HeadRowHeight, 不然该策略类会失效(会使用 SimpleRowHeightStyleStrategy 进行行高的设置)
注册策略类
EasyExcelServiceImpl