Browse Source

word模板导出demo

aqm-ops-supervision-platform
gjh 2 weeks ago
parent
commit
a1f3106c1e
  1. 5
      ruoyi-modules/ruoyi-demo/pom.xml
  2. 82
      ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/domain/CheckItem.java
  3. 67
      ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/domain/ReportData.java
  4. 303
      ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/domain/WordReportExporter.java

5
ruoyi-modules/ruoyi-demo/pom.xml

@ -16,6 +16,11 @@
</description> </description>
<dependencies> <dependencies>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>5.2.4</version>
</dependency>
<!-- 通用工具--> <!-- 通用工具-->
<dependency> <dependency>

82
ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/domain/CheckItem.java

@ -0,0 +1,82 @@
package org.dromara.demo.domain;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
/**
* 检查项实体类平铺结构中的最小单位
* 每个字段说明如下
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class CheckItem {
/**
* 检查项目用于合并单元格
* 示例值"01 站房环境保障情况"
* 多个 CheckItem 可共享相同的 checkProject表示属于同一类别
*/
private String checkProject;
/**
* 具体检查子项描述
* 示例值"站房清洁与规范管理"
* 表示该项目下的具体考核条目
*/
private String checkItem;
/**
* 该项满分分数
* 示例值2
* 表示该检查项的总分为 2
*/
private Integer itemScore;
/**
* 实际获得分数
* 示例值1
* 表示本次检查实际得分为 1
*/
private Integer obtainedScore;
/**
* 评分细则列表前端传入的多条依据
* 示例值
* [
* "1)站房环境脏,有明显灰尘和异味;",
* "2)物品摆放不整齐;"
* ]
* 每条为独立字符串导出时需换行显示
*/
private List<String> scoringBasis;
/**
* 扣分规则说明
* 示例值"一项不合格扣0.5分,扣完为止"
* 用于说明扣分逻辑通常显示在评分细则下方
*/
private String deductionRule;
/**
* 格式化评分说明用于导出 Word
* 将评分细则和扣分规则拼接成带换行符的字符串
*
* @return 格式化后的完整说明文本
*/
public String formatScoringExplanation() {
StringBuilder sb = new StringBuilder();
if (this.scoringBasis != null) {
for (String basis : this.scoringBasis) {
sb.append(basis).append("\n");
}
}
if (this.deductionRule != null && !this.deductionRule.trim().isEmpty()) {
sb.append(this.deductionRule);
}
return sb.toString().trim();
}
}

67
ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/domain/ReportData.java

@ -0,0 +1,67 @@
package org.dromara.demo.domain;
import lombok.Data;
import java.time.LocalDate;
import java.util.List;
/**
* 监理服务现场技术核查评分报告数据模型
* 注意checkItems 是平铺列表每个 CheckItem 都包含 checkProject 字段
* 用于后续按 checkProject 值进行合并单元格Word 或前端表格
*/
@Data
public class ReportData {
/** 报告标题,如:监理服务现场技术核查评分报告 */
private String reportTitle;
/** 站点名称,如:浦东新区张江空气质量监测站 */
private String siteName;
/** 所属区或管委会,如:浦东新区 */
private String district;
/** 所属街道、乡或镇,如:张江镇 */
private String street;
/** 运维单位,如:上海环保科技有限公司 */
private String operationUnit;
/** 检查时间,格式:yyyy-MM-dd */
private LocalDate checkTime;
/** 检查人员,多人用顿号或逗号分隔,如:张三、李四 */
private String checkPersonnel;
/**
* 检查项列表平铺结构
* 每个 CheckItem 自带 checkProject 字段
* 多个 CheckItem 可能具有相同的 checkProject用于合并单元格
*/
private List<CheckItem> checkItems;
/** 总分 */
private Integer totalScore;
/** 备注 */
private String remarks;
public int getTotalPossibleScore() {
return getCheckItems().stream()
.mapToInt(CheckItem::getItemScore)
.sum();
}
public int getTotalObtainedScore() {
return getCheckItems().stream()
.mapToInt(CheckItem::getObtainedScore)
.sum();
}
// 可选:返回格式化字符串 "85/100"
public String getFormattedTotalScore() {
int obtained = getTotalObtainedScore();
int total = getTotalPossibleScore();
return obtained + "/" + total;
}
}

