前言
目前打印功能 lucky sheet 官方提供了收费的版本,免费的都是社区提供的测试用例,也有很多小伙伴已经实现了相关功能,网上的参考文章也很多,下面针对Luckysheet-crdt 版本做下打印的适配,仅作为参考示例哈。
代码均为个人实现,未参考任何文章、链接,如有雷同,请联系考证!
预览视图

打印效果

预览视图
源码分析
我们分析下源码内部的实现:

搜索一下这个 type='viewPage':

源码已经预留了实现函数 printLineAndNumberCreate,如果我们直接在这个函数内绘制的话,会导致层级问题,我直接说结论了哈,原因是,最后执行了 reflesh:

在刷新视图的最后,再执行视图绘制,就不会有问题了:

到这,已经识别到需要修改的位置了,下面正式来实现一下打印视图效果吧。
视图原理
我们来看下实现视图预览的原理:

Step 1
一张A4的尺寸是固定的,就是 210x297mm ,需要转换为页面像素值! 才可以进行绘制哈。
Step 2
还需要通过整个表格的宽高,确定一共可以分成几张A4,并且计算得到每一张A4的边界位置。
Step 3
有了上诉信息,可以进行绘制处理了
代码实现
如何将 mm 转换成 px 呢?源码给我们提供了一种非常巧妙的方法:
js
// 获取1mm的像素
function getOneMmsPx() {
let div = document.createElement("div");
div.style.width = "1mm";
document.querySelector("body").appendChild(div);
let mm1 = div.getBoundingClientRect();
let w = mm1.width;
$(div).remove();
return w;
}
计算一共能分成多少个页面:
js
// 这里是循环当前行一共可分为多少页
for (let r = 0; r < rowCount; r++) {
// 如果行被隐藏了 则跳过
if (Store.config["rowhidden"] != null && Store.config["rowhidden"][r] != null) continue;
// 当前行的 Y 坐标 - 真实的 y 坐标,与可视区无关
let current_end_y = Store.visibledatarow[r];
// 前一行的 Y 坐标 - 真实的 y 坐标,与可视区无关
let prev_end_y = r > 0 ? Store.visibledatarow[r - 1] : 0;
// 计算相对于前一个分页线的坐标
let relativeCurrentY = current_end_y;
let relativePrevY = prev_end_y;
// 如果已经有分页线,则基于上一个分页线计算相对位置
if (horizontalCells.length > 0) {
let lastPageBreak = horizontalCells[horizontalCells.length - 1].rowIndex;
let lastPageBreakY = Store.visibledatarow[lastPageBreak];
relativeCurrentY = current_end_y - lastPageBreakY;
relativePrevY = prev_end_y - lastPageBreakY;
}
// 计算当前行属于第几个页面(基于页面高度的倍数)
let currentPageIndex = Math.floor(relativeCurrentY / paperHeight);
// 计算前一行属于第几个页面
let prevPageIndex = Math.floor(relativePrevY / paperHeight);
// 如果当前行和前一行属于不同的页面,说明当前行是新页面的开始行
if (currentPageIndex > prevPageIndex) {
horizontalCells.push({ rowIndex: r, y: current_end_y });
}
}
表格上下文对象:
js
// 获取canvas上下文
const luckysheetTableContent = $("#luckysheetTableContent").get(0).getContext("2d");
luckysheetTableContent.save();
// 保持与 drawMain 一致的缩放
luckysheetTableContent.scale(Store.devicePixelRatio, Store.devicePixelRatio);
开始绘制:
js
// 一行可以被分为多少页?
const rowPageCount = verticalCells.length;
// 则每新增一行,都以 rowPageCount 为倍数递增即可
for (let i = 0; i < horizontalCells.length; i++) {
// 这个是行的 Y 值哈
const row = horizontalCells[i];
// 为了实现居中,当前间隔的中心坐标是
const currentY = row.y;
const prevY = i > 0 ? horizontalCells[i - 1].y : 0;
const centerY = currentY - (currentY - prevY) / 2 - scrollHeight;
// 绘制水平线
drawPrintLine(luckysheetTableContent, offsetLeft, row.y - scrollHeight + offsetTop, sheetWidth, row.y - scrollHeight + offsetTop);
for (let j = 0; j < verticalCells.length; j++) {
// 实现 X 居中,原理类似
const col = verticalCells[j];
const currentX = col.x;
const prevX = j > 0 ? verticalCells[j - 1].x : 0;
const centerX = currentX - (currentX - prevX) / 2 - scrollWidth + offsetLeft;
// 绘制垂直线 - 类似
drawPrintLine(
luckysheetTableContent,
col.x - scrollWidth + offsetLeft,
offsetTop,
col.x - scrollWidth + offsetLeft,
sheetHeight
);
drawPageNumbers(luckysheetTableContent, centerX, centerY, i * rowPageCount + j + 1);
}
}

当然,这里说的比较笼统哈,但是思路是这样的,就是计算全部的行列能被分成几个页面,然后分别记录被切割的单元格 r c 值(我这边设计的是跟随单元格哈,如果大家直接设计为固定宽高,会更简单),说下两者的区别:

正常来说,一张A4的宽高是不会直接对齐边角的,这时候,如果你设计为固定宽高,那么 单元格可能被分成两页打印,理解吧。我这种设计模式就是以最小能绘制的单元格为单位,图例中,会保存 D 单元格,以确保每一个单元格都能完整绘制。
代码中出现了很多参数,含义大家可能不理解,需要理解源码才透彻,什么fill_col_ed
、fill_row_ed、visibledatarow、offset、scroll 啥的,大家多分析下 draw.js @luckysheetDrawMain 方法,一定要先理解源码,才能修改源码。
打印实现
打印就更加简单了,源码给我们提供了 获取选区截图结果的API:

内部的关键函数,就是创建新的 canvas,重新调用 drawMain 渲染主画布,并指定绘制区域即可:

配合上我们刚才实现的页码,可以组合出很多的可能性,什么指定打印页码,指定打印选区,指定打印sheet ,实现思路都是一致的。
js
// 截取指定页码的页面
function getScreenshotByPageNum(pageNum = 1) {
// 通过页码,计算得出有限范围坐标
const ch_width = 222;
const rh_height = 300;
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 });
// 进行后续的操纵
handleScreenshot(newCanvas, ch_width, rh_height);
}
js
/**
* @description 截图的后续处理工作
*/
function handleScreenshot(newCanvas, ch_width, rh_height) {
let scrollWidth = $("#luckysheet-cell-main").scrollLeft();
let scrollHeight = $("#luckysheet-cell-main").scrollTop();
// 绘制页面主要内容 - ch_width 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();
// 补齐统计图 图片
let url = newCanvas.get(0).toDataURL("image/png");
console.log(url);
}

看,是不是一摸一样? 请注意哈!这种实现方式是通过 canvas 画布绘制转DataUrl 实现的哈,因此,统计图表、图片等 div 创建的,是不能直接截图到画布上的,应该通过新创建的 canvas,通过别的技术,将 div 内容转存到 canvas 上,再进行toDataUrl,就可以了。

总结
大致思路是这样,因为具体的代码很多,有些细节需要大家自行优化。大家如果对这部分还有啥疑问的话,可以留言一起交流。
绘制预览视图部分有很多变量,大家不要觉得难,都是研究 drawMain 中的 单元格绘制 方法中直接拿过来的,有了实现思路后,就感觉不难了。
代码请移步:print.js
🎉麻烦点个 start: Luckysheet-crdt
有疑问欢迎留言交流哦~~~