使用EasyExcel导出带公式的excel

EasyExcel 导出带公式的excel,打开显示公式字符串而不是对应值

解决使用EasyExcel导出带公式的Excel,公式不自动计算问题

使用EasyExcel导出带公式的 Excel 打开后发现显示的字符串而不是计算后的值,需要点击被引用的列,公式才会自动计算。说实话这不算是 Bug 更多是我写法有问题,原因就是封装Excel导出模板,这个模板把每一格数据类型统一改为了String,导致公式无法自动计算、、计算。 。所以当你遇到这种问题检查一下导出的数据格式是否正确

EasyExcel / FastExcel版本

1
2
3
4
5
6
<!-- fastExcel工具 -->
<dependency>
    <groupId>cn.idev.excel</groupId>
    <artifactId>fastexcel</artifactId>
    <version>1.2.0</version> <!-- Use the latest version -->
</dependency>

特别说明: 我不是基于 EasyExcel 的注解形式导出的,因为导出的数据以及布局比较定制化且复杂。想看注解的可以走了,以下内容不是基于注解的

测试类: 经过我实际使用测试发现带公式导出,只需要做两步,在官网或者github看到类似问题解决方法都是调用以下API,最终发现调没调用都不影响

  1. setForceFormulaRecalculation(true): 强行计算公式
  2. evaluateAll(): 计算所有公式

第一步:实现CellWriteHandler接口

实现接口之后,并注册到ExcelWriterBuilder中详细代码在最后,这里只给出关键代码

1
2
//表格格式设置为计算类型
cell.setCellFormula();

第二步:设置数值类型

1
2
3
4
//这一步没有的话,导出的数据类型都是 String ,只有数值类型公式才会自动计算
//Tip: 其实FastExcel 支持输入 Object 但是业务需求常常需要对输出进行处理所以我这里统一使用 String
//但是我想说的时候如果公式没自动自动计算有可能是你的类型没识别成数值类型
cell.setCellValue(Integer.parseInt(cell.getStringCellValue()));

完整测试代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
/**
 * 测试导出 excel
 * @author jamesaks
 * @since 2025/10/25
 */
@Slf4j
@SpringBootTest(classes = MaterialApplication.class)
public class TestExportExcelStyle {

    public void reportExport(ExcelTemplate template) {
        // 2. 文件路径
        String filePath = "/Users/jamesaks/Downloads/excel-test/" + System.currentTimeMillis() + ".xlsx";
        File file = new File(filePath);
        if (!file.getParentFile().exists()) {
            boolean mkdirSuccess = file.getParentFile().mkdirs();
            if (!mkdirSuccess) {
                throw new RuntimeException("创建目录失败");
            }
        }

        // 3. 写文件(注册样式)
        try (ExcelWriter writer = EasyExcel.write(file).build()) {
            // 1. 检查数据
            template.checkIsEmpty();

            //写入
            WriteSheet sheet = template
                    .getSheetBuilder(EasyExcel.writerSheet(template.sheetName()))
                    .useDefaultStyle(false)
                    .needHead(true)
                    .head(template.getHead())
                    .build();

            //区分 Table 和 sheet 概念
            writer.write(template.getBody(), sheet);
        }
    }

    @Test
    void exportExcel() {
        reportExport(new ExcelTemplate() {
            @Override
            public String sheetName() {
                return "测试";
            }

            @Override
            public List<List<ExcelCell>> rows() {
                //外层为一行
                //内层为每行的列数
                return List.of(
                        List.of(new ExcelCell(3), new ExcelCell(4), new ExcelCell("=SUM(A2:B2)"), new ExcelCell("=A2-B2")),
                        List.of(new ExcelCell(100), new ExcelCell(200), new ExcelCell("=SUM(A3:B3)"), new ExcelCell("=A3-B3"))
                );
            }

            @Override
            public List<List<ExcelCell>> head() {
                //外层表达每行列数
                //内层为有几行
                return List.of(
                        List.of(new ExcelCell("数值1")),
                        List.of(new ExcelCell("数值2")),
                        List.of(new ExcelCell("相加")),
                        List.of(new ExcelCell("相减"))
                );
            }

            @Override
            public ExcelWriterSheetBuilder getSheetBuilder(ExcelWriterSheetBuilder writerBuilder) {
                //实现公式自动相加的核心逻辑
                return writerBuilder.registerWriteHandler(new CellWriteHandler() {
                    @Override
                    public void afterCellDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, List<WriteCellData<?>> cellDataList, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) {
                        if (!isHead && CellType.STRING.equals(cell.getCellType()) && cell.getStringCellValue().contains("=")) {
                            cell.setCellFormula(cell.getStringCellValue().substring(1));
                        }

                        if (StrUtil.isNumeric(cell.getStringCellValue())) {
                            //将字符串转为数值类型
                            cell.setCellValue(Integer.parseInt(cell.getStringCellValue()));
                        }
                    }
                });
            }
        });
        log.info("生成成功");
    }
}    

Excel导出模板接口

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
/**
 * Excel 导出模板接口
 *
 * @author jamesaks
 * @see ExportServiceImpl#reportExport(String, ExcelTemplate)
 * @since 2025/10/9
 */
