不简说

V1

2023/04/14阅读:132主题:自定义主题1

细思极恐,是我太过单纯。这种动态生成打印模板,没点心思通不了!

不知道标题有没有让你眼前一亮😄

日常工作中、难免遇到各种离谱的需求。本篇以【vue-plugin-hiprint】打印插件为引,和码友们分享一下,如何去分析并实现各种需求。

思考🤔

日常中,需求 ≈(约等于) 需要实现的功能/效果。 既然是功能/效果,那我们肯定需要先分析啦! 那我们应该从哪些方面进行分析呢?

比如:

  • 输出到底是什么要得到的功能/效果);
    • 比如 浏览器读取文件
  • 是否需要 硬件(底层) 支持;
    • 比如浏览器支持不支持访问文件
  • 是否已有 案例分析目前技术栈能不能做到);
    • 比如xxx有这个功能
    • 或者能实现哪一步
  • 如果以上两点都不支持,那么我们就需要考虑:
    • 哪些技术方便实现类似功能/效果);
    • 需要哪些硬件(底层)支持;
    • 可能遇到的问题
    • 时间/人员安排(需要现学否?);

以下内容以打印模板为例子🌰,希望各位码友有所感悟。效果如下图:

动态生成模板效果图
动态生成模板效果图

本篇源码地址: https://github.com/CcSimple/vue-plugin-hiprint-start

1.前言⁉️

本篇以一名刚接触vue-plugin-hiprint插件的小伙伴视野来阐述。

需求:把多个标签打印模板模板中包含多个元素,模板可拖拽调整),铺满 A4 纸打印。也可能其他纸张

已知:该插件支持多面板多模板批量打印;当前标签模板大小A4纸大小、目前返回的打印数据一维数组;该插件默认是没有自动铺满功能的。

根据需求已知条件,我们试着分析一下

  • 需求到底要的是输出什么内容;(目前插件能实现到哪一步);
  • 熟悉插件的实现逻辑(能做些什么原理是什么);
  • 可能存在的一些问题;(插件的不足之处);

👌🏻进入正题,老规矩列个目录

  1. 分析打印输出的效果
  2. 剖析插件模板打印渲染原理
  3. 实现铺满 A4 纸的需求
  4. 思考🤔这样实现存在的问题

分析 打印 输出的效果

已知有两个标签打印模板,如下图:

左侧标签模板
左侧标签模板
右侧标签模板
右侧标签模板

两个模板的内容(包含多个打印元素)可以微调的。想要铺满A4纸,意思就是这A4纸可以容纳多个这两种模板。分析嘛,先随便画个草图:

分析草图
分析草图

我们把左侧模板右侧模板看作一个整体,大概输出的效果就是这个样子。

至于模板中更多的打印元素,我们就需要进一步分析该插件的渲染原理了。比如它是怎么定位的、怎么排序等等。

剖析 插件 模板打印渲染原理

打开在线demo,作为一名前端开发者,一些基础的调试操作肯定是需要熟悉的!先看看渲染的DOM

模板对象的DOM:

模板对象DOM
模板对象DOM

模板内元素的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纸 需求

首先定义两个模板对象,并把它显示出来,以方便调试/分析。如图:

定义两个模板
定义两个模板

这里就不贴代码了,相信你看我的前几篇文章,已经很清楚怎么定义模板了。

根据前面分析合并去生成新的模板,需要计算模板能够容纳多少个这样的模板,而且还需要去动态处理模板内的元素定位

👌上重点代码🚀:

  1. 能容纳多少个小的模板:
// A4纸张大小
const { height, width } = {
  width: 210,
  height: 296.6
}
// 70:小模板的高 105:小模板的宽
// 垂直方向, 可以容纳多少个模板
const ver = parseInt(height / 70);
// 水平方向, 可以容纳多少个模板
const hor = parseInt(width / 105);
// 那么可想而知,新模板,一共可以容纳 ver * hor 个小模板。
  1. 定义新的模板对象,并填充小模板
// 新的模板,宽高单位是 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 得到总共能容纳多少个小模板

  1. 处理新模板需要的打印数据
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

至此,动态生成模板功能实现。


思考🤔 存在的问题

  1. 如果多个的小模板不是统一大小,能不能实现这个功能?
  2. 打印数据不是刚好充满整个纸张,有多余不必要的内容又该怎么处理?

日常工作中,可能随时会遇到这种类似的问题分析实践是必然的。

不要排斥、需要实践!试着去动动手、动动脑分析吧!

总结

  • 实践分析过程中,我不仅进一步学会了如何去使用这个插件而且更一步清楚了它的原理
  • 分析中,不断去思考、实践、试错;会发现其实实现起来也不复杂重要的还是思路以及对一些原理的掌握
  • 同时在记录这篇文章的时候,又学会了使用ScreenToGif工具等等。

分析、实践、试错!不要怕,各位码友们!

欢迎各位码友转发留言反馈,觉得不错就点个赞再走咯~

分类:

前端

标签:

Vue.js

作者介绍

不简说
V1