303
ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/domain/WordReportExporter.java

@ -0,0 +1,303 @@
package org.dromara.demo.domain;
import cn.hutool.json.JSONUtil;
import org.apache.poi.xwpf.usermodel.*;
import org.dromara.demo.domain.ReportData;
import org.dromara.demo.domain.CheckItem;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTblWidth;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.STTblWidth;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.File;
import java.math.BigInteger;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class WordReportExporter {
// 获取桌面路径(跨平台)
private static String getDesktopPath() {
return "D:\\Users\\14212\\Desktop";
}
public static void export(ReportData reportData) throws IOException {
System.out.println(JSONUtil.toJsonStr(reportData));
String desktopPath = getDesktopPath();
String filename = "数据购买监理服务现场技术核查评分表.docx";
String fullPath = desktopPath + File.separator + filename;
XWPFDocument document = new XWPFDocument();
// 1. 添加标题
XWPFParagraph titlePara = document.createParagraph();
titlePara.setAlignment(ParagraphAlignment.CENTER);
XWPFRun titleRun = titlePara.createRun();
titleRun.setText(reportData.getReportTitle());
titleRun.setBold(true);
titleRun.setFontSize(16);
titleRun.setFontFamily("宋体");
// 2. 添加基本信息
addBasicInfo(document, reportData);
// 3. 添加评分表格
addScoreTable(document, reportData);
// 4. 添加总分和备注
addTotalAndRemarks(document, reportData);
// 保存到桌面
try (FileOutputStream out = new FileOutputStream(fullPath)) {
document.write(out);
}
document.close();
System.out.println("✅ 报告已成功导出到桌面:");
System.out.println("📄 " + fullPath);
}
private static void addBasicInfo(XWPFDocument document, ReportData reportData) {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
// 站点名称
XWPFParagraph p = document.createParagraph();
p.setSpacingAfter(10);
XWPFRun label = p.createRun();
label.setFontFamily("宋体");
label.setFontSize(12);
label.setBold(true);
label.setText("站点名称:");
XWPFRun value = p.createRun();
value.setFontFamily("宋体");
value.setFontSize(12);
value.setBold(false);
value.setText(reportData.getSiteName());
// 区(管委会)
p = document.createParagraph();
p.setSpacingAfter(10);
label = p.createRun();
label.setFontFamily("宋体");
label.setFontSize(12);
label.setBold(true);
label.setText("区(管委会):");
value = p.createRun();
value.setFontFamily("宋体");
value.setFontSize(12);
value.setBold(false);
value.setText(reportData.getDistrict());
// 街道(乡、镇)
p = document.createParagraph();
p.setSpacingAfter(10);
label = p.createRun();
label.setFontFamily("宋体");
label.setFontSize(12);
label.setBold(true);
label.setText("街道(乡、镇):");
value = p.createRun();
value.setFontFamily("宋体");
value.setFontSize(12);
value.setBold(false);
value.setText(reportData.getStreet());
// 运维单位
p = document.createParagraph();
p.setSpacingAfter(10);
label = p.createRun();
label.setFontFamily("宋体");
label.setFontSize(12);
label.setBold(true);
label.setText("运维单位:");
value = p.createRun();
value.setFontFamily("宋体");
value.setFontSize(12);
value.setBold(false);
value.setText(reportData.getOperationUnit());
// 检查时间
p = document.createParagraph();
p.setSpacingAfter(10);
label = p.createRun();
label.setFontFamily("宋体");
label.setFontSize(12);
label.setBold(true);
label.setText("检查时间:");
value = p.createRun();
value.setFontFamily("宋体");
value.setFontSize(12);
value.setBold(false);
value.setText(reportData.getCheckTime().format(formatter));
// 检查人员
p = document.createParagraph();
p.setSpacingAfter(10);
label = p.createRun();
label.setFontFamily("宋体");
label.setFontSize(12);
label.setBold(true);
label.setText("检查人员:");
value = p.createRun();
value.setFontFamily("宋体");
value.setFontSize(12);
value.setBold(false);
value.setText(reportData.getCheckPersonnel());
}
private static void addScoreTable(XWPFDocument document, ReportData reportData) {
List<CheckItem> items = reportData.getCheckItems();
if (items == null || items.isEmpty()) return;
XWPFTable table = document.createTable();
CTTblWidth width = table.getCTTbl().getTblPr().addNewTblW();
width.setW(BigInteger.valueOf(9000));
width.setType(STTblWidth.DXA);
// 表头
XWPFTableRow header = table.getRow(0);
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 分组
Map<String, List<CheckItem>> grouped = items.stream()
.collect(Collectors.groupingBy(CheckItem::getCheckProject));
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("");
}
setCellTextAndStyle(row.getCell(1), item.getCheckItem(), 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);
}
}
}
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);
}
}
private static void addTotalAndRemarks(XWPFDocument document, ReportData reportData) {
XWPFParagraph totalPara = document.createParagraph();
XWPFRun totalRun = totalPara.createRun();
totalRun.setBold(true);
totalRun.setText("总分:" + reportData.getTotalScore());
XWPFParagraph remarksPara = document.createParagraph();
XWPFRun remarksRun = remarksPara.createRun();
remarksRun.setText("备注:" + reportData.getRemarks());
}
// ==================== 测试入口 ====================
public static void main(String[] args) {
try {
ReportData report = new ReportData();
report.setReportTitle("数据购买监理服务现场技术核查评分表");
report.setSiteName("杭州西湖空气质量监测站");
report.setDistrict("西湖区");
report.setStreet("龙翔桥");
report.setOperationUnit("国研信息");
report.setCheckTime(java.time.LocalDate.now());
report.setCheckPersonnel("张三、李四");
//report.setTotalScore(95);
report.setRemarks("注1:《环境空气环境空气气态污染物(SO2、NO2、O3、CO)连续自动监测系统技术要求及检测方法HJ654-2013》\n" +
"*标项一包含的项目,标项二不包含。\n" +
"04质量控制效果中相关质控因子有缺项的,分值按比例分摊。\n");
CheckItem item1 = new CheckItem();
item1.setCheckProject("01 站房环境保障情况");
item1.setCheckItem("站房清洁与规范管理");
item1.setItemScore(2);
item1.setObtainedScore(1);
item1.setScoringBasis(List.of(
"1)站房环境脏,有明显灰尘和异味;",
"2)物品摆放不整齐;"
));
item1.setDeductionRule("一项不合格扣0.5分,扣完为止(评分规则)");
CheckItem item2 = new CheckItem();
item2.setCheckProject("01 站房环境保障情况");
item2.setCheckItem("仪器运行状态检查");
item2.setItemScore(3);
item2.setObtainedScore(3);
item2.setScoringBasis(List.of("1)仪器运行正常;"));
item2.setDeductionRule("发现异常未处理扣1分");
CheckItem item3 = new CheckItem();
item3.setCheckProject("02 站房环境保障情况");
item3.setCheckItem("仪器运行状态检查");
item3.setItemScore(3);
item3.setObtainedScore(3);
item3.setScoringBasis(List.of("1)仪器运行正常;"));
item3.setDeductionRule("发现异常未处理扣1分");
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.setTotalScore(totalScore);
// ✅ 默认导出到桌面
WordReportExporter.export(report);
} catch (Exception e) {
e.printStackTrace();
System.err.println("❌ 导出失败:" + e.getMessage());
}
}
}
Loading…
Cancel
Save