Java 树形结构数据生成导出excel文件V2

** >> 相对于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);
        }

    }

}
相关推荐
远望清一色1 分钟前
基于MATLAB边缘检测博文
开发语言·算法·matlab
何曾参静谧9 分钟前
「Py」Python基础篇 之 Python都可以做哪些自动化?
开发语言·python·自动化
Prejudices13 分钟前
C++如何调用Python脚本
开发语言·c++·python
Daniel 大东14 分钟前
idea 解决缓存损坏问题
java·缓存·intellij-idea
wind瑞20 分钟前
IntelliJ IDEA插件开发-代码补全插件入门开发
java·ide·intellij-idea
HappyAcmen20 分钟前
IDEA部署AI代写插件
java·人工智能·intellij-idea
马剑威(威哥爱编程)26 分钟前
读写锁分离设计模式详解
java·设计模式·java-ee
我狠狠地刷刷刷刷刷26 分钟前
中文分词模拟器
开发语言·python·算法
鸽鸽程序猿27 分钟前
【算法】【优选算法】前缀和(上)
java·算法·前缀和
修道-032327 分钟前
【JAVA】二、设计模式之策略模式
java·设计模式·策略模式