js开发打印证书功能(二)

在上一篇的基础上,实现了一下另外一种方式。

上一篇地址:https://www.cnblogs.com/ljwsyt/p/9525290.html

首先,该方式也是有几种方法。

1.在上一篇的基础上,将生成的html转化成canvas,然后就可以直接对canvas进行打印和保存。

需要注意的是,canvas打印的时候是一片空白的,需要先转化为图片然后打印。而生成canvas之后可以直接右键保存了,也可以增加按钮进行保存,保存的时候也是先转化为base64图片然后再进行保存。

方法:使用html2canvas插件进行转化,只需引入就可以直接运行,

html2canvas(document.querySelector("#toPrint")).then(canvas => {
    document.body.appendChild(canvas)
});

其源码应该也是根据元素的位置绘制的canvas。

2.直接绘制canvas。

html代码:增加了两个按钮

 1 <div>
 2     <div id="printArea">
 3         <!--startprint-->
 4         <canvas id="toPrint">
 5         </canvas>
 6         <!--endprint-->
 7     </div>
 8     <div id="bottom_btns">
 9         <button onclick="printNotifier()" class="layui-btn">打印</button>
10         <button onclick="saveNotifier1()" class="layui-btn">保存</button>
11     </div>
12 </div>

css代码:

 1 #toPrint {
 2     position:absolute;
 3     left: 50%;
 4     top: 50%;
 5 }
 6 
 7 #bottom_btns {
 8     position: absolute;
 9     bottom: 10px;
10     left: 50%;
11     /* 按钮宽度加缩进 */
12     margin-left: -70px;
13 }

