在现代企业应用中,数据导出功能是非常常见的需求。特别是在处理大量数据时,将数据导出为 Excel
文件不仅方便用户查看和分析,还能提高数据处理的效率。Apache POI
是一个常用的 Java Excel 处理库,但它在处理大数据量时性能较差。为此,阿里巴巴开源了 EasyExcel
,这是一个基于 Java
的简单、优雅的 Excel
操作库,它在处理大数据量时表现优异。
本文将详细介绍如何使用 EasyExcel
实现自定义表头导出,并实现数据格式化转换。
官网 : EasyExcel
封装了很多导出方法针对不同的业务场景,这里直接展示自定义表头的代码 .
1. 使用示例
-
Vo 示例(部分字段)
-
使用方法 export
用户自定义表头的 字段显示名称 , 需跟导出注解
@ExcelProperty
的value
相对应 , 不然无法取到值
-
页面显示数据
-
导出 Excel 数据
2. 工具类
/**
* Excel相关处理
*
* @author 鲁子狄
*/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class ExcelUtil {
/**
* 导出 Excel 文件
*
* @param list 导出数据集合
* @param sheetName 工作表的名称
* @param heads 表头
* @param response 响应体
*/
public static <T> void exportExcel(List<T> list, Class<T> clazz, String sheetName, List<List<String>> heads, HttpServletResponse response) {
try {
// 将对象列表转换为自定义的数据列表
List<List<Object>> data = ExcelUtil.convertObjectsToExcelRows(list, heads);
// 重置响应体,设置响应头
ExcelUtil.resetResponse(sheetName, response);
// 获取响应输出流
ServletOutputStream os = response.getOutputStream();
// 导出 Excel 文件
ExcelUtil.exportExcel(data, clazz, heads, sheetName, false, os, null);
} catch (IOException e) {
throw new RuntimeException("导出 Excel 异常");
}
}
/**
* 重置响应体
*/
private static void resetResponse(String sheetName, HttpServletResponse response) throws UnsupportedEncodingException {
String filename = ExcelUtil.encodingFilename(sheetName);
FileUtils.setAttachmentResponseHeader(response, filename);
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=UTF-8");
}
/**
* 导出 Excel 文件
*
* @param data 导出数据集合
* @param heads 表头
* @param sheetName 工作表的名称
* @param merge 是否合并单元格
* @param os 输出流
* @param options 级联下拉选内容
*/
public static <T> void exportExcel(List<List<Object>> data, Class<T> clazz, List<List<String>> heads, String sheetName, boolean merge,
OutputStream os, List<DropDownOptions> options) {
ExcelWriterSheetBuilder builder = EasyExcel.write(os, clazz)
.autoCloseStream(false)
// 自动适配列宽
.registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
// 大数值自动转换,防止失真
.registerConverter(new ExcelBigNumberConvert())
.sheet(sheetName)
.head(heads);
if (merge) {
// 注册合并单元格处理器
builder.registerWriteHandler(new CellMergeStrategy(data, true));
}
// 添加下拉框操作
builder.registerWriteHandler(new ExcelDownHandler(options, heads));
// 写入数据
builder.doWrite(data);
}
/**
* convertObjectsToExcelRows 将对象列表转换为 Excel 行数据
*
* @param list 对象集合
* @param heads 表头
* @return {@link List<List<Object>>} Excel 行数据
*/
private static <T> List<List<Object>> convertObjectsToExcelRows(List<T> list, List<List<String>> heads) {
return list.stream()
// 将对象转换为 Map
.map(ExcelUtil::convertObjectToMap)
.map(map -> {
List<Object> row = new ArrayList<>();
for (List<String> header : heads) {
// 假设表头只有一个元素
String key = header.get(0);
// 根据表头获取对应的值
row.add(map.get(key));
}
return row;
})
.toList();
}
/**
* convertObjectToMap 将对象转换为 Map,键为 @ExcelProperty 注解中的列标题,值为字段值。
*
* @param vo 对象
* @return {@link Map<String,Object>} 包含列标题和字段值的 Map
*/
private static Map<String, Object> convertObjectToMap(Object vo) {
// 获取对象的所有字段
return Arrays.stream(vo.getClass().getDeclaredFields())
// 过滤带有 @ExcelProperty 注解的字段
.filter(field -> field.isAnnotationPresent(ExcelProperty.class))
.map(field -> {
try {
// 设置字段可访问
field.setAccessible(true);
// 获取字段名和值
String fieldName = field.getDeclaredAnnotation(ExcelProperty.class).value()[0];
Object fieldValue = field.get(vo);
// 检查是否有 @ExcelDictFormat 注解
ExcelDictFormat dictFormat = field.getDeclaredAnnotation(ExcelDictFormat.class);
if (dictFormat != null) {
fieldValue = convertToExcelData(fieldValue, dictFormat);
}
return new AbstractMap.SimpleEntry<>(fieldName, fieldValue);
} catch (IllegalAccessException e) {
throw new IllegalStateException("获取字段值失败: " + field.getName(), e);
}
})
// 过滤掉值为 null 的条目
.filter(entry -> entry.getValue() != null)
.collect(Collectors.toMap(
// 键为字段名
AbstractMap.SimpleEntry::getKey,
// 值为字段值
AbstractMap.SimpleEntry::getValue,
// 如果键重复,保留第一个值
(existing, replacement) -> existing,
// 使用 HashMap 存储结果
HashMap::new
));
}
/**
* convertToExcelData 根据 @ExcelDictFormat 注解转换数据
*
* @param value 字段值
* @param dictFormat 注解信息
* @return 转换后的值
*/
private static String convertToExcelData(Object value, ExcelDictFormat dictFormat) {
String type = dictFormat.dictType();
String label;
if (StringUtils.isBlank(type)) {
label = convertByExp(value.toString(), dictFormat.readConverterExp(), dictFormat.separator());
} else {
label = SpringUtils.getBean(DictService.class).getDictLabel(type, value.toString(), dictFormat.separator());
}
return label;
}
}
/**
* 解析导出值 0=男,1=女,2=未知
*
* @param propertyValue 参数值
* @param converterExp 翻译注解
* @param separator 分隔符
* @return 解析后值
*/
public static String convertByExp(String propertyValue, String converterExp, String separator) {
StringBuilder propertyString = new StringBuilder();
String[] convertSource = converterExp.split(StringUtils.SEPARATOR);
for (String item : convertSource) {
String[] itemArray = item.split("=");
if (StringUtils.containsAny(propertyValue, separator)) {
for (String value : propertyValue.split(separator)) {
if (itemArray[0].equals(value)) {
propertyString.append(itemArray[1] + separator);
break;
}
}
} else {
if (itemArray[0].equals(propertyValue)) {
return itemArray[1];
}
}
}
return StringUtils.stripEnd(propertyString.toString(), separator);
}
注意
- 在使用
EasyExcel
导出 Excel 文件时,convertToExcelData
方法的调用与否取决于你如何传递数据以及数据的结构。- 传入单个对象或对象列表时
当你传入一个对象列表(如List<User>
),EasyExcel
会根据对象的属性和字段上的注解来决定如何处理每个字段。如果某个字段上有@ExcelDictFormat
注解,并且你已经注册了相应的转换器(如ExcelDictConvert
),那么EasyExcel
会在导出时调用convertToExcelData
方法。 - 传入嵌套列表
List<List<Object>>
时 (自定义表头使用)
当你传入一个嵌套列表List<List<Object>>
时,EasyExcel
不会自动解析对象的属性和字段注解。这是因为List<List<Object>>
是一个二维数组,每个元素都是一个Object
,EasyExcel
无法知道这些Object
应该如何转换。 - 因为自定义表头使用
List<List<Object>>
, 所以我的解决方案(convertObjectToMap
方法)是:Map
表示动态数据:将List<List<Object>>
转换为List<Map<String, Object>>
,其中键是字段名,值是字段值。- 手动调用转换器:在导出数据之前,手动调用
convertToExcelData
方法进行数据转换。
- 传入单个对象或对象列表时
3. ExcelBigNumberConvert 大数值自动转换,防止失真
/**
* 大数值转换
* Excel 数值长度位15位 大于15位的数值转换位字符串
*/
@Slf4j
public class ExcelBigNumberConvert implements Converter<Long> {
@Override
public Class<Long> supportJavaTypeKey() {
return Long.class;
}
@Override
public CellDataTypeEnum supportExcelTypeKey() {
return CellDataTypeEnum.STRING;
}
@Override
public Long convertToJavaData(ReadCellData<?> cellData, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) {
return Convert.toLong(cellData.getData());
}
@Override
public WriteCellData<Object> convertToExcelData(Long object, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) {
if (ObjectUtil.isNotNull(object)) {
String str = Convert.toStr(object);
if (str.length() > 15) {
return new WriteCellData<>(str);
}
}
WriteCellData<Object> cellData = new WriteCellData<>(new BigDecimal(object));
cellData.setType(CellDataTypeEnum.NUMBER);
return cellData;
}
}
4. CellMergeStrategy 注册合并单元格处理器
**
* 列值重复合并策略
*/
@Slf4j
public class CellMergeStrategy extends AbstractMergeStrategy implements WorkbookWriteHandler {
private final List<CellRangeAddress> cellList;
private final boolean hasTitle;
private int rowIndex;
public CellMergeStrategy(List<?> list, boolean hasTitle) {
this.hasTitle = hasTitle;
// 行合并开始下标
this.rowIndex = hasTitle ? 1 : 0;
this.cellList = handle(list, hasTitle);
}
@Override
protected void merge(Sheet sheet, Cell cell, Head head, Integer relativeRowIndex) {
//单元格写入了,遍历合并区域,如果该Cell在区域内,但非首行,则清空
final int rowIndex = cell.getRowIndex();
if (CollUtil.isNotEmpty(cellList)){
for (CellRangeAddress cellAddresses : cellList) {
final int firstRow = cellAddresses.getFirstRow();
if (cellAddresses.isInRange(cell) && rowIndex != firstRow){
cell.setBlank();
}
}
}
}
@Override
public void afterWorkbookDispose(final WorkbookWriteHandlerContext context) {
//当前表格写完后,统一写入
if (CollUtil.isNotEmpty(cellList)){
for (CellRangeAddress item : cellList) {
context.getWriteContext().writeSheetHolder().getSheet().addMergedRegion(item);
}
}
}
@SneakyThrows
private List<CellRangeAddress> handle(List<?> list, boolean hasTitle) {
List<CellRangeAddress> cellList = new ArrayList<>();
if (CollUtil.isEmpty(list)) {
return cellList;
}
Field[] fields = ReflectUtils.getFields(list.get(0).getClass(), field -> !"serialVersionUID".equals(field.getName()));
// 有注解的字段
List<Field> mergeFields = new ArrayList<>();
List<Integer> mergeFieldsIndex = new ArrayList<>();
for (int i = 0; i < fields.length; i++) {
Field field = fields[i];
if (field.isAnnotationPresent(CellMerge.class)) {
CellMerge cm = field.getAnnotation(CellMerge.class);
mergeFields.add(field);
mergeFieldsIndex.add(cm.index() == -1 ? i : cm.index());
if (hasTitle) {
ExcelProperty property = field.getAnnotation(ExcelProperty.class);
rowIndex = Math.max(rowIndex, property.value().length);
}
}
}
Map<Field, RepeatCell> map = new HashMap<>();
// 生成两两合并单元格
for (int i = 0; i < list.size(); i++) {
for (int j = 0; j < mergeFields.size(); j++) {
Field field = mergeFields.get(j);
Object val = ReflectUtils.invokeGetter(list.get(i), field.getName());
int colNum = mergeFieldsIndex.get(j);
if (!map.containsKey(field)) {
map.put(field, new RepeatCell(val, i));
} else {
RepeatCell repeatCell = map.get(field);
Object cellValue = repeatCell.getValue();
if (cellValue == null || "".equals(cellValue)) {
// 空值跳过不合并
continue;
}
if (!cellValue.equals(val)) {
if ((i - repeatCell.getCurrent() > 1)) {
cellList.add(new CellRangeAddress(repeatCell.getCurrent() + rowIndex, i + rowIndex - 1, colNum, colNum));
}
map.put(field, new RepeatCell(val, i));
} else if (i == list.size() - 1) {
if (i > repeatCell.getCurrent() && isMerge(list, i, field)) {
cellList.add(new CellRangeAddress(repeatCell.getCurrent() + rowIndex, i + rowIndex, colNum, colNum));
}
} else if (!isMerge(list, i, field)) {
if ((i - repeatCell.getCurrent() > 1)) {
cellList.add(new CellRangeAddress(repeatCell.getCurrent() + rowIndex, i + rowIndex - 1, colNum, colNum));
}
map.put(field, new RepeatCell(val, i));
}
}
}
}
return cellList;
}
private boolean isMerge(List<?> list, int i, Field field) {
boolean isMerge = true;
CellMerge cm = field.getAnnotation(CellMerge.class);
final String[] mergeBy = cm.mergeBy();
if (StrUtil.isAllNotBlank(mergeBy)) {
//比对当前list(i)和list(i - 1)的各个属性值一一比对 如果全为真 则为真
for (String fieldName : mergeBy) {
final Object valCurrent = ReflectUtil.getFieldValue(list.get(i), fieldName);
final Object valPre = ReflectUtil.getFieldValue(list.get(i - 1), fieldName);
if (!Objects.equals(valPre, valCurrent)) {
//依赖字段如有任一不等值,则标记为不可合并
isMerge = false;
}
}
}
return isMerge;
}
@Data
@AllArgsConstructor
static class RepeatCell {
private Object value;
private int current;
}
}
5. ExcelDownHandler 添加下拉框操作
/**
* <h1>Excel表格下拉选操作</h1>
* 考虑到下拉选过多可能导致Excel打开缓慢的问题,只校验前1000行
* <p>
* 即只有前1000行的数据可以用下拉框,超出的自行通过限制数据量的形式,第二次输出
*/
@Slf4j
public class ExcelDownHandler implements SheetWriteHandler {
/**
* Excel表格中的列名英文
* 仅为了解析列英文,禁止修改
*/
private static final String EXCEL_COLUMN_NAME = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
/**
* 单选数据Sheet名
*/
private static final String OPTIONS_SHEET_NAME = "options";
/**
* 联动选择数据Sheet名的头
*/
private static final String LINKED_OPTIONS_SHEET_NAME = "linkedOptions";
/**
* 下拉可选项
*/
private final List<DropDownOptions> dropDownOptions;
/**
* 表头
*/
private final List<List<String>> heads;
/**
* 当前单选进度
*/
private int currentOptionsColumnIndex;
/**
* 当前联动选择进度
*/
private int currentLinkedOptionsSheetIndex;
private final DictService dictService;
public ExcelDownHandler(List<DropDownOptions> options, List<List<String>> heads) {
dropDownOptions = options;
this.heads = heads;
currentOptionsColumnIndex = 0;
currentLinkedOptionsSheetIndex = 0;
dictService = SpringUtils.getBean(DictService.class);
}
/**
* <h2>开始创建下拉数据</h2>
* 1.通过解析传入的@ExcelProperty同级是否标注有@DropDown选项
* 如果有且设置了value值,则将其直接置为下拉可选项
* <p>
* 2.或者在调用ExcelUtil时指定了可选项,将依据传入的可选项做下拉
* <p>
* 3.二者并存,注意调用方式
*/
@Override
public void afterSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {
Sheet sheet = writeSheetHolder.getSheet();
// 开始设置下拉框 HSSFWorkbook
DataValidationHelper helper = sheet.getDataValidationHelper();
Workbook workbook = writeWorkbookHolder.getWorkbook();
FieldCache fieldCache = ClassUtils.declaredFields(writeWorkbookHolder.getClazz(), writeWorkbookHolder);
for (Map.Entry<Integer, FieldWrapper> entry : fieldCache.getSortedFieldMap().entrySet()) {
Integer index = entry.getKey();
FieldWrapper wrapper = entry.getValue();
Field field = wrapper.getField();
// 循环实体中的每个属性
// 可选的下拉值
List<String> options = new ArrayList<>();
if (field.isAnnotationPresent(ExcelDictFormat.class)) {
// 如果指定了@ExcelDictFormat,则使用字典的逻辑
ExcelDictFormat format = field.getDeclaredAnnotation(ExcelDictFormat.class);
String dictType = format.dictType();
String converterExp = format.readConverterExp();
if (StringUtils.isNotBlank(dictType)) {
// 如果传递了字典名,则依据字典建立下拉
Collection<String> values = Optional.ofNullable(dictService.getAllDictByDictType(dictType))
.orElseThrow(() -> new ServiceException(String.format("字典 %s 不存在", dictType)))
.values();
options = new ArrayList<>(values);
} else if (StringUtils.isNotBlank(converterExp)) {
// 如果指定了确切的值,则直接解析确切的值
List<String> strList = StringUtils.splitList(converterExp, format.separator());
options = StreamUtils.toList(strList, s -> StringUtils.split(s, "=")[1]);
}
} else if (field.isAnnotationPresent(ExcelEnumFormat.class)) {
// 否则如果指定了@ExcelEnumFormat,则使用枚举的逻辑
ExcelEnumFormat format = field.getDeclaredAnnotation(ExcelEnumFormat.class);
List<Object> values = EnumUtil.getFieldValues(format.enumClass(), format.textField());
options = StreamUtils.toList(values, String::valueOf);
}
if (ObjectUtil.isNotEmpty(options)) {
// 如过自定义表头不为空
if (CollUtil.isNotEmpty(heads)) {
// 获取对应的下拉表头的下标
index = IntStream.range(0, heads.size())
.filter(i -> wrapper.getHeads()[0].equals(heads.get(i).get(0)))
.findFirst().orElse(0);
}
// 仅当下拉可选项不为空时执行
if (options.size() > 20) {
// 这里限制如果可选项大于20,则使用额外表形式
dropDownWithSheet(helper, workbook, sheet, index, options);
} else {
// 否则使用固定值形式
dropDownWithSimple(helper, sheet, index, options);
}
}
}
if (CollUtil.isEmpty(dropDownOptions)) {
return;
}
dropDownOptions.forEach(everyOptions -> {
// 如果传递了下拉框选择器参数
if (!everyOptions.getNextOptions().isEmpty()) {
// 当二级选项不为空时,使用额外关联表的形式
dropDownLinkedOptions(helper, workbook, sheet, everyOptions);
} else if (everyOptions.getOptions().size() > 10) {
// 当一级选项参数个数大于10,使用额外表的形式
dropDownWithSheet(helper, workbook, sheet, everyOptions.getIndex(), everyOptions.getOptions());
} else if (!everyOptions.getOptions().isEmpty()) {
// 当一级选项个数不为空,使用默认形式
dropDownWithSimple(helper, sheet, everyOptions.getIndex(), everyOptions.getOptions());
}
});
}
/**
* <h2>简单下拉框</h2>
* 直接将可选项拼接为指定列的数据校验值
*
* @param celIndex 列index
* @param value 下拉选可选值
*/
private void dropDownWithSimple(DataValidationHelper helper, Sheet sheet, Integer celIndex, List<String> value) {
if (ObjectUtil.isEmpty(value)) {
return;
}
markOptionsToSheet(helper, sheet, celIndex, helper.createExplicitListConstraint(ArrayUtil.toArray(value, String.class)));
}
/**
* <h2>额外表格形式的级联下拉框</h2>
*
* @param options 额外表格形式存储的下拉可选项
*/
private void dropDownLinkedOptions(DataValidationHelper helper, Workbook workbook, Sheet sheet, DropDownOptions options) {
String linkedOptionsSheetName = String.format("%s_%d", ExcelDownHandler.LINKED_OPTIONS_SHEET_NAME, currentLinkedOptionsSheetIndex);
// 创建联动下拉数据表
Sheet linkedOptionsDataSheet = workbook.createSheet(WorkbookUtil.createSafeSheetName(linkedOptionsSheetName));
// 将下拉表隐藏
workbook.setSheetHidden(workbook.getSheetIndex(linkedOptionsDataSheet), true);
// 完善横向的一级选项数据表
List<String> firstOptions = options.getOptions();
Map<String, List<String>> secoundOptionsMap = options.getNextOptions();
// 创建名称管理器
Name name = workbook.createName();
// 设置名称管理器的别名
name.setNameName(linkedOptionsSheetName);
// 以横向第一行创建一级下拉拼接引用位置
String firstOptionsFunction = String.format("%s!$%s$1:$%s$1",
linkedOptionsSheetName,
ExcelDownHandler.getExcelColumnName(0),
ExcelDownHandler.getExcelColumnName(firstOptions.size())
);
// 设置名称管理器的引用位置
name.setRefersToFormula(firstOptionsFunction);
// 设置数据校验为序列模式,引用的是名称管理器中的别名
markOptionsToSheet(helper, sheet, options.getIndex(), helper.createFormulaListConstraint(linkedOptionsSheetName));
for (int columIndex = 0; columIndex < firstOptions.size(); columIndex++) {
// 先提取主表中一级下拉的列名
String firstOptionsColumnName = ExcelDownHandler.getExcelColumnName(columIndex);
// 一次循环是每一个一级选项
int finalI = columIndex;
// 本次循环的一级选项值
String thisFirstOptionsValue = firstOptions.get(columIndex);
// 创建第一行的数据
Optional.ofNullable(linkedOptionsDataSheet.getRow(0))
// 如果不存在则创建第一行
.orElseGet(() -> linkedOptionsDataSheet.createRow(finalI))
// 第一行当前列
.createCell(columIndex)
// 设置值为当前一级选项值
.setCellValue(thisFirstOptionsValue);
// 第二行开始,设置第二级别选项参数
List<String> secondOptions = secoundOptionsMap.get(thisFirstOptionsValue);
if (CollUtil.isEmpty(secondOptions)) {
// 必须保证至少有一个关联选项,否则将导致Excel解析错误
secondOptions = Collections.singletonList("暂无_0");
}
// 以该一级选项值创建子名称管理器
Name sonName = workbook.createName();
// 设置名称管理器的别名
sonName.setNameName(thisFirstOptionsValue);
// 以第二行该列数据拼接引用位置
String sonFunction = String.format("%s!$%s$2:$%s$%d",
linkedOptionsSheetName,
firstOptionsColumnName,
firstOptionsColumnName,
secondOptions.size() + 1
);
// 设置名称管理器的引用位置
sonName.setRefersToFormula(sonFunction);
// 数据验证为序列模式,引用到每一个主表中的二级选项位置
// 创建子项的名称管理器,只是为了使得Excel可以识别到数据
String mainSheetFirstOptionsColumnName = ExcelDownHandler.getExcelColumnName(options.getIndex());
for (int i = 0; i < 100; i++) {
// 以一级选项对应的主体所在位置创建二级下拉
String secondOptionsFunction = String.format("=INDIRECT(%s%d)", mainSheetFirstOptionsColumnName, i + 1);
// 二级只能主表每一行的每一列添加二级校验
markLinkedOptionsToSheet(helper, sheet, i, options.getNextIndex(), helper.createFormulaListConstraint(secondOptionsFunction));
}
for (int rowIndex = 0; rowIndex < secondOptions.size(); rowIndex++) {
// 从第二行开始填充二级选项
int finalRowIndex = rowIndex + 1;
int finalColumIndex = columIndex;
Row row = Optional.ofNullable(linkedOptionsDataSheet.getRow(finalRowIndex))
// 没有则创建
.orElseGet(() -> linkedOptionsDataSheet.createRow(finalRowIndex));
Optional
// 在本级一级选项所在的列
.ofNullable(row.getCell(finalColumIndex))
// 不存在则创建
.orElseGet(() -> row.createCell(finalColumIndex))
// 设置二级选项值
.setCellValue(secondOptions.get(rowIndex));
}
}
currentLinkedOptionsSheetIndex++;
}
/**
* <h2>额外表格形式的普通下拉框</h2>
* 由于下拉框可选值数量过多,为提升Excel打开效率,使用额外表格形式做下拉
*
* @param celIndex 下拉选
* @param value 下拉选可选值
*/
private void dropDownWithSheet(DataValidationHelper helper, Workbook workbook, Sheet sheet, Integer celIndex, List<String> value) {
// 创建下拉数据表
Sheet simpleDataSheet = Optional.ofNullable(workbook.getSheet(WorkbookUtil.createSafeSheetName(ExcelDownHandler.OPTIONS_SHEET_NAME)))
.orElseGet(() -> workbook.createSheet(WorkbookUtil.createSafeSheetName(ExcelDownHandler.OPTIONS_SHEET_NAME)));
// 将下拉表隐藏
workbook.setSheetHidden(workbook.getSheetIndex(simpleDataSheet), true);
// 完善纵向的一级选项数据表
for (int i = 0; i < value.size(); i++) {
int finalI = i;
// 获取每一选项行,如果没有则创建
Row row = Optional.ofNullable(simpleDataSheet.getRow(i))
.orElseGet(() -> simpleDataSheet.createRow(finalI));
// 获取本级选项对应的选项列,如果没有则创建
Cell cell = Optional.ofNullable(row.getCell(currentOptionsColumnIndex))
.orElseGet(() -> row.createCell(currentOptionsColumnIndex));
// 设置值
cell.setCellValue(value.get(i));
}
// 创建名称管理器
Name name = workbook.createName();
// 设置名称管理器的别名
String nameName = String.format("%s_%d", ExcelDownHandler.OPTIONS_SHEET_NAME, celIndex);
name.setNameName(nameName);
// 以纵向第一列创建一级下拉拼接引用位置
String function = String.format("%s!$%s$1:$%s$%d",
ExcelDownHandler.OPTIONS_SHEET_NAME,
ExcelDownHandler.getExcelColumnName(currentOptionsColumnIndex),
ExcelDownHandler.getExcelColumnName(currentOptionsColumnIndex),
value.size());
// 设置名称管理器的引用位置
name.setRefersToFormula(function);
// 设置数据校验为序列模式,引用的是名称管理器中的别名
markOptionsToSheet(helper, sheet, celIndex, helper.createFormulaListConstraint(nameName));
currentOptionsColumnIndex++;
}
/**
* 挂载下拉的列,仅限一级选项
*/
private void markOptionsToSheet(DataValidationHelper helper, Sheet sheet, Integer celIndex,
DataValidationConstraint constraint) {
// 设置数据有效性加载在哪个单元格上,四个参数分别是:起始行、终止行、起始列、终止列
CellRangeAddressList addressList = new CellRangeAddressList(1, 1000, celIndex, celIndex);
markDataValidationToSheet(helper, sheet, constraint, addressList);
}
/**
* 挂载下拉的列,仅限二级选项
*/
private void markLinkedOptionsToSheet(DataValidationHelper helper, Sheet sheet, Integer rowIndex,
Integer celIndex, DataValidationConstraint constraint) {
// 设置数据有效性加载在哪个单元格上,四个参数分别是:起始行、终止行、起始列、终止列
CellRangeAddressList addressList = new CellRangeAddressList(rowIndex, rowIndex, celIndex, celIndex);
markDataValidationToSheet(helper, sheet, constraint, addressList);
}
/**
* 应用数据校验
*/
private void markDataValidationToSheet(DataValidationHelper helper, Sheet sheet,
DataValidationConstraint constraint, CellRangeAddressList addressList) {
// 数据有效性对象
DataValidation dataValidation = helper.createValidation(constraint, addressList);
// 处理Excel兼容性问题
if (dataValidation instanceof XSSFDataValidation) {
//数据校验
dataValidation.setSuppressDropDownArrow(true);
//错误提示
dataValidation.setErrorStyle(DataValidation.ErrorStyle.STOP);
dataValidation.createErrorBox("提示", "此值与单元格定义数据不一致");
dataValidation.setShowErrorBox(true);
//选定提示
dataValidation.createPromptBox("填写说明:", "填写内容只能为下拉中数据,其他数据将导致导入失败");
dataValidation.setShowPromptBox(true);
sheet.addValidationData(dataValidation);
} else {
dataValidation.setSuppressDropDownArrow(false);
}
sheet.addValidationData(dataValidation);
}
/**
* <h2>依据列index获取列名英文</h2>
* 依据列index转换为Excel中的列名英文
* <p>例如第1列,index为0,解析出来为A列</p>
* 第27列,index为26,解析为AA列
* <p>第28列,index为27,解析为AB列</p>
*
* @param columnIndex 列index
* @return 列index所在得英文名
*/
private static String getExcelColumnName(int columnIndex) {
// 26一循环的次数
int columnCircleCount = columnIndex / 26;
// 26一循环内的位置
int thisCircleColumnIndex = columnIndex % 26;
// 26一循环的次数大于0,则视为栏名至少两位
String columnPrefix = columnCircleCount == 0
? StrUtil.EMPTY
: StrUtil.subWithLength(ExcelDownHandler.EXCEL_COLUMN_NAME, columnCircleCount - 1, 1);
// 从26一循环内取对应的栏位名
String columnNext = StrUtil.subWithLength(ExcelDownHandler.EXCEL_COLUMN_NAME, thisCircleColumnIndex, 1);
// 将二者拼接即为最终的栏位名
return columnPrefix + columnNext;
}
}
6. ExcelDictFormat 字典格式化注解
/**
* 字典格式化
*/
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface ExcelDictFormat {
/**
* 如果是字典类型,请设置字典的type值 (如: sys_user_sex)
*/
String dictType() default "";
/**
* 读取内容转表达式 (如: 0=男,1=女,2=未知)
*/
String readConverterExp() default "";
/**
* 分隔符,读取字符串组内容
*/
String separator() default StringUtils.SEPARATOR;
}
7. ExcelDictConvert 字典格式化转换处理 (普通导出方法调用)
/**
* 字典格式化转换处理
*/
@Slf4j
public class ExcelDictConvert implements Converter<Object> {
@Override
public Class<Object> supportJavaTypeKey() {
return Object.class;
}
@Override
public CellDataTypeEnum supportExcelTypeKey() {
return null;
}
@Override
public Object convertToJavaData(ReadCellData<?> cellData, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) {
ExcelDictFormat anno = getAnnotation(contentProperty.getField());
String type = anno.dictType();
String label = cellData.getStringValue();
String value;
if (StringUtils.isBlank(type)) {
value = ExcelUtil.reverseByExp(label, anno.readConverterExp(), anno.separator());
} else {
value = SpringUtils.getBean(DictService.class).getDictValue(type, label, anno.separator());
}
return Convert.convert(contentProperty.getField().getType(), value);
}
@Override
public WriteCellData<String> convertToExcelData(Object object, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) {
if (ObjectUtil.isNull(object)) {
return new WriteCellData<>("");
}
ExcelDictFormat anno = getAnnotation(contentProperty.getField());
String type = anno.dictType();
String value = Convert.toStr(object);
String label;
if (StringUtils.isBlank(type)) {
label = ExcelUtil.convertByExp(value, anno.readConverterExp(), anno.separator());
} else {
label = SpringUtils.getBean(DictService.class).getDictLabel(type, value, anno.separator());
}
return new WriteCellData<>(label);
}
private ExcelDictFormat getAnnotation(Field field) {
return AnnotationUtil.getAnnotation(field, ExcelDictFormat.class);
}
}
标签:return,String,自定义,List,表头,field,value,new,下拉框
From: https://blog.csdn.net/LuChangQiu/article/details/143117324