前端打印功能(vue +springboot)

news2024/10/25 1:27:56

后端

  • 后端
    • 依赖
    • 生成pdf的方法
    • pdf转图片
    • 使用(用的打印模版是带参数的 ,参数是aaa)
    • 总结
  • 前端
    • 页面
  • 效果

后端

依赖

依赖 一个是用模版生成对应的pdf,一个是用来将pdf转成图片需要的

      <!--打印的-->
        <dependency>
            <groupId>net.sf.jasperreports</groupId>
            <artifactId>jasperreports</artifactId>
        </dependency>

        <dependency>
            <groupId>org.apache.pdfbox</groupId>
            <artifactId>pdfbox</artifactId>
            <version>3.0.0</version>
        </dependency>

所需的文件资源 jasper是模版,然后font是pdf转图片的时候需要的字体
在这里插入图片描述
由于有资源 所以pom配置俩包下面不允许压缩

    <build>
        <resources>
            <resource>
                <targetPath>${project.build.directory}/classes</targetPath>
                <directory>src/main/resources</directory>
                <filtering>true</filtering>
                <excludes>
                    <exclude>**/*.jasper</exclude>
                    <exclude>**/*.jrxml</exclude>
                    <exclude>**/*.TTF</exclude>
                </excludes>
            </resource>
            <resource>
                <targetPath>${project.build.directory}/classes</targetPath>
                <directory>src/main/resources</directory>
                <filtering>false</filtering>
                <includes>
                    <include>**/*.jasper</include>
                    <include>**/*.jrxml</include>
                    <include>**/*.TTF</include>
                </includes>
            </resource>
        </resources>
    </build>

生成pdf的方法

用到的实体类

package com.xueyi.common.core.utils.print.dto;

import lombok.Data;

@Data
public class PdfInfoDto {
    private Integer height;
    private Integer width;
    private byte[] pdfBytes;
}

生成pdf的方法

package com.xueyi.common.core.utils.print;

import com.xueyi.common.core.utils.print.dto.PdfInfoDto;
import net.sf.jasperreports.engine.*;
import net.sf.jasperreports.engine.export.JRPdfExporter;
import net.sf.jasperreports.engine.util.JRLoader;
import net.sf.jasperreports.export.SimpleExporterInput;
import net.sf.jasperreports.export.SimpleOutputStreamExporterOutput;
import net.sf.jasperreports.export.SimplePdfExporterConfiguration;
import org.springframework.core.io.ClassPathResource;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;

public class PrintUtil {

    public static PdfInfoDto printPDF(String modelName, HashMap hashMap) throws IOException, JRException {
        // 获取模板路径
        ClassPathResource classPathResource = new ClassPathResource("/jasper/"+modelName+".jasper");


        // 获取模板输入流
        InputStream inputStream = classPathResource.getInputStream();
        // 读取模板
        JasperReport report = (JasperReport) JRLoader.loadObject(inputStream);
        //数据库数据填充报表
        JasperPrint jprint = JasperFillManager.fillReport(report, hashMap,new JREmptyDataSource());

       DefaultJasperReportsContext defaultJasperReportsContext=DefaultJasperReportsContext.getInstance();
//        defaultJasperReportsContext.setProperty("net.sf.jasperreports.fonts.STSong-Light", "path/to/STSong-Light.ttf");

        //创建导出对象
        JRPdfExporter exporter = new JRPdfExporter(defaultJasperReportsContext);

        //设置要导出的流
        exporter.setExporterInput(new SimpleExporterInput(jprint));
        ByteArrayOutputStream byteArrayOutputStream=new ByteArrayOutputStream();
        exporter.setExporterOutput(new SimpleOutputStreamExporterOutput(byteArrayOutputStream));
        SimplePdfExporterConfiguration configuration = new SimplePdfExporterConfiguration();
        configuration.setCreatingBatchModeBookmarks(true);
        exporter.setConfiguration(configuration);
        // 导出pdf
        exporter.exportReport();
        byte[] bytes=byteArrayOutputStream.toByteArray();
        byteArrayOutputStream.close();

        PdfInfoDto pdfInfo=new PdfInfoDto();
        pdfInfo.setPdfBytes(bytes);
        pdfInfo.setHeight(jprint.getPageHeight());
        pdfInfo.setWidth(jprint.getPageWidth());
        return pdfInfo;
    }

}




pdf转图片

用到的实体类

package com.xueyi.common.core.utils.print.dto;

import lombok.Data;

@Data
public class ImageInfoDto {
    private Integer height;
    private Integer width;
    private byte[] imageBytes;
}

