13 changed files with 697 additions and 62 deletions
@ -0,0 +1,84 @@ |
|||||||
|
package com.mh.web.controller.ai; |
||||||
|
|
||||||
|
import com.mh.common.config.MHConfig; |
||||||
|
import com.mh.common.core.controller.BaseController; |
||||||
|
import org.springframework.core.io.FileSystemResource; |
||||||
|
import org.springframework.core.io.Resource; |
||||||
|
import org.springframework.http.HttpHeaders; |
||||||
|
import org.springframework.http.MediaType; |
||||||
|
import org.springframework.http.ResponseEntity; |
||||||
|
import org.springframework.web.bind.annotation.GetMapping; |
||||||
|
import org.springframework.web.bind.annotation.PathVariable; |
||||||
|
import org.springframework.web.bind.annotation.PostMapping; |
||||||
|
import org.springframework.web.bind.annotation.RequestMapping; |
||||||
|
import org.springframework.web.bind.annotation.RestController; |
||||||
|
|
||||||
|
import java.io.File; |
||||||
|
import java.net.URLEncoder; |
||||||
|
import java.nio.charset.StandardCharsets; |
||||||
|
|
||||||
|
/** |
||||||
|
* AI文件下载控制器 |
||||||
|
* 处理AI生成的报表文件下载请求 |
||||||
|
* |
||||||
|
* @author mh |
||||||
|
*/ |
||||||
|
@RestController |
||||||
|
@RequestMapping("/ai/files") |
||||||
|
public class AiFileController extends BaseController { |
||||||
|
|
||||||
|
/** |
||||||
|
* 下载AI生成的文件 |
||||||
|
* |
||||||
|
* @param filename 文件名 |
||||||
|
* @return 文件响应 |
||||||
|
*/ |
||||||
|
@GetMapping("/{filename:.+}") |
||||||
|
public ResponseEntity<Resource> downloadFile(@PathVariable String filename) { |
||||||
|
return getResourceResponseEntity(filename); |
||||||
|
} |
||||||
|
|
||||||
|
private ResponseEntity<Resource> getResourceResponseEntity(@PathVariable String filename) { |
||||||
|
try { |
||||||
|
// 构建文件路径
|
||||||
|
String storageDir = MHConfig.getProfile(); |
||||||
|
File file = new File(storageDir, filename); |
||||||
|
|
||||||
|
// 检查文件是否存在
|
||||||
|
if (!file.exists()) { |
||||||
|
return ResponseEntity.notFound().build(); |
||||||
|
} |
||||||
|
|
||||||
|
// 创建资源
|
||||||
|
Resource resource = new FileSystemResource(file); |
||||||
|
|
||||||
|
// 使用RFC 5987标准格式,支持UTF-8编码
|
||||||
|
String encodedFileName = URLEncoder.encode(filename, StandardCharsets.UTF_8); |
||||||
|
String disposition = String.format("attachment; filename*=UTF-8''%s", encodedFileName); |
||||||
|
|
||||||
|
HttpHeaders headers = new HttpHeaders(); |
||||||
|
headers.add(HttpHeaders.CONTENT_DISPOSITION, disposition); |
||||||
|
headers.add(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, HttpHeaders.CONTENT_DISPOSITION); |
||||||
|
|
||||||
|
return ResponseEntity.ok() |
||||||
|
.headers(headers) |
||||||
|
.contentLength(file.length()) |
||||||
|
.contentType(MediaType.APPLICATION_OCTET_STREAM) |
||||||
|
.body(resource); |
||||||
|
|
||||||
|
} catch (Exception e) { |
||||||
|
return ResponseEntity.internalServerError().build(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 设备分析导出 |
||||||
|
* |
||||||
|
* @param filename 文件名 |
||||||
|
* @return 文件响应 |
||||||
|
*/ |
||||||
|
@PostMapping("/{filename:.+}") |
||||||
|
public ResponseEntity<Resource> deviceAnalyzeExport(@PathVariable String filename) { |
||||||
|
return getResourceResponseEntity(filename); |
||||||
|
} |
||||||
|
} |
||||||
@ -1,39 +0,0 @@ |
|||||||
package com.mh.web.controller.ai; |
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Value; |
|
||||||
import org.springframework.core.io.Resource; |
|
||||||
import org.springframework.core.io.UrlResource; |
|
||||||
import org.springframework.http.HttpHeaders; |
|
||||||
import org.springframework.http.ResponseEntity; |
|
||||||
import org.springframework.web.bind.annotation.GetMapping; |
|
||||||
import org.springframework.web.bind.annotation.PathVariable; |
|
||||||
import org.springframework.web.bind.annotation.RequestMapping; |
|
||||||
|
|
||||||
import java.nio.file.Path; |
|
||||||
import java.nio.file.Paths; |
|
||||||
|
|
||||||
/** |
|
||||||
* @author LJF |
|
||||||
* @version 1.0 |
|
||||||
* @project EEMCS |
|
||||||
* @description 文件下载链接 |
|
||||||
* @date 2026-02-27 11:40:07 |
|
||||||
*/ |
|
||||||
@RequestMapping("/api/files") |
|
||||||
public class FileDownloadController { |
|
||||||
@Value("${mh.profile:/tmp/energy-reports}") |
|
||||||
private String storageDir; |
|
||||||
|
|
||||||
@GetMapping("/{filename}") |
|
||||||
public ResponseEntity<Resource> downloadFile(@PathVariable String filename) throws Exception { |
|
||||||
Path filePath = Paths.get(storageDir).resolve(filename); |
|
||||||
Resource resource = new UrlResource(filePath.toUri()); |
|
||||||
if (resource.exists()) { |
|
||||||
return ResponseEntity.ok() |
|
||||||
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + filename + "\"") |
|
||||||
.body(resource); |
|
||||||
} |
|
||||||
return ResponseEntity.notFound().build(); |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
@ -0,0 +1,22 @@ |
|||||||
|
package com.mh.common.core.domain.dto; |
||||||
|
|
||||||
|
import lombok.AllArgsConstructor; |
||||||
|
import lombok.Data; |
||||||
|
import lombok.NoArgsConstructor; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author LJF |
||||||
|
* @version 1.0 |
||||||
|
* @project EEMCS |
||||||
|
* @description 异步任务事件dto |
||||||
|
* @date 2026-02-28 |
||||||
|
*/ |
||||||
|
@Data |
||||||
|
@NoArgsConstructor |
||||||
|
@AllArgsConstructor |
||||||
|
public class AsyncTaskEvent { |
||||||
|
|
||||||
|
private String event; // 事件类型:reasoning, COMPLETED, FAILED
|
||||||
|
private Object data; // 事件数据
|
||||||
|
|
||||||
|
} |
||||||
@ -0,0 +1,471 @@ |
|||||||
|
package com.mh.system.service.ai.impl; |
||||||
|
|
||||||
|
import org.apache.poi.xwpf.model.XWPFHeaderFooterPolicy; |
||||||
|
import org.apache.poi.xwpf.usermodel.*; |
||||||
|
import com.mh.common.core.domain.dto.EnergyReportDTO; |
||||||
|
import com.mh.common.core.domain.dto.ReportRequestDTO; |
||||||
|
import com.mh.system.service.ai.IFileGenerationService; |
||||||
|
import org.openxmlformats.schemas.wordprocessingml.x2006.main.*; |
||||||
|
import org.springframework.stereotype.Service; |
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream; |
||||||
|
import java.io.IOException; |
||||||
|
import java.math.BigInteger; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author MH |
||||||
|
* @version 2.0 |
||||||
|
* @project EEMCS |
||||||
|
* @description 高级Word文档生成服务 - 支持表格和水印 |
||||||
|
* @date 2026-02-28 |
||||||
|
*/ |
||||||
|
@Service |
||||||
|
public class AdvancedWordGenerationServiceImpl implements IFileGenerationService { |
||||||
|
|
||||||
|
@Override |
||||||
|
public byte[] generateReport(EnergyReportDTO report, ReportRequestDTO request) throws IOException { |
||||||
|
try (XWPFDocument document = new XWPFDocument()) { |
||||||
|
// 添加专业水印
|
||||||
|
if (request.getWatermark()) { |
||||||
|
addProfessionalWatermark(document, request.getWatermarkText()); |
||||||
|
} |
||||||
|
|
||||||
|
// 创建专业的文档结构
|
||||||
|
createProfessionalDocument(document, report); |
||||||
|
|
||||||
|
ByteArrayOutputStream baos = new ByteArrayOutputStream(); |
||||||
|
document.write(baos); |
||||||
|
return baos.toByteArray(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 创建专业的文档结构 |
||||||
|
*/ |
||||||
|
private void createProfessionalDocument(XWPFDocument document, EnergyReportDTO report) { |
||||||
|
// 添加封面页
|
||||||
|
addCoverPage(document, report); |
||||||
|
|
||||||
|
// 添加目录(可选)
|
||||||
|
addTableOfContents(document); |
||||||
|
|
||||||
|
// 添加正文内容
|
||||||
|
addMainContent(document, report); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 添加封面页 |
||||||
|
*/ |
||||||
|
private void addCoverPage(XWPFDocument document, EnergyReportDTO report) { |
||||||
|
// 标题
|
||||||
|
XWPFParagraph titlePara = document.createParagraph(); |
||||||
|
titlePara.setAlignment(ParagraphAlignment.CENTER); |
||||||
|
XWPFRun titleRun = titlePara.createRun(); |
||||||
|
titleRun.setText("中央空调能效分析报告"); |
||||||
|
titleRun.setBold(true); |
||||||
|
titleRun.setFontSize(28); |
||||||
|
titleRun.addBreak(); |
||||||
|
|
||||||
|
// 副标题
|
||||||
|
XWPFParagraph subtitlePara = document.createParagraph(); |
||||||
|
subtitlePara.setAlignment(ParagraphAlignment.CENTER); |
||||||
|
XWPFRun subtitleRun = subtitlePara.createRun(); |
||||||
|
subtitleRun.setText("Central Air Conditioning Energy Efficiency Analysis Report"); |
||||||
|
subtitleRun.setFontSize(16); |
||||||
|
subtitleRun.setColor("666666"); |
||||||
|
subtitleRun.addBreak(); |
||||||
|
subtitleRun.addBreak(); |
||||||
|
|
||||||
|
// 报告日期
|
||||||
|
XWPFParagraph datePara = document.createParagraph(); |
||||||
|
datePara.setAlignment(ParagraphAlignment.CENTER); |
||||||
|
XWPFRun dateRun = datePara.createRun(); |
||||||
|
dateRun.setText("报告日期: " + report.getReportDate()); |
||||||
|
dateRun.setFontSize(14); |
||||||
|
dateRun.addBreak(); |
||||||
|
dateRun.addBreak(); |
||||||
|
dateRun.addBreak(); |
||||||
|
|
||||||
|
// 添加分页
|
||||||
|
document.createParagraph().createRun().addBreak(BreakType.PAGE); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 添加目录 |
||||||
|
*/ |
||||||
|
private void addTableOfContents(XWPFDocument document) { |
||||||
|
XWPFParagraph tocTitle = document.createParagraph(); |
||||||
|
tocTitle.setAlignment(ParagraphAlignment.CENTER); |
||||||
|
XWPFRun tocRun = tocTitle.createRun(); |
||||||
|
tocRun.setText("目 录"); |
||||||
|
tocRun.setBold(true); |
||||||
|
tocRun.setFontSize(18); |
||||||
|
tocRun.addBreak(); |
||||||
|
|
||||||
|
// 目录项
|
||||||
|
addTocItem(document, "1. 基本信息", 1); |
||||||
|
addTocItem(document, "2. 设备能效明细", 1); |
||||||
|
addTocItem(document, "3. 异常情况分析", 1); |
||||||
|
addTocItem(document, "4. 优化建议", 1); |
||||||
|
|
||||||
|
document.createParagraph().createRun().addBreak(BreakType.PAGE); |
||||||
|
} |
||||||
|
|
||||||
|
private void addTocItem(XWPFDocument document, String text, int level) { |
||||||
|
XWPFParagraph para = document.createParagraph(); |
||||||
|
if (level > 1) { |
||||||
|
para.setIndentationLeft(400); |
||||||
|
} |
||||||
|
XWPFRun run = para.createRun(); |
||||||
|
run.setText(text); |
||||||
|
run.setFontSize(12); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 添加主要内容 |
||||||
|
*/ |
||||||
|
private void addMainContent(XWPFDocument document, EnergyReportDTO report) { |
||||||
|
// 第一章:基本信息
|
||||||
|
addChapter(document, "第一章 基本信息", report); |
||||||
|
|
||||||
|
// 第二章:设备能效明细(包含表格)
|
||||||
|
addDeviceDetailsChapter(document, report); |
||||||
|
|
||||||
|
// 第三章:异常情况分析
|
||||||
|
addAnomaliesChapter(document, report); |
||||||
|
|
||||||
|
// 第四章:优化建议
|
||||||
|
addSuggestionsChapter(document, report); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 添加章节标题 |
||||||
|
*/ |
||||||
|
private void addChapter(XWPFDocument document, String title, EnergyReportDTO report) { |
||||||
|
XWPFParagraph chapterPara = document.createParagraph(); |
||||||
|
chapterPara.setStyle("Heading1"); |
||||||
|
XWPFRun chapterRun = chapterPara.createRun(); |
||||||
|
chapterRun.setText(title); |
||||||
|
chapterRun.setBold(true); |
||||||
|
chapterRun.setFontSize(16); |
||||||
|
chapterRun.addBreak(); |
||||||
|
|
||||||
|
// 添加基本信息内容
|
||||||
|
addBasicInfoContent(document, report); |
||||||
|
document.createParagraph().createRun().addBreak(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 添加基本信息内容 |
||||||
|
*/ |
||||||
|
private void addBasicInfoContent(XWPFDocument document, EnergyReportDTO report) { |
||||||
|
String[] infoItems = { |
||||||
|
"报表日期: " + report.getReportDate(), |
||||||
|
"整体能效评分: " + report.getOverallScore(), |
||||||
|
"系统综合能效比(EER): " + report.getSystemEER(), |
||||||
|
"总能耗: " + formatDouble(report.getTotalEnergyConsumption()) + " kWh" |
||||||
|
}; |
||||||
|
|
||||||
|
for (String item : infoItems) { |
||||||
|
XWPFParagraph para = document.createParagraph(); |
||||||
|
para.setIndentationLeft(400); |
||||||
|
XWPFRun run = para.createRun(); |
||||||
|
run.setText("• " + item); |
||||||
|
run.setFontSize(12); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 添加设备明细章节(包含专业表格) |
||||||
|
*/ |
||||||
|
private void addDeviceDetailsChapter(XWPFDocument document, EnergyReportDTO report) { |
||||||
|
XWPFParagraph chapterPara = document.createParagraph(); |
||||||
|
chapterPara.setStyle("Heading1"); |
||||||
|
XWPFRun chapterRun = chapterPara.createRun(); |
||||||
|
chapterRun.setText("第二章 设备能效明细"); |
||||||
|
chapterRun.setBold(true); |
||||||
|
chapterRun.setFontSize(16); |
||||||
|
chapterRun.addBreak(); |
||||||
|
|
||||||
|
EnergyReportDTO.DeviceEnergy[] devices = report.getDeviceDetails().toArray(new EnergyReportDTO.DeviceEnergy[0]); |
||||||
|
if (devices != null && devices.length > 0) { |
||||||
|
createDeviceDetailsTable(document, devices); |
||||||
|
} else { |
||||||
|
XWPFParagraph para = document.createParagraph(); |
||||||
|
para.setIndentationLeft(400); |
||||||
|
XWPFRun run = para.createRun(); |
||||||
|
run.setText("暂无设备数据"); |
||||||
|
run.setFontSize(12); |
||||||
|
} |
||||||
|
document.createParagraph().createRun().addBreak(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 创建专业的设备明细表格 |
||||||
|
*/ |
||||||
|
private void createDeviceDetailsTable(XWPFDocument document, EnergyReportDTO.DeviceEnergy[] devices) { |
||||||
|
// 创建表格 (行数 = 表头1行 + 数据行数)
|
||||||
|
int rows = devices.length + 1; |
||||||
|
int cols = 5; |
||||||
|
XWPFTable table = document.createTable(rows, cols); |
||||||
|
|
||||||
|
// 设置表格样式
|
||||||
|
setTableStyle(table); |
||||||
|
|
||||||
|
// 设置表头
|
||||||
|
String[] headers = {"设备名称", "设备类型", "能耗(kWh)", "效率(%)", "偏差率(%)"}; |
||||||
|
XWPFTableRow headerRow = table.getRow(0); |
||||||
|
for (int i = 0; i < headers.length; i++) { |
||||||
|
XWPFTableCell cell = headerRow.getCell(i); |
||||||
|
cell.setColor("3366FF"); |
||||||
|
XWPFParagraph para = cell.getParagraphs().get(0); |
||||||
|
para.setAlignment(ParagraphAlignment.CENTER); |
||||||
|
XWPFRun run = para.createRun(); |
||||||
|
run.setText(headers[i]); |
||||||
|
run.setBold(true); |
||||||
|
run.setColor("FFFFFF"); |
||||||
|
run.setFontSize(12); |
||||||
|
} |
||||||
|
|
||||||
|
// 填充数据
|
||||||
|
for (int i = 0; i < devices.length; i++) { |
||||||
|
EnergyReportDTO.DeviceEnergy device = devices[i]; |
||||||
|
XWPFTableRow row = table.getRow(i + 1); |
||||||
|
|
||||||
|
String[] values = { |
||||||
|
device.getDeviceName(), |
||||||
|
device.getDeviceType(), |
||||||
|
formatDouble(device.getEnergyConsumption()), |
||||||
|
formatDouble(device.getEfficiency()), |
||||||
|
formatDouble(device.getDeviationRate()) |
||||||
|
}; |
||||||
|
|
||||||
|
for (int j = 0; j < values.length; j++) { |
||||||
|
XWPFTableCell cell = row.getCell(j); |
||||||
|
XWPFParagraph para = cell.getParagraphs().get(0); |
||||||
|
para.setAlignment(ParagraphAlignment.CENTER); |
||||||
|
XWPFRun run = para.createRun(); |
||||||
|
run.setText(values[j]); |
||||||
|
run.setFontSize(11); |
||||||
|
|
||||||
|
// 根据偏差率设置背景色
|
||||||
|
if (j == 4) { // 偏差率列
|
||||||
|
double deviation = device.getDeviationRate() != null ? device.getDeviationRate() : 0; |
||||||
|
if (deviation > 10) { |
||||||
|
cell.setColor("FFCCCC"); // 红色背景表示高偏差
|
||||||
|
} else if (deviation > 5) { |
||||||
|
cell.setColor("FFE5CC"); // 橙色背景表示中等偏差
|
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 添加异常情况分析章节 |
||||||
|
*/ |
||||||
|
private void addAnomaliesChapter(XWPFDocument document, EnergyReportDTO report) { |
||||||
|
XWPFParagraph chapterPara = document.createParagraph(); |
||||||
|
chapterPara.setStyle("Heading1"); |
||||||
|
XWPFRun chapterRun = chapterPara.createRun(); |
||||||
|
chapterRun.setText("第三章 异常情况分析"); |
||||||
|
chapterRun.setBold(true); |
||||||
|
chapterRun.setFontSize(16); |
||||||
|
chapterRun.addBreak(); |
||||||
|
|
||||||
|
EnergyReportDTO.Anomaly[] anomalies = report.getAnomalies().toArray(new EnergyReportDTO.Anomaly[0]); |
||||||
|
if (anomalies != null && anomalies.length > 0) { |
||||||
|
createAnomaliesTable(document, anomalies); |
||||||
|
} else { |
||||||
|
XWPFParagraph para = document.createParagraph(); |
||||||
|
para.setIndentationLeft(400); |
||||||
|
XWPFRun run = para.createRun(); |
||||||
|
run.setText("系统运行正常,无异常情况"); |
||||||
|
run.setFontSize(12); |
||||||
|
} |
||||||
|
document.createParagraph().createRun().addBreak(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 创建异常情况表格 |
||||||
|
*/ |
||||||
|
private void createAnomaliesTable(XWPFDocument document, EnergyReportDTO.Anomaly[] anomalies) { |
||||||
|
int rows = anomalies.length + 1; |
||||||
|
int cols = 5; |
||||||
|
XWPFTable table = document.createTable(rows, cols); |
||||||
|
setTableStyle(table); |
||||||
|
|
||||||
|
// 表头
|
||||||
|
String[] headers = {"设备名称", "异常类型", "异常描述", "严重程度", "预估损失"}; |
||||||
|
XWPFTableRow headerRow = table.getRow(0); |
||||||
|
for (int i = 0; i < headers.length; i++) { |
||||||
|
XWPFTableCell cell = headerRow.getCell(i); |
||||||
|
cell.setColor("FF6666"); |
||||||
|
XWPFParagraph para = cell.getParagraphs().get(0); |
||||||
|
para.setAlignment(ParagraphAlignment.CENTER); |
||||||
|
XWPFRun run = para.createRun(); |
||||||
|
run.setText(headers[i]); |
||||||
|
run.setBold(true); |
||||||
|
run.setColor("FFFFFF"); |
||||||
|
run.setFontSize(12); |
||||||
|
} |
||||||
|
|
||||||
|
// 数据行
|
||||||
|
for (int i = 0; i < anomalies.length; i++) { |
||||||
|
EnergyReportDTO.Anomaly anomaly = anomalies[i]; |
||||||
|
XWPFTableRow row = table.getRow(i + 1); |
||||||
|
|
||||||
|
String[] values = { |
||||||
|
anomaly.getDeviceName(), |
||||||
|
anomaly.getAnomalyType(), |
||||||
|
anomaly.getDescription(), |
||||||
|
anomaly.getSeverity(), |
||||||
|
formatDouble(anomaly.getEstimatedLoss()) |
||||||
|
}; |
||||||
|
|
||||||
|
for (int j = 0; j < values.length; j++) { |
||||||
|
XWPFTableCell cell = row.getCell(j); |
||||||
|
XWPFParagraph para = cell.getParagraphs().get(0); |
||||||
|
para.setAlignment(ParagraphAlignment.LEFT); |
||||||
|
XWPFRun run = para.createRun(); |
||||||
|
run.setText(values[j]); |
||||||
|
run.setFontSize(11); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 添加优化建议章节 |
||||||
|
*/ |
||||||
|
private void addSuggestionsChapter(XWPFDocument document, EnergyReportDTO report) { |
||||||
|
XWPFParagraph chapterPara = document.createParagraph(); |
||||||
|
chapterPara.setStyle("Heading1"); |
||||||
|
XWPFRun chapterRun = chapterPara.createRun(); |
||||||
|
chapterRun.setText("第四章 优化建议"); |
||||||
|
chapterRun.setBold(true); |
||||||
|
chapterRun.setFontSize(16); |
||||||
|
chapterRun.addBreak(); |
||||||
|
|
||||||
|
EnergyReportDTO.Suggestion[] suggestions = report.getSuggestions().toArray(new EnergyReportDTO.Suggestion[0]); |
||||||
|
if (suggestions != null && suggestions.length > 0) { |
||||||
|
for (int i = 0; i < suggestions.length; i++) { |
||||||
|
addSuggestionItem(document, suggestions[i], i + 1); |
||||||
|
} |
||||||
|
} else { |
||||||
|
XWPFParagraph para = document.createParagraph(); |
||||||
|
para.setIndentationLeft(400); |
||||||
|
XWPFRun run = para.createRun(); |
||||||
|
run.setText("暂无优化建议"); |
||||||
|
run.setFontSize(12); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 添加建议项 |
||||||
|
*/ |
||||||
|
private void addSuggestionItem(XWPFDocument document, EnergyReportDTO.Suggestion suggestion, int index) { |
||||||
|
// 建议标题
|
||||||
|
XWPFParagraph titlePara = document.createParagraph(); |
||||||
|
titlePara.setIndentationLeft(400); |
||||||
|
XWPFRun titleRun = titlePara.createRun(); |
||||||
|
titleRun.setText(index + ". " + suggestion.getTitle()); |
||||||
|
titleRun.setBold(true); |
||||||
|
titleRun.setFontSize(13); |
||||||
|
titleRun.addBreak(); |
||||||
|
|
||||||
|
// 建议详情
|
||||||
|
String[] details = { |
||||||
|
"优先级: " + suggestion.getPriority(), |
||||||
|
"预期节能: " + formatDouble(suggestion.getExpectedSaving()) + "%", |
||||||
|
"建议操作: " + suggestion.getAction(), |
||||||
|
"详细说明: " + suggestion.getDescription() |
||||||
|
}; |
||||||
|
|
||||||
|
for (String detail : details) { |
||||||
|
XWPFParagraph para = document.createParagraph(); |
||||||
|
para.setIndentationLeft(800); |
||||||
|
XWPFRun run = para.createRun(); |
||||||
|
run.setText("• " + detail); |
||||||
|
run.setFontSize(11); |
||||||
|
} |
||||||
|
|
||||||
|
document.createParagraph().createRun().addBreak(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 添加专业水印 |
||||||
|
*/ |
||||||
|
private void addProfessionalWatermark(XWPFDocument document, String watermarkText) { |
||||||
|
try { |
||||||
|
// 使用POI的水印API(适用于较新版本)
|
||||||
|
// 注意:POI 4.1.2对水印支持有限,这里提供兼容性实现
|
||||||
|
|
||||||
|
// 方法1:在页眉中添加水印文本
|
||||||
|
addWatermarkToHeader(document, watermarkText); |
||||||
|
|
||||||
|
} catch (Exception e) { |
||||||
|
System.err.println("添加水印时出错: " + e.getMessage()); |
||||||
|
// 水印失败不影响文档生成
|
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 在页眉中添加水印文本 |
||||||
|
*/ |
||||||
|
private void addWatermarkToHeader(XWPFDocument document, String watermarkText) { |
||||||
|
try { |
||||||
|
// 获取或创建页眉
|
||||||
|
XWPFHeaderFooterPolicy policy = document.getHeaderFooterPolicy(); |
||||||
|
if (policy == null) { |
||||||
|
policy = document.createHeaderFooterPolicy(); |
||||||
|
} |
||||||
|
|
||||||
|
// 创建页眉
|
||||||
|
XWPFHeader header = policy.getDefaultHeader(); |
||||||
|
if (header == null) { |
||||||
|
header = policy.createHeader(XWPFHeaderFooterPolicy.DEFAULT); |
||||||
|
} |
||||||
|
|
||||||
|
// 添加水印段落
|
||||||
|
XWPFParagraph watermarkPara = header.createParagraph(); |
||||||
|
watermarkPara.setAlignment(ParagraphAlignment.CENTER); |
||||||
|
|
||||||
|
XWPFRun watermarkRun = watermarkPara.createRun(); |
||||||
|
watermarkRun.setText(watermarkText); |
||||||
|
watermarkRun.setFontSize(48); |
||||||
|
watermarkRun.setColor("D3D3D3"); |
||||||
|
watermarkRun.setBold(true); |
||||||
|
|
||||||
|
} catch (Exception e) { |
||||||
|
System.err.println("添加页眉水印失败: " + e.getMessage()); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 设置表格样式 |
||||||
|
*/ |
||||||
|
private void setTableStyle(XWPFTable table) { |
||||||
|
// 设置表格边框
|
||||||
|
table.setTableAlignment(TableRowAlign.CENTER); |
||||||
|
|
||||||
|
// 设置表格宽度
|
||||||
|
table.setWidth("80%"); |
||||||
|
|
||||||
|
// 设置行高
|
||||||
|
for (XWPFTableRow row : table.getRows()) { |
||||||
|
row.setHeight(300); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 格式化双精度数字 |
||||||
|
*/ |
||||||
|
private String formatDouble(Double value) { |
||||||
|
return value != null ? String.format("%.2f", value) : "-"; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public String getFileExtension() { |
||||||
|
return ".docx"; |
||||||
|
} |
||||||
|
} |
||||||
Loading…
Reference in new issue