<!-- PDF预览模态框 -->
<n-modal
v-model:show="pdfModalVisible"
title="投诉统计报告预览"
:closable="false"
:mask-closable="false"
@positive-click="closePdfModal"
positive-text="关闭"
:width="900"
:height="1700"
:content-style="{ padding: 0, height: '170vh' }"
>
<!-- PDF预览区域 -->
<iframe
id="pdf-preview-iframe"
:src="pdfUrl"
style="width: 100%; height: 900px; border: none;"
></iframe>
</n-modal>
// PDF预览模态框状态
const pdfModalVisible = ref(false)
const pdfUrl = ref('')
const pdfFileName = ref('')
// 预览并打印PDF
const handlePrint = async () => {
try {
// 准备参数
let data = selectedMonth.value;
data = new Date(new Date().getFullYear(), data).toISOString().slice(0, 7);
// 显示加载提示
window.$message.info('正在加载PDF文件...');
// 调用 PDF 导出接口
const apiUrl = `/api/manager/cmComplaintStatistics/exportReportPdf?yearMonth=${data}`;
const response = await axios.get(apiUrl, {
responseType: 'blob',
headers: { 'x-token': `Bearer ${ssoClient.getToken()}` }
});
// 处理 PDF 流
const blob = new Blob([response.data], { type: 'application/pdf' });
pdfUrl.value = window.URL.createObjectURL(blob);
pdfFileName.value = `${data}投诉统计报告.pdf`;
// 显示PDF预览模态框
pdfModalVisible.value = true;
// 隐藏加载提示
window.$message.success('PDF加载成功');
} catch (error) {
window.$message.error('获取PDF文件失败');
console.error('接口请求错误:', error);
}
};
// 打印当前预览的PDF
const printCurrentPdf = () => {
const pdfIframe = document.getElementById('pdf-preview-iframe');
if (pdfIframe && pdfIframe.contentWindow) {
pdfIframe.contentWindow.print();
}
};
// 下载当前预览的PDF
const downloadCurrentPdf = () => {
const link = document.createElement('a');
link.href = pdfUrl.value;
link.download = pdfFileName.value;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
};
// 关闭PDF预览时释放资源
const closePdfModal = () => {
pdfModalVisible.value = false;
// 延迟释放URL以避免打印时资源已被回收
setTimeout(() => {
window.URL.revokeObjectURL(pdfUrl.value);
pdfUrl.value = '';
}, 3000);
};
后端:
@GetMapping("/exportReportPdf")
@ApiOperationSupport(order = 8)
@ApiOperation(value = "导出报告 PDF 文档", notes = "正式节点才能导出报告")
public void exportReportPdf(@RequestParam String yearMonth, HttpServletResponse response) {
try {
// 设置 PDF 响应头
response.setContentType("application/pdf");
response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(yearMonth + "投诉统计报告.pdf", "UTF-8"));
// 生成临时文件名
String uuid = UUID.randomUUID().toString();
String fileName = uuid + ".docx";
Path docxPath = Paths.get(pathProperties.getReport(), uuid, fileName);
Path pdfPath = Paths.get(pathProperties.getReport(), uuid, fileName + ".pdf");
File docxFile = docxPath.toFile();
File pdfFile = pdfPath.toFile();
// 创建临时目录
docxFile.getParentFile().mkdirs();
// 生成 Word 文档
XWPFTemplate template = getXwpfTemplate(null);
try (FileOutputStream fos = new FileOutputStream(docxFile)) {
template.write(fos);
}
// 转换 Word 到 PDF
if (docxFile.exists()) {
convertWordToPdf(docxFile, pdfFile);
}
// 将 PDF 文件内容写入响应流
if (pdfFile.exists()) {
try (InputStream in = new FileInputStream(pdfFile);
OutputStream out = response.getOutputStream()) {
byte[] buffer = new byte[4096];
int bytesRead;
while ((bytesRead = in.read(buffer)) != -1) {
out.write(buffer, 0, bytesRead);
}
out.flush();
} finally {
// 可选:删除临时文件(建议在文件使用完毕后异步删除)
docxFile.delete();
pdfFile.delete();
}
} else {
throw new IOException("PDF 文件生成失败");
}
} catch (Exception e) {
log.error("导出PDF失败", e);
try {
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "导出失败:" + e.getMessage());
} catch (IOException ex) {
log.error("设置响应错误信息失败", ex);
}
}
}
private XWPFTemplate getXwpfTemplate(CmComplaintVO cmComplaintVO) throws IOException {
Map<String, Object> map = new HashMap<>();
// 1. 处理文本参数(保持原有逻辑)
map.put("work_order_time",
Optional.ofNullable(LocalDateTime.now())
.map(time -> time.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")))
.orElse(null)
);
// 处理图片的核心代码
Resource imageResource = new ClassPathResource("templates/statistic_chart.png");
try (InputStream imageStream = imageResource.getInputStream()) {
// 1. 将输入流转换为 BufferedImage(直接从流转换,避免中间字节数组)
BufferedImage bufferedImage = ImageIO.read(imageStream);
if (bufferedImage == null) {
throw new IOException("无法解析图片流,可能是图片格式不支持");
}
// 2. 使用 Pictures.ofBufferedImage() 创建图片对象
PictureRenderData pictureData = Pictures.ofBufferedImage(bufferedImage, PictureType.PNG)
.size(712, 500) // 设置图片宽高(像素)
.create(); // 创建 PictureRenderData
map.put("image", pictureData); // 绑定到模板占位符 {{image}}
} catch (IOException e) {
log.error("处理图片失败", e);
// 可选:添加默认图片或抛出友好异常
throw new RuntimeException("导出Word失败:图片处理异常", e);
}
// 3. 编译模板(必须绑定图片渲染策略)
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
Resource templateResource = resolver.getResource("classpath:/templates/cm_statistics.docx");
Configure config = Configure.builder()
.bind("image", new PictureRenderPolicy()) // 绑定图片渲染策略
.build();
XWPFTemplate template = XWPFTemplate.compile(templateResource.getInputStream(), config).render(map);
return template;
}
/**
* 将Word文件转换为PDF文件
* @param wordFile Word文件
* @param pdfFile PDF文件
*/
public void convertWordToPdf(File wordFile, File pdfFile) {
try (InputStream docxInputStream = new FileInputStream(wordFile);
OutputStream outputStream = new FileOutputStream(pdfFile)) {
IConverter converter = LocalConverter.builder().build();
converter.convert(docxInputStream).as(DocumentType.DOCX).to(outputStream).as(DocumentType.PDF).execute();
System.out.println("Word转PDF成功: " + wordFile.getName());
} catch (Exception e) {
e.printStackTrace();
System.err.println("Word转PDF失败: " + wordFile.getName() + ", 错误: " + e.getMessage());
} finally {
// 删除临时文件
if (wordFile.exists()) {
wordFile.delete();
}
}
}