liujie
2026-05-16 539fa09b48cfe03c573284381cfba4ba092e8007
update
2个文件已添加
194 ■■■■■ 已修改文件
admin-server/src/main/java/com/estate/expert/admin/stat/report/render/DeviceGroupTablePolicy.java 128 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
admin-server/src/main/java/com/estate/expert/admin/stat/report/render/WordNumberingSanitizer.java 66 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
admin-server/src/main/java/com/estate/expert/admin/stat/report/render/DeviceGroupTablePolicy.java
New file
@@ -0,0 +1,128 @@
package com.estate.expert.admin.stat.report.render;
import com.alibaba.fastjson.JSONObject;
import com.deepoove.poi.policy.DynamicTableRenderPolicy;
import com.deepoove.poi.util.TableTools;
import org.apache.poi.xwpf.usermodel.ParagraphAlignment;
import org.apache.poi.xwpf.usermodel.XWPFTable;
import org.apache.poi.xwpf.usermodel.XWPFTableCell;
import org.apache.poi.xwpf.usermodel.XWPFTableRow;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.STBorder;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.STTblWidth;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.STVerticalJc;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import java.math.BigInteger;
import java.util.List;
/**
 * 设备分组表格渲染策略:同一设备类型内合并类型和数量,设备类型变更时重新分组。
 */
public class DeviceGroupTablePolicy extends DynamicTableRenderPolicy {
    private static final int TYPE_COL = 0;
    private static final int COUNT_COL = 1;
    private static final int NUMBER_COL = 2;
    @Override
    public void render(XWPFTable table, Object data) {
        var groups = (List<Object>) data;
        int templateRowIndex = findTemplateRowIndex(table);
        if (templateRowIndex < 0) {
            return;
        }
        table.removeRow(templateRowIndex);
        if (CollectionUtils.isEmpty(groups)) {
            return;
        }
        int insertIndex = templateRowIndex;
        int groupStart = templateRowIndex;
        int groupSize = 0;
        for (Object item : groups) {
            if (!(item instanceof JSONObject group)) {
                continue;
            }
            boolean firstRowOfGroup = StringUtils.hasText(group.getString("设备类型"));
            XWPFTableRow row = table.insertNewTableRow(insertIndex++);
            renderRow(row, group, firstRowOfGroup);
            if (firstRowOfGroup) {
                mergeGroup(table, groupStart, groupSize);
                groupStart = insertIndex - 1;
                groupSize = 1;
            } else {
                groupSize++;
            }
        }
        mergeGroup(table, groupStart, groupSize);
    }
    private int findTemplateRowIndex(XWPFTable table) {
        for (int i = 0; i < table.getRows().size(); i++) {
            if (rowText(table.getRow(i)).contains("[设备编号]")) {
                return i;
            }
        }
        return -1;
    }
    private String rowText(XWPFTableRow row) {
        if (row == null) {
            return "";
        }
        return row.getTableCells().stream()
                .map(XWPFTableCell::getText)
                .reduce("", String::concat);
    }
    private void renderRow(XWPFTableRow row, JSONObject group, boolean firstRowOfGroup) {
        renderCell(row, firstRowOfGroup ? group.getString("设备类型") : "", 2972);
        renderCell(row, firstRowOfGroup ? group.getString("设备数量") : "", 1531);
        renderCell(row, group.getString("设备编号"), 4706);
    }
    private void renderCell(XWPFTableRow row, String text, int width) {
        XWPFTableCell cell = row.createCell();
        var tcPr = cell.getCTTc().isSetTcPr() ? cell.getCTTc().getTcPr() : cell.getCTTc().addNewTcPr();
        tcPr.addNewTcW().setW(BigInteger.valueOf(width));
        tcPr.getTcW().setType(STTblWidth.DXA);
        tcPr.addNewGridSpan().setVal(BigInteger.valueOf(2));
        tcPr.addNewVAlign().setVal(STVerticalJc.CENTER);
        var borders = tcPr.addNewTcBorders();
        borders.addNewTop().setVal(STBorder.SINGLE);
        borders.addNewBottom().setVal(STBorder.SINGLE);
        borders.addNewLeft().setVal(STBorder.SINGLE);
        borders.addNewRight().setVal(STBorder.SINGLE);
        borders.getTop().setColor("000000");
        borders.getBottom().setColor("000000");
        borders.getLeft().setColor("000000");
        borders.getRight().setColor("000000");
        borders.getTop().setSz(BigInteger.valueOf(4));
        borders.getBottom().setSz(BigInteger.valueOf(4));
        borders.getLeft().setSz(BigInteger.valueOf(4));
        borders.getRight().setSz(BigInteger.valueOf(4));
        var paragraph = cell.getParagraphs().get(0);
        paragraph.setAlignment(ParagraphAlignment.CENTER);
        var run = paragraph.createRun();
        run.setFontFamily("黑体");
        run.setFontSize(12);
        run.setText(text == null ? "" : text);
        cell.setVerticalAlignment(XWPFTableCell.XWPFVertAlign.CENTER);
    }
    private void mergeGroup(XWPFTable table, int groupStart, int groupSize) {
        if (groupSize <= 1) {
            return;
        }
        TableTools.mergeCellsVertically(table, TYPE_COL, groupStart, groupStart + groupSize - 1);
        TableTools.mergeCellsVertically(table, COUNT_COL, groupStart, groupStart + groupSize - 1);
    }
}
admin-server/src/main/java/com/estate/expert/admin/stat/report/render/WordNumberingSanitizer.java
New file
@@ -0,0 +1,66 @@
package com.estate.expert.admin.stat.report.render;
import com.deepoove.poi.XWPFTemplate;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.apache.poi.xwpf.usermodel.XWPFParagraph;
import org.apache.poi.xwpf.usermodel.XWPFTableCell;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTDecimalNumber;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTNumPr;
import java.math.BigInteger;
/**
 * 修复模板中缺失层级的编号段落,避免 POI 转换时读取 ilvl 空指针。
 */
