• 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='"+$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

    其他还有图片转化与保存及自定义文件名的方法,大家也可以作为参照。
    FIGHTING
  • 相关阅读:
    python并发编程之gevent协程(四)
    python并发编程之asyncio协程(三)
    python并发编程之multiprocessing进程(二)
    python并发编程之threading线程(一)
    python设计模式之内置装饰器使用(四)
    python设计模式之装饰器详解(三)
    python设计模式之迭代器与生成器详解(五)
    EF code First数据迁移学习笔记
    15.02.13-代码小技巧
    Route学习笔记之Area的Route注册
  • 原文地址:https://www.cnblogs.com/ljwsyt/p/9530525.html
Copyright © 2020-2023  润新知