跳转至内容
  • 版块
  • 最新
  • 热门
  • 标签
  • 积分榜
  • 用户
  • 群组
皮肤
  • Light
  • Cerulean
  • Cosmo
  • Flatly
  • Journal
  • Litera
  • Lumen
  • Lux
  • Materia
  • Minty
  • Morph
  • Pulse
  • Sandstone
  • Simplex
  • Sketchy
  • Spacelab
  • United
  • Yeti
  • Zephyr
  • Dark
  • Cyborg
  • Darkly
  • Quartz
  • Slate
  • Solar
  • Superhero
  • Vapor

  • 默认(不使用皮肤)
  • 不使用皮肤
折叠
品牌标识

百得社区

  1. 主页
  2. 技术交流
  3. 大后端交流区
  4. 【WEB打印实用】分享一个关于非标打印的实践案例

【WEB打印实用】分享一个关于非标打印的实践案例

已定时 已固定 已锁定 已移动 大后端交流区
4 帖子 3 发布者 62 浏览
  • 从旧到新
  • 从新到旧
  • 最多赞同
登录后回复
此主题已被删除。只有拥有主题管理权限的用户可以查看。
  • Jetbrains 中国J 离线
    Jetbrains 中国J 离线
    Jetbrains 中国
    编写于 最后由 Jetbrains 中国 编辑
    #1

    先说结论

    我们经过三个方案的调试,最后选择了方案三做为标定方案,才达到客户满意的效果。后续也推荐对于非标打印,按照方案三执行。

    祝大家少走弯路~

    需求

    客户想实现80mm * 60mm / 100mm * 80mm的非标打印。

    尝试过的方案

    方案一

    使用后端word模板渲染出最终的word数据,将word输出给前端,前端通过docx-preview预览word,然后使用print-js来选取DOM进行打印。

    效果
    实现了单张对应尺寸的打印。

    缺陷
    单张打印的效果好,但是对于批量打印如50张同时打印,就会出现表格跨页问题,具体见下图。
    6bd96efa-05d4-4378-9ef8-34adbd1e8d6c-805076609052ccbeaa9c7e0a51953c67.jpg

    原因分析
    word渲染表格,表格的高度没办法做精确定位,即便定位的很准,但是丝毫误差在数量多的情况下,就会出现误差放大的情况。

    方案二

    直接用前端表格组件库,如table来渲染表格,然后通过print-js来选取DOM进行打印,打印尺寸用css来控制。

    效果
    实现了单张对应尺寸的打印。

    缺陷

    1. 出现了跨页打印表格的问题(但是精确度比方案一好了很多)
    2. 出现了打印内容丢失的问题
      见下图
      fb049fac-89b4-4e17-ac98-5566b62ac46e-image.png

    原因分析
    跨页问题基本是一致的,失之毫厘,差之千里。
    那么为什么会出现打印内容的丢失呢?因为使用纯前端进行Table绘制,Table的大小一方面受基础的css控制,另外一方面,还会受单元格的文字大小、文字长度控制,如果文字过长,单元格会自适应加长,表格会被膨胀,导致尺寸超过了预定的尺寸,因而打印内容丢失。(有人问:为什么不控制单元格内容溢出隐藏?因为当前方案存在方案一的缺陷,所以再努力也是不满足需求的,所以没有继续调下去)

    方案三(最终方案)

    后端采用itextpdf直接渲染pdf,然后通过流式下载到浏览器,高版本的浏览器会自动识别PDF文件进行主动预览,预览效果好不说,还能直接调用打印机进行打印。
    重点是因为itextpdf的每一页内容都是通过document.newPage()方法来手动控制的,这样就可以非常有效的控制每个页面的大小和尺寸,不像word和html在调度打印机的时候,分页是靠整个文章的高度然后除以每页的高度计算出来的。

    效果
    完美解决跨页表格问题,完美支持百张批量打印,主要是客户满意!

    缺陷

    1. 复杂的表格内容,itextpdf绘制可能不太方便
    2. itextpdf需要引入中文字体库,否则中文字体无法渲染(坑点)

    样例代码展示

       /**
         * 生成pdfone
         *
         * @param outputStream                    输出流
         * @param widthMM                         宽度
         * @param heightMM                        高度mm
         * @param productionOrderCardPrintResults 生产订单卡片打印结果
         */
        private void generatePDFOne(ServletOutputStream outputStream, Float widthMM, Float heightMM, List<ProductionOrderCardPrintResult> productionOrderCardPrintResults) {
            // 定义列宽(毫米)
            float width = widthMM * MM_TO_POINT;
            float height = heightMM * MM_TO_POINT;
    
            float widthItem = width / 76;
            float heightItem = height / 59;
            // 缩放比例(保持宽高等比)
            float scale = widthMM / BASE_WIDTH_MM;
    
            float[] columnWidths = {
                    widthItem * 10,  // A列
                    widthItem * 25,  // B列
                    widthItem * 10,  // C列
                    widthItem * 25   // D列
            };
    
            // 定义行高(毫米)
            float normalHeight = heightItem * 7;
            float row3Height = heightItem * 12;
            // 二维码大小
            float qrImageWidth = Math.min(widthItem * 35, normalHeight * 4);
    
            // 创建自定义页面大小
            Rectangle pageSize = new Rectangle(width, height);
    
            // 创建文档边距
            Document document = new Document(pageSize, widthItem, 0, heightItem, 0);
    
            try {
                PdfWriter.getInstance(document, outputStream);
                document.open();
                BaseFont baseFont = loadFont();
                Font font = new Font(baseFont, 11 * scale, Font.BOLD);
    
                for (int i = 0; i < productionOrderCardPrintResults.size(); i++) {
                    if (i > 0) {
                        // 这里手动新增一个新页,从而保证打印的尺寸是精确的
                        document.newPage();
                    }
                    ProductionOrderCardPrintResult productionOrderCardPrintResult = productionOrderCardPrintResults.get(i);
                    String workOrderNo = productionOrderCardPrintResult.getWorkOrderNo();
                    String actualOrderNo = productionOrderCardPrintResult.getActualOrderNo();
                    String materialName = productionOrderCardPrintResult.getMaterialName();
                    String materialNumber = productionOrderCardPrintResult.getMaterialNumber();
                    String specification = productionOrderCardPrintResult.getSpecification();
                    String number = productionOrderCardPrintResult.getNumber();
                    String deliveryTime = productionOrderCardPrintResult.getDeliveryTime();
                    String customerName = productionOrderCardPrintResult.getCustomerName();
                    String remark = productionOrderCardPrintResult.getRemark();
                    Integer printCount = productionOrderCardPrintResult.getPrintCount();
    
    
                    String workOrderNoFormat = StrUtil.blankToDefault(workOrderNo, DEFAULT_STR);
                    String actualOrderNoFormat = StrUtil.blankToDefault(actualOrderNo, DEFAULT_STR);
                    String materialNumberFormat = StrUtil.blankToDefault(materialNumber, DEFAULT_STR);
                    String numberFormat = StrUtil.blankToDefault(number, DEFAULT_STR);
                    String deliveryTimeFormat = StrUtil.blankToDefault(deliveryTime, DEFAULT_STR);
                    String customerNameFormat = StrUtil.blankToDefault(customerName, DEFAULT_STR);
                    String specificationFormat = StrUtil.blankToDefault(specification, DEFAULT_STR);
                    String remarkFormat = StrUtil.blankToDefault(remark, DEFAULT_STR);
    
                    // 创建表格
                    PdfPTable table = new PdfPTable(4);
                    table.setTotalWidth(columnWidths);
                    table.setLockedWidth(true);
    
                    addCell(table, "工单", font, normalHeight, 1, 1);
                    addCell(table, workOrderNoFormat, font, normalHeight, 1, 3);
                    addCell(table, "物料", font, normalHeight, 1, 1);
                    addCell(table, materialNumberFormat, font, normalHeight, 1, 1);
                    addCell(table, "订单", font, normalHeight, 1, 1);
                    addCell(table, actualOrderNoFormat, font, normalHeight, 1, 1);
                    addCell(table, "描述", font, row3Height, 1, 1);
                    addCell(table, specificationFormat, font, row3Height, 1, 3);
                    addCell(table, "数量", font, normalHeight, 1, 1);
                    addCell(table, numberFormat, font, normalHeight, 1, 1);
    
                    // C4-D7合并单元格,放置二维码
                    PdfPCell qrCell = new PdfPCell();
                    qrCell.setRowspan(4);
                    qrCell.setColspan(2);
                    qrCell.setFixedHeight(normalHeight * 4);
                    qrCell.setHorizontalAlignment(Element.ALIGN_CENTER);
                    qrCell.setVerticalAlignment(Element.ALIGN_CENTER);
                    // 生成二维码
                    JSONObject jsonObject = new JSONObject();
                    jsonObject.put("workOrderNo", workOrderNo);
                    Image qrImage = createQrCodeImage(jsonObject.toJSONString(), qrImageWidth);
                    qrImage.scaleToFit(qrImageWidth * 0.9f, qrImageWidth * 0.9f);
                    qrCell.addElement(qrImage);
                    table.addCell(qrCell);
    
                    // 第5行
                    addCell(table, "交期", font, normalHeight, 1, 1);
                    addCell(table, deliveryTimeFormat, font, normalHeight, 1, 1);
    
                    // 第6行
                    addCell(table, "客户", font, normalHeight, 1, 1);
                    addCell(table, customerNameFormat, font, normalHeight, 1, 1);
    
                    // 第7行
                    addCell(table, "备注", font, normalHeight, 1, 1);
                    addCell(table, remarkFormat, font, normalHeight, 1, 1);
    
                    document.add(table);
                }
    
            } catch (Exception e) {
                log.error("生成PDF失败", e);
            } finally {
                document.close();
            }
        }
    
        // 使用 Hutool 生成二维码(无白边)
        private Image createQrCodeImage(String content, float qrSize) throws Exception {
            QrConfig config = new QrConfig((int) qrSize, (int) qrSize);
            // 去掉白边
            config.setMargin(0);
            config.setErrorCorrection(com.google.zxing.qrcode.decoder.ErrorCorrectionLevel.M);
            // 黑色
            config.setForeColor(Color.BLACK);
            // 白底
            config.setBackColor(Color.WHITE);
    
            // 生成 BufferedImage
            java.awt.image.BufferedImage qrImage = QrCodeUtil.generate(content, config);
    
            // 转成 iText Image
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            javax.imageio.ImageIO.write(qrImage, "png", baos);
            return Image.getInstance(baos.toByteArray());
        }
    
        /**
         * 添加单元格
         */
        private static void addCell(PdfPTable table, String text, Font font,
                                    float height, int rowspan, int colspan) {
            PdfPCell cell = new PdfPCell(new Phrase(text, font));
            cell.setFixedHeight(height);
            cell.setRowspan(rowspan);
            cell.setColspan(colspan);
            cell.setVerticalAlignment(Element.ALIGN_MIDDLE);
            cell.setHorizontalAlignment(Element.ALIGN_LEFT);
            table.addCell(cell);
        }
    
    
    1 条回复 最后回复
    4
    • 温 离线
      温 离线
      温暖的火龙果687
      编写于 最后由 编辑
      #2

      itextpdf 开源协议好像是APGL,这个方便深入调研一下么,后续如果使用会不会有其他纠纷

      Jetbrains 中国J 1 条回复 最后回复
      3
      • 重复过往重 离线
        重复过往重 离线
        重复过往
        编写于 最后由 编辑
        #3

        学习了

        这次不得不冲了!

        1 条回复 最后回复
        2
        • Jetbrains 中国J 离线
          Jetbrains 中国J 离线
          Jetbrains 中国
          回复了温暖的火龙果687 最后由 编辑
          #4

          @温暖的火龙果687 感谢提醒!另补充,可以使用Apache PDFBox替代iText库,本次分享只是提供一个参考方案。

          1 条回复 最后回复
          2

          Powered by NodeBB | Contributors
          • 登录

          • 登录或注册以进行搜索。
          • 第一个帖子
            最后一个帖子
          0
          • 版块
          • 最新
          • 热门
          • 标签
          • 积分榜
          • 用户
          • 群组