前端(客户端)
- 使用vue-esign
<el-collapse-item title="买方签字">
<div class="signWrapper">
<vue-esign ref="contractBuyer" :width="300" :height="200" :isCrop="isCrop" :lineWidth="lineWidth"
:lineColor="lineColor" :bgColor.sync="bgColor"/>
</div>
<div flex="main:center cross:center" class="pt-5">
<el-button size="mini" @click="handleResetContractBuyer">重签</el-button>
<el-button size="mini" type="primary" @click="handleGenerateContractBuyer">确定</el-button>
</div>
</el-collapse-item>
- js
handleGenerateContractBuyer() {
let that = this
this.$refs.contractBuyer.generate().then(res => {
that.form.signImg = res
that.activeCollapse = 0
}).catch(err => {
Toast("未签字") // 画布没有签字时会执行这里 'Not Signned'
that.activeCollapse = 0
return false;
})
},
前端将签名的base64传给后端
后端
逻辑:收到前端传的签名图片和其他参数后,用Poi-tl将参数插入word中,然后用 docx4j 将word转为pdf,如果需要,用pdfbox将pdf转为图片返回给前端展示
- pom (注意相关包版本必须一直)
<!-- poi-tl -->
<dependency>
<groupId>com.deepoove</groupId>
<artifactId>poi-tl</artifactId>
<version>1.8.2</version>
</dependency>
<!-- docx4j -->
<dependency>
<groupId>org.docx4j</groupId>
<artifactId>docx4j-JAXB-Internal</artifactId>
<version>8.3.1</version>
</dependency>
<dependency>
<groupId>org.docx4j</groupId>
<artifactId>docx4j-JAXB-ReferenceImpl</artifactId>
<version>8.3.1</version>
</dependency>
<dependency>
<groupId>org.docx4j</groupId>
<artifactId>docx4j-export-fo</artifactId>
<version>8.3.1</version>
</dependency>
<!--pdf转图片-->
<dependency>
<groupId>org.apache.pdfbox</groupId>
<artifactId>pdfbox</artifactId>
<version>2.0.15</version>
</dependency>
<dependency>
<groupId>org.apache.pdfbox</groupId>
<artifactId>fontbox</artifactId>
<version>2.0.15</version>
</dependency>
word模板中对应的值{{@signImg}}、{{param}},详情见文档
/**
* 组装文档内容
*/
public static ArrayList<UploadFile> generateFlie(Map param) {
HashMap<String, Object> contentMap = new HashMap<>();
//设置输出内容的样式,详情见poi-tl文档
Style style = new Style();
style.setUnderLine(true);
contentMap.put("param", new TextRenderData(map.get("otherParam"),style));
//签名的base64:"data:image/png;base64,iVBORw0KGgoAAAANSUhEU....." ,需要将前边的"data:image/png;base64,"截掉
String signImg = map.get("signImg").substring(22);
contentMap.put("signImg", new PictureRenderData(100, 40, ".png", Base64.decode(signatureSellerBase64)));
//contentMap组装好之后,调文档转换方法
return convert(contentMap, "Contract.docx");
}
/**
* 文档转换
*
* @param masterId 申请表主键
* @param contentMap 组装文档内容
* @param template 模板
* @return
*/
private static ArrayList<UploadFile> convert(Map<String, Object> contentMap, String template) {
// 取classpath下的模板
File templateFile = null;
try {
templateFile = ResourceUtils.getFile(ResourceUtils.CLASSPATH_URL_PREFIX + "template/" + template);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
// 获得File对象,当然也可以获取输入流对象
String templatePath = templateFile.getAbsolutePath();
String outputPath = RuoYiConfig.getProfile() + "/download/" + templateFile.getName();
try {
// 生成word
XWPFTemplate.compile(templatePath).render(contentMap).writeToFile(outputPath);
WordprocessingMLPackage mlPackage = WordprocessingMLPackage.load(new File(outputPath));
// 设置字体
Mapper fontMapper = new IdentityPlusMapper();
fontMapper.put("隶书", PhysicalFonts.get("LiSu"));
fontMapper.put("宋体", PhysicalFonts.get("SimSun"));
fontMapper.put("微软雅黑", PhysicalFonts.get("Microsoft Yahei"));
fontMapper.put("黑体", PhysicalFonts.get("SimHei"));
fontMapper.put("楷体", PhysicalFonts.get("KaiTi"));
fontMapper.put("新宋体", PhysicalFonts.get("NSimSun"));
fontMapper.put("华文行楷", PhysicalFonts.get("STXingkai"));
fontMapper.put("华文仿宋", PhysicalFonts.get("STFangsong"));
fontMapper.put("仿宋", PhysicalFonts.get("FangSong"));
fontMapper.put("幼圆", PhysicalFonts.get("YouYuan"));
fontMapper.put("华文宋体", PhysicalFonts.get("STSong"));
fontMapper.put("华文中宋", PhysicalFonts.get("STZhongsong"));
fontMapper.put("等线", PhysicalFonts.get("SimSun"));
fontMapper.put("等线 Light", PhysicalFonts.get("SimSun"));
fontMapper.put("华文琥珀", PhysicalFonts.get("STHupo"));
fontMapper.put("华文隶书", PhysicalFonts.get("STLiti"));
fontMapper.put("华文新魏", PhysicalFonts.get("STXinwei"));
fontMapper.put("华文彩云", PhysicalFonts.get("STCaiyun"));
fontMapper.put("方正姚体", PhysicalFonts.get("FZYaoti"));
fontMapper.put("方正舒体", PhysicalFonts.get("FZShuTi"));
fontMapper.put("华文细黑", PhysicalFonts.get("STXihei"));
fontMapper.put("宋体扩展", PhysicalFonts.get("simsun-extB"));
fontMapper.put("仿宋_GB2312", PhysicalFonts.get("FangSong_GB2312"));
fontMapper.put("新細明體", PhysicalFonts.get("SimSun"));
mlPackage.setFontMapper(fontMapper);
// word转pdf
File pdfPath = new File(outputPath.replace(".docx", ".pdf"));
Docx4J.toPDF(mlPackage, new FileOutputStream(pdfPath));
// pdf转image
PDDocument doc = PDDocument.load(pdfPath);
PDFRenderer renderer = new PDFRenderer(doc);
int pageCount = doc.getNumberOfPages();
ArrayList<UploadFile> images = new ArrayList<>();
for (int i = 0; i < pageCount; i++) {
BufferedImage image = renderer.renderImageWithDPI(i, 296);
UploadFile uploadFile = new UploadFile();
uploadFile.setName("page" + (i + 1));
uploadFile.setUrl("data:image/png;base64," + Base64.encode(ImgUtil.toBytes(image, ImgUtil.IMAGE_TYPE_PNG)));
images.add(uploadFile);
}
return images;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
- UploadFile
@Data
public class UploadFile {
private String name;
private String url;
private String uid;
private String status;
}
可能遇到的问题:word转pdf的时候中文乱码,原因可能是本机没有word中的字体
补充:有时候可能需要在word中插入特殊符号:带方框的√,空的方框等,可以通过设置字体来实现,详情见博客:使用Poi-tl 生成word文档 处理word特殊符号方框带勾选 解决方法
如果生成pdf,使用wingdings,方框带√的编码是:“\uF0FE”,不带√的编码:“\uF06F”
Style style = new Style("Wingdings", 14);
contentMap.put("water", new TextRenderData(bool?"\uF0FE":"\uF06F",style));