页面效果:
1.maven配置
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itextpdf</artifactId>
<version>5.5.10</version>
</dependency>
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itext-asian</artifactId>
<version>5.2.0</version>
</dependency>
<dependency>
<groupId>com.google.zxing</groupId>
<artifactId>core</artifactId>
<version>3.3.0</version>
</dependency>
<dependency>
<groupId>com.google.zxing</groupId>
<artifactId>javase</artifactId>
<version>3.3.0</version>
</dependency>
<dependency>
<groupId>org.apache.pdfbox</groupId>
<artifactId>fontbox</artifactId>
<version>2.0.11</version>
</dependency>
<dependency>
<groupId>org.apache.pdfbox</groupId>
<artifactId>pdfbox</artifactId>
<version>2.0.11</version>
</dependency>
2.PDF生成
package com.beaver.util;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.beaver.common.utils.file.FileUtils;
import com.beaver.domain.vo.QRCodeParam;
import com.beaver.domain.vo.QRCodeRequest;
import com.beaver.domain.vo.QRCodeResponse;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.EncodeHintType;
import com.google.zxing.WriterException;
import com.google.zxing.client.j2se.MatrixToImageWriter;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.qrcode.QRCodeWriter;
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
import com.itextpdf.text.*;
import com.itextpdf.text.pdf.BaseFont;
import com.itextpdf.text.pdf.PdfWriter;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import java.io.*;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class QrCodeUtil {
private static final Log log = LogFactory.getLog(QrCodeUtil.class);
private static final QRCodeWriter QR_CODE_WRITER = new QRCodeWriter();
public static final String QR_CODE_IMAGE_PATH = "code";
// 设置默认参数,可以根据需要进行修改
private static final int QRCOLOR = 0xFF000000; // 默认是黑色
private static final int BGWHITE = 0xFFFFFFFF; // 背景颜色
private static final int WIDTH = 80; // 二维码宽
private static final int HEIGHT = 80; // 二维码高
/**
* 生成二维码字节数组.
*/
public static byte[] generateQrCode(String text, String format, int width, int height) {
try (ByteArrayOutputStream os = new ByteArrayOutputStream()) {
BitMatrix bitMatrix = QR_CODE_WRITER.encode(text, BarcodeFormat.QR_CODE, width, height,hints);
MatrixToImageWriter.writeToStream(bitMatrix, format, os);
return os.toByteArray();
} catch (WriterException e) {
throw new RuntimeException(String.format("fail to generate qr code:text[%s]", text), e);
} catch (IOException e) {
throw new RuntimeException(String.format("fail to writeToStream when generating qr code: text[%s]", text), e);
}
}
/**
* 保存二维码图片到文件系统.
*/
public static void generateQrCodeAndSave(String text, String format, int width, int height, String path) {
try {
BitMatrix bitMatrix = QR_CODE_WRITER.encode(text, BarcodeFormat.QR_CODE, width, height,hints);
MatrixToImageWriter.writeToPath(bitMatrix, format, Paths.get(path));
} catch (WriterException e) {
throw new RuntimeException(String.format("fail to generate qr code:text[%s]", text), e);
} catch (IOException e) {
throw new RuntimeException(String.format("fail to writeToStream when generating qr code: text[%s]", text), e);
}
}
/**
* 用于设置QR二维码参数
* com.google.zxing.EncodeHintType:编码提示类型,枚举类型
* EncodeHintType.CHARACTER_SET:设置字符编码类型
* EncodeHintType.ERROR_CORRECTION:设置误差校正
* ErrorCorrectionLevel:误差校正等级,L = ~7% correction、M = ~15% correction、Q = ~25% correction、H = ~30% correction
* 不设置时,默认为 L 等级,等级不一样,生成的图案不同,但扫描的结果是一样的
* EncodeHintType.MARGIN:设置二维码边距,单位像素,值越小,二维码距离四周越近
* */
private static Map<EncodeHintType, Object> hints = new HashMap<EncodeHintType, Object>() {
private static final long serialVersionUID = 1L;
{
put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H);// 设置QR二维码的纠错级别(H为最高级别)具体级别信息
put(EncodeHintType.CHARACTER_SET, "utf-8");// 设置编码方式
put(EncodeHintType.MARGIN, 0);
}
};
public static String convertEmpty(String str){
if(str == null){
return "";
}
return str;
}
public static QRCodeResponse drawPDFQRCode(QRCodeRequest qRCodeRequest) {
QRCodeResponse qRCodeResponse = new QRCodeResponse();
FileOutputStream fileOutputStream = null;
try {
String absolutePath = FileUtils.getAbsolutePath(new File(""));
File stockPath = new File(absolutePath + "/" + QR_CODE_IMAGE_PATH);
if (!stockPath.exists()) {
stockPath.mkdirs();
stockPath.setReadable(true);
stockPath.setWritable(true);
}
String key = IdWorker.getIdStr();
String pdfPath = QR_CODE_IMAGE_PATH + "/" + key+ ".pdf";
File file = new File(absolutePath+"/"+pdfPath);
fileOutputStream = new FileOutputStream(file);
createPdfQr(qRCodeRequest, qRCodeRequest.getWidth(), qRCodeRequest.getHeight(), fileOutputStream);
String imagePath = PdfUtil.PdfToImg(file, key);
qRCodeResponse.setImagePath(imagePath);
qRCodeResponse.setPdfPath(pdfPath);
} catch (IOException | DocumentException e) {
log.error("二维码写入IO流异常", e);
} finally {
try {
if (null != fileOutputStream) {
fileOutputStream.flush();
fileOutputStream.close();
}
} catch (IOException ioe) {
log.error("二维码关流异常", ioe);
}
}
return qRCodeResponse;
}
private static void createPdfQr(QRCodeRequest qRCodeRequest, Float width, Float height, FileOutputStream out ) throws IOException, DocumentException {
List<QRCodeParam> qrCodeParams = qRCodeRequest.getQrCodeParams();
int paramSize = qrCodeParams.size();
Rectangle tRectangle = new Rectangle(0, 0, height/0.3523F, width/0.3523F); // 页面大小
Document doc = new Document(tRectangle);// 定义文档
doc = new Document(tRectangle.rotate());// 横向打印
PdfWriter writer = PdfWriter.getInstance(doc, out);// 书写器
writer.setPdfVersion(PdfWriter.PDF_VERSION_1_2);
// 3-综合属性:
doc.setMargins(width*0.1F, width*0.1F, width*0.3F, width*0.1F);// 页边空白
doc.open();// 打开文档
float size = width*0.6F;
byte[] bytes = generateQrCode(qRCodeRequest.getContent(),"png",(int)(width*0.9F),(int)(width*0.9F));
Image image = Image.getInstance(bytes);
float asx = width * 2F;
float asy = height * 0.4F;
image.setAbsolutePosition(asx, asy);
image.scaleAbsolute(size, size);
for (QRCodeParam qrCodeParam : qrCodeParams) {
doc.add(getParagraph(paramSize,height,qrCodeParam.getAttrName()+": " + convertEmpty(qrCodeParam.getAttrValue())));
}
doc.add(image);
doc.newPage();
doc.close();
}
private static Paragraph getParagraph( int paramSize,float height, String content) throws DocumentException, IOException {
float scale = height-paramSize*2;
BaseFont bfChinese = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);
Paragraph paragraph = new Paragraph(content, new Font(bfChinese, scale*0.15F, Font.NORMAL));
paragraph.setAlignment(0); //设置文字居中 0靠左 1,居中 2,靠右
paragraph.setIndentationLeft(scale*0.2F); //设置左缩
paragraph.setFirstLineIndent(scale*0.1F); //设置首行缩进
paragraph.setLeading(scale*0.25F); //行间距
return paragraph;
}
}
package com.beaver.domain.vo;
import lombok.Data;
import java.util.List;
@Data
public class QRCodeRequest {
private float width;
private float height;
private List<QRCodeParam> qrCodeParams;
public String getContent(){
StringBuffer sb = new StringBuffer();
for (QRCodeParam qrCodeParam : qrCodeParams) {
sb.append(qrCodeParam.getAttrValue()).append("$");
}
if(sb.length()>1){
sb = sb.deleteCharAt(sb.length() -1);
}
return sb.toString();
}
}
package com.beaver.domain.vo;
import lombok.Data;
@Data
public class QRCodeResponse {
private String pdfPath;
private String imagePath;
}
3.生成图片
package com.beaver.util;
import cn.hutool.core.io.FileUtil;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.beaver.common.config.ProjectConfig;
import com.beaver.common.utils.file.FileUtils;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.rendering.PDFRenderer;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.WritableRaster;
import java.io.File;
import java.io.IOException;
public class PdfUtil {
/**
* <p>Description:pdf文件转img文件 </p>
*/
public static String PdfToImg(File pdfFile,String key) {
PDDocument pdDocument;
try {
String imgPath = QrCodeUtil.QR_CODE_IMAGE_PATH + "/" + key +".png";
pdDocument = PDDocument.load(pdfFile);
PDFRenderer renderer = new PDFRenderer(pdDocument);
/* 第二位参数越大转换后越清晰,相对转换速度越慢 */
BufferedImage image = renderer.renderImageWithDPI(0, 1000);
BufferedImage bufferedImage = resizeBufferedImage(image, 500,450 , true);
ImageIO.write(bufferedImage, "png", new File(FileUtils.getAbsolutePath(new File("")) + "/"+ imgPath));
pdDocument.close();
return imgPath;
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
/**
* 调整bufferedimage大小
* @param source BufferedImage 原始image
* @param targetW int 目标宽
* @param targetH int 目标高
* @param flag boolean 是否同比例调整
* @return BufferedImage 返回新image
*/
private static BufferedImage resizeBufferedImage(BufferedImage source, int targetW, int targetH, boolean flag) {
int type = source.getType();
BufferedImage target = null;
double sx = (double) targetW / source.getWidth();
double sy = (double) targetH / source.getHeight();
if (flag && sx > sy) {
sx = sy;
targetW = (int) (sx * source.getWidth());
} else if(flag && sx <= sy){
sy = sx;
targetH = (int) (sy * source.getHeight());
}
if (type == BufferedImage.TYPE_CUSTOM) { // handmade
ColorModel cm = source.getColorModel();
WritableRaster raster = cm.createCompatibleWritableRaster(targetW, targetH);
boolean alphaPremultiplied = cm.isAlphaPremultiplied();
target = new BufferedImage(cm, raster, alphaPremultiplied, null);
} else {
target = new BufferedImage(targetW, targetH, type);
}
Graphics2D g = target.createGraphics();
g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
g.drawRenderedImage(source, AffineTransform.getScaleInstance(sx, sy));
g.dispose();
return target;
}
}
3.页面
<template>
<div class="app-container">
<el-card class="param-card" >
<div class="param-card-content">
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="primary"
plain
size="mini"
round
@click="showDynamicAttr"
>新增动态属性</el-button>
</el-col>
</el-row>
<el-form ref="form" label-width="80px" style="margin-top: 40px">
<div style="margin-left: 10%;display: inline-block;">
<el-form-item label="标签宽度" >
<el-input style="width: 100%;" v-model="tagWidth" placeholder="请输入标签宽度" />
</el-form-item>
</div>
<div style=" float: right;position: relative;margin-right: 15%">
<el-form-item label="标签高度" >
<el-input style="width: 100%;" v-model="tagHeight" placeholder="请输入标签高度" />
</el-form-item>
</div>
<el-divider></el-divider>
<div v-for="(item,index) in formData">
<el-form-item :label="item.attrName" :prop="item.attrCode" style="margin-left: 5%">
<el-input style="width: 90%;" v-model="item.attrValue" :placeholder="'请输入'+item.attrName" />
<el-button type="danger" icon="el-icon-delete" circle style="margin-left: 2%" @click="deleteFlexAttr(index)"></el-button>
</el-form-item>
</div>
</el-form>
<div class="plus-attr-button">
<el-button type="primary" @click="_generateQRCode">生成包装码</el-button>
</div>
</div>
</el-card>
<el-dialog title="动态新增属性" width="800px" :visible="showDynamic" @close="showDynamic = false">
<div v-for="(item,index) in dynamicAttrData">
<div style="margin-top: 2%">
<el-input style="width: 30%;margin-left: 5%" v-model="item.attrName" placeholder="属性名称" />
<el-input style="width: 30%;margin-left: 5%" v-model="item.attrCode" placeholder="属性编码" />
<el-button type="primary" icon="el-icon-plus" style="margin-left: 5%" circle @click="addDynamicAttr"></el-button>
<el-button type="danger" icon="el-icon-delete" circle style="margin-left: 2%" @click="deleteDynamicAttr(index)"></el-button>
</div>
</div>
<div class="plus-attr-button" >
<el-button type="primary" @click="confirmDynamicAttr">确 定</el-button>
</div>
</el-dialog>
<el-card class="qrcode-card">
<div class="param-card-code">
<el-card shadow="hover" class="image">
<el-image :src="imageUrl" >
<div slot="error" class="image-slot error">
<div>
</div>
</div>
</el-image>
</el-card>
<div class="print-button" >
<el-button type="primary" @click="printQRCode">打印包装码</el-button>
</div>
</div>
</el-card>
</div>
</template>
<script>
import { generateQRCode } from "@/api/business/tag";
import printJS from "print-js";
export default {
name: "qrCode",
data() {
return {
tagWidth: 110,
tagHeight:90,
formData: [
{attrName:'物料编码',attrCode:'coding',attrValue:'1010010'},
{attrName:'物料名称',attrCode:'name',attrValue:'CPU'},
{attrName:'物料规格',attrCode:'mode',attrValue:'GH8989HJ'},
{attrName:'批次',attrCode:'batch',attrValue:'20221205'},
{attrName:'生产厂商',attrCode:'supplier',attrValue:'西安'},
{attrName:'单位',attrCode:'unit',attrValue:'个'},
{attrName:'单重',attrCode:'singleWeight',attrValue:'0.3'},
{attrName:'数量',attrCode:'qty',attrValue:'50'}
],
dynamicAttrData:[
{attrName:'',attrCode:'',attrValue:''},
],
showDynamic:false,
imageUrl:'',
printUrl:''
};
},
created() {
},
methods: {
showDynamicAttr(){
this.showDynamic = true
},
addDynamicAttr(){
this.dynamicAttrData.push({attrName:'',attrCode:'',attrValue:''});
},
deleteFlexAttr(index){
if(this.formData.length >1){
this.formData.splice(index, 1);
}
},
deleteDynamicAttr(index){
if(this.dynamicAttrData.length >1){
this.dynamicAttrData.splice(index, 1);
}
},
confirmDynamicAttr(){
for(let item of this.dynamicAttrData){
this.formData.push(item)
}
this.showDynamic = false
this.dynamicAttrData = [];
this.dynamicAttrData.push({attrName:'',attrCode:'',attrValue:''});
},
_generateQRCode(){
let params = {};
params.width = this.tagWidth;
params.height = this.tagHeight;
params.qrCodeParams = this.formData;
generateQRCode(params).then(response => {
this.imageUrl = process.env.VUE_APP_HOST+response.data.imagePath;
this.printUrl = process.env.VUE_APP_HOST+response.data.pdfPath;
});
},
printQRCode(){
printJS({ printable: this.printUrl, type: 'pdf' })
}
}
};
</script>
<style lang ="scss">
.param-card {
width: 50%;
display: inline-block;
overflow: visible;
}
.param-card-content {
height:999999px;
overflow: visible;
}
.param-card-code {
height:999999px;
overflow: hidden;
}
.qrcode-card{
width: 50%;
position: relative;
float: right;
}
.plus-attr-button{
margin-left: 40%;
margin-top: 5%;
}
.image{
margin-top: 20%;
margin-left: 20%;
width: 500px;
height: 450px;
}
.card-image{
width: 100%;
height: 100%;
}
.print-button{
margin-left: 47%;
margin-top: 5%;
}
</style>