16 Star 244 Fork 53

wangguanquan / eec

 / 详情

能否支持导出树结构

待办的
创建于  
2024-02-22 14:17

输入图片说明

评论 (13)

javanasoda 创建了任务

从你这个图片里没看到有树结构,方便的话可以将示例文件发到我邮箱,我研究下。

点右上角可以看到我的邮箱

已收,我看了下示例文件,现在肯定是不支持的。可以通过继承XMLWorksheetWriter类来支持,我空了可以写一段示例代码。

大致分如下几个步骤:

  1. 实体支持树结构(实体属性必须包含自身),如果不是树结构则需要添加level来分级
  2. 设置一个扩展参数,标记使用树结构类型导出。
  3. XMLWorksheetWriter添加树结构属性,root节点不需要特殊处理,1级在row节点上添加属性outlineLevel="1",2级添加outlineLevel="2"。
    @Test public void testTree() throws IOException {
        List<TreeNode> root = new ArrayList<>();
        TreeNode class1 = new TreeNode("一年级", (94 + 97) / 2.0D);
        root.add(class1);
        class1.children = (Arrays.asList(new TreeNode("张一", 94), new TreeNode("李一", 97)));
        TreeNode class2 = new TreeNode("二年级", (75 + 100 + 90) / 3.0D);
        root.add(class2);
        class2.children = (Arrays.asList(new TreeNode("张二", 75), new TreeNode("李二", 100), new TreeNode("王二", 90)));

        new Workbook().addSheet(new ListSheet<TreeNode>(root) {
            @Override
            protected EntryColumn createColumn(AccessibleObject ao) {
                EntryColumn column = super.createColumn(ao);
                if (column == null && ao.isAnnotationPresent(TreeLevel.class)) {
                    column = new EntryColumn();
                    column.setColIndex(99); // <- 设置一个不存在特殊列
                }
                return column;
            }

            @Override
            protected void mergeGlobalSetting(Class<?> clazz) {
                super.mergeGlobalSetting(clazz);
                // 如果对象头有TreeStyle注解则添加一个扩展参数,参数名随意,在worksheetWriter能取就行
                if (clazz.isAnnotationPresent(TreeStyle.class)) {
                    putExtProp("tree_style", "1");
                }
            }

            @Override
            protected void calculateRealColIndex() {
                super.calculateRealColIndex();
                // 将上面设置的特殊列号改到尾列
                columns[columns.length - 1].getTail().colIndex = columns[columns.length - 2].getTail().colIndex + 1;
                columns[columns.length - 1].getTail().realColIndex = columns[columns.length - 2].getTail().realColIndex + 1;
            }

            // 将树结构降维,如果由level区分等级则不需要这一步
            @Override
            public void resetBlockData() {
                if (!eof && left() < rowBlock.capacity()) {
                    append();
                }
                // EOF
                int left = left();
                if (left == 0) return;
                List<TreeNode> nodes = new ArrayList<>(left);
                for (TreeNode e : data) {
                    nodes.add(e);
                    e.level = 0;
                    List<TreeNode> sub = e.children;
                    e.children = null;
                    for (TreeNode o : sub) {
                        nodes.add(o);
                        o.level = 1;
                        o.children = null;
                    }
                }
                this.data = nodes; // <- 替换原有数据
                this.start = 0;
                this.end += nodes.size() - left; // <- 重置尾下标

                super.resetBlockData();
            }
        }.setSheetWriter(new XMLWorksheetWriter() {
            boolean isTreeStyle;
            @Override
            protected void writeBefore() throws IOException {
                super.writeBefore();

                // 判断是否以tree结构输出
                isTreeStyle = "1".equals(sheet.getExtPropValue("tree_style"));
            }

            protected int startRow(int rows, int columns, Double rowHeight, int level) throws IOException {
                // Row number
                int r = rows + startRow;

                bw.write("<row r=\"");
                bw.writeInt(r);
                // default data row height 16.5
                if (rowHeight != null && rowHeight >= 0D) {
                    bw.write("\" customHeight=\"1\" ht=\"");
                    bw.write(rowHeight);
                }
                if (this.columns.length > 0) {
                    bw.write("\" spans=\"");
                    bw.writeInt(this.columns[0].realColIndex);
                    bw.write(':');
                    bw.writeInt(this.columns[this.columns.length - 1].realColIndex);
                } else {
                    bw.write("\" spans=\"1:");
                    bw.writeInt(columns);
                }
                // 除root节点外,其余节点需要添加level属性
                if (isTreeStyle && level > 0) {
                    bw.write("\" outlineLevel=\"");
                    bw.writeInt(level);
                }
                bw.write("\">");
                return r;
            }

            @Override
            protected int writeHeaderRow() throws IOException {
                // Write header
                int rowIndex = 0, subColumnSize = columns[0].subColumnSize(), defaultStyleIndex = sheet.defaultHeadStyleIndex();
                int realColumnLen = isTreeStyle ? columns.length - 1 : columns.length;
                Column[][] columnsArray = new Column[realColumnLen][];
                for (int i = 0; i < realColumnLen; i++) {
                    columnsArray[i] = columns[i].toArray();
                }
                // Merge cells if exists
                @SuppressWarnings("unchecked")
                List<Dimension> mergeCells = (List<Dimension>) sheet.getExtPropValue(Const.ExtendPropertyKey.MERGE_CELLS);
                Grid mergedGrid = mergeCells != null && !mergeCells.isEmpty() ? GridFactory.create(mergeCells) : null;
                for (int i = subColumnSize - 1; i >= 0; i--) {
                    // Custom row height
                    double ht = getHeaderHeight(columnsArray, i);
                    if (ht < 0) ht = sheet.getHeaderRowHeight();
                    int row = startRow(rowIndex++, realColumnLen, ht);

                    String name;
                    for (int j = 0, c = 0; j < realColumnLen; j++) {
                        Column hc = columnsArray[j][i];
                        name = isNotEmpty(hc.getName()) ? hc.getName() : mergedGrid != null && mergedGrid.test(i + 1, hc.getRealColIndex()) && !isFirstMergedCell(mergeCells, i + 1, hc.getRealColIndex()) ? null : hc.key;
                        writeString(name, row, c++, hc.getHeaderStyleIndex() == -1 ? defaultStyleIndex : hc.getHeaderStyleIndex());
                    }

                    // Write header comments
                    for (int j = 0; j < realColumnLen; j++) {
                        Column hc = columnsArray[j][i];
                        if (hc.headerComment != null) {
                            if (comments == null) comments = sheet.createComments();
                            comments.addComment(new String(int2Col(hc.getRealColIndex())) + row, hc.headerComment);
                        }
                    }
                    bw.write("</row>");
                }
                return subColumnSize;
            }

            @Override
            protected void writeRow(Row row) throws IOException {
                Cell[] cells = row.getCells();
                int len = isTreeStyle ? cells.length - 1 : cells.length;
                int r = isTreeStyle ? startRow(row.getIndex(), len, row.getHeight(), cells[columns.length - 1].intVal) : startRow(row.getIndex(), len, row.getHeight());

                // 以下代码为父类不变
                for (int i = 0; i < len; i++) {
                    Cell cell = cells[i];
                    int xf = cell.xf;
                    switch (cell.t) {
                        case INLINESTR:
                        case SST:          writeString(cell.stringVal, r, i, xf);      break;
                        case NUMERIC:      writeNumeric(cell.intVal, r, i, xf);        break;
                        case LONG:         writeNumeric(cell.longVal, r, i, xf);       break;
                        case DATE:
                        case DATETIME:
                        case DOUBLE:
                        case TIME:         writeDouble(cell.doubleVal, r, i, xf);      break;
                        case BOOL:         writeBool(cell.boolVal, r, i, xf);          break;
                        case DECIMAL:      writeDecimal(cell.decimal, r, i, xf);       break;
                        case CHARACTER:    writeChar(cell.charVal, r, i, xf);          break;
                        case REMOTE_URL:   writeRemoteMedia(cell.stringVal, r, i, xf); break;
                        case BINARY:       writeBinary(cell.binary, r, i, xf);         break;
                        case FILE:         writeFile(cell.path, r, i, xf);             break;
                        case INPUT_STREAM: writeStream(cell.isv, r, i, xf);            break;
                        case BYTE_BUFFER:  writeBinary(cell.byteBuffer, r, i, xf);     break;
                        case BLANK:
                        case EMPTY_TAG:    writeNull(r, i, xf);                        break;
                        default:
                    }
                }
                bw.write("</row>");
            }
        })).writeTo(Paths.get("d://tree style.xlsx"));
    }

    @TreeStyle
    public static class TreeNode {
        @ExcelColumn
        String name;
        @ExcelColumn
        double score; // <- root节点表示平均成绩
        @TreeLevel // <- 层级
        int level; 
        public TreeNode() { }
        public TreeNode(String name, double score) {
            this.name = name;
            this.score = score;
        }

        List<TreeNode> children;
    }
    @Target({ ElementType.TYPE })
    @Retention(RetentionPolicy.RUNTIME)
    @Inherited
    @Documented
    public static @interface TreeStyle { }

    @Target({ ElementType.FIELD, ElementType.METHOD })
    @Retention(RetentionPolicy.RUNTIME)
    @Inherited
    @Documented
    public static @interface TreeLevel {}