package com.xueyi.common.core.utils.print;


import com.xueyi.common.core.utils.print.dto.ImageInfoDto;
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.rendering.PDFRenderer;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

public class Pdf2PngUtil {

    public static List<ImageInfoDto> pdf2png(byte[] bytes) throws IOException {
          ArrayList<ImageInfoDto> list=new ArrayList();
            PDDocument doc=Loader.loadPDF(bytes);
            PDFRenderer renderer = new PDFRenderer(doc);
            int pageCount = doc.getNumberOfPages();
            for (int i = 0; i < pageCount; i++) {
                // dpi为144,越高越清晰,转换越慢
                BufferedImage image = renderer.renderImageWithDPI(i, 144); // Windows native DPI
                ByteArrayOutputStream byteArrayOutputStream=new ByteArrayOutputStream();
                ImageIO.createImageOutputStream(byteArrayOutputStream);
                ImageIO.write(image, "png", byteArrayOutputStream);

                ImageInfoDto imageInfo=new ImageInfoDto();
                imageInfo.setHeight(image.getHeight());
                imageInfo.setWidth(image.getWidth());
                imageInfo.setImageBytes(byteArrayOutputStream.toByteArray());
                list.add(imageInfo);
                byteArrayOutputStream.close();
            }
            doc.close();
            return  list;
    }
}





使用(用的打印模版是带参数的 ,参数是aaa)

返回类

@Data
public class PrintDto {
    @Serial
    private static final long serialVersionUID = 1L;
    private String jasperName;
    private HashMap hashMap;
    private List<ImageInfoDto> imageInfoDtoList;
    private PdfInfoDto pdfInfoDto;
}

使用

    @PostMapping("/print")
    public AjaxResult print(@RequestBody TransSchedulerQuery transSchedulerQuery) throws IOException, JRException {
        PrintDto printDto=new PrintDto();
        printDto.setJasperName(transSchedulerQuery.getJasperName());
        HashMap hashMap=new HashMap();
        hashMap.put("aaa","你看得到吗?");
        printDto.setPdfInfoDto(PrintUtil.printPDF(transSchedulerQuery.getJasperName(),hashMap));
        printDto.setImageInfoDtoList(Pdf2PngUtil.pdf2png(printDto.getPdfInfoDto().getPdfBytes()));
        return  AjaxResult.success(printDto);
    }

总结

后端的思路其实很简单,就是用带参数的打印模版,然后把对应参数送进去生成pdf,由于前端需要图片和pdf两种,所以又把生成的pdf转图片生成了一下图片的list.然后不管事pdf和图片,都是直接把文件本身传递回去了,没有存到本地,用url的方法.直接把文件的byte数组传递到前端了(一般序列化的方式就是base64,所以json序列化的时候自动转好了的前端接到的是字符串形式的)

前端

前端其实很简单,就是把得到的文件信息拿到,转成blob形式,然后预览一下.唯一的问题就是拿到的字符串需要进行转码,从base64变回byte数组.因为要有预览的效果,所以一直尝试前端pdf转换然后要自定义打印啥的(Lodop),用了很多无用的代码,后续定下来打印就调用浏览器的打印啥的,那个打印按钮有打印前和打印后的回调函数的,已经满足需求了.

export function base64ToByteArray(base64String: string): Uint8Array {
  const binaryString = atob(base64String);
  const length = binaryString.length;
  const bytes = new Uint8Array(length);

  for (let i = 0; i < length; i++) {
    bytes[i] = binaryString.charCodeAt(i);
  }

  return bytes;
}

页面

<template>
  <BasicModal  v-bind="$attrs" :width="ModalWidthEnum.COMMON" @register="registerModal" :canFullscreen="false" :title="variable.title" :height="400"   :okText="t('public.button.print')"   @ok="handleSubmit" >


    <div style="margin-top: 5px;text-align: center;">
      <span> {{t("public.describe.all")}}{{variable.imagesUrlList.length}}{{t("public.describe.page")}}{{t("public.describe.clickToTurnOver")}}</span>
    </div>

    <iframe ref="pdfFrameRef" :src="variable.pdfUrl" style="display: none"></iframe>

    <Row ref="rowRef">
