Easyexcel 实现 合并单元格&动态列头&批注&列头样式 Demo

前言

其实 easyexcel 官网例子十分多,也很完善,但是实际工作中,我们遇到的情况往往是这些例子的复数情况。当我们重叠一起使用,就发现有些地方没法像官网那样直接使用。我这里就遇到了,所以写下我的解决方案,记录下。因为工作中的报表具有商业价值,所以没法直接使用。我就针对我觉得比较麻烦的几种情况,自己想个复合例子,写个 demo 都包含进去。

使用版本

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>easyexcel</artifactId>
    <version>4.0.3</version>
</dependency>

期望生成的报表

sheet1

image.png

还有下拉


image.png

sheet2

image.png

示例代码

我这里偷懒了,有很多能抽出来复用的代码,我没抽,只是简单实现 demo,用于实际工作要自己优化下


import com.alibaba.easyexcel.test.util.TestFileUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.annotation.ExcelIgnore;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.util.BooleanUtils;
import com.alibaba.excel.util.ListUtils;
import com.alibaba.excel.write.handler.RowWriteHandler;
import com.alibaba.excel.write.handler.SheetWriteHandler;
import com.alibaba.excel.write.handler.context.SheetWriteHandlerContext;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
import com.alibaba.excel.write.metadata.holder.WriteTableHolder;
import com.alibaba.excel.write.metadata.style.WriteCellStyle;
import com.alibaba.excel.write.style.HorizontalCellStyleStrategy;
import com.alibaba.fastjson2.JSON;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddressList;
import org.apache.poi.xssf.usermodel.XSSFClientAnchor;
import org.apache.poi.xssf.usermodel.XSSFRichTextString;
import org.assertj.core.util.Sets;
import org.junit.jupiter.api.Test;

import java.lang.reflect.Field;
import java.util.*;

/**
* @author CaiZhuliang
* @date 2023/5/10
*/
@Slf4j
public class MyWriteTest {

   @Test
   public void complexDemo() {
       // 合并单元格+动态列头+同个单元格内多样式+列头填充颜色+多sheet+批注
       String fileName = TestFileUtil.getPath() + "complexDemo" + System.currentTimeMillis() + ".xlsx";
       try (ExcelWriter excelWriter = EasyExcel.write(fileName, ComplexDemoDTO.class).inMemory(Boolean.TRUE).build()) {
           // 头部样式
           WriteCellStyle headWriteCellStyle = new WriteCellStyle();
           headWriteCellStyle.setFillForegroundColor(IndexedColors.YELLOW.getIndex());
           HorizontalCellStyleStrategy horizontalCellStyleStrategy = new HorizontalCellStyleStrategy();
           horizontalCellStyleStrategy.setHeadWriteCellStyle(headWriteCellStyle);

           WriteSheet sheet1 = EasyExcel.writerSheet("资料")
               // 背景填充颜色
               .registerWriteHandler(horizontalCellStyleStrategy)
               // 下拉
               .registerWriteHandler(new SheetWriteHandler() {
                   @Override
                   public void afterSheetCreate(SheetWriteHandlerContext context) {
                       // 由于 性别 在 (2,1),因此数据从 (2,2) 开始输入。假设现在我给两百行都做下拉
                       CellRangeAddressList cellRangeAddressList = new CellRangeAddressList(2, 201, 2, 2);
                       DataValidationHelper helper = context.getWriteSheetHolder().getSheet().getDataValidationHelper();
                       DataValidationConstraint constraint = helper.createExplicitListConstraint(new String[]{"男", "女"});
                       DataValidation dataValidation = helper.createValidation(constraint, cellRangeAddressList);
                       context.getWriteSheetHolder().getSheet().addValidationData(dataValidation);
                   }
               })
               // sheet2 必填标识变成红色
               .registerWriteHandler(new RowWriteHandler() {
                   @Override
                   public void afterRowDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder,
                                               Row row, Integer relativeRowIndex, Boolean isHead) {
                       if (BooleanUtils.isTrue(isHead) && relativeRowIndex.compareTo(2) == 0) {
                           Iterator<Cell> iterator = row.cellIterator();
                           while (iterator.hasNext()) {
                               Cell cell = iterator.next();
                               String stringCellValue = cell.getStringCellValue();
                               if (stringCellValue.startsWith("*")) {
                                   Workbook workbook = writeSheetHolder.getSheet().getWorkbook();
                                   Font font = workbook.createFont();
                                   font.setColor(Font.COLOR_RED);
                                   RichTextString richStringCellValue = cell.getRichStringCellValue();
                                   richStringCellValue.applyFont(0, 1, font);
                               }
                           }
                       }
                   }
               })
               // 批注
               .registerWriteHandler(new RowWriteHandler() {
                   private final Set<String> COMMENT_HEAD = Sets.set("*入职日期", "*离职日期");

                   @Override
                   public void afterRowDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder,
                                               Row row, Integer relativeRowIndex, Boolean isHead) {
                       /* 只处理 入职日期 离职日期,它们分别在 工作经历 -> 经历1/经历2 下面,所以批注要在以下坐标上创建
                          (5,2) (6,2) (8,2) (9,2) */
                       if (BooleanUtils.isTrue(isHead) && relativeRowIndex.compareTo(2) == 0) {
                           // 因为目标单元格都在第三行,所以我们处理点在第三行上
                           Drawing<?> drawingPatriarch = writeSheetHolder.getSheet().createDrawingPatriarch();
                           Iterator<Cell> iterator = row.cellIterator();
                           while (iterator.hasNext()) {
                               Cell cell = iterator.next();
                               int columnIndex = cell.getColumnIndex();
                               short col1 = (short) (columnIndex + 1);
                               short col2 = (short) (columnIndex + 3);
                               if (COMMENT_HEAD.contains(cell.getStringCellValue())) {
                                   // 创建批注
                                   Comment comment = drawingPatriarch.createCellComment(new XSSFClientAnchor(0, 0,
                                       0, 0, col1, 2, col2, 4));
                                   comment.setString(new XSSFRichTextString("1、必填\n" +
                                       "2、填写格式:yyyy-mm-dd"));
                                   row.getCell(columnIndex).setCellComment(comment);
                               }
                           }
                       }
                   }
               })
               .build();

