From 27f168f99c97ca3efed7d14063defe249d4b1dbc Mon Sep 17 00:00:00 2001 From: gjh <1421wake> Date: Wed, 3 Sep 2025 15:05:08 +0800 Subject: [PATCH] =?UTF-8?q?word=E6=A8=A1=E6=9D=BF=E5=AF=BC=E5=87=BAdemov3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../org/dromara/demo/domain/CheckItem.java | 20 +++ .../org/dromara/demo/domain/ReportData.java | 5 - .../demo/domain/WordReportExporter.java | 146 ++++++++++++------ 3 files changed, 115 insertions(+), 56 deletions(-) diff --git a/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/domain/CheckItem.java b/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/domain/CheckItem.java index d29b521..dc3b91d 100644 --- a/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/domain/CheckItem.java +++ b/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/domain/CheckItem.java @@ -22,6 +22,7 @@ public class CheckItem { */ private String checkProject; + /** * 具体检查子项描述 * 示例值:"站房清洁与规范管理" @@ -29,6 +30,9 @@ public class CheckItem { */ private String checkItem; + // TODO 需要设置集合 + private List checkItemBasis; + /** * 该项满分分数 * 示例值:2 @@ -79,4 +83,20 @@ public class CheckItem { } return sb.toString().trim(); } + + /** + * 格式化检查要点(用于导出 Word) + * 将检查要点拼接成带换行符的字符串 + * + * @return 格式化后的完整说明文本 + */ + public String formatCheckItem() { + StringBuilder sb = new StringBuilder(); + if (this.checkItemBasis != null) { + for (String basis : this.checkItemBasis) { + sb.append(basis).append("\n"); + } + } + return sb.toString().trim(); + } } diff --git a/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/domain/ReportData.java b/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/domain/ReportData.java index f650a21..0d9d04f 100644 --- a/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/domain/ReportData.java +++ b/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/domain/ReportData.java @@ -19,11 +19,6 @@ public class ReportData { /** 站点名称,如:浦东新区张江空气质量监测站 */ private String siteName; - /** 所属区或管委会,如:浦东新区 */ - //private String district; - - /** 所属街道、乡或镇,如:张江镇 */ - //private String street; /** 运维单位,如:上海环保科技有限公司 */ private String operationUnit; 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 7c19388..0974ec0 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 @@ -2,8 +2,7 @@ package org.dromara.demo.domain; import cn.hutool.json.JSONUtil; import org.apache.poi.xwpf.usermodel.*; -import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTblWidth; -import org.openxmlformats.schemas.wordprocessingml.x2006.main.STTblWidth; +import org.openxmlformats.schemas.wordprocessingml.x2006.main.*; import java.io.File; import java.io.FileOutputStream; @@ -134,81 +133,121 @@ public class WordReportExporter { List items = reportData.getCheckItems(); if (items == null || items.isEmpty()) return; + // ✅ 1. 创建表格 XWPFTable table = document.createTable(); - CTTblWidth width = table.getCTTbl().getTblPr().addNewTblW(); - width.setW(BigInteger.valueOf(9000)); - width.setType(STTblWidth.DXA); - // 表头 + // ✅ 2. 设置表格宽度 + CTTblPr tblPr = table.getCTTbl().getTblPr(); + CTTblWidth tblWidth = tblPr.addNewTblW(); + tblWidth.setW(BigInteger.valueOf(9000)); + tblWidth.setType(STTblWidth.DXA); + + // ✅ 3. 强制第一行有 5 列(必须!否则 createRow 后 getCell 越界) XWPFTableRow header = table.getRow(0); + for (int i = 1; i < 5; i++) { + header.addNewTableCell(); + } + + // ✅ 4. 设置表头文本和样式(不要提前操作 TcPr) setCellTextAndStyle(header.getCell(0), "检查项目", true); - header.addNewTableCell(); setCellTextAndStyle(header.getCell(1), "检查要点", true); - header.addNewTableCell(); setCellTextAndStyle(header.getCell(2), "单项分值", true); - header.addNewTableCell(); setCellTextAndStyle(header.getCell(3), "得分", true); - header.addNewTableCell(); setCellTextAndStyle(header.getCell(4), "评分说明", true); - // 按 checkProject 分组,并按项目编号排序 + // ✅ 5. 分组数据 Map> grouped = items.stream() .sorted(Comparator.comparing(CheckItem::getCheckProject)) .collect(Collectors.groupingBy( - CheckItem::getCheckProject, // 分组依据 - TreeMap::new, // ✅ 用 TreeMap 保持 key 有序 - Collectors.toList() // 每组是一个 List + CheckItem::getCheckProject, + TreeMap::new, + Collectors.toList() )); + int totalScore = 0; + for (Map.Entry> entry : grouped.entrySet()) { List groupItems = entry.getValue(); String project = entry.getKey(); for (int i = 0; i < groupItems.size(); i++) { CheckItem item = groupItems.get(i); - XWPFTableRow row = table.createRow(); - - if (i == 0) { - XWPFTableCell cell = row.getCell(0); - cell.getCTTc().addNewTcPr().addNewVMerge().setVal(org.openxmlformats.schemas.wordprocessingml.x2006.main.STMerge.RESTART); - setCellTextAndStyle(cell, project, false); - } else { - XWPFTableCell cell = row.getCell(0); - cell.getCTTc().addNewTcPr().addNewVMerge().setVal(org.openxmlformats.schemas.wordprocessingml.x2006.main.STMerge.CONTINUE); - cell.setText(""); - } + XWPFTableRow row = table.createRow(); // ✅ 自动 5 列 - setCellTextAndStyle(row.getCell(1), item.getCheckItem(), false); + // ✅ 先设置文本和样式 + 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列的合并 + XWPFTableCell cell = row.getCell(0); + setCellTextAndStyle(cell, i == 0 ? project : "", i == 0); // 只有第一行显示项目名 + + // ✅ 最后设置垂直合并 + CTTcPr tcPr = cell.getCTTc().addNewTcPr(); // ✅ 这里可以 addNew,因为 setCellTextAndStyle 已完成 + CTVMerge merge = tcPr.addNewVMerge(); + merge.setVal(i == 0 ? STMerge.RESTART : STMerge.CONTINUE); + } + } + + // ✅ 6. 总分行 + XWPFTableRow totalRow = table.createRow(); + XWPFTableCell labelCell = totalRow.getCell(0); + setCellTextAndStyle(labelCell, "总分", true); + + XWPFTableCell scoreCell = totalRow.getCell(1); + setCellTextAndStyle(scoreCell, String.valueOf(totalScore), true); + + // ✅ 合并第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) { + cell.setText(""); } } } private static void setCellTextAndStyle(XWPFTableCell cell, String text, boolean isHeader) { - cell.getCTTc().addNewTcPr().addNewTcW().setW(BigInteger.valueOf(1800)); - XWPFParagraph p = cell.getParagraphs().get(0); - p.setSpacingAfter(50); - XWPFRun r = p.createRun(); - r.setText(text); - r.setFontFamily("宋体"); - r.setFontSize(10); - if (isHeader) { - r.setBold(true); + // 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"); // 1. 总分 - XWPFParagraph totalPara = document.createParagraph(); +/* XWPFParagraph totalPara = document.createParagraph(); XWPFRun totalRun = totalPara.createRun(); totalRun.setBold(false); totalRun.setText("总分:" + reportData.getTotalScore()); totalRun.setFontFamily("宋体"); - totalRun.setFontSize(12); + totalRun.setFontSize(12);*/ // 2. 备注 XWPFParagraph remarksPara = document.createParagraph(); @@ -236,12 +275,9 @@ public class WordReportExporter { report.setReportTitle("数据购买监理服务现场技术核查评分表"); report.setSiteName("临安区龙岗镇站"); -// report.setDistrict("西湖区"); -// report.setStreet("龙翔桥"); report.setOperationUnit("国研信息"); report.setCheckTime(LocalDateTime.now()); report.setCheckPersonnel("张三、李四"); - //report.setTotalScore(95); report.setRemarks("注1:《环境空气环境空气气态污染物(SO2、NO2、O3、CO)连续自动监测系统技术要求及检测方法HJ654-2013》\n" + "*标项一包含的项目,标项二不包含。\n" + "04质量控制效果中相关质控因子有缺项的,分值按比例分摊。\n"); @@ -256,6 +292,12 @@ public class WordReportExporter { "2)物品摆放不整齐;" )); item1.setDeductionRule("一项不合格扣0.5分,扣完为止(评分规则)"); + item1.setCheckItemBasis(List.of( + "1)站房环境脏,有明显灰尘和异味;", + "2)物品摆放不整齐;" + )); + + CheckItem item2 = new CheckItem(); item2.setCheckProject("01站房环境保障情况(5分)"); @@ -264,6 +306,11 @@ public class WordReportExporter { item2.setObtainedScore(3); item2.setScoringBasis(List.of("1)仪器运行正常;")); item2.setDeductionRule("发现异常未处理扣1分"); + item2.setCheckItemBasis(List.of( + "动态校准仪质量流量控制器(MFC)单点流量测试(要求相对误差≤±2%):", + "零气MFC流量: L/min,标准流量计测值: L/min,相对误差: %", + "标气MFC流量: ml/min,标准流量计测值: ml/min,相对误差: %" + )); CheckItem item3 = new CheckItem(); item3.setCheckProject("02采样系统维护效果(10分)"); @@ -272,19 +319,16 @@ public class WordReportExporter { item3.setObtainedScore(3); item3.setScoringBasis(List.of("1)仪器运行正常;")); item3.setDeductionRule("发现异常未处理扣1分"); + item3.setCheckItemBasis(List.of( + "动态校准仪质量流量控制器(MFC)单点流量测试(要求相对误差≤±2%):", + "零气MFC流量: L/min,标准流量计测值: L/min,相对误差: %", + "标气MFC流量: ml/min,标准流量计测值: ml/min,相对误差: %" + )); - report.setCheckItems(List.of(item1, item2, item3)); - // 🔢 动态计算总分 - int totalScore = report.getCheckItems().stream() - .mapToInt(CheckItem::getObtainedScore) - .sum(); - int maxScore = report.getCheckItems().stream() - .mapToInt(CheckItem::getItemScore) - .sum(); + // 添加到集合 + report.setCheckItems(List.of(item1, item2, item3)); - // ✅ 设置为格式化字符串(推荐) - report.setTotalScore(totalScore); // ✅ 默认导出到桌面 WordReportExporter.export(report);