Electron内嵌网页实现打印预览功能的示例代码
作者:依了个旧
前言
目前在业务碰到一个需求,之前是一个网页的应用,现在要求把这个应用内嵌到Electron里面打包成一个安装包,由于之前应用内涉及到大量的打印功能,且采用的都是window.print()
的方式来实现的,这种方式在谷歌等现代浏览器上本身就能够实现预览,虽然内嵌到Electron后还是能成功打印,但是无法进行预览,只会出现如下图的系统页面
所以希望在不调整之前系统的打印逻辑前提下,能够实现在Electron中进行打印预览并成功打印,经过摸索,得出了以下的解决方案:
1. 创建Electron应用,内嵌网页
首先创建一个electron窗口,直接通过mainWindow.loadURL()
方法嵌入线上应用,这里便于演示,我采用加载本地静态html的方式来实现
// main.js const { app, BrowserWindow, ipcMain, screen } = require('electron'); let mainWindow; // 主窗口 let previewWindow; // 预览窗口 function createWindow() { // 获取主屏幕的尺寸 const primaryDisplay = screen.getPrimaryDisplay() const { width, height } = primaryDisplay.workAreaSize mainWindow = new BrowserWindow({ width: width, height: height, webPreferences: { nodeIntegration: true, contextIsolation: false } }); // 作为演示,采用本地静态文件 mainWindow.loadFile('index.html') // 可通过以下方式内嵌网页 //mainWindow.loadURL(`http://www.example.com`) // 打开开发者工具 mainWindow.webContents.openDevTools(); } app.whenReady().then(() => { createWindow(); app.on('activate', function () { if (BrowserWindow.getAllWindows().length === 0) createWindow(); }); }); // 退出 app.on('window-all-closed', function () { if (process.platform !== 'darwin') app.quit(); });
2. 拦截系统打印
这里通过重写window.print()
方法,拦截系统自带的打印功能,自己来写一个在electron内用于实现打印预览的页面,需要获取到需要打印的dom节点,同时为了保证样式生效,我们这里同时把当前页面的所有style标签和link样式文件都获取到,一并传给预览页面
<!-- index.html --> <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>打印预览示例</title> <style> .print-button { padding: 10px 20px; background-color: #4CAF50; color: white; border: none; border-radius: 4px; cursor: pointer; } </style> </head> <body> <h1>打印预览示例</h1> <button class="print-button" onclick="showPreview()">打印预览</button> <div id="printTable"> 打印内容 </div> <script> const { ipcRenderer } = require('electron'); // 拦截默认打印行为 const originalPrint = window.print; window.print = function () { console.log('渲染进程拦截到打印请求'); try { // 获取要打印的内容 const printContent = document.getElementById("printTable").innerHTML // 获取所有<style>标签 const styleEles = Array.from(document.querySelectorAll('style')); let styleTags = []; styleEles.forEach((style, index) => { styleTags.push({ id: style.id || `style-${index}`, content: style.textContent, }); }); // 获取所有<link>标签中的样式表 const linkEles = Array.from(document.querySelectorAll('link[rel="stylesheet"]')); let linkTags = []; linkEles.forEach((link, index) => { linkTags.push({ id: link.id || `stylesheet-${index}`, href: link.href, }); }); // 调用渲染进程中的预览方法,并把打印内容和样式一起传过去 ipcRenderer.invoke('show-preview', { printContent: printContent, styleTags: styleTags, linkTags: linkTags }); } catch (error) { console.error('打印错误:', error); } }; function showPreview() { window.print(); } </script> </body> </html>
3. 加载打印预览界面
获取到要打印的内容后,单独开启一个无边框和菜单栏的窗口,窗口内加载一个用于展示要打印内容的静态文件preview.html
,在这个静态文件里面处理需要展示的内容和打印的逻辑
// main.js // 创建预览窗口 function createPreviewWindow(printData) { // 获取主屏幕的尺寸 const primaryDisplay = screen.getPrimaryDisplay() const { width, height } = primaryDisplay.workAreaSize previewWindow = new BrowserWindow({ width: parseInt(width * 0.7), height: parseInt(height * 0.9), frame: false, autoHideMenuBar: true, webPreferences: { nodeIntegration: true, contextIsolation: false } }); // 加载预览页面 previewWindow.loadFile('preview.html'); // 等待页面加载完成后发送打印数据 previewWindow.webContents.on('did-finish-load', () => { previewWindow.webContents.send('print-data', printData); }); previewWindow.webContents.openDevTools() } // 处理打印预览请求 ipcMain.handle('show-preview', async (event, printData) => { try { // 调用创建预览窗口 createPreviewWindow(printData); return { success: true }; } catch (error) { console.error('预览错误:', error); return { success: false, error: error.message }; } });
4. 预览页面的实现
预览页面可以自由编写布局和样式,主要逻辑是把需要打印的内容和样式加入到页面中,同时加载本地可用的打印机和其他打印配置
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>打印预览</title> <style> body { margin: 0; padding: 0; display: flex; height: 100vh; overflow: hidden; background-color: #f0f0f0; } .main-container { display: flex; width: 100%; height: 100%; } .print-document { flex-grow: 1; display: flex; justify-content: center; align-items: center; padding: 20px; overflow-y: auto; } /* 这里不能缺少,用于打印的时候不把侧边栏也打印出来 */ @media print { .print-sidebar { display: none; } } </style> </head> <body> <div class="main-container"> <div id="print-container" class="print-document"> </div> <div class="print-sidebar"> <div class="sidebar-header"> <h2>打印</h2> </div> <div class="print-section"> <label>目标打印机</label> <div class="select-wrapper"> <select id="printer"></select> </div> </div> <div class="print-section"> <label>页面</label> <div class="select-wrapper"> <select id="pageSize"> <option value="A4">全部</option> <option value="A3">A3</option> <option value="B5">B5</option> <option value="letter">Letter</option> </select> </div> </div> <!-- 其它你需要的配置项 --> <div class="sidebar-actions"> <button class="print-button" onclick="print()">打印</button> <button class="cancel-button" onclick="closePreview()">取消</button> </div> </div> </div> <script> const { ipcRenderer } = require('electron'); // 接收打印数据 ipcRenderer.on('print-data', (event, data) => { if (data.printContent) { // 把要打印的内容放到页面上 const printEle = document.getElementById('print-container') printEle.innerHTML = data.printContent } const head = document.head || document.getElementsByTagName('head')[0]; // 如果有style标签,把style标签放到页面中 if (data.styleTags && data.styleTags.length > 0) { data.styleTags.forEach((style, index) => { const styleElement = document.createElement('style'); styleElement.textContent = style.content; head.appendChild(styleElement); }); } // 如果有link标签,添加link标签 if (data.linkTags && data.linkTags.length > 0) { data.linkTags.forEach((link, index) => { const linkElement = document.createElement('link'); linkElement.rel = 'stylesheet'; linkElement.type = 'text/css'; linkElement.href = link.href; head.appendChild(linkElement); }); } // 加载本机可用的打印机列表 loadPrinters(); }); // 加载打印机列表 async function loadPrinters() { try { const printers = await ipcRenderer.invoke('get-printers'); const printerSelect = document.getElementById('printer'); printerSelect.innerHTML = ''; printers.forEach(printer => { const option = document.createElement('option'); option.value = printer.name; option.textContent = printer.name; printerSelect.appendChild(option); }); } catch (error) { console.error('加载打印机列表失败:', error); } } // 打印功能 async function print() { try { const options = { deviceName: document.getElementById('printer').value, pageSize: document.getElementById('pageSize').value, landscape: document.getElementById('orientation').value === 'landscape', color: document.getElementById('color').value === 'true', printBackground: true, marginType: 'printableArea' }; // 调用主线程的打印功能 await ipcRenderer.invoke('do-print', options); } catch (error) { alert('打印错误:' + error.message); } } // 关闭预览窗口 function closePreview() { ipcRenderer.invoke('close-preview'); } // 初始加载打印机 document.addEventListener('DOMContentLoaded', () => { loadPrinters(); }); </script> </body> </html>
以下是获取打印机列表和执行打印的逻辑
// main.js // 获取打印机列表 ipcMain.handle('get-printers', async () => { try { const printers = await mainWindow.webContents.getPrintersAsync(); return printers; } catch (error) { console.error('获取打印机列表失败:', error); return []; } }); // 处理实际打印请求 ipcMain.handle('do-print', async (event, options) => { try { const printOptions = { silent: true, //静默打印 printBackground: true, deviceName: options.deviceName || '', copies: options.copies || 1, pageSize: options.pageSize || 'A4', margins: { marginType: options.marginType || 'printableArea' }, landscape: options.landscape || false, scale: options.scale || 1.0, color: options.color || true, headerFooter: options.headerFooter || false, pageRanges: options.pageRanges || '', collate: options.collate || true, duplex: options.duplex || 'none' }; await previewWindow.webContents.print(printOptions); } catch (error) { console.error('打印错误:', error); } });
通过以上方式就实现了不改变原来应用调用window.print()
的方式,实现打印预览和打印的逻辑,具体业务中,可以把重写window.print()
这一段逻辑单独写成一个js文件,全局加载,如果希望保留原有的打印,可以判断一下是否在electron的环境中,如果在electron的环境才进行方法覆盖,这样在就能保证无论是通过浏览器打开或者在electron打开,都可以完成对应的预览功能。
以上就是Electron内嵌网页实现打印预览功能的示例代码的详细内容,更多关于Electron内嵌网页打印预览的资料请关注脚本之家其它相关文章!