luckysheet打印功能的另类实现(包含解决打印插图)

最近使用luckysheet时发现,luckysheet目前并不支持打印功能,luckysheet的升级版univer虽然支持打印,但是univer的打印功能并不开源,需要付费升级才可使用打印功能。

虽然luckysheet不支持打印功能,但是可以通过luckysheet提供的api接口实现简单的打印。

本文的项目使用的是vue开发

准备工作:

1.安装vue-print-nb打印插件npm install vue-print-nb --save

2.添加打印按钮

<el-button size="mini" @click="getRangeShow" v-print="'#print-container'">打印</el-button>

3.在页面准备一个隐藏的嵌套div,外层div用于隐藏,不影响页面布局,内层div用于实际打印,内层div不可隐藏,否则打印页面显示空白

<!-- 外层div用于隐藏,不影响页面布局,内层div用于实际打印,内层div不可隐藏,否则打印页面显示空白 -->
<div style="display:none;">
    <div id="print-container" style="text-align: center;"></div>
</div>

以下是luckysheet实现打印的三种方法:

第一种(不推荐)

1.调用luckysheet.getRangeHtml将需要打印的区域转换成html代码格式

2.将得到的html内容插入到print-container的div中

3.使用vue-print-nb将div中的内容打印

之所以不推荐这种方法,是在具体使用时发现luckysheet.getRangeHtml()得到的html样式与实际表格中的样式差异有些大,如果表格中不带特殊样式也可以使用这种方法

第二种(推荐,但无法打印插入的图片)

1.调用luckysheet.setRangeShow将需要打印的区域选中

2.调用luckysheet.getScreenshot将选中的区域转为图片的base64格式

3.将得到的图片base64内容插入到print-container的div中

let img = "<img src=" + imgBase64 + " style="max-width: 100%;" />"
document.querySelector("#print-container").innerHTML = img

4.使用vue-print-nb将div中的内容打印

如果表格中不含图片、图表之类的信息使用这种方法足够了

第三种(推荐,但需要修改luckysheet源码,添加支持打印插入图片的api)

1.进入luckysheet的仓库地址下载源码:https://gitee.com/mengshukeji/Luckysheet

2.具体安装和运行请参照luckysheet的说明

3.打开/src/global/api.js,在api.js中添加以下代码增加支持获取图片的api:


/**
 * 获取当前屏幕dpi
 * @returns dpi
 */
function getDPI() {
  var tempDiv = document.createElement("div");
  tempDiv.style.width = "1in";
  tempDiv.style.visibility = "hidden";
  document.body.appendChild(tempDiv);
  var dpi = tempDiv.offsetWidth;
  document.body.removeChild(tempDiv);
  return dpi;
}
/**
 * 毫米转像素
 * @param {int} mm 毫米
 * @param {int} dpi dpi
 * @returns 
 */
function mmToPixel(mm, dpi) {
  // 1 inch = 25.4 mm
  var inches = mm / 25.4;
  var pixels = inches * dpi;
  return Math.round(pixels);
}

function calculateA4PaperSize() {
  var dpi = getDPI();
  var width_mm = 210; // A4纸宽度,单位:毫米
  var height_mm = 297; // A4纸高度,单位:毫米
  var width_px = mmToPixel(width_mm, dpi);
  var height_px = mmToPixel(height_mm, dpi);
  return { width: width_px, height: height_px };
}

/**
 * 将选中区域进行截图,截图完成后调用指定委托函数,(修复支持插入的图片及图表截图):ALAN-JI
 * @param {Object} options 可选参数
 * @param {Object | String} options.range 选区范围,只能为单个选区;默认为当前选区
 * @param {function} action 委托执行函数,当所有绘图完成后,执行此委托函数(委托函数中可用于触发图片打印,或图片显示)
 */
