diff --git a/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/domain/WordReportExporter.java b/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/domain/WordReportExporter.java index 0974ec0..cd7b74f 100644 --- a/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/domain/WordReportExporter.java +++ b/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/domain/WordReportExporter.java @@ -129,33 +129,40 @@ public class WordReportExporter { } - private static void addScoreTable(XWPFDocument document, ReportData reportData) { + private static void addScoreTable(XWPFDocument document, ReportData reportData) { List items = reportData.getCheckItems(); if (items == null || items.isEmpty()) return; // ✅ 1. 创建表格 XWPFTable table = document.createTable(); - // ✅ 2. 设置表格宽度 + // ✅ 2. 设置表格总宽度 (9000 TWIP ≈ 页面宽度) CTTblPr tblPr = table.getCTTbl().getTblPr(); CTTblWidth tblWidth = tblPr.addNewTblW(); tblWidth.setW(BigInteger.valueOf(9000)); tblWidth.setType(STTblWidth.DXA); - // ✅ 3. 强制第一行有 5 列(必须!否则 createRow 后 getCell 越界) + // ✅ 3. 强制第一行有 5 列 XWPFTableRow header = table.getRow(0); for (int i = 1; i < 5; i++) { header.addNewTableCell(); } - // ✅ 4. 设置表头文本和样式(不要提前操作 TcPr) + // ✅ 4. 设置表头文本和样式 setCellTextAndStyle(header.getCell(0), "检查项目", true); setCellTextAndStyle(header.getCell(1), "检查要点", true); setCellTextAndStyle(header.getCell(2), "单项分值", true); setCellTextAndStyle(header.getCell(3), "得分", true); setCellTextAndStyle(header.getCell(4), "评分说明", true); - // ✅ 5. 分组数据 + // ✅ 5. 设置列宽(关键:20% / 35% / 10% / 10% / 25%) + setCellWidth(header.getCell(0), 1500); // 检查项目 + setCellWidth(header.getCell(1), 2800); // 检查要点 + setCellWidth(header.getCell(2), 950); // 单项分值(窄) + setCellWidth(header.getCell(3), 950); // 得分(窄) + setCellWidth(header.getCell(4), 2200); // 评分说明 + + // ✅ 6. 分组数据 Map> grouped = items.stream() .sorted(Comparator.comparing(CheckItem::getCheckProject)) .collect(Collectors.groupingBy( @@ -172,29 +179,28 @@ public class WordReportExporter { for (int i = 0; i < groupItems.size(); i++) { CheckItem item = groupItems.get(i); - XWPFTableRow row = table.createRow(); // ✅ 自动 5 列 + XWPFTableRow row = table.createRow(); - // ✅ 先设置文本和样式 + // 设置内容 setCellTextAndStyle(row.getCell(1), item.formatCheckItem(), false); setCellTextAndStyle(row.getCell(2), String.valueOf(item.getItemScore()), false); setCellTextAndStyle(row.getCell(3), String.valueOf(item.getObtainedScore()), false); setCellTextAndStyle(row.getCell(4), item.formatScoringExplanation(), false); - // ✅ 累加得分 + // ✅ 累加总分 totalScore += item.getObtainedScore(); - // ✅ 再设置第0列的合并 + // 设置第0列(检查项目)及垂直合并 XWPFTableCell cell = row.getCell(0); - setCellTextAndStyle(cell, i == 0 ? project : "", i == 0); // 只有第一行显示项目名 + setCellTextAndStyle(cell, i == 0 ? project : "", i == 0); - // ✅ 最后设置垂直合并 - CTTcPr tcPr = cell.getCTTc().addNewTcPr(); // ✅ 这里可以 addNew,因为 setCellTextAndStyle 已完成 + CTTcPr tcPr = cell.getCTTc().addNewTcPr(); CTVMerge merge = tcPr.addNewVMerge(); merge.setVal(i == 0 ? STMerge.RESTART : STMerge.CONTINUE); } } - // ✅ 6. 总分行 + // ✅ 7. 总分行 XWPFTableRow totalRow = table.createRow(); XWPFTableCell labelCell = totalRow.getCell(0); setCellTextAndStyle(labelCell, "总分", true); @@ -202,11 +208,11 @@ public class WordReportExporter { XWPFTableCell scoreCell = totalRow.getCell(1); setCellTextAndStyle(scoreCell, String.valueOf(totalScore), true); - // ✅ 合并第1~4列(共4列) + // 合并第1~4列(共4列) CTTcPr tcPr = scoreCell.getCTTc().addNewTcPr(); tcPr.addNewGridSpan().setVal(BigInteger.valueOf(4)); - // ✅ 清空后面单元格(可选) + // 清空后面单元格(避免显示多余文本) for (int i = 2; i <= 4; i++) { XWPFTableCell cell = totalRow.getCell(i); if (cell != null) { @@ -215,29 +221,57 @@ public class WordReportExporter { } } - private static void setCellTextAndStyle(XWPFTableCell cell, String text, boolean isHeader) { - // 1. 先设置文本(✅ 支持 \n 换行) - cell.setText(text); - - // 2. 获取或创建单元格属性,设置宽度 + // ✅ 辅助方法:设置单元格宽度 + private static void setCellWidth(XWPFTableCell cell, int width) { CTTc ctTc = cell.getCTTc(); CTTcPr tcPr = ctTc.isSetTcPr() ? ctTc.getTcPr() : ctTc.addNewTcPr(); - CTTblWidth tcW = tcPr.getTcW() != null ? tcPr.getTcW() : tcPr.addNewTcW(); - tcW.setW(BigInteger.valueOf(2000)); - tcW.setType(STTblWidth.DXA); // 推荐设置单位 - - // 3. 遍历所有段落和 Run,设置样式 - for (XWPFParagraph p : cell.getParagraphs()) { - p.setSpacingAfter(50); // 段后间距 - for (XWPFRun r : p.getRuns()) { - r.setFontFamily("宋体"); - r.setFontSize(10); - if (isHeader) { - r.setBold(true); - } + CTTblWidth tcW = tcPr.isSetTcW() ? tcPr.getTcW() : tcPr.addNewTcW(); + tcW.setW(BigInteger.valueOf(width)); + tcW.setType(STTblWidth.DXA); + } + private static void setCellTextAndStyle(XWPFTableCell cell, String text, boolean isHeader) { + cell.removeParagraph(0); + XWPFParagraph p = cell.addParagraph(); + p.setAlignment(isHeader ? ParagraphAlignment.CENTER : ParagraphAlignment.LEFT); + p.setSpacingAfter(0); + p.setSpacingBefore(0); + + String[] lines = (text == null ? "" : text).split("\n", -1); // 保留末尾空行 + + for (int i = 0; i < lines.length; i++) { + if (i > 0) { + p.createRun().addCarriageReturn(); } + XWPFRun run = p.createRun(); + run.setText(lines[i]); + run.setBold(isHeader); + run.setFontSize(10); + run.setFontFamily("等线"); } } +// private static void setCellTextAndStyle(XWPFTableCell cell, String text, boolean isHeader) { +// // 1. 先设置文本(✅ 支持 \n 换行) +// cell.setText(text); +// +// // 2. 获取或创建单元格属性,设置宽度 +// CTTc ctTc = cell.getCTTc(); +// CTTcPr tcPr = ctTc.isSetTcPr() ? ctTc.getTcPr() : ctTc.addNewTcPr(); +// CTTblWidth tcW = tcPr.getTcW() != null ? tcPr.getTcW() : tcPr.addNewTcW(); +// tcW.setW(BigInteger.valueOf(2000)); +// tcW.setType(STTblWidth.DXA); // 推荐设置单位 +// +// // 3. 遍历所有段落和 Run,设置样式 +// for (XWPFParagraph p : cell.getParagraphs()) { +// p.setSpacingAfter(50); // 段后间距 +// for (XWPFRun r : p.getRuns()) { +// r.setFontFamily("宋体"); +// r.setFontSize(10); +// if (isHeader) { +// r.setBold(true); +// } +// } +// } +// } private static void addTotalAndRemarks(XWPFDocument document, ReportData reportData) { DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");