easyexcel导出样式之单元格部分字体标红

2,509 阅读5分钟

需求背景:要把这样的页面导出

相信明眼人已经看出了这个导出样式的几个关键点了:

  1. 单元格有深灰,有浅灰
  2. 同一个单元格的字体还会区分颜色
  3. 单元格内的数据展示会涉及到换行

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博客

EasyExcel导出数据根据导出值大小,设置字体颜色_writefont设置颜色-CSDN博客