• 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外部定义win
      let win = null as BrowserWindow | null
    
    
    // 提供register
      register() {
        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 = true
      const 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.children
        for (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 {
      // 打印完成,调用destory
        await window.api.printer.destroyPrintWindow()
      }
    }
    script>
    

    踩坑

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

  • 相关阅读:
    vue2.0 监听用户无操作页面停留时长然后弹窗提示
    RS485信号的测量
    59.【初识JFrame和数据库——聊天系统】
    基于SSM的医院住院管理系统的设计与实现
    2022年高教社杯国赛C题 | 古代玻璃制品的成分分析与鉴别(Matlab实现)
    哪些问题会让企业申报不了高新技术企业?
    遗传算法(GA)求解基于栅格地图的机器人最优路径规划,可以自行修改地图(提供MATLAB代码)
    金融工程学学习笔记第一章
    appliedzkp zkevm(8)中的Plookup Table
    Mysql中的not in和null
  • 原文地址:https://www.cnblogs.com/rion1234567/p/18075830