electron實現靜默列印(各種踩坑解決)

大禹不治水發表於2024-03-18

前車之鑑

也是閱讀了很多資料和前人踩的坑,直接使用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不出問題就完事~

相關文章