不知道标题有没有让你眼前一亮😄
日常工作中、难免遇到各种离谱的需求。本篇以【vue-plugin-hiprint】打印插件为引,和码友们
分享一下,如何去分析并实现各种需求。
思考🤔
日常中,需求 ≈(约等于) 需要实现的功能/效果。 既然是功能/效果,那我们肯定需要先分析啦! 那我们应该从哪些方面进行分析呢?
比如:
输出的到底是什么(要得到的功能/效果);- 比如
浏览器读取文件
- 比如
- 是否需要
硬件(底层)支持;- 比如
浏览器支持不支持访问文件;
- 比如
- 是否已有
案例(分析目前技术栈能不能做到);- 比如
xxx有这个功能; - 或者
能实现到哪一步;
- 比如
- 如果
以上两点都不支持,那么我们就需要考虑:哪些技术方便实现(类似功能/效果);需要哪些硬件(底层)支持;可能遇到的问题;时间/人员安排(需要现学否?);
以下内容以打印模板为例子🌰,希望各位码友有所感悟。效果如下图:
本篇源码地址: github.com/CcSimple/vu…
1.前言⁉️
本篇以一名刚接触vue-plugin-hiprint插件的小伙伴视野来阐述。
需求:把多个标签打印模板(模板中包含多个元素,模板可拖拽调整),铺满 A4 纸打印。也可能是其他纸张。
已知:该插件支持多面板、多模板、批量打印;当前标签模板大小、A4纸大小、目前返回的打印数据是一维数组;该插件默认是没有自动铺满功能的。
根据需求和已知条件,我们试着分析一下:
需求到底要的是输出什么内容;(目前插件能实现到哪一步);熟悉该插件的实现逻辑(能做些什么,原理是什么);可能存在的一些问题;(插件的不足之处);
👌🏻进入正题,老规矩先列个目录:
分析打印输出的效果;剖析插件模板打印渲染原理;实现铺满 A4 纸的需求;思考🤔这样实现存在的问题;
分析 打印 输出的效果
已知有两个标签打印模板,如下图:
这两个模板的内容(包含多个打印元素)是可以微调的。想要铺满A4纸,意思就是这A4纸可以容纳多个这两种模板。分析嘛,先随便画个草图:
我们把左侧模板和右侧模板看作一个整体,大概输出的效果就是这个样子。
至于模板中更多的打印元素,我们就需要进一步分析该插件的渲染原理了。比如它是怎么定位的、怎么排序等等。
剖析 插件 模板打印渲染原理
打开在线demo,作为一名前端开发者,一些基础的调试操作肯定是需要熟悉的!先看看渲染的DOM:
模板对象的DOM:
模板内元素的DOM:
如上图,我们可以清楚,打印模板设置的纸张大小,它的单位是mm,而模板内元素的单位是pt,且元素都是absolute定位。
1.所以我们在处理的时候,需要注意单位之间的转换。
再🤔思考一下模板内的打印元素怎么办呢?
如上图,假设是这样填充到 A4 纸内,那元素A和元素B的定位也是必然需要跟着调整。
2.所以我们在处理的时候,需要处理模板内的所有元素。
至于打印怎么填充的数据,可以看我的另一篇文章:【vue-plugin-hiprint】使用-打印篇
还是言简意赅的絮叨一下吧:
填充数据,就是当元素设置了【字段名】,比如 name,我们在调用 预览:getHtml、浏览器打印:print、客户端直接打印:print2 等方法的时候,把这个name当做key,传入对应的数据;如:
let printData = {name:"这是打印填充的数据"};
hiprintTemplate.print(printData);
如果是批量打印,那么仅需要把这个printData包装成数组;如:
let printData = [{name:"数据1"},{name:"数据2"}];
hiprintTemplate.print(printData);
这样就实现了批量打印。这仅仅是这是在一个模板上!
分析只是一个思考的过程。
光想是没有意义的,实践才是硬道理。有问题再思考🤔就好了
实现 铺满A4纸 需求
首先定义两个模板对象,并把它显示出来,以方便调试/分析。如图:
这里就不贴代码了,相信你看我的前几篇文章,已经很清楚怎么定义模板了。
根据前面的分析,合并去生成新的模板,需要计算模板能够容纳多少个这样的模板,而且还需要去动态处理模板内的元素定位。
👌上重点代码🚀:
能容纳多少个小的模板:
// A4纸张大小
const { height, width } = {
width: 210,
height: 296.6
}
// 70:小模板的高 105:小模板的宽
// 垂直方向, 可以容纳多少个模板
const ver = parseInt(height / 70);
// 水平方向, 可以容纳多少个模板
const hor = parseInt(width / 105);
// 那么可想而知,新模板,一共可以容纳 ver * hor 个小模板。
定义新的模板对象,并填充小模板
// 新的模板,宽高单位是 mm
let template = {
panels: [
{
index: 0,
width,
height,
// 我们实际需要打印的元素,都存放在这个数组里
printElements: [],
},
],
};
// 根据道垂直方向、水平方向 循环添加打印元素
// 记录新模板有多少个整体, 以便于处理返回的数据!
// ?思考🤔 这里为什么不用 ver * hor
let limitCount = 0;
// top: 记录 打印元素 top值(注意单位); 提示:相当于记录上次填充到哪儿了
for (let v = 1, top = 0; v <= ver; v++) {
// left: 记录 打印元素 left值
for (let h = 1, left = 0; h <= hor; h++) {
// 这里就需要获取到小模板内的所有打印元素了,然后更新它的定位
// 为了更好理解, 我这里拆分来写
// 获取 模板1 的 打印元素, 及 [左][右] 中的 [左]
if (h == 1) {
let printElements1 = hiprintTemplateMap[1].getJson().panels[0].printElements;
printElements1 = printElements1.map((item) => {
// 偏移量计算
item.options.top += top;
item.options.left += left;
// !! 元素的字段名 肯定不能重复呀! 所以需要特殊处理!!!
if (item.options.field) {
// 及变成 字段名 + 第 v 行 第 h 列
// 如: qrcode 变成 qrcode_1_1 (第 1 行 的 第 1 列)
item.options.field += `_${v}_${h}`;
}
return item;
});
template.panels[0].printElements = template.panels[0].printElements.concat(printElements1);
}
// 3.2. 获取 模板2 的 打印元素, 及 [左][右] 中的 [右]
if (h == 2) {
let printElements2 = hiprintTemplateMap[2].getJson().panels[0].printElements;
printElements2 = printElements2.map((item) => {
// 偏移量计算
item.options.top += top;
item.options.left += left;
// !! 元素的字段名 肯定不能重复呀! 所以需要特殊处理!!!
if (item.options.field) {
// 及变成 字段名 + 第 v 行 第 h 列
// 如: qrcode 变成 qrcode_1_1 (第 1 行 的 第 1 列)
item.options.field += `_${v}_${h}`;
}
return item;
});
template.panels[0].printElements = template.panels[0].printElements.concat(printElements2);
}
// 3.3. 计算 下一列 模板的 left 值 (单位转换)
left += hinnn.mm.toPt(105);
// 记录整体个数
limitCount++;
}
// 3.4 计算下一行 模板的 top 值 (单位转换)
top += hinnn.mm.toPt(70);
}
思考🤔为什么不直接使用
ver * hor得到总共能容纳多少个小模板
处理新模板需要的打印数据
printData = []; // 清空数据
const len = dataList.length;
let keys = Object.keys(dataList[0]); // 这里假设 每个模板数据格式一样, 取对象的 key
// 需要多少个这样的面板(批量打印), 才能打印全部的数据
let needPanel = Math.ceil(len / limitCount);
// curIndex:记录已经处理了多少数据
for (let p = 0, curIndex = 0; p < needPanel; p++) {
// 当前面板的打印数据
for (let v = 1; v <= ver; v++) {
for (let h = 1; h <= hor; h++) {
// 数据已处理完
if (curIndex >= len) {
break;
}
keys.forEach((key) => {
panelPrintData[`${key}_${v}_${h}`] = dataList[curIndex][key];
});
curIndex++;
}
}
printData.push(panelPrintData);
}
整体下来,就是和上一步对应,处理生成
打印数据的key
至此,动态生成模板功能实现。
思考🤔 存在的问题
如果多个的小模板不是统一大小,能不能实现这个功能?打印数据不是刚好充满整个纸张,有多余不必要的内容又该怎么处理?
日常工作中,可能随时会遇到这种类似的问题,分析与实践是必然的。
不要排斥、需要实践!试着去动动手、动动脑分析吧!
总结
- 在
实践分析的过程中,我不仅进一步学会了如何去使用这个插件,而且更一步清楚了它的原理。 - 在
分析中,不断去思考、实践、试错;会发现其实实现起来也不复杂,重要的还是思路,以及对一些原理的掌握。 - 同时
在记录这篇文章的时候,又学会了使用ScreenToGif工具等等。
分析、实践、试错!不要怕,各位码友们!
欢迎各位码友转发及留言反馈,觉得不错就点个赞再走咯~