EasyExcel 导入模板踩坑:row.add(““)导致文本格式失效与单元格保护问题解决方案

目录

在处理导入模板时遇到一个矛盾:为了实现指定列保护,必须提前创建"导入月份"列的 Cell(通过 row.add("")),否则该列不存在,无法设置 locked=false;但创建空字符串单元格后,EasyExcel 对空字符串和非空字符串的处理路径不同,导致文本格式 @ 失效,用户输入 2026-01 时仍可能被 Excel 自动识别为日期。而直接使用 row.add("2026-01") 时,由于单元格被识别为字符串类型,文本格式可以正常生效。

一、问题背景

生成 Excel 导入模板时,经常遇到两个问题:

  1. 指定列需要保护,用户不能修改;
  2. 日期类字符串(如 2026-01)不能被 Excel 自动转换。

例如:

text 复制代码
2026-01

Excel 默认可能变成:

text 复制代码
Jan-26

同时,在使用 EasyExcel 生成模板时,还会遇到一个特殊问题:

row.add("2026-01") 可以保持文本格式,但是 row.add("") 却可能导致文本格式失效。

原因在于 EasyExcel 对空字符串和非空字符串的处理路径不同


二、列保护问题

Excel 开启保护后:

text 复制代码
locked=true  → 不可编辑
locked=false → 可编辑

但是 Excel 单元格默认:

text 复制代码
locked=true

所以:

只设置需要保护列:

java 复制代码
style.setLocked(true);

是不够的。

必须同时设置:

text 复制代码
保护列:
locked=true

非保护列:
locked=false

否则开启:

java 复制代码
sheet.protectSheet("123456");

后,所有列都会锁定。


三、为什么需要 maxColumnIndex

例如业务数据:

java 复制代码
row.add("序号");
row.add("单位");
row.add("项目");
row.add("类型");
row.add("面积");
row.add("等级");

实际只有:

text 复制代码
A-F

但是模板需要:

text 复制代码
A-J

如果使用:

java 复制代码
row.getLastCellNum()

只能得到已有列:

text 复制代码
6

导致 G-J 没有处理。

开启保护后:

text 复制代码
G-J 默认 locked=true

结果:

text 复制代码
全部列锁定

原因:

Excel 中不存在的 Cell 无法通过 Handler 修改样式。

解决:

增加:

java 复制代码
exportSheet.setMaxColumnIndex(9);

Handler 按:

java 复制代码
0~9

遍历所有列。


四、为什么 row.add("") 会导致文本失效

很多人认为:

java 复制代码
row.add("");

只是创建一个空单元格。

实际上,EasyExcel 对:

java 复制代码
row.add("2026-01")

和:

java 复制代码
row.add("")

处理路径不同。


1. 非空字符串处理路径

例如:

java 复制代码
row.add("2026-01");

EasyExcel 会认为:

text 复制代码
这是一个有效字符串数据

处理流程:

text 复制代码
String("2026-01")
        ↓
WriteCellData<String>
        ↓
CellType.STRING
        ↓
写入Excel
        ↓
应用CellStyle

此时单元格已经明确为字符串类型。

因此:

text 复制代码
2026-01

通常不会被 Excel 自动转换。


2. 空字符串处理路径

例如:

java 复制代码
row.add("");

EasyExcel 会将其视为空内容处理。

流程可能变为:

text 复制代码
空字符串("")
        ↓
判断无有效内容
        ↓
创建空白Cell或Blank类型Cell
        ↓
后续样式处理

此时:

  • 没有实际字符串值;
  • 单元格类型不是明确的 STRING;
  • Excel 打开后可能仍按照默认格式 General 处理。

最终:

text 复制代码
单元格格式:
General

用户输入:

text 复制代码
2026-01

Excel 根据常规规则自动识别:

text 复制代码
Jan-26

五、正确方案

不要业务代码补:

java 复制代码
row.add("");

因为:

java 复制代码
row.add("")

只是创建空数据,并不能保证文本格式。

推荐:

1. ExportSheet 增加列数

java 复制代码
private Integer maxColumnIndex;

例如:

java 复制代码
exportSheet.setMaxColumnIndex(9);

2. Handler 创建缺失 Cell(待验证,当前方案未生效)

使用:

java 复制代码
Cell cell =
    row.getCell(
        columnIndex,
        Row.MissingCellPolicy.CREATE_NULL_AS_BLANK
    );

作用:

即使业务数据没有这一列,也主动创建 Cell。

然后统一设置:

锁定

java 复制代码
style.setLocked(true/false);

文本

java 复制代码
style.setDataFormat(
    workbook.createDataFormat()
            .getFormat("@")
);

这样创建出来的 Cell:

  • 存在;
  • 有文本格式;
  • 用户输入时不会被 Excel 自动转换。

六、EasyExcel 生命周期选择

afterCellCreate

执行太早:

text 复制代码
创建Cell
 ↓
后续写入
 ↓
样式可能被覆盖

不推荐作为最终样式设置位置。


afterCellDataConverted

适合:

已有数据导出。

例如:

java 复制代码
String code="00123";

可以设置:

java 复制代码
cellData.setType(CellDataTypeEnum.STRING);

防止数字转换。

但是:

不适合空模板。


afterCellDispose

执行最后:

text 复制代码
数据写入
 ↓
样式处理
 ↓
afterCellDispose

最适合设置:

  • 文本格式;
  • 锁定状态;
  • 最终 CellStyle。

七、最终设计

推荐:

java 复制代码
ExportSheet

 ├── maxColumnIndex
 ├── textCols
 └── protectedColumns
java 复制代码
        for (EmployeeStaffingVo vo : dataList) {
            EmployeeStaffingImportVo importVo = new EmployeeStaffingImportVo();
            BeanUtils.copyProperties(vo, importVo);
            // 项目 GUID 用于导入时按项目匹配,不展示在模板中
            importVo.setFldProjectGuid(vo.getFldProjectGuid());

            List<Object> row = new ArrayList<>();
            row.add(rowNo++);
            row.add(defaultString(importVo.getFldCompanyName()));
            row.add(defaultString(importVo.getFldProjectName()));
            row.add(defaultString(importVo.getFldProjectBusinessType()));
            row.add(defaultString(importVo.getFldBuildingArea()));
            row.add(defaultString(importVo.getFldServiceLevel()));
            dataRows.add(row);
        }

        ExportSheet exportSheet = new ExportSheet();
        exportSheet.setSheetName("人员配备统计表");
        exportSheet.setHeadList(headList);
        exportSheet.setDataList(dataRows);
        exportSheet.setCommentConfigs(commentConfigs);

        // *导入月份 列强制为文本格式,避免 Excel 把 "2026-01" 自动识别为日期
        exportSheet.setTextCols(Arrays.asList(6));
        exportSheet.setMaxColumnIndex(9);
        exportSheet.setEnhancedProtectedColumns(Arrays.asList(0, 1, 2, 3, 4, 5));

        EsEasyExcelUtil.multipleSheetExport("人员配备统计表导入模板",
                Collections.singletonList(exportSheet), response);
    }

最终效果:

效果
A-F 锁定
G 导入月份 文本,可输入 2026-01
H-J 可编辑

实现:

✅ 不需要 row.add("")

✅ 避免 EasyExcel 空字符串处理导致文本格式失效

✅ 不会日期自动转换

✅ 空模板支持

✅ 适用于 EasyExcel 4.0.3 导入模板生成场景

相关推荐
霸道流氓气质1 小时前
Spring AI Alibaba 系列总结:Java 工程师 AI 能力全景图谱
java·人工智能·spring
Csvn1 小时前
模板字面量类型:用类型做字符串的正则
前端
二十雨辰1 小时前
[Java]-场景题
java·开发语言
MZA6661 小时前
Spring Boot 实战:基于自定义注解 + AOP 实现 Kafka 消息无侵入异步上送
java·spring boot·kafka
我是大猴子2 小时前
为什么SSE无法代替Webscoket
java
倾颜2 小时前
AI Chat 长会话性能实践:消息虚拟化、动态高度与流式滚动设计
前端·react.js·node.js
重庆小透明2 小时前
Kafka 完全指南:从基础组件到核心原理(包含面试题)
java·分布式·微服务·架构·kafka
幸运小圣2 小时前
SSE 与 WebSocket 新手入门:前端实时通信完全指南【JavaScript】
前端·javascript·websocket
艾伦野鸽ggg2 小时前
25级开学 JS 考核题解
前端·javascript