目录
- 一、问题背景
- 二、列保护问题
- [三、为什么需要 maxColumnIndex](#三、为什么需要 maxColumnIndex)
- [四、为什么 row.add("") 会导致文本失效](#四、为什么 row.add("") 会导致文本失效)
- 五、正确方案
- [六、EasyExcel 生命周期选择](#六、EasyExcel 生命周期选择)
- 七、最终设计
在处理导入模板时遇到一个矛盾:为了实现指定列保护,必须提前创建"导入月份"列的 Cell(通过 row.add("")),否则该列不存在,无法设置 locked=false;但创建空字符串单元格后,EasyExcel 对空字符串和非空字符串的处理路径不同,导致文本格式 @ 失效,用户输入 2026-01 时仍可能被 Excel 自动识别为日期。而直接使用 row.add("2026-01") 时,由于单元格被识别为字符串类型,文本格式可以正常生效。
一、问题背景
生成 Excel 导入模板时,经常遇到两个问题:
- 指定列需要保护,用户不能修改;
- 日期类字符串(如
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 导入模板生成场景