public final class WordNumberingSanitizer {
    private WordNumberingSanitizer() {
    }
    public static XWPFTemplate sanitize(XWPFTemplate template) {
        if (template == null) {
            return null;
        }
        sanitizeDocument(template.getXWPFDocument());
        return template;
    }
    private static void sanitizeDocument(XWPFDocument document) {
        if (document == null) {
            return;
        }
        document.getParagraphs().forEach(WordNumberingSanitizer::sanitizeParagraph);
        document.getTables().forEach(table -> table.getRows().forEach(row ->
                row.getTableCells().forEach(WordNumberingSanitizer::sanitizeCell)));
        document.getHeaderList().forEach(header -> header.getParagraphs().forEach(WordNumberingSanitizer::sanitizeParagraph));
        document.getFooterList().forEach(footer -> footer.getParagraphs().forEach(WordNumberingSanitizer::sanitizeParagraph));
    }
    private static void sanitizeCell(XWPFTableCell cell) {
        if (cell == null) {
            return;
        }
        cell.getParagraphs().forEach(WordNumberingSanitizer::sanitizeParagraph);
        cell.getTables().forEach(table -> table.getRows().forEach(row ->
                row.getTableCells().forEach(WordNumberingSanitizer::sanitizeCell)));
    }
    private static void sanitizeParagraph(XWPFParagraph paragraph) {
        if (paragraph == null || !paragraph.getCTP().isSetPPr()) {
            return;
        }
        var paragraphProperties = paragraph.getCTP().getPPr();
        if (!paragraphProperties.isSetNumPr()) {
            return;
        }
        CTNumPr numPr = paragraphProperties.getNumPr();
        if (numPr.getNumId() != null && numPr.getIlvl() == null) {
            CTDecimalNumber ilvl = numPr.addNewIlvl();
            ilvl.setVal(BigInteger.ZERO);
        }
    }
}