js代码:移动端兼容也很OK

  1 myApp.controller('notifierController2', function ($rootScope, $scope, services, $sce, $stateParams, $state) {
  2     $scope.services = services;
  3     
  4     //查询录取通知书内容
  5     services["getApplyStatus"] = function (param) {
  6         return $rootScope.serverAction('/apply/queryDegreeApplyInfo', param, "GET");
  7     };
  8     
  9     $scope.mobile = /Android|webOS|iPhone|iPod|BlackBerry/i.test(navigator.userAgent);
 10     if(1 == $rootScope._USERINFO.role || 2 == $rootScope._USERINFO.role) {
 11         if($scope.mobile) {
 12             $rootScope._ALLMENU = [{
 13                 children: [{
 14                     res_name: "查看审核状态",
 15                     res_url: "#status_mobile",
 16                     res_id: "#status_mobile"
 17                 },{
 18                     res_name: "查看修改申请资料",
 19                     res_url: "#registerMsg#review",
 20                     res_id: "#registerMsg#review"
 21                 },{
 22                     res_name: "打印录取通知书",
 23                     res_url: "#notifier",
 24                     res_id: "#notifier"
 25                 }]
 26             }];
 27             //$rootScope.mobile_regstatus = true;
 28         } else {
 29             $rootScope._ALLMENU = [{
 30                 children: [{
 31                     res_name: "查看审核状态",
 32                     res_url: "#status",
 33                     res_id: "#status"
 34                 },{
 35                     res_name: "查看修改申请资料",
 36                     res_url: "#registerMsg#review",
 37                     res_id: "#registerMsg#review"
 38                 },{
 39                     res_name: "打印录取通知书",
 40                     res_url: "#notifier",
 41                     res_id: "#notifier"
 42                 }]
 43             }];
 44         }
 45     }
 46     $rootScope.curentSel = "#notifier";
 47     $rootScope.setContent = function(url) {
 48         if($scope.mobile) {
 49             $('#main-layout').removeClass('hide-side');
 50             if(-1 < url.indexOf("#registerMsg")) {
 51                 window.open(encodeURI(encodeURI('/pages/index_mobile.html#/registers#review?id=' + $rootScope._USERINFO.id)));
 52                 window.location.href = "/pages/index_mobile.html#/home";
 53                 return;
 54             } else {
 55                 $rootScope.curentSel = url;
 56             }
 57         } else {
 58             if(-1 < url.indexOf("#registerMsg")) {
 59                 window.open(encodeURI(encodeURI('/pages/index.html#/registers#review?id=' + $rootScope._USERINFO.id)));
 60                 window.location.href = "/pages/index.html#/home";
 61                 return;
 62             } else {
 63                 $rootScope.curentSel = url;
 64             }
 65         }
 66     }
 67     
 68     //模板
 69     $scope.printObj = {
 70         notifierObj:{
 71             "url": "/res/img/notifications.png",
 72             "height": "631",
 73             "width": "942"
 74         },
 75         paramList:[{
 76                 "objName":"黄大明",
 77                 "left":"133",
 78                 "top":"191",
 79                 "size": "28"
 80             },{
 81                 "objName":"SXXX小学",
 82                 "left":"460",
 83                 "top":"272",
 84                 "size": "28"
 85             },{
 86                 "objName":"2018",
 87                 "left":"195",
 88                 "top":"312",
 89                 "size": "28"
 90             },{
 91                 "objName":"8",
 92                 "left":"325",
 93                 "top":"312",
 94                 "size": "28"
 95             },{
 96                 "objName":"31",
 97                 "left":"405",
 98                 "top":"312",
 99                 "size": "28"
100             }]
101     }
102     
103     services.getApplyStatus('token').success(function(res) {
104         if ('OK' == res.result) {
105             if(res.msg) {
106                 userName = res.msg.studentName;
107                 $scope.printObj.paramList[0].objName = res.msg.studentName;
108                 $scope.printObj.paramList[1].objName = res.msg.applySchoolName;
109                 
110                 //屏幕自适应
111                 suitScreen($scope);
112                 //画图
113                 drawNotifier($scope);
114                 //组装页面
115                 //assembleHtml($scope);                
116                 //打印
117                 //printNotifier();
118             }
119         } else {
120             layer.alert(res.msg);
121         }
122     });
123     
124 });
125 
126 var userName;
127 
128 function saveNotifier1() {
129     //一样需要先转化为图片后保存
130     var type = 'png';//格式可以自定义
131     var imgData = $("#toPrint")[0].toDataURL(type);
132     // 加工image data,替换mime type
133     imgData = imgData.replace(_fixType(type),'image/octet-stream');
134     //可以直接用以下下载,但是下载的文件名没有后缀
135     //window.location.href=image; // it will save locally
136     //文件名可以自定义
137     var filename = '录取通知书_' + userName + '.' + type;
138     saveFile(imgData,filename);
139 }
140 
141 function _fixType(type) {
142     //imgData是一串string,base64
143     type = type.toLowerCase().replace(/jpg/i, 'jpeg');
144     var r = type.match(/png|jpeg|bmp|gif/)[0];
145     return 'image/' + r;
146 }
147 
148 function saveFile(data, filename) {
149     //命名空间
150     var save_link = document.createElementNS('http://www.w3.org/1999/xhtml', 'a');
151     save_link.href = data;
152     save_link.download = filename;
153    
154     //window.location = save_link;//此方法可下载但是文件名无效
155     //下载
156     var event = document.createEvent('MouseEvents');
157     event.initMouseEvent('click', true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
158     save_link.dispatchEvent(event);
159 }
160 
161 function printNotifier() {
162     try{
163         print.portrait = false;//横向打印 ,去掉页眉页脚
164     }catch(e){
165         //alert("不支持此方法");
166     }
167 
168     //canvas无法直接打印,需先转换成img
169     $(convertCanvasToImage($("#toPrint")[0])).jqprint();   
170 }
171 
172 function convertCanvasToImage(canvas) {
173     var image = new Image();
174     image.src = canvas.toDataURL("image/png");
175     return image;
176 }
177 
178 function suitScreen($scope) {
179     //下方留30放按钮
180     var effectiveHeight = findParam("#printArea", "height") - 30;
181     var effectiveWidth = findParam("#printArea","width");
182     if($scope.printObj.notifierObj.width/effectiveWidth > $scope.printObj.notifierObj.height/effectiveHeight) {
183         //取最接近的一个属性进行自适应,并适当调小一些
184         var suitTimes = $scope.printObj.notifierObj.width/effectiveWidth*1.2;
185     } else {
186         var suitTimes = $scope.printObj.notifierObj.height/effectiveHeight*1.2;
187     }
188     $scope.printObj.notifierObj.width = $scope.printObj.notifierObj.width/suitTimes;
189     $scope.printObj.notifierObj.height = $scope.printObj.notifierObj.height/suitTimes;
190     for(i=0;i<$scope.printObj.paramList.length;i++) {
191         $scope.printObj.paramList[i].size = $scope.printObj.paramList[i].size/suitTimes;
192         $scope.printObj.paramList[i].left = $scope.printObj.paramList[i].left/suitTimes;
193         $scope.printObj.paramList[i].top = $scope.printObj.paramList[i].top/suitTimes;
194     }
195 }
196 
197 function drawNotifier($scope) {
198     //canvas需要先定位好,否则画好再动就清除了
199     $("#toPrint").css("margin-left", (0-$scope.printObj.notifierObj.width)/2+"px");
200     //上移30放按钮
201     $("#toPrint").css("margin-top", (0-$scope.printObj.notifierObj.height-60)/2+"px");
202     var canvas = document.getElementById("toPrint");
203     canvas.width = $scope.printObj.notifierObj.width;
204     canvas.height = $scope.printObj.notifierObj.height;
205     var ctx = canvas.getContext("2d");
206     var img=new Image();
207     img.src = $scope.printObj.notifierObj.url;
208     img.onload=function() {
209         //需要onload方法接收,否则画不出
210         ctx.drawImage(img, 0, 0, $scope.printObj.notifierObj.width, $scope.printObj.notifierObj.height);
211         //写文字,且要在画好图片之后写,否则会被图片覆盖
212         $.each($scope.printObj.paramList, function(index, e) {
213             //canvas的字体不会有12px的兼容性问题
214             ctx.font = "bold "+e.size+"px KaiTi";
215             //canvas写字以字体的左下角为基准,因而要再加一个字体大小的高度
216             ctx.fillText(e.objName,e.left, e.top+e.size);
217         });
218     }
219     
220 }
221 
222 function assembleHtml($scope) {
223     var htmlStr = "<img src='" + $scope.printObj.notifierObj.url+"' style='width:"+$scope.printObj.notifierObj.width+"px;height:"+
224         $scope.printObj.notifierObj.height+"px'>";
225     for(i=0;i<$scope.printObj.paramList.length;i++) {
226         var nowObj = $scope.printObj.paramList[i];
227         if(nowObj.size < 12) {
228             htmlStr += "<div style='font-size:"+nowObj.size+"px;top:"+nowObj.top+"px;left:"+nowObj.left+
229             //谷歌浏览器字体小于12px时会不再变小,使用-webkit-transform兼容,并设置已左上角作为变换原点
230                 "px;-webkit-transform:scale("+nowObj.size/12+","+nowObj.size/12+");transform-origin:0 0'>"+nowObj.objName+"</div>";
231         } else {
232             htmlStr += "<div style='font-size:"+nowObj.size+"px;top:"+nowObj.top+"px;left:"+nowObj.left+
233                 "px'>"+nowObj.objName+"</div>";
234         }
235     }
236     $("#toPrint").css("margin-left", (0-$scope.printObj.notifierObj.width)/2+"px");
237     //上移30放按钮
238     $("#toPrint").css("margin-top", (0-$scope.printObj.notifierObj.height-60)/2+"px");
239     $("#toPrint").css("height", $scope.printObj.notifierObj.height+"px");
240     $("#toPrint").css("width", $scope.printObj.notifierObj.width+"px");
241     $("#toPrint").append(htmlStr);
242 }
243 
244 //获取有效区域
245 function findParam(targetObj, attribute) {
246     //取数字
247     if($(targetObj).css(attribute) && $(targetObj).css(attribute).replace(/[^0-9]/ig,"") != '0') {
248         return $(targetObj).css(attribute).replace(/[^0-9]/ig,"");
249     } else {
250         //递归
251         return findParam($(targetObj).parent(), attribute);
252     }
253 }

几个需要注意的点:

(1)由于需要留出30像素高的底部放按钮,所有在计算绘制区域的有效高度时应减去30;

(2)绘制顺序:先调整好画布的高宽和位置-->绘制图片-->绘制文字。否则绘制后再调画布会清空,而且先绘制图片再绘制文字时文字覆盖图片而不是反过来;

(3)绘制图片和文字要在图片的onload事件中进行,否则图片还未加载完成就绘制的话会是一片空白区域;

(4)canvas的字体大小不必考虑12px的兼容性问题;

(5)fillText和strokeText,前者是绘制实心文字,后者是空心文字;

(6)画布在未设置宽和高的情况下,会有默认100多的高宽,没有研究源码,但是调试的时候发现的,所有我们取有效区域的时候,就不能直接用toPrint这个canvas进行取了,而要根据其父元素进行取;

(7)createElementNS,下载时用到的,创建带有指定命名空间的元素节点,和createElement类似;

(8)在定义好字体后绘制之前,可以
cxt.fillStyle = "blue";
进行设置颜色

(9)最后就是canvas转图片的方法了,

   var image = new Image();
    image.src = canvas.toDataURL("image/png");

其中

canvas.toDataURL("image/png")就可以用来进行图片转base64.首先绘制canvas,画图片进去,然后就可以生成了。

 

附另外一种图片转base64的方法

使用FileReader

 1      var reader = new FileReader();
 2         var AllowImgFileSize = 2100000; //上传图片最大值(单位字节)( 2 M = 2097152 B )超过2M上传失败
 3         var file = $("#img1")[0].files[0];
 4         var imgUrlBase64;
 5         if (file) {
 6             //将文件以Data URL形式读入页面  
 7             imgUrlBase64 = reader.readAsDataURL(file);
 8             reader.onload = function (e) {
 9               //var ImgFileSize = reader.result.substring(reader.result.indexOf(",") + 1).length;//截取base64码部分(可选可不选,需要与后台沟通)
10               if (AllowImgFileSize != 0 && AllowImgFileSize < reader.result.length) {
11                     alert( '上传失败,请上传不大于2M的图片!');
12                     return;
13                 }else{
14                     //执行上传操作
15                     //alert(reader.result);
16                     var tempPhoto;
17                     for(var i=0;i<$scope.registerMsg.userPhotoInfos.length;i++) {
18                         //其他允许多张,否则只允许一张
19                         if($scope.img_url_code == $scope.registerMsg.userPhotoInfos[i].photoType
20                                 //&& 12 != $scope.registerMsg.userPhotoInfos[i].photoType
21                                 ) {
22                             tempPhoto = $scope.registerMsg.userPhotoInfos[i];
23                             $scope.registerMsg.userPhotoInfos.splice(i, 1);
24                             break;
25                         }
26                     }
27                     if(tempPhoto) {
28                         /*if(tempPhoto.photoName) {
29                             tempPhoto.photoName = $("#img1")[0].files[0].name;
30                         } else if(!$scope.review) {
31                             tempPhoto['photoName'] = $("#img1")[0].files[0].name;
32                         }*/
33                         tempPhoto.photoUrl = "";
34                         tempPhoto.base64 = reader.result; 
35                         $scope.registerMsg.userPhotoInfos.push(tempPhoto);
36                     } else {
37                         $scope.registerMsg.userPhotoInfos.push({
38                                 "id": '',   //记录的id(更新接口需要带上)
39                                 "extendProperty": null,
40                                 "photoPath": "",
41                                 "photoUrl": "", //照片的预览路径
42                                 "userId": $scope.userId, //对应的user的id
43                                 "createTime": 0,
44                                 "photoType": $scope.img_url_code,
45                                 "updateTime": 0,
46                                 //"photoName": $("#img1")[0].files[0].name,
47                                 "base64": reader.result  //图片的base64编码
48                             })
49                     }
50 
51                     ......
52                 }
53             }
54          }

 手机端会有些模糊,原因是canvas在绘制后,进行手机端兼容的情况下会缩放

<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=0">

 

因而,又对上一篇的方案1进行了修改,增加手动打印和保存,在保存时先html转canvas,再canvas转图片进行保存。

由于jquery的版本问题出现了一些兼容性,高一点版本的代码中已经没有$.browser对象了,所有与jqprint出现了不兼容,解决方法是再拼接进去。

代码:

 1 (function(jQuery){ 
 2  
 3 if(jQuery.browser) return; 
 4  
 5 jQuery.browser = {}; 
 6 jQuery.browser.mozilla = false; 
 7 jQuery.browser.webkit = false; 
 8 jQuery.browser.opera = false; 
 9 jQuery.browser.msie = false; 
10  
11 var nAgt = navigator.userAgent; 
12 jQuery.browser.name = navigator.appName; 
13 jQuery.browser.fullVersion = ''+parseFloat(navigator.appVersion); 
14 jQuery.browser.majorVersion = parseInt(navigator.appVersion,10); 
15 var nameOffset,verOffset,ix; 
16  
17 // In Opera, the true version is after "Opera" or after "Version" 
18 if ((verOffset=nAgt.indexOf("Opera"))!=-1) { 
19 jQuery.browser.opera = true; 
20 jQuery.browser.name = "Opera"; 
21 jQuery.browser.fullVersion = nAgt.substring(verOffset+6); 
22 if ((verOffset=nAgt.indexOf("Version"))!=-1) 
23 jQuery.browser.fullVersion = nAgt.substring(verOffset+8); 
24 } 
25 // In MSIE, the true version is after "MSIE" in userAgent 
26 else if ((verOffset=nAgt.indexOf("MSIE"))!=-1) { 
27 jQuery.browser.msie = true; 
28 jQuery.browser.name = "Microsoft Internet Explorer"; 
29 jQuery.browser.fullVersion = nAgt.substring(verOffset+5); 
30 } 
31 // In Chrome, the true version is after "Chrome" 
32 else if ((verOffset=nAgt.indexOf("Chrome"))!=-1) { 
33 jQuery.browser.webkit = true; 
34 jQuery.browser.name = "Chrome"; 
35 jQuery.browser.fullVersion = nAgt.substring(verOffset+7); 
36 } 
37 // In Safari, the true version is after "Safari" or after "Version" 
38 else if ((verOffset=nAgt.indexOf("Safari"))!=-1) { 
39 jQuery.browser.webkit = true; 
40 jQuery.browser.name = "Safari"; 
41 jQuery.browser.fullVersion = nAgt.substring(verOffset+7); 
42 if ((verOffset=nAgt.indexOf("Version"))!=-1) 
43 jQuery.browser.fullVersion = nAgt.substring(verOffset+8); 
44 } 
45 // In Firefox, the true version is after "Firefox" 
46 else if ((verOffset=nAgt.indexOf("Firefox"))!=-1) { 
47 jQuery.browser.mozilla = true; 
48 jQuery.browser.name = "Firefox"; 
49 jQuery.browser.fullVersion = nAgt.substring(verOffset+8); 
50 } 
51 // In most other browsers, "name/version" is at the end of userAgent 
52 else if ( (nameOffset=nAgt.lastIndexOf(' ')+1) < 
53 (verOffset=nAgt.lastIndexOf('/')) ) 
54 { 
55 jQuery.browser.name = nAgt.substring(nameOffset,verOffset); 
56 jQuery.browser.fullVersion = nAgt.substring(verOffset+1); 
57 if (jQuery.browser.name.toLowerCase()==jQuery.browser.name.toUpperCase()) { 
58 jQuery.browser.name = navigator.appName; 
59 } 
60 } 
61 // trim the fullVersion string at semicolon/space if present 
62 if ((ix=jQuery.browser.fullVersion.indexOf(";"))!=-1) 
63 jQuery.browser.fullVersion=jQuery.browser.fullVersion.substring(0,ix); 
64 if ((ix=jQuery.browser.fullVersion.indexOf(" "))!=-1) 
65 jQuery.browser.fullVersion=jQuery.browser.fullVersion.substring(0,ix); 
66  
67 jQuery.browser.majorVersion = parseInt(''+jQuery.browser.fullVersion,10); 
68 if (isNaN(jQuery.browser.majorVersion)) { 
69 jQuery.browser.fullVersion = ''+parseFloat(navigator.appVersion); 
70 jQuery.browser.majorVersion = parseInt(navigator.appVersion,10); 
71 } 
72 jQuery.browser.version = jQuery.browser.majorVersion; 
73 })(jQuery);

 

整改项目的代码在

https://github.com/MRlijiawei/enroll

其他还有图片转化与保存及自定义文件名的方法,大家也可以作为参照。
posted @ 2018-08-24 16:17  TheFirstDream  阅读(1025)  评论(0编辑  收藏  举报