public interface ExcelTemplate {
    /**
     * 获取 sheet 名称
     *
     * @return sheet名称
     */
    String sheetName();

    /**
     * 除了head之外的数据 rows 外层的 List 代表一行数据,里面的List 代表每行的每列数据
     * {@link cn.idev.excel.ExcelWriter#write(Collection, WriteSheet)}
     *
     * @return 每行数据
     */
    List<List<ExcelCell>> rows();

    /**
     * Excel 标题,外层的 List 表达每一列,里面的List 代表有多少行,决定头有几行是里面的 List 决定的
     * {@link AbstractParameterBuilder#head(List)}
     *
     * @return 标题
     */
    List<List<ExcelCell>> head();

    /**
     * 获取除标题外数据
     */
    default List<List<String>> getBody() {
        //有序合并
        return rows().stream()
                .map(list -> list.stream().map(ExcelCell::getValue).toList())
                .toList();
    }

    /**
     * 获取excel头
     */
    default List<List<String>> getHead() {
        //有序合并
        return head().stream()
                .map(list -> list.stream().map(ExcelCell::getValue).toList())
                .toList();
    }

    /**
     * sheet 构建者,样式相关的可以在这设置,不是复杂样式使用excelStyle即可,不使用 easy excel 默认样式则需要重写这个
     */
    default ExcelWriterSheetBuilder getSheetBuilder(ExcelWriterSheetBuilder writerBuilder) {
        return writerBuilder.registerWriteHandler(getExcelStyle());
    }

    /**
     * 获取样式
     */
    default WriteHandler getExcelStyle() {
        if (excelStyle() != null) {
            return excelStyle();
        }
        return defaultStyle();
    }

    default void checkIsEmpty() {
        if (rows() == null || rows().isEmpty()) {
            throw new BusinessException(BusinessErrorCode.ERROR, "Excel列数据不存在");
        }

        if (head() == null || head().isEmpty()) {
            throw new BusinessException(BusinessErrorCode.ERROR, "Excel标题数据不存在");
        }
    }

    /**
     * 子类选择是否重写,不重写使用默认的样式
     */
    default WriteHandler excelStyle() {
        return null;
    }

    /**
     * 默认样式
     */
    private WriteHandler defaultStyle() {
        //创建 EasyExcel 写样式(头/内容:不换行,字体 12,居中)
        WriteCellStyle headStyle = new WriteCellStyle();
        WriteFont headFont = new WriteFont();
        headFont.setFontHeightInPoints((short) 12);
        headStyle.setWriteFont(headFont);
        headStyle.setWrapped(false);
        headStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
        headStyle.setVerticalAlignment(VerticalAlignment.CENTER);

        WriteCellStyle contentStyle = new WriteCellStyle();
        WriteFont contentFont = new WriteFont();
        contentFont.setFontHeightInPoints((short) 12);
        contentStyle.setWriteFont(contentFont);
        // 关键:不自动换行
        contentStyle.setWrapped(false);
        contentStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
        contentStyle.setVerticalAlignment(VerticalAlignment.CENTER);
        return new HorizontalCellStyleStrategy(headStyle, contentStyle);
    }

    /**
     * Excel 每一格
     */
    @Builder
    @NoArgsConstructor
    @AllArgsConstructor
    @Data
    class ExcelCell {

        private static final String EMPTY = "";

        /**
         * 一格的数据,为多个表示这一格有多行
         */
        private List<Object> cellValues;

        private boolean useSafeConvert = true;

        public ExcelCell(Object... values) {
            if (values.length == 0) {
                throw new BusinessException(BusinessErrorCode.ERROR, "Excel列数据不能为空");
            }
            this.cellValues = Arrays.stream(values).toList();
        }

        public ExcelCell(Object value, boolean useSafeConvert) {
            this(List.of(Objects.isNull(value) ? EMPTY : value), useSafeConvert);
        }

        /**
         * 转为字符串
         * 目前只考虑一格只有一行的情况
         */
        public String getValue() {
            //转为字符串
            return cellValues.stream().map(this::safeConvertString).findFirst().orElse(EMPTY);
        }

        public String safeConvertString(Object s) {
            if (!useSafeConvert) {
                return Objects.toString(s);
            }

            if (s == null) {
                return EMPTY;
            }

            if (s instanceof String str && StrUtil.isBlank(str)) {
                return EMPTY;
            }

            // 0 转为 "0"
            if (s instanceof Number num && num.intValue() == 0) {
                return "0";
            }

            //double去掉小数点
            if (s instanceof Double d && d.intValue() == d) {
                return String.valueOf(d.intValue());
            }

            // 把 NULL_STR 文本变为空
            if ("null".equals(s)) {
                return EMPTY;
            }
            return s.toString();
        }

        /**
         * 返回空格子在 excel 中就是这个格没有展示如何内容
         */
        public static ExcelCell empty() {
            return new ExcelCell(EMPTY);
        }
    }
}
Last updated on Nov 06, 2025 16:45 UTC