后端
- 后端
- 依赖
- 生成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>