实现在线预览pdf功能,后台下载PDF

发布于:2025-07-13 ⋅ 阅读:(22) ⋅ 点赞:(0)
  <!-- 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();
			}
		}
	}


网站公告

今日签到

点亮在社区的每一天
去签到