|
|
@ -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<CheckItem> 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<String, List<CheckItem>> 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<String, List<CheckItem>> entry : grouped.entrySet()) { |
|
|
|
List<CheckItem> 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); |
|
|
|
|
|
|
|