export function getScreenshotWithImg(action, options = {}) {
  let {
      range = Store.luckysheet_select_save[Store.luckysheet_select_save.length - 1],
  } = { ...options }

  if (getObjType(range) == 'string') {
      if (!formula.iscelldata(range)) {
          return tooltip.info("The range parameter is invalid.", "");
      }

      let cellrange = formula.getcellrange(range);
      range = {
          "row": cellrange.row,
          "column": cellrange.column
      };
  }

  if (getObjType(range) != 'object' || range.row == null || range.column == null) {
      return tooltip.info("The range parameter is invalid.", "");
  }

  let str = range.row[0],
      edr = range.row[1],
      stc = range.column[0],
      edc = range.column[1];

  let has_PartMC = hasPartMC(Store.config, str, edr, stc, edc);

  if (has_PartMC) {
      return tooltip.info('Cannot perform this operation on partially merged cells', '');
  }

  let visibledatarow = Store.visibledatarow;
  let visibledatacolumn = Store.visibledatacolumn;

  let scrollHeight, rh_height;
  if (str - 1 < 0) {
      scrollHeight = 0;
      rh_height = visibledatarow[edr];
  }
  else {
      scrollHeight = visibledatarow[str - 1];
      rh_height = visibledatarow[edr] - visibledatarow[str - 1];
  }

  //根据当前分辨率计算出A4纸宽度,高度(高度不用限制,默认会自动分页)
  let a4Size=calculateA4PaperSize();
  let scrollWidth, ch_width=a4Size.width;
  if (stc - 1 < 0) {
      scrollWidth = 0;
      ch_width = visibledatacolumn[edc]<a4Size.width ? visibledatacolumn[edc]:a4Size.width;
  }
  else {
      scrollWidth = visibledatacolumn[stc - 1];
      ch_width = visibledatacolumn[edc] - visibledatacolumn[stc - 1]<a4Size.width ? visibledatacolumn[edc] - visibledatacolumn[stc - 1]:a4Size.width;
  }

  let newCanvas = $("<canvas>").attr({
      width: Math.ceil(ch_width * Store.devicePixelRatio),
      height: Math.ceil(rh_height * Store.devicePixelRatio)
  }).css({ width: ch_width, height: rh_height });

  luckysheetDrawMain(scrollWidth, scrollHeight, ch_width, rh_height, 1, 1, null, null, newCanvas);
  
  let ctx_newCanvas = newCanvas.get(0).getContext("2d");

  //补上 左边框和上边框
  ctx_newCanvas.beginPath();
  ctx_newCanvas.moveTo(
      0,
      0
  );
  ctx_newCanvas.lineTo(
      0,
      Store.devicePixelRatio * rh_height
  );
  ctx_newCanvas.lineWidth = Store.devicePixelRatio * 2;
  ctx_newCanvas.strokeStyle = luckysheetdefaultstyle.strokeStyle;
  ctx_newCanvas.stroke();
  ctx_newCanvas.closePath();

  ctx_newCanvas.beginPath();
  ctx_newCanvas.moveTo(
      0,
      0
  );
  ctx_newCanvas.lineTo(
      Store.devicePixelRatio * ch_width,
      0
  );
  ctx_newCanvas.lineWidth = Store.devicePixelRatio * 2;
  ctx_newCanvas.strokeStyle = luckysheetdefaultstyle.strokeStyle;
  ctx_newCanvas.stroke();
  ctx_newCanvas.closePath();
  
  //获取插入图片的元素,并在canvas上进行绘制
  if ($('#luckysheet-image-showBoxs')) {
      var imgs = $('#luckysheet-image-showBoxs img');
      imgs.each(function (i) {
          var parent = $(this).parent().parent();
          var left = parent.css("left").replace('px', '');
          var top = parent.css("top").replace('px', '');
          var width = parent.css("width").replace('px', '');
          var height = parent.css("height").replace('px', '');
          var img = new Image()
          img.src = $(this).attr("src");
          ctx_newCanvas.drawImage(img, left, top, width, height);
      });
  }
  //获取统计图元素,并在canvas上进行绘制
  let targetDoms = document.querySelectorAll('.luckysheet-modal-dialog.luckysheet-modal-dialog-chart.luckysheet-data-visualization-chart');
  var chartCount = 0;
  if (targetDoms && targetDoms.length > 0)
  {
      repaintChartScreenshot(newCanvas,ctx_newCanvas,targetDoms,action,0,targetDoms.length);
  }
  if (!targetDoms || targetDoms.length == 0) {
      if (action) {
        action(newCanvas.get(0).toDataURL("image/png", 1));
      }
  }
}