<!--      <Col :span="1" >-->
<!--        <div style="height: 100%;">-->
<!--          <CaretLeftOutlined style="position: absolute;top: 45%"/>-->
<!--        </div>-->
<!--      </Col>-->
      <Col :span="24">
        <div style="border: black 1px solid;" >
          <Image :src="variable.imagesUrlList[0]" width="100%" :preview="variable.preview" @click="variable.preview = true"/>
          <div style="display: none">
            <Image.PreviewGroup :preview="{ visible:variable.preview, onVisibleChange: vis => (variable.preview = vis) }">
              <Image v-for="(item) in variable.imagesUrlList"  :src="item" width="100%" />
            </Image.PreviewGroup>
          </div>

        </div>

      </Col>
<!--      <Col :span="1" >-->
<!--        <div style="height: 100%;">-->
<!--          <CaretRightOutlined style="position: absolute;top: 45%"/>-->
<!--        </div>-->

<!--      </Col>-->

    </Row>










  </BasicModal>
</template>

<script setup lang="ts">
import {reactive, ref} from 'vue';
import {useMessage} from '@/hooks/web/useMessage';
import {BasicModal, useModalInner} from '@/components/Modal';
import {useI18n} from "@/hooks/web/useI18n";
import {ModalWidthEnum} from "@/enums";
import {Image,Row,Col,} from 'ant-design-vue';
// 定义国际化
const { t } = useI18n();
const emit = defineEmits(['success']);
const {createMessage} = useMessage();
const isUpdate = ref(true);
import {base64ToByteArray} from "@/utils/commonFunctions/commonFunctions";
/** 标题初始化 */
const variable = reactive<any>({
  ids: [],
  fatherParam:{},
  title:"",
  pdfUrl:null,
  pdfBlob:null,
  imagesUrlList:[],
  currentIndex:0,
  currentHeight:0,
  preview:false,
  afterPrintFunction:null
});

const pdfFrameRef=ref()
const rowRef=ref()


const [registerModal, {setModalProps, closeModal,changeOkLoading,changeLoading}] = useModalInner(async (data) => {

  variable.fatherParam=JSON.parse(JSON.stringify(data.fatherParam))
  variable.afterPrintFunction=data.afterPrintFunction
  variable.title=data.fatherParam.title
  variable.imagesUrlList=[]
  variable.currentIndex=0
  variable.preview=false
  //有图片的情况
  if (data.fatherParam.data.imageInfoDtoList?.length>0){
    variable.currentHeight=variable.fatherParam.data.imageInfoDtoList[0].height
    variable.pdfBlob = new Blob([base64ToByteArray(variable.fatherParam.data?.pdfInfoDto?.pdfBytes)], { type: 'application/pdf'});
    variable.pdfUrl= window.URL.createObjectURL(variable.pdfBlob)
    variable.fatherParam.data.imageInfoDtoList.forEach(item=>{
      const blob1 = new Blob([base64ToByteArray(item.imageBytes)], { type: 'image/png'});
      variable.imagesUrlList.push(window.URL.createObjectURL(blob1))
    })
  }


});


// const getPrintDevice = () => {
//   var loop = getLodop(); // 创建一个LODOP对象
//   let counter = loop.GET_PRINTER_COUNT(); // 获取打印机个数
//   //初始化下printNameList打印数组
//   variable.printDeviceList = [];
//   for (let i = 0; i < counter; i++) {
//     //将打印机存入printList数组中
//     variable.printDeviceList.push({name:loop.GET_PRINTER_NAME(i),value:i});
//   }
//
//   //获取默认打印机并设置
//   var defaultName =loop.GET_PRINTER_NAME(-1);
//   variable.printDeviceList.forEach(item=>{
//     if(item.name==defaultName){
//       variable.printDevice=item.value
//     }
//   })
// }

// const doPrint = () => {
//   var LODOP = getLodop(); // 创建一个LODOP对象
//   // LODOP.ADD_PRINT_IMAGE(0, 0, "<img>", "EMF", variable.pdfUrl);
//   LODOP.PRINT_INIT("打印控件功能演示_Lodop功能_按网址打印");
//   let base64="data:image/png;base64,"+variable.fatherParam.data.imageListByte[0]
//   LODOP.ADD_PRINT_IMAGE(0,0,"100%","100%",base64);
//   console.log(base64)
//   LODOP.PRINT();
// }


// // 创建 LODOP 实例
// function getLodop() {
//   let LODOP;
//   if (window.LODOP) {
//     LODOP = window.LODOP;
//   } else if (window.parent.LODOP) {
//     LODOP = window.parent.LODOP;
//   } else {
//     LODOP = (function () {
//       var LODOP = null;
//       var s = document.createElement('script');
//       s.src = '/LODOPfuncs.js';
//       s.id = 'LODOPfuncs';
//       s.type = 'text/javascript';
//       document.getElementsByTagName('head')[0].appendChild(s);
//       document.onpropertychange = function (e) {
//         if (e.propertyName === 'LODOP') {
//           LODOP = e.srcElement.LODOP;
//         }
//       };
//       return LODOP;
//     })();
//   }
//   return LODOP;
// }