           WriteSheet sheet2 = EasyExcel.writerSheet("面试结果")
               .head(dynamicHead())
               // 背景填充颜色
               .registerWriteHandler(horizontalCellStyleStrategy)
               // sheet2 必填标识变成红色
               .registerWriteHandler(new RowWriteHandler() {
                   @Override
                   public void afterRowDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder,
                                               Row row, Integer relativeRowIndex, Boolean isHead) {
                       if (BooleanUtils.isTrue(isHead) && relativeRowIndex.compareTo(1) == 0) {
                           Iterator<Cell> iterator = row.cellIterator();
                           while (iterator.hasNext()) {
                               Cell cell = iterator.next();
                               String stringCellValue = cell.getStringCellValue();
                               if (stringCellValue.startsWith("*")) {
                                   Workbook workbook = writeSheetHolder.getSheet().getWorkbook();
                                   Font font = workbook.createFont();
                                   font.setColor(Font.COLOR_RED);
                                   RichTextString richStringCellValue = cell.getRichStringCellValue();
                                   richStringCellValue.applyFont(0, 1, font);
                               }
                           }
                       }
                   }
               })
               .build();
           excelWriter.write(Collections.EMPTY_LIST, sheet1);
           excelWriter.write(Collections.EMPTY_LIST, sheet2);
       }
   }

   private List<List<String>> dynamicHead() {
       // 里面的 list 是纵向维度,而最外面的 list 是横向维度,所以要看目标列头所在的位置填充
       List<List<String>> headColList = ListUtils.newArrayList();
       headColList.add(ListUtils.newArrayList("评价", "*序号"));
       headColList.add(ListUtils.newArrayList("评价", "*姓名"));
       headColList.add(ListUtils.newArrayList("评价", "*综合得分"));
       headColList.add(ListUtils.newArrayList("评价", "*一面评分"));
       headColList.add(ListUtils.newArrayList("评价", "*二面评分"));
       headColList.add(ListUtils.newArrayList("评价", "三面评分"));
       headColList.add(ListUtils.newArrayList("评价", "*HR评分"));
       return headColList;
   }

   @Data
   public static class ComplexDemoDTO {
       @ExcelProperty("序号")
       private String no;
       @ExcelProperty({"基础信息", "*姓名"})
       private String name;
       @ExcelProperty({"基础信息", "性别"})
       private String sex;
       @ExcelProperty({"基础信息", "*年龄"})
       private Integer age;

       @ExcelProperty({"工作经历", "经历1", "*公司名称"})
       private String companyOfOne;
       @ExcelProperty({"工作经历", "经历1", "*入职日期"})
       private String startDateOfOne;
       @ExcelProperty({"工作经历", "经历1", "*离职日期"})
       private String endDateOfOne;

       @ExcelProperty({"工作经历", "经历2", "*公司名称"})
       private String companyOfTwo;
       @ExcelProperty({"工作经历", "经历2", "*入职日期"})
       private String startDateOfTwo;
       @ExcelProperty({"工作经历", "经历2", "*离职日期"})
       private String endDateOfTwo;
   }
}