/**
* 递归获取所有统计图,并重绘chart到截屏图上:ALAN-JI
* @param {object} newCanvas canvas画布元素对象
* @param {object} ctx_newCanvas 当前画布用于绘制新图的对象
* @param {NodeListOf} targetDoms 所有统计图Nodes集合
* @param {function} action 委托函数,当所有绘图完成后,执行此委托函数(委托函数中可用于触发图片打印,或图片显示)
* @param {int} index 当前统计图下标
* @param {int} length 统计图总集合长度
*/
export function repaintChartScreenshot(newCanvas,ctx_newCanvas,targetDoms,action,index,length) {
  let targetDom = targetDoms[index];
  var left = targetDom.style.left.replace('px', '');
  var top = targetDom.style.top.replace('px', '');
  var width = targetDom.style.width.replace('px', '');
  var height = targetDom.style.height.replace('px', '');

  // 此处是实现滚动元素长截图的关键 start
  let copyDom = targetDom.cloneNode(true)
  copyDom.setAttribute('id', 'copyDom')  // 更改id 避免与targetDom id重复
  copyDom.style.width = targetDom.scrollWidth + 'px'
  copyDom.style.height = targetDom.scrollHeight + 'px'
  var chartCanvas = targetDom.getElementsByTagName("canvas")[0];
  var canvasToImg = new Image();
  canvasToImg.src = chartCanvas.toDataURL('image/png', 1);
  canvasToImg.style = chartCanvas.style;
  copyDom.innerHTML = copyDom.innerHTML.replace(chartCanvas.outerHTML, canvasToImg.outerHTML)
  copyDom.style.zIndex=-50
  document.querySelector('body').appendChild(copyDom);
  // 此处是实现滚动元素长截图的关键 end

  /* 
  *如不需要长截图,或者要截取的元素无滚动即完全显示。
  *下方要截取的元素改为targetDom,并把copyDom相关代码删除即可 
  */
  // html2canvas截屏,屏幕有滚动条时截图为空
  html2canvas(copyDom, {
      backgroundColor: "transparent",
      allowTaint: true,
      useCORS: true,
      scale: window.devicePixelRatio * 3,
      logging: false,
      imageTimeout: 15000,
      removeContainer: true,
  }).then(canvas => {
      var chartImg = new Image()
      chartImg.src = canvas.toDataURL('image/png', 1);
      document.querySelector('body').removeChild(copyDom)
      chartImg.onload = function () {
          index++;
          ctx_newCanvas.drawImage(chartImg, left, top, width, height);
          if (index >= length && action) {
              action(newCanvas.get(0).toDataURL("image/png", 1));
          }
          else
          {
              //递归调用重绘图片
              repaintChartScreenshot(newCanvas,ctx_newCanvas,targetDoms,action,index,length);
          }
      }
  });
}

4.将luckysheet的源码npm run build打包

5.将打包后dist文件夹中的所有文件(index.html除外)复制到自己的项目中

6.如果是vue项目,按照luckysheet官网的本地引入方法,将luckysheet引入到自己的项目中

7.在项目中调用luckysheet.setRangeShow将需要打印的区域选中

8.调用luckysheet.getScreenshotWithImg将得到的图片base64内容插入到print-container的div中

luckysheet.getScreenshotWithImg((imgSrc)=>{
  let img = `<img src=${imgSrc} style="max-width: 100%;" />`
  this.$nextTick(() => {
    document.querySelector("#print-box").innerHTML = img
  })
})

9.使用vue-print-nb将div中的内容打印

PS: 

本文内容大部分来自https://blog.youkuaiyun.com/alan_ji198573/article/details/128624832,但该文章在添加luckysheet源码部分内容并不齐全,本文的第三种方法是在该文章的基础上补全了代码。

若有侵权请联系删除,同时欢迎各路大神一起交流心得。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值