/** 提交按钮 */
async function handleSubmit() {
  pdfFrameRef.value.contentWindow.print();
  window.onafterprint = afterPrint;
}

/**
 * 浏览器打印的回调
 */
const afterPrint = async () =>  {
  variable.afterPrintFunction()
}

</script>


效果

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2210974.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

探索 YOLO11:更快、更智能、更高效

点击下方卡片&#xff0c;关注“小白玩转Python”公众号 在人工智能这个不断进化的世界中&#xff0c;有一件事我们可以肯定&#xff1a;模型不断变得更好、更快、更智能。就在你以为 YOLO 系列已经达到顶峰时&#xff0c;Ultralytics 推出了最新升级——YOLO11。没错&#xff…

K-means 聚类算法:目标函数推导、迭代过程及可视化解析

一、K-means 的背景 在机器学习领域&#xff0c;许多任务涉及 训练模型来做预测或分类 。比如&#xff0c;医生可能希望通过以往的病例数据来预测某个病人未来是否会患上某种疾病&#xff0c;或者新闻网站可能需要根据文章的主题将新闻自动分类。这些任务通常依赖于有标签的数…

Qt之TCP收发图片的例子

一.效果 二.实现 1.发图片 void MainWindow::slotSendImage() {matrix.rotate(90);QPixmap tempPixmap = pixmap.transformed(matrix);QBuffer buffer;tempPixmap.save(&buffer,"jpg");ui->labelImage->setPixmap(tempPixmap);int dataLength = buffer.d…

UE4 材质学习笔记09(雨水水坑着色器/完整雨水着色器)

一.雨水水坑着色器 要用到这样一个噪声贴图&#xff0c;我们要做的就是&#xff0c;做出水坑并让水坑在这种浑浊的噪点中产生&#xff0c;因此水坑将从最暗的斑点生长&#xff0c;然后随着它继续占据越来越亮的像素而生长 现在水坑将从上到下投射到世界空间中&#xff0c;所以…

C++:模拟priority_queue

目录 priority_queue的介绍 概念 特点 priority_queue的使用 基本操作 演示代码 ​编辑 priority_queue的模拟实现 仿函数 向上调整和向下调整 模拟实现的代码 priority_queue的介绍 概念 在C标准库中&#xff0c;priority_queue是一个基于优先级堆的容器适配器。…

设计感十足的喇叭裤来咯,亲子款get~

微喇叭的设计&#xff0c;时尚感爆棚&#xff0c;瞬间让宝贝成为冬日里的小潮人。而且这种设计非常显瘦&#xff0c;能够很好地修饰宝贝的腿型&#xff0c;穿上秒变大长腿。表面磨毛质感&#xff0c;摸起来舒软绒顺&#xff0c;温柔气质&#xff0c;下脚隐形拉链&#xff0c;拉…

如何通过 Nginx 只允许 www 域名访问并禁止裸域名访问

个人名片 &#x1f393;作者简介&#xff1a;java领域优质创作者 &#x1f310;个人主页&#xff1a;码农阿豪 &#x1f4de;工作室&#xff1a;新空间代码工作室&#xff08;提供各种软件服务&#xff09; &#x1f48c;个人邮箱&#xff1a;[2435024119qq.com] &#x1f4f1…

learn C++ NO.23——map、set的模拟实现

STL库的实现方式 map和set的底层用的红黑树是一样的吗&#xff1f;从容器特点的角度出发&#xff0c;这里个容器的底层应该分别用key搜索模型的红黑树和key value 搜索模型的红黑树。但是&#xff0c;从库的设计角度出发&#xff0c;这两者用同一份红黑树代码更好。而STL就是用…

第十五章 RabbitMQ延迟消息之延迟插件

目录 一、引言 二、延迟插件安装 2.1. 下载插件 2.2. 安装插件 2.3. 确认插件是否生效 三、核心代码 四、运行效果 五、总结 一、引言 上一章我们讲到通过死信队列组合消息过期时间来实现延迟消息&#xff0c;但相对而言这并不是比较好的方式。它的代码实现相对来说比…

Java->排序

目录 一、排序 1.概念 2.常见的排序算法 二、常见排序算法的实现 1.插入排序 1.1直接插入排序 1.2希尔排序(缩小增量法) 1.3直接插入排序和希尔排序的耗时比较 2.选择排序 2.1直接选择排序 2.2堆排序 2.3直接选择排序与堆排序的耗时比较 3.交换排序 3.1冒泡排序…

你知道C++多少——继承

&#x1f308;个人主页&#xff1a;小新_- &#x1f388;个人座右铭&#xff1a;“成功者不是从不失败的人&#xff0c;而是从不放弃的人&#xff01;”&#x1f388; &#x1f381;欢迎各位→点赞&#x1f44d; 收藏⭐️ 留言&#x1f4dd; &#x1f3c6;所属专栏&#xff1…

蓝桥杯模块三:蜂鸣器和继电器的基本控制

模块训练题目&#xff1a; 一、蜂鸣器电路图 1.电路图 2.电路分析 138译码器控制Y5,Y5控制Y5C&#xff0c;Y5C低电平控制芯片开启P0口控制ULN2003继而控制蜂鸣器端口和继电器端口 二、程序代码 1.138译码器控制端口函数 建立初始化函数选择锁存器 2.实现题目功能 在LED代…

24-10-13-读书笔记(二十五)-《一只特立独行的猪》([中] 王小波)用一生来学习艺术

文章目录 《一只特立独行的猪》&#xff08;[中] 王小波&#xff09;目录阅读笔记记录总结 《一只特立独行的猪》&#xff08;[中] 王小波&#xff09; 十月第五篇&#xff0c;放慢脚步&#xff0c;秋季快要过去了&#xff0c;要步入冬季了&#xff0c;心中也是有些跌宕起伏&am…

Guitar Pro怎么制作伴奏谱,吉他谱制作软件guitar pro教程

在诸多教学吉他谱制作软件中Guitar Pro是一款非常优秀的软件&#xff0c;它是专为吉他和其他弦乐器设计&#xff0c;且能提供乐谱编辑、音轨录制和播放、和弦与音阶库等功能的强大软件。Guitar Pro不仅具有强大的乐谱编辑功能&#xff0c;其用户界面也易于上手&#xff0c;更支…

ThingsBoard规则链节点:Script节点详解

引言 脚本节点简介 用法 含义 应用场景 实际项目运用示例 智能楼宇管理系统 工业自动化生产线 结论 引言 ThingsBoard是一个功能强大的物联网平台&#xff0c;它支持设备管理、数据收集与处理以及实时监控。其核心组件之一是规则引擎&#xff0c;允许用户定义复杂的业务…

vue特效,一片动态星空

vue实现漂亮星空&#xff0c;超级简单 1.创建vue项目&#xff1a; vue create demo 2.注册vuecli : npm i element-ui -S 3.加载依赖 &#xff1a;npm i 4.运行项目 :npm run serve <!DOCTYPE html> <html lang"en"> <head><…

JavaWeb 19 AJAX

"我就是希望你好&#xff0c;就像很多人希望我好一样&#xff0c;特别简单&#xff0c;特别真挚。也不为了什么&#xff0c;就是希望你好" —— 24.10.13 一、什么是AJAX AJAX Asynchronous JavaScript and XML(异步的JavaScript和XML) AJAX不是新的编程语言&…

仿新版QQ的聊天小软件

仿新版QQ的聊天小软件 文章说明核心源码效果展示源码下载 文章说明 新开一个聊天组件的项目的想法主要来源于想学习一下消息队列的使用&#xff0c;后来在书写界面和一些功能模块时&#xff0c;又想到可以抽离出来&#xff0c;分别写几篇文章&#xff0c;主要介绍扫码登陆、消息…

MQ快速入门【详细】个人笔记 讲解通俗易懂

1.同步通讯和异步通讯 同步通讯&#xff1a;如果举个例子来说&#xff0c;同步通讯就像是两个人在打电话&#xff0c;一方说的话&#xff0c;能够立马传给另一方&#xff0c;消息的时效性非常高&#xff0c;但是相对的&#xff0c;只能是给一个人通讯&#xff0c;如果这个时候&…

React (三) 创建安装脚手架,类组件与函数式组件;生命周期;父子通信props;插槽;非父子通信Context

文章目录 一、脚手架的创建与安装1. 认识脚手架2. 安装脚手架3. 创建react项目4. 项目结构 二、从0编写三、组件化开发1. 什么是组件化开发2. 类组件3. render函数4. 函数式组件 四、生命周期1. 挂载Mount2. 更新Update3. 卸载Unmount4. 不常用的生命周期 五、父子组件通信1. 父…