需求背景:要把这样的页面导出
相信明眼人已经看出了这个导出样式的几个关键点了:
- 单元格有深灰,有浅灰
- 同一个单元格的字体还会区分颜色
- 单元格内的数据展示会涉及到换行
ok,既然分析出了这几个点 就可以准备动手了。
不过在动手之前,请各位回想一下导出excel的经历,这写样式好不好做。对于本人来说,单元格颜色还好也做过,但是这个部分文字换色是第一次,也不知道能不能实现。于是就去知识的海洋里遨游了一番,发现可行,核心就是通过自定义实现接口CellWriteHandler,重写方法afterCellDispose,配合富文本XSSFRichTextString来实现效果。
那就开整。顺便提一嘴:这里的easyexcel版本是3.3.4 ,如果老版本的话afterCellDispose方法的参数是不一样的
数据层面的处理
对于床位使用信息对应单元格内的内容,从数据层面来说 是这样的结构
{
"roomId": "roomId_477f4dc21bd8",
"areaId": "areaId_3a8abbe29ea1",
"buildingId": "buildingId_2ed29d53fbee",
"floorId": "floorId_b3e6086b38c3",
"roomNum": "roomNum_b436d97c509c",
"roomType": "roomType_f2446af5b76c",
"tagList": [
"tagList_d22f2a594307"
],
"genderType": "genderType_459223d90f5c",
"roomFace": "roomFace_c1fc3c1ab713",
"bedUseList": [
{
"bedNum": 0,
"useType": "useType_3874361c62de",
"stuTempList": [
{
"id": "id_1cd6a9a8417c",
"enrollId": "enrollId_669f62c95f1f",
"stuName": "stuName_b1f5beacae9b",
"startDate": "2024-08-26",
"endDate": "2024-08-26",
"status": "status_aa71518f016d",
"redFlag": false
}
]
}
]
}
所以首先要做的就是 先把床位的信息做好字符拼接 通过\n 来告诉excel换行
// 获取符合条件的房间信息
List<DormitoryBedUsePageResp> list = listByCondition(dormitoryBedUsePageReq);
// 准备excel的数据格式
List<BedUseDownloadTemp> downloadTempList = list.stream().map(DormitoryBedUseConvertor.INSTANCE::pageToDownloadTemp).collect(toList());
实体的映射 使用mapstruct,通过抽象类DormitoryBedUseDecorator来增强默认生成的实现
@Mapper
@DecoratedWith(DormitoryBedUseDecorator.class)
public interface DormitoryBedUseConvertor {
DormitoryBedUseConvertor INSTANCE = Mappers.getMapper(DormitoryBedUseConvertor.class);
BedUseDownloadTemp pageToDownloadTemp(DormitoryBedUsePageResp page);
}
这里就啰嗦到这里好了 有个细节要提一嘴,最后导出样式处理的时候 对于easyexcel来说操作的对象是纯字符串,所以他并不像业务层那样可以通过实体的属性如 redFlage来知道这个值应该标红,所以我的处理方式是在组装数据的时候对于需要标红的字符串用{}包起来,后续再处理的时候再通过正则提取出来中间的内容
样式的处理
上面提到 主要核心就是通过重写CellWriteHandler接口的afterCellDispose方法
public class WriteHandlerStrategy implements CellWriteHandler {
@Override
public void afterCellCreate(CellWriteHandlerContext context) {
CellWriteHandler.super.afterCellCreate(context);
}
@Override
public void afterCellDispose(CellWriteHandlerContext context) {
Cell cell = context.getCell();
// 拿到poi的workbook
Workbook workbook = context.getWriteWorkbookHolder().getWorkbook();
CellStyle baseCellStyle = getBaseCellStyle(workbook);
int rowIndex = cell.getRowIndex();
// 只处理非表头行
if (rowIndex == 0) {
return;
}
// 如果内容不是字符串类型
if (cell.getCellType() == CellType.STRING && cell.getColumnIndex() > 8) {
String stringValue = cell.getStringCellValue();
// 如果单元格内容为 null,则设置单元格背景色为深灰色
if (stringValue == null || stringValue.isEmpty()) {
baseCellStyle.setFillForegroundColor(IndexedColors.GREY_50_PERCENT.getIndex());
baseCellStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
} else if ("占用".equals(stringValue)) {
baseCellStyle.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
baseCellStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
} else {
// 处理字符串
// 如果内容中有{},提取到内容 并且计算索引位置 对{}中的值进行颜色设置
List<String> result = Lists.newArrayList();
Pattern BRACES = Pattern.compile("\{([^}]+)\}");
Matcher matcher = BRACES.matcher(stringValue);
while (matcher.find()) {
String group = matcher.group(1);
// 既然是工具,就尽量不要依赖于外部的包,这里可以用StringUtils.isBlank
if (group != null && !group.isBlank()) {
result.add(group);
}
}
// result 不为空 即为有需要改色的内容
if (CollUtil.isNotEmpty(result)) {
stringValue = stringValue.replaceAll("[{}]", "");
// xlsx格式,如果是老版本格式的话就用 HSSFRichTextString
XSSFRichTextString richString = new XSSFRichTextString(stringValue);
// 创建字体
Font font = workbook.createFont();
font.setColor(IndexedColors.RED.getIndex());
// 提取到内容 并且计算索引位置 对{}中的值进行颜色设置 清除掉{}
for (String s : result) {
int startIndex = stringValue.indexOf(s);
int endIndex = startIndex + s.length();
richString.applyFont(startIndex, endIndex, font);
}
// 再设置回每个单元格里
cell.setCellValue(richString);
}
}
}
cell.setCellStyle(baseCellStyle);
// 这里要把 WriteCellData的样式清空, 不然后面还有一个拦截器 FillStyleCellWriteHandler 默认会将 WriteCellStyle 设置到
// cell里面去 会导致自己设置的不一样(很关键)
context.getFirstCellData().setWriteCellStyle(null);
}
private CellStyle getBaseCellStyle(Workbook workbook) {
CellStyle style = workbook.createCellStyle();
// 设置表格内容垂直居中
style.setVerticalAlignment(VerticalAlignment.CENTER);
// 设置表格内容水平居中
style.setAlignment(HorizontalAlignment.CENTER);
style.setWrapText(true);
return style;
}
}
有几个关键点需要提一嘴:
context.getFirstCellData().setWriteCellStyle(null)作用是清空别的样式,防止在此处设置的样式在后面别覆盖了style.setWrapText(true)开启自动换行,否则导出的文件单元格内容不会自动换行,双击玩才会换行,影响效果cell.getCellType() == CellType.STRING这个是有必要判断的 一般第一列都是编号,在执行cell.getStringCellValue(),会直接报错- 最重要也容易被忽视的,只有单元格内容不为null时,才会进入拦截器,才会进入方法执行自定义逻辑,如果单元格对应的字段值为null,那么会跳过,我的一个需求是针对没有值的单元格 需要设置深灰背景色,一开始以为都会进入方法,结果发现没有值的话不会进入方法,所以在处理数据的时候给赋值了
"",以便于能进入方法做后续的样式处理 - 所有样式 都要在这一个地方写 不然会有样式擦除的问题
总结
对与样式的自定义,主要是通过自定义各种实现类来重写样式,但也要注意样式擦除问题,尽量在同一个实现类里搞定所有需要的样式。最后, 愿产品少设计文件导出方面的需求,简单直接省事最好。
感谢前辈门的无私分享
easyExcel同一单元格部分文字颜色、样式修改_java代码easyexcel一个单元格中写不同颜色的字体-CSDN博客