Luckysheet 打印终极指南(预览视图+打印功能) : 2025 最新实现

前言

目前打印功能 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

有疑问欢迎留言交流哦~~~

相关推荐
奕辰杰4 小时前
关于npm前端项目编译时栈溢出 Maximum call stack size exceeded的处理方案
前端·npm·node.js
JiaLin_Denny5 小时前
如何在NPM上发布自己的React组件(包)
前端·react.js·npm·npm包·npm发布组件·npm发布包
_Kayo_6 小时前
VUE2 学习笔记14 nextTick、过渡与动画
javascript·笔记·学习
路光.6 小时前
触发事件,按钮loading状态,封装hooks
前端·typescript·vue3hooks
我爱996!6 小时前
SpringMVC——响应
java·服务器·前端
咔咔一顿操作7 小时前
Vue 3 入门教程7 - 状态管理工具 Pinia
前端·javascript·vue.js·vue3
kk爱闹7 小时前
用el-table实现的可编辑的动态表格组件
前端·vue.js
鹦鹉0078 小时前
SpringMVC的基本使用
java·spring·html·jsp
漂流瓶jz8 小时前
JavaScript语法树简介:AST/CST/词法/语法分析/ESTree/生成工具
前端·javascript·编译原理