electron实现静默打印(各种踩坑解决)

前车之鉴

也是阅读了很多资料和前人踩的坑,直接使用webContent.print方法进行打印。其他方式要不就是Bug多,官方修复也有问题;要不就是官方升级版本后不再支持等
不赘述

需求思路

  • main里面实现printerHandle,暴露给渲染线程去调用打印等功能
  • 点击打印后,调出打印页面(新建窗口再隐藏)
  • 通过路径指向打印页面的路由地址,在此页面进行html和css编码,实现打印内容编辑
  • onMounted事件上直接执行打印操作,实现静默
  • 打印完成后,销毁窗口(此过程用户无感)

具体实现

main
  • getPrinter

获取打印机列表,有array.length再继续

  private async getPrinters(event: IpcMainInvokeEvent) {const printers = await event.sender.getPrintersAsync()return printers}
  • print

打印功能,使用官方提供API

  private print(event: IpcMainInvokeEvent, options: WebContentsPrintOptions) {return new Promise(resolve => {event.sender.print(options, (success: boolean, failureReason: string) => {resolve({ success, failureReason })})})}
  • createPrint

创建打印窗口(显示可预览,隐藏可静默)
这里有一个print页面要写,路径指向此页面路由
区分开发环境和生产环境
数据我是通过query传参方式通信,也可以用其他方式(store,cookie等)

  private createPrint(_, data: string) {if (win) {win.destroy()win = null}win = new BrowserWindow({titleBarStyle: 'hidden',width: 1240,height: 768,useContentSize: true,frame: false,show: false,webPreferences: {preload: join(__dirname, '../preload/index.js'),sandbox: false}})const url = is.dev ? new URL(process.env.ELECTRON_RENDERER_URL!) : new URL('file://')url.pathname = is.dev ? '' : join(__dirname, '../renderer/index.html')url.hash = `#/print?data=${data}`win.loadURL(url.href)// win.webContents.openDevTools()win.setMenu(null)win.on('ready-to-show', () => {// win?.show()win?.hide()})win.on('closed', () => {win = null})}
  • destroyPrint
  private destroyPrint() {if (win) {win.destroy()win = null}}
  • 其他代码
// 在class外部定义winlet win = null as BrowserWindow | null// 提供registerregister() {ipcMain.handle('get-printers', this.getPrinters)ipcMain.handle('print', this.print)ipcMain.handle('create-print-window', this.createPrint)ipcMain.handle('destroy-print-window', this.destroyPrint)}
preload
const api = {printer: {getPrinter: () => ipcRenderer.invoke('get-printers'),print: (options: WebContentsPrintOptions) => ipcRenderer.invoke('print', options),createPrintWindow: (data: string) => ipcRenderer.invoke('create-print-window', data),destroyPrintWindow: () => ipcRenderer.invoke('destroy-print-window')}
}contextBridge.exposeInMainWorld('api', api)
renderer
  • 触发打印功能
const printClick = ref(false)
const handlePrint = async (data: Order) => {if (printClick.value) {return}printClick.value = trueconst list = await window.api.printer.getPrinter()console.log(list)if (!list.length) {toast('没有检测到打印设备!', 'error')return}toast('正在打印出货单...', 'info')await window.api.printer.createPrintWindow(JSON.stringify({ ...data, createTime: formatDate(data.createTime) }))printClick.value = false
}
  • 打印窗口页面
<template>
........
<!-- 打印内容和样式 -->
<!-- handle里面 win.show()和控制台功能可临时调试放开注释 -->
</template><script setup name="Print" lang="ts">
import { WebContentsPrintOptions } from 'electron'
import { onMounted } from 'vue'
import { useRoute } from 'vue-router'// 从query获取内容
const query = useRoute().query
const { data } = query
const order: Order = JSON.parse(data as string)// 这里加了延时,后面解释...
onMounted(() => {setTimeout(print, 100)
})// 这里解释
// el-table看到的样式和打印出来的样式区别更大,在于style内联样式的问题
// 渲染后会在.el-table__header,.el-table__body等DOM上计算出宽度来优化样式
// 如果是用户自己点击打印按钮,再去做样式处理setTableFrame是没有问题的,因为样式是后来我们自己加上的100%
// 而为了实现静默下载,需要在页面渲染完成就立即打印,此时elementui也刚刚计算好宽度赋值,而覆盖掉我们的逻辑
// 所以延时了一波,样式没变化,但打印出来的样式就和我们看到的页面样式一样了
const setTableFrame = () => {//el-table设置宽度100%const tableNodes = document.querySelectorAll('.el-table__header,.el-table__body') as NodeListOf<HTMLElement>tableNodes.forEach(table => {table.style.width = '100%'const children = table.childrenfor (let i = 0; i < children.length; i++) {const child = children[i]if (child.localName === 'colgroup') {child.innerHTML = ''}}})//el-table cell设置每个宽度100%const cells = document.querySelectorAll('.cell') as NodeListOf<HTMLElement>cells.forEach(cell => {cell.style.width = '100%'cell.removeAttribute('style')})
}// 打印,先重置el-table样式
const print = async () => {setTableFrame()try {// 设置打印参数,具体看文档const options: WebContentsPrintOptions = {silent: true,margins: { marginType: 'none' },pageSize: 'A4'}await window.api.printer.print(options)} catch (error) {console.log(error)} finally {// 打印完成,调用destoryawait window.api.printer.destroyPrintWindow()}
}
</script>

踩坑

如果是普通下载(非静默),到此就没有问题了
我的版本是electron@27,设置silent: true后,有问题,会缩放很小,而且居中展示
那么有问题,就肯定不止我一个人遇到,就肯定有解决方法
不过@24官方已经不支持更新维护了,但是基本没啥问题(打印功能很迷,据说时不时一个版本好,一个版本又坏,然后又好)
后期项目还要支持win7,还得降级到@21,没bug不出问题就完事~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/3005.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

基于单片机的羽毛球计分器(含proteus仿真和程序)

目录 完整文本及仿真、程序可私信我获取 前言 第一章 设计任务及方案 1.1 设计任务 1.2 总体设计分析 1.3 功能模块方案设计 1.4 方案确定 第二章、硬件设计 2.1 AT89C51 单片机芯片介绍 2.1.1 主要特性 2.1.2 管脚说明 2.1.3 元件清单 2.2 电路介绍 2…

自动化测试用例设计

知人者智&#xff0c;自知者明。大家好&#xff0c;给大家分享一下关于自动化测试用例的设计心得&#xff0c;首先完整的熟悉业务是第一步要做的&#xff0c;不熟悉业务的前提下不会设计出高效且合理的用例&#xff0c;其次是我们要有明确的测试目标&#xff0c;确保我们写的每…

Redis(单/多)线程

一、 Redis 单线程 与 多线程 怎么说&#xff1f; &#xff08;1&#xff09;重要的版本迭代 redis4 之前仅支持 单线程&#xff0c; redis 4之后慢慢 支持多线程&#xff0c; 直到redis6/7后才稳定 &#xff08;2&#xff09;redis 的 工作线程 是 单线程的 &#xff08…

阿里云难题学习笔记

1、下列内存区段增长方是向低地址方向的有&#xff08; &#xff09;&#xff1f; A: 文本段 B: 数据段 C: 堆区 D: 栈区 解析&#xff1a; 在内存管理中&#xff0c;不同的内存区段增长方向是不同的。栈区&#xff08;Stack&#xff09;的增长方向是向低地址方向的&…

Nacos和Eureka有什么区别!!!

一致性模型&#xff1a; Eureka&#xff1a;采用的是 AP&#xff08;Availability, Partition Tolerance&#xff09;模型&#xff0c;即在面临网络分区或部分节点故障时优先保证系统的可用性&#xff0c;牺牲一定的数据一致性。Eureka 通过自我保护机制&#xff0c;允许在节点…

Python构建学生信息管理系统:构建RESTful API - 学生信息管理系统的后端逻辑

在之前的博客里&#xff0c;我们已经完成了项目初始化&#xff0c;在本篇博客中&#xff0c;我们将深入探讨如何使用Flask框架实现学生信息管理系统的后端逻辑&#xff0c;特别是通过RESTful API来实现学生信息的增删改查&#xff08;CRUD&#xff09;操作。 Flask RESTful AP…

go的内存分配机制

Go 语言的内存分配机制可以分为几个主要类别&#xff0c;每个类别都有其特定的行为和优化&#xff1a; 1. 栈&#xff08;Stack&#xff09;分配 局部变量&#xff1a;在函数内部定义的变量通常分配在栈上。大小限制&#xff1a;栈的大小有限&#xff0c;适用于生命周期短、大…

C系统编程:从零手搓一个shell

背景 这么久没更新就是在干这件事&#xff01;&#xff01;因为系统编程已经学的差不多了&#xff0c;所以想找几个项目练练手&#xff0c;之前就一直想写一个自己的shell&#xff01;&#xff01;现在终于有机会实现了。 首先说明一下我的操作系统&#xff1a;Arch linux 服务…

pandas 读取JSON字符串解析长整形丢失数据精度,读取值与实际值不一致

目录 背景&#xff1a; JSON字符串 解析代码 解决方案 背景&#xff1a; 在使用pandas read_json方法读取JSON存为Excel文件时&#xff0c;发现Excel中order_no的值与JSON字符串中的值不一致&#xff0c;开始怀疑是Excel保存精度问题&#xff0c;但是Excel输出实际为字符串…

【OceanBase系列】—— 常用 SQL

作者简介&#xff1a; 花名&#xff1a;绪宁&#xff0c;OceanBase 数据库解决方案架构师 对使用OB过程中常用的一些SQL进行了整理&#xff0c;对应的版本是 4.x。 集群信息 查看版本 show variables like version_comment; 查看集群ID和集群名 show parameters like %clust…

函数的查询

Oracle从入门到总裁:​​​​​​https://blog.csdn.net/weixin_67859959/article/details/135209645 在实际使用中经常会需要查询数据库中已有的函数或者某一个函数的内容&#xff0c;下面就介绍一下如何查询函数。 和存储过程类似&#xff0c;这也需要使用到数据字典user_s…

Spring - 4 ( 11000 字 Spring 入门级教程 )

一&#xff1a;Spring IoC&DI 在前⾯的章节中, 我们学习了 Spring Boot 和 Spring MVC 的开发, 可以完成⼀些基本功能的开发了, 但是什么是 Spring 呢? Spring, Spring Boot 和 SpringMVC 又有什么关系呢? 咱们还是带着问题去学习.我们先看什么是Spring 1.1 Spring 是什…

设计模式学习笔记 - 开源实战四(下):总结Spring中用到的11种设计模式

概述 上篇文章&#xff0c;讲解了 Spring 中支持扩展功能的两种设计模式&#xff1a;观察者模式和模板模式。这两种模式帮助我们创建扩展点&#xff0c;让框架的使用者在不修改源码的情况下&#xff0c;基于扩展点定制化框架功能。 实际上&#xff0c;Spring 框架中用到的设计…

vue项目中定位组件来源的查找思路

vue项目中定位组件来源的查找思路 先去【package.json】里面看看有没有看【a】开头或者【a-】开头的插件名 例如&#xff1a;如果我不知道【el-tree】&#xff0c;先去【package.json】里面找【el】或者【el-】开头的插件名&#xff0c;结果知道了【element-ui】这样就可以直接…

更新至2022年上市公司数字化转型数据合集(四份数据合集)

更新至2022年上市公司数字化转型数据合集&#xff08;四份数据合集&#xff09; 一、2000-2022年上市公司数字化转型数据&#xff08;年报词频、文本统计&#xff09; 二、2007-2022年上市公司数字化转型数据&#xff08;年报和管理层讨论&#xff09;&#xff08;含原始数据…

微前端是如何实现作用域隔离的?

微前端是如何实现作用域隔离的&#xff1f; 一、前言 沙箱&#xff08;Sandbox&#xff09;是一种安全机制&#xff0c;目的是让程序运行在一个相对独立的隔离环境&#xff0c;使其不对外界的程序造成影响&#xff0c;保障系统的安全。作为开发人员&#xff0c;我们经常会同沙…

UE5 GAS开发P35,36,37,38,39 将药水修改为AbilitySystem效果

这几节课都是将药水修改成更方便使用的AbilitySystem效果的Actor,分别为增加血量,增加蓝量,暂时获得最大生命值上限 AuraEffectActor.h // Fill out your copyright notice in the Description page of Project Settings. #pragma once #include "CoreMinimal.h" #…

设计模式- 策略模式(Strategy Pattern)结构|原理|优缺点|场景|示例

设计模式&#xff08;分类&#xff09; 设计模式&#xff08;六大原则&#xff09; 创建型&#xff08;5种&#xff09; 工厂方法 抽象工厂模式 单例模式 建造者模式 原型模式 结构型&#xff08;7种&#xff09; 适配器…

前端vue+xgVIdeo集成rstp流播放

注意&#xff1a;rstp流需要对应的西瓜视频插件 项目&#xff1a; petition-manager 代码概览&#xff1a; 1. video-player 子 组件 <template><div id"video-player" class"video-player"></div> </template> <script&g…

Java面试之封装、继承和多态(简洁易懂版)

一、封装&#xff1a; 1.1、什么是封装&#xff1f; 封装是指将类的某些信息隐藏在类内部&#xff0c;不允许外部直接访问&#xff0c;而是通过类提供的方法来实现对隐藏信息的操作和访问。通过封装&#xff0c;可以提高代码的安全性和可靠性。在Java中&#xff0c;使用访问修…