当前位置: 首页 > news >正文

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

  <!-- PDF预览模态框 --><n-modalv-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预览区域 --><iframeid="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 到 PDFif (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(); // 创建 PictureRenderDatamap.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();}}}

http://www.lryc.cn/news/584819.html

相关文章:

  • 使用gdal读取shp及filegdb文件
  • 通过ETL工具,高效完成达梦数据库数据同步至数仓Oracle的具体实现
  • Primer Premier 5分子生物学引物设计软件 PCR引物设计工具
  • Swift 解 LeetCode 324:一步步实现摆动排序 II,掌握数组重排的节奏感
  • 智能文本抽取在合同管理实战应用
  • P1484 种树,特殊情形下的 WQS 二分转化。
  • 【9】PostgreSQL 之 vacuum 死元组清理
  • 从语音识别到智能助手:Voice Agent 的技术进化与交互变革丨Voice Agent 学习笔记
  • 如何将 iPhone 文件传到 Mac?
  • 模型训练的常用方法及llama-factory支持的数据训练格式
  • 微服务引擎 MSE 及云原生 API 网关 2025 年 6 月产品动态
  • 力扣热门算法题 204.计数质数,207.课程表,213.打家劫舍II
  • uniapp语音播报天气预报微信小程序
  • Axios之核心语法详解
  • CSS3的核心功能介绍及实战使用示例
  • string模拟实现
  • 【Linux】C++项目分层架构:核心三层与关键辅助
  • iOS 数组如何设计线程安全
  • 速学 RocketMQ
  • 较为深入的了解c++中的string类(2)
  • Vue集成MarkDown
  • 在 React Three Fiber 中实现 3D 模型点击扩散波效果
  • CSS和CSS3区别对比
  • 【深度学习新浪潮】什么是AI个性化医疗?
  • 黑马点评系列问题之P55优惠券秒杀 快捷键问题 Ctrl+D显示不出来老师给的界面
  • 【数据结构】8. 二叉树
  • FastAPI + SQLAlchemy (异步版)连接数据库时,对数据进行加密
  • React Three Fiber 实现 3D 模型点击高亮交互的核心技巧
  • Gin 中常见参数解析方法
  • 用TensorFlow进行逻辑回归(二)