固定表头&动态表头

可能还会遇到固定+动态表头的情况,我这里是用了反射解决的

image.png

示例代码如下


import com.alibaba.easyexcel.test.util.TestFileUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.annotation.ExcelIgnore;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.util.ListUtils;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;

import java.lang.reflect.Field;
import java.util.Date;
import java.util.List;

/**
 * @author CaiZhuliang
 * @date 2023/5/10
 */
@Slf4j
public class MyWriteTest {

    /**
     * 动态头,实时生成头写入
     * 思路:反射读取固定表头字段,再加动态部分表头,通过自己指定 head 的方式生成表头
     */
    @Test
    public void dynamicHeadWrite() {
        String fileName = TestFileUtil.getPath() + "dynamicHeadWrite" + System.currentTimeMillis() + ".xlsx";
        EasyExcel.write(fileName)
            // 这里放入动态头
            .head(myHead())
            .sheet("模板")
            // 当然这里数据也可以用 List<List<String>> 去传入
            .doWrite(myData());
    }

    private List<List<String>> myHead() {
        List<List<String>> list = ListUtils.newArrayList();
        Field[] selfFields = MyDemoData.class.getDeclaredFields();
        Field[] supperFields = MyDemoData.class.getSuperclass().getDeclaredFields();
        List<Field> fieldList = ListUtils.newArrayList(supperFields);
        fieldList.addAll(ListUtils.newArrayList(selfFields));
        for (Field field : fieldList) {
            ExcelIgnore annotation = field.getAnnotation(ExcelIgnore.class);
            if (null != annotation) {
                continue;
            }
            ExcelProperty excelProperty = field.getAnnotation(ExcelProperty.class);
            if (null != excelProperty) {
                list.add(ListUtils.newArrayList(excelProperty.value()));
            }
        }
        // 动态字段
        list.add(ListUtils.newArrayList("扩展字段1"));
        list.add(ListUtils.newArrayList("扩展字段2"));
        return list;
    }

    private List<List<Object>> myData() {
        // 假设从db拿到的数据
        MyDemoData data = new MyDemoData();
        data.setString("字符串" + 0);
        data.setDate(new Date());
        data.setDoubleData(0.56);
        // 动态扩展字段
        data.setExtColumnList(ListUtils.newArrayList("ext1", "ext2"));
        List<MyDemoData> dataList = ListUtils.newArrayList(data);

        Field[] selfFields = MyDemoData.class.getDeclaredFields();
        Field[] supperFields = MyDemoData.class.getSuperclass().getDeclaredFields();
        List<Field> fieldList = ListUtils.newArrayList(supperFields);
        fieldList.addAll(ListUtils.newArrayList(selfFields));
        List<List<Object>> list = ListUtils.newArrayList();
        for (MyDemoData myData : dataList) {
            List<Object> columnList = ListUtils.newArrayList();
            for (Field field : fieldList) {
                ExcelIgnore annotation = field.getAnnotation(ExcelIgnore.class);
                if (null != annotation) {
                    continue;
                }
                field.setAccessible(true);
                try {
                    Object obj = field.get(myData);
                    if (obj instanceof List) {
                        columnList.addAll((List<?>) obj);
                    } else {
                        columnList.add(obj);
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            list.add(columnList);
        }
        return list;
    }

    @Data
    static class MyDemoData {
        @ExcelProperty("字符串标题")
        private String string;
        @ExcelProperty("日期标题")
        private Date date;
        @ExcelProperty("数字标题")
        private Double doubleData;
        private List<String> extColumnList;
    }
}

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,163评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,301评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,089评论 0 352
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,093评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,110评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,079评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,005评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,840评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,278评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,497评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,667评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,394评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,980评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,628评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,796评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,649评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,548评论 2 352

推荐阅读更多精彩内容