** >> 相对于V1版本,优化了代码逻辑,合理使用递归计算树数据的坐标 << **
1、效果
2、使用方法
java
import com.alibaba.fastjson.JSONArray;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.Workbook;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.List;
public class Tree2ExcelDemo {
public static void main(String[] args) throws IOException {
String jsonStr = "[{\"name\":\"aaa\",\"children\":[{\"name\":\"bbb\",\"children\":[{\"name\":\"eee\"},{\"name\":\"fff\",\"children\":[{\"name\":\"iii\"},{\"name\":\"jjj\",\"children\":[{\"name\":\"qqq\"},{\"name\":\"ttt\"}]}]},{\"name\":\"www\"}]},{\"name\":\"ccc\",\"children\":[{\"name\":\"ggg\"},{\"name\":\"hhh\",\"children\":[{\"name\":\"kkk\",\"children\":[{\"name\":\"ttt\"},{\"name\":\"mmm\"}]},{\"name\":\"uuu\"}]},{\"name\":\"ooo\"}]},{\"name\":\"ddd\",\"children\":[{\"name\":\"ggg\"},{\"name\":\"hhh\",\"children\":[{\"name\":\"kkk\"},{\"name\":\"uuu\"}]}]}]}]";
List<TestDemo.TreeE> list = JSONArray.parseArray(jsonStr, TestDemo.TreeE.class);
String path = "C:\\Users\\LZY\\Desktop\\" + System.currentTimeMillis() + ".xls";
File file = new File(path);
file.createNewFile();
Workbook workbook = new HSSFWorkbook();
Tree2ExcelUtil.handle(workbook, list, "TOP", Tree2ExcelUtil.Type.TOP,
null, null, 1, 1, 1);
Tree2ExcelUtil.handle(workbook, list, "MIDDLE", Tree2ExcelUtil.Type.MIDDLE,
null, null, 1, 1, 1);
Tree2ExcelUtil.handle(workbook, list, "BOTTOM", Tree2ExcelUtil.Type.BOTTOM,
null, null, 1, 1, 1);
try (
FileOutputStream fos = new FileOutputStream(file)
) {
workbook.write(fos);
fos.flush();
} catch (Exception e) {
e.printStackTrace();
} finally {
workbook.close();
}
}
}
3、源码
java
import org.apache.poi.ss.usermodel.*;
import java.lang.reflect.Field;
import java.util.*;
/**
* 树形结构数据导出生成excel文件
* <p>
* Created by lzy on 2024/1/13 14:09
*/
@SuppressWarnings("unchecked")
public class Tree2ExcelUtil {
/**
* 处理填充 树数据 到工作簿
*
* @param workbook 工作簿
* @param treeList 树数据,children结构
* @param <T> 数据泛型
*/
public static <T> void handle(Workbook workbook, List<T> treeList) {
handle(workbook, treeList, null, Type.MIDDLE, null, null, 1, 1, 1);
}
/**
* 处理填充 树数据 到工作簿
*
* @param workbook 工作簿
* @param treeList 树数据,children结构
* @param type 树类型
* @param <T> 数据泛型
*/
public static <T> void handle(Workbook workbook, List<T> treeList, Type type) {
handle(workbook, treeList, null, type, null, null, 1, 1, 1);
}
/**
* 处理填充 树数据 到工作簿
*
* @param workbook 工作簿
* @param treeList 树数据,children结构
* @param sheetName 工作表名,用于给指定工作表填充树数据
* @param type 树类型
* @param <T> 数据泛型
*/
public static <T> void handle(Workbook workbook, List<T> treeList, String sheetName, Type type) {
handle(workbook, treeList, sheetName, type, null, null, 1, 1, 1);
}
/**
* 处理填充 树数据 到工作簿
*
* @param workbook 工作簿
* @param treeList 树数据,children结构
* @param type 树类型
* @param lableField 标签字段名
* @param childrenField 孩子集合字段名
* @param <T> 数据泛型
*/
public static <T> void handle(Workbook workbook, List<T> treeList, Type type, String lableField, String childrenField) {
handle(workbook, treeList, null, type, lableField, childrenField, 1, 1, 1);
}
/**
* 处理填充 树数据 到工作簿
*
* @param workbook 工作簿
* @param treeList 树数据,children结构
* @param sheetName 工作表名,用于给指定工作表填充树数据
* @param type 树类型
* @param lableField 标签字段名
* @param childrenField 孩子集合字段名
* @param <T> 数据泛型
*/
public static <T> void handle(Workbook workbook, List<T> treeList, String sheetName, Type type,
String lableField, String childrenField) {
handle(workbook, treeList, sheetName, type, lableField, childrenField, 1, 1, 1);
}
/**
* 处理填充 树数据 到工作簿
*
* @param workbook 工作簿
* @param treeList 树数据,children结构
* @param sheetName 工作表名,用于给指定工作表填充树数据
* @param type 树类型
* @param lableField 标签字段名
* @param childrenField 孩子集合字段名
* @param startRow 开始行数,默认:1
* @param rowOffset 间隔行数,默认:1
* @param startCol 开始列数,默认:1
* @param <T> 数据泛型
*/
public static <T> void handle(Workbook workbook, List<T> treeList,
String sheetName, Type type,
String lableField, String childrenField,
int startRow, int rowOffset, int startCol) {
if (startRow < 0) {
throw new Tree2ExcelException("开始行数不能小于0");
}
if (rowOffset < 0) {
throw new Tree2ExcelException("间隔行数不能小于0");
}
if (startCol < 1) {
throw new Tree2ExcelException("开始列数不能小于1");
}
List<Map<String, Object>> newList = new ArrayList<>();
childrenField = emptyToDefault(childrenField, CHILDREN_FIELD);
handleCoord(treeList, childrenField, startRow, rowOffset, startCol, newList);
//配置单元格背景色
CellStyle style1 = getCellStyle(workbook, IndexedColors.LIGHT_GREEN);
CellStyle style2 = getCellStyle(workbook, IndexedColors.LIGHT_YELLOW);
sheetName = emptyToDefault(sheetName, "Sheet1");
Sheet sheet = workbook.getSheet(sheetName);
if (sheet == null) {
sheet = workbook.createSheet(sheetName);
}
heandleExcel(sheet, type, newList,
emptyToDefault(lableField, LABLE_FIELD),
childrenField, style1, style2);
}
/*
列字段标识
*/
private static final String COL = "_$col";
/*
开始行字段标识
*/
private static final String S_ROW = "_$s_row";
/*
中间行字段标识
*/
private static final String C_ROW = "_$c_row";
/*
结束行字段标识
*/
private static final String E_ROW = "_$e_row";
/*
标签字段标识
*/
private static final String LABLE_FIELD = "name";
/*
子集字段标识
*/
private static final String CHILDREN_FIELD = "children";
/**
* 处理树数据坐标
*
* @param treeList 树数据,children结构
* @param childrenField 孩子集合字段名
* @param startRow 开始行数
* @param rowOffset 间隔行数
* @param startCol 开始列数
* @param result 引用传递
* @param <T> 数据泛型
* @return 子集的行高
*/
private static <T> int handleCoord(List<T> treeList, String childrenField,
int startRow, int rowOffset, int startCol,
List<Map<String, Object>> result) {
if (isCollNotEmpty(treeList)) {
// 当前所在元素高度
int tempRow = 0;
// 所有子集的高度和
int childrenRows = 0;
for (int i = 0; i < treeList.size(); i++) {
Map<String, Object> item = convertBeanToMap(treeList.get(i));
tempRow = i + i * rowOffset;
item.put(COL, startCol);
item.put(S_ROW, startRow + tempRow + childrenRows);
// 子集合的高度
int childRow = 0;
List<T> children = (List<T>) item.get(childrenField);
if (isCollNotEmpty(children)) {
List<Map<String, Object>> childrenRes = new ArrayList<>();
item.put(childrenField, childrenRes);
childRow = handleCoord(children, childrenField, startRow + tempRow + childrenRows,
rowOffset, startCol + 2, childrenRes);
}
item.put(C_ROW, startRow + tempRow + childrenRows + (Math.floorDiv(childRow, 2)));
childrenRows += childRow;
item.put(E_ROW, startRow + tempRow + childrenRows);
result.add(item);
}
return tempRow + childrenRows;
}
return 0;
}
/**
* 处理Excel
*
* @param sheet 工作表
* @param type 树类型
* @param list 带有坐标的数据
* @param lableField 标签字段名
* @param childrenField 孩子集合字段名
* @param style1 文本样式
* @param style2 连线样式
*/
private static void heandleExcel(Sheet sheet, Type type, List<Map<String, Object>> list,
String lableField, String childrenField,
CellStyle style1, CellStyle style2) {
if (isCollNotEmpty(list)) {
int startRow = -1, endRow = -1, fullCol = -1;
int size = list.size();
for (int i = 0; i < size; i++) {
Map<String, Object> item = list.get(i);
int x = toInt(item.get(COL));
int y = toInt(item.get(TYPE_KEY_MAP.get(type)));
Cell cell = getOrCreateCell(sheet, x, y);
if (cell != null) {
cell.setCellStyle(style1);
cell.setCellValue(toStringOrNull(item.get(lableField)));
}
List<Map<String, Object>> children = (List<Map<String, Object>>) item.get(childrenField);
if (isCollNotEmpty(children)) {
heandleExcel(sheet, type, children, lableField, childrenField, style1, style2);
}
if (i == 0) {
startRow = y;
}
if (i == size - 1) {
endRow = y;
}
fullCol = x;
}
if (startRow != -1 && endRow != -1 && fullCol > 0) {
sheet.setColumnWidth(fullCol, 256 * 20);
sheet.setColumnWidth(fullCol - 1, 256);
for (; startRow <= endRow; startRow++) {
Cell cell = getOrCreateCell(sheet, fullCol - 1, startRow);
if (cell != null) {
cell.setCellStyle(style2);
}
}
}
}
}
/**
* 获取或创建 Cell
*
* @param sheet 工作表
* @param x 列
* @param y 行
* @return Cell
*/
private static Cell getOrCreateCell(Sheet sheet, int x, int y) {
Row row = sheet.getRow(y);
if (row == null) {
row = sheet.createRow(y);
}
Cell cell = row.getCell(x);
if (cell == null) {
cell = row.createCell(x);
}
return cell;
}
/**
* 获取Cell样式
*
* @param workbook 工作簿
* @param indexedColors 颜色
* @return 样式
*/
private static CellStyle getCellStyle(Workbook workbook, IndexedColors indexedColors) {
CellStyle style = workbook.createCellStyle();
style.setFillForegroundColor(indexedColors.getIndex());
style.setFillPattern(FillPatternType.SOLID_FOREGROUND);
return style;
}
/**
* 转整型
*
* @param val 值
* @return 整型值
*/
private static int toInt(Object val) {
try {
return Integer.parseInt(String.valueOf(val));
} catch (NumberFormatException ignored) {
}
return 0;
}
/**
* 转字符串
*
* @param obj 值
* @return 字符串值
*/
public static String toStringOrNull(Object obj) {
return null == obj ? null : obj.toString();
}
/**
* 字符串若空则默认
*
* @param str 字符串
* @param defaultStr 默认值
* @return 字符串
*/
public static String emptyToDefault(CharSequence str, String defaultStr) {
return str == null || str.length() == 0 ? defaultStr : str.toString();
}
/**
* 集合是否为空
*
* @param collection 集合
* @return 是否为空
*/
private static boolean isCollNotEmpty(Collection<?> collection) {
return !(collection == null || collection.isEmpty());
}
/**
* 实体类转Map
*
* @param bean 实体类
* @return Map值
*/
public static Map<String, Object> convertBeanToMap(Object bean) {
if (bean instanceof List || bean instanceof Set) {
throw new Tree2ExcelException("传递的元素值不符合要求");
}
if (bean instanceof Map) {
return (Map<String, Object>) bean;
}
Map<String, Object> map = new HashMap<>();
try {
Class<?> clazz = bean.getClass();
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
field.setAccessible(true);
map.put(field.getName(), field.get(bean));
}
} catch (IllegalAccessException ignored) {
}
return map;
}
private static final Map<Type, String> TYPE_KEY_MAP = new HashMap<>();
static {
TYPE_KEY_MAP.put(Type.TOP, S_ROW);
TYPE_KEY_MAP.put(Type.MIDDLE, C_ROW);
TYPE_KEY_MAP.put(Type.BOTTOM, E_ROW);
}
public static enum Type {
/**
* 上对齐
*/
TOP,
/**
* 中对齐
*/
MIDDLE,
/**
* 下对齐
*/
BOTTOM;
}
@SuppressWarnings("unused")
public static class Tree2ExcelException extends RuntimeException {
public Tree2ExcelException() {
super();
}
public Tree2ExcelException(String message) {
super(message);
}
public Tree2ExcelException(String message, Throwable cause) {
super(message, cause);
}
public Tree2ExcelException(Throwable cause) {
super(cause);
}
}
}