效果图

还有那种财务报表之类的,能不能加入模板导出,感觉写样式合并之类的太过于麻烦

希望大佬能采纳

方便的话你可以将类似的模板和Java实体发我一份,点右上角头像可以查看邮箱地址。

发送到大佬邮箱了,希望能看到

模板表格好做,代码调样式不好调 :joy:

feature/template分支临时提交了一版,使用你的模板自测可用,急用的话可以拉取该分支自用,有问题再反馈。目前只适用你的示例模板远达不到可以发版的程度

new Workbook()
    .addSheet(new TemplateSheet(testResourceRoot().resolve("template2.xlsx"))
        .setData(YzEntity.mock()) // 绑定对象
        .setData("YzEntity", YzOrderEntity.randomData()) // 绑定数组对象到YzEntity命名空间
    ).writeTo(Paths.get("./模板测试.xlsx"));

效果
模板效果

模板工作表正在开发中,会新增一个TemplateSheet以支持模板,开发分支 feature/template

测试示例

@Test public void testSimpleTemplate() throws IOException {
    new Workbook()
        .addSheet(new TemplateSheet("模板 1.xlsx", Paths.get("template/1.xlsx"))) // <- 模板工作表
        .addSheet(new ListSheet<>("普通工作表",Item.randomTestData())) // <- 普通工作表
        .addSheet(new TemplateSheet("模板 fracture merged.xlsx", Paths.get("template/fracture.xlsx"))) // <- 模板工作表
        .writeTo(Paths.get("模板excel.xlsx"));
}

万能通用版的不好做,大佬已经很厉害了,崇拜

登录 后才可以发表评论

状态
负责人
里程碑
Pull Requests
关联的 Pull Requests 被合并后可能会关闭此 issue
分支
开始日期   -   截止日期
-
置顶选项
优先级
参与者(3)
1796832 javanasoda 1578960005
Java
1
https://gitee.com/wangguanquan/eec.git
git@gitee.com:wangguanquan/eec.git
wangguanquan
eec
eec

搜索帮助

53164aa7 5694891 3bd8fe86 5694891