鸿蒙PC Electron 打印服务实现详解

📋 目录


问题背景

在鸿蒙平台上开发 Electron 应用时,需要实现打印功能。打印功能是桌面应用的基础需求之一,但在跨平台开发中,不同平台的打印 API 和实现方式存在差异。

需求分析

  1. 基本打印功能:用户点击打印按钮后,能够打印当前页面内容
  2. 打印预览:显示系统打印对话框,允许用户选择打印机和打印选项
  3. 打印样式优化:打印时隐藏不必要的 UI 元素,优化打印效果
  4. 跨平台兼容:在鸿蒙平台上稳定运行,同时兼容其他平台

实现方案

方案对比

方案 优点 缺点 适用场景
浏览器原生 window.print() 简单直接,兼容性好 功能有限,无法自定义 简单打印需求
Electron webContents.print() 功能强大,可配置选项多 在某些平台上可能不稳定 需要高级打印功能
鸿蒙原生打印 API 平台深度集成 需要额外的适配层 需要平台特定功能

最终方案

采用双重保障方案

  1. 优先使用浏览器原生 APIwindow.print() - 在鸿蒙平台上更可靠
  2. 降级到 Electron APIwebContents.print() - 作为备选方案
  3. IPC 通信:通过 Electron IPC 在主进程和渲染进程之间通信

代码实现

1. 主进程实现(main.js)

在主进程中监听打印请求,并调用 Electron 的打印 API:

const { app, BrowserWindow, ipcMain } = require('electron');
const path = require('path');

// 禁用硬件加速(可选,解决某些打印兼容性问题)
app.disableHardwareAcceleration();
app.commandLine.appendSwitch('disable-gpu');

// 打印功能处理
ipcMain.on('print', (event) => {
  console.log('收到打印请求');
  const window = BrowserWindow.fromWebContents(event.sender);
  if (!window) {
    console.error('无法获取窗口对象');
    return;
  }
  
  console.log('准备调用打印 API');
  try {
    // 尝试使用 Electron 的打印 API
    if (window.webContents && typeof window.webContents.print === 'function') {
      window.webContents.print({
        silent: false,           // 显示打印对话框
        printBackground: true,   // 打印背景色和图片
        deviceName: ''          // 使用默认打印机
      }, (success, failureReason) => {
        if (success) {
          console.log('打印成功');
        } else {
          console.error('打印失败:', failureReason);
          // 如果 Electron 打印失败,尝试使用浏览器原生打印
          console.log('尝试使用浏览器原生打印');
          window.webContents.executeJavaScript('window.print()').catch(err => {
            console.error('浏览器原生打印也失败:', err);
          });
        }
      });
    } else {
      console.warn('webContents.print 不可用,使用浏览器原生打印');
      window.webContents.executeJavaScript('window.print()').catch(err => {
        console.error('浏览器原生打印失败:', err);
      });
    }
  } catch (error) {
    console.error('打印过程中发生错误:', error);
    // 降级方案:使用浏览器原生打印
    try {
      window.webContents.executeJavaScript('window.print()').catch(err => {
        console.error('降级打印也失败:', err);
      });
    } catch (e) {
      console.error('所有打印方案都失败:', e);
    }
  }
});

2. 预加载脚本(preload.js)

在预加载脚本中暴露安全的打印 API:

const { contextBridge, ipcRenderer } = require('electron');

contextBridge.exposeInMainWorld('electronAPI', {
    versions: {
        chrome: process.versions.chrome,
        node: process.versions.node,
        electron: process.versions.electron
    },
    // 打印功能
    print: () => {
        ipcRenderer.send('print');
    }
});

3. 渲染进程实现(index.html)

在页面中添加打印按钮和打印函数:

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Hello World</title>
    <style>
        /* 打印样式 */
        @media print {
            body {
                background: white !important;
                color: black !important;
            }
            .button-container {
                display: none !important;
            }
            h1 {
                color: black !important;
            }
        }
    </style>
</head>
<body>
    <h1>Hello World</h1>
    <div class="button-container">
        <button onclick="printPage()">🖨️ 打印</button>
    </div>

    <script>
        function printPage() {
            console.log('点击打印按钮');
            try {
                // 方法1: 直接使用浏览器原生打印 API(在鸿蒙平台上更可靠)
                if (typeof window.print === 'function') {
                    console.log('调用 window.print()');
                    window.print();
                } else if (window.electronAPI && window.electronAPI.print) {
                    // 方法2: 使用 Electron IPC 打印
                    console.log('使用 Electron IPC 打印');
                    window.electronAPI.print();
                } else {
                    alert('打印功能不可用');
                }
            } catch (error) {
                console.error('打印失败:', error);
                alert('打印功能出错: ' + error.message);
            }
        }
    </script>
</body>
</html>

遇到的问题与解决方案

问题一:点击打印按钮没有反应

现象:点击打印按钮后,没有任何反应,打印对话框不弹出。

原因分析

  1. Electron 的 webContents.print() API 在鸿蒙平台上可能不稳定
  2. IPC 通信可能存在问题
  3. 打印权限可能未正确配置

解决方案

  1. 添加详细的调试日志
function printPage() {
    console.log('点击打印按钮');
    // 添加调试信息
    console.log('window.print 可用:', typeof window.print === 'function');
    console.log('electronAPI.print 可用:', window.electronAPI && window.electronAPI.print);
    
    // 执行打印
    window.print();
}
  1. 优先使用浏览器原生 API
// 直接使用 window.print(),这是最可靠的方式
function printPage() {
    if (typeof window.print === 'function') {
        window.print();  // 优先使用浏览器原生 API
    } else if (window.electronAPI && window.electronAPI.print) {
        window.electronAPI.print();  // 降级到 Electron API
    }
}
  1. 禁用硬件加速(可选):
// 在 main.js 开头添加
app.disableHardwareAcceleration();
app.commandLine.appendSwitch('disable-gpu');

问题二:打印时包含不需要的元素

现象:打印时,按钮、导航栏等 UI 元素也被打印出来。

解决方案:使用 CSS @media print 媒体查询隐藏不需要的元素:

@media print {
    /* 隐藏按钮容器 */
    .button-container {
        display: none !important;
    }
    
    /* 优化打印样式 */
    body {
        background: white !important;
        color: black !important;
    }
    
    /* 确保文字可读 */
    h1 {
        color: black !important;
    }
}

问题三:打印背景色和图片不显示

现象:打印时,背景色和背景图片不显示。

解决方案:在打印选项中启用 printBackground

window.webContents.print({
    silent: false,
    printBackground: true,  // 启用背景打印
    deviceName: ''
}, callback);

打印样式优化

1. 基本打印样式

@media print {
    /* 重置页面样式 */
    * {
        margin: 0;
        padding: 0;
    }
    
    /* 优化页面布局 */
    body {
        background: white !important;
        color: black !important;
        font-size: 12pt;
        line-height: 1.5;
    }
    
    /* 隐藏不需要的元素 */
    .no-print {
        display: none !important;
    }
    
    /* 分页控制 */
    .page-break {
        page-break-after: always;
    }
    
    /* 避免在元素中间分页 */
    h1, h2, h3 {
        page-break-after: avoid;
    }
}

2. 打印布局优化

@media print {
    /* 设置页面边距 */
    @page {
        margin: 2cm;
    }
    
    /* 优化表格打印 */
    table {
        border-collapse: collapse;
        width: 100%;
    }
    
    /* 确保图片完整显示 */
    img {
        max-width: 100%;
        height: auto;
    }
}

3. 打印按钮样式

<!-- 使用 no-print 类标记不需要打印的元素 -->
<div class="button-container no-print">
    <button onclick="printPage()">🖨️ 打印</button>
</div>

最佳实践

1. 错误处理

function printPage() {
    try {
        if (typeof window.print === 'function') {
            window.print();
        } else {
            throw new Error('打印功能不可用');
        }
    } catch (error) {
        console.error('打印失败:', error);
        // 友好的错误提示
        alert('打印功能暂时不可用,请稍后重试');
    }
}

2. 用户体验优化

function printPage() {
    // 显示加载提示
    const loadingMsg = document.createElement('div');
    loadingMsg.textContent = '正在准备打印...';
    loadingMsg.style.cssText = 'position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);background:#333;color:#fff;padding:20px;border-radius:5px;z-index:9999;';
    document.body.appendChild(loadingMsg);
    
    // 延迟执行打印,确保提示显示
    setTimeout(() => {
        window.print();
        document.body.removeChild(loadingMsg);
    }, 100);
}

3. 打印前数据准备

function printPage() {
    // 打印前准备数据
    preparePrintData();
    
    // 执行打印
    window.print();
    
    // 打印后清理
    cleanupAfterPrint();
}

function preparePrintData() {
    // 例如:加载打印所需的数据
    // 格式化数据
    // 更新页面内容
}

function cleanupAfterPrint() {
    // 清理临时数据
    // 恢复页面状态
}

4. 打印配置选项

// Electron 打印选项配置
const printOptions = {
    silent: false,              // 显示打印对话框
    printBackground: true,      // 打印背景
    deviceName: '',             // 打印机名称(空表示默认)
    color: true,                // 彩色打印
    margins: {
        marginType: 'default'   // 边距类型:default, none, printableArea, custom
    },
    landscape: false,           // 横向打印
    scaleFactor: 100,          // 缩放比例
    pagesPerSheet: 1,          // 每张纸打印的页数
    collate: true,             // 自动分页
    copies: 1                  // 打印份数
};

window.webContents.print(printOptions, callback);

完整示例代码

main.js(完整版)

const { app, BrowserWindow, ipcMain } = require('electron');
const path = require('path');

// 禁用硬件加速(可选)
app.disableHardwareAcceleration();
app.commandLine.appendSwitch('disable-gpu');

let mainWindow;

function createWindow() {
  mainWindow = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
      nodeIntegration: false,
      contextIsolation: true,
      preload: path.join(__dirname, 'preload.js'),
    },
  });

  mainWindow.loadFile('index.html');
  return mainWindow;
}

// 打印功能
ipcMain.on('print', (event) => {
  const window = BrowserWindow.fromWebContents(event.sender);
  if (!window) {
    console.error('无法获取窗口对象');
    return;
  }
  
  try {
    if (window.webContents && typeof window.webContents.print === 'function') {
      window.webContents.print({
        silent: false,
        printBackground: true,
        deviceName: ''
      }, (success, failureReason) => {
        if (!success) {
          console.error('打印失败:', failureReason);
          // 降级到浏览器原生打印
          window.webContents.executeJavaScript('window.print()');
        }
      });
    } else {
      window.webContents.executeJavaScript('window.print()');
    }
  } catch (error) {
    console.error('打印错误:', error);
    window.webContents.executeJavaScript('window.print()');
  }
});

app.whenReady().then(() => {
  createWindow();
});

app.on('window-all-closed', () => {
  if (process.platform !== 'darwin') {
    app.quit();
  }
});

preload.js(完整版)

const { contextBridge, ipcRenderer } = require('electron');

contextBridge.exposeInMainWorld('electronAPI', {
    versions: {
        chrome: process.versions.chrome,
        node: process.versions.node,
        electron: process.versions.electron
    },
    print: () => {
        ipcRenderer.send('print');
    }
});

index.html(完整版)

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>打印示例</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            padding: 20px;
        }
        
        .button-container {
            margin: 20px 0;
        }
        
        button {
            padding: 10px 20px;
            font-size: 16px;
            cursor: pointer;
        }
        
        @media print {
            .button-container {
                display: none !important;
            }
            body {
                background: white !important;
                color: black !important;
            }
        }
    </style>
</head>
<body>
    <h1>打印示例页面</h1>
    <p>这是要打印的内容。</p>
    
    <div class="button-container">
        <button onclick="printPage()">🖨️ 打印</button>
    </div>

    <script>
        function printPage() {
            try {
                if (typeof window.print === 'function') {
                    window.print();
                } else if (window.electronAPI && window.electronAPI.print) {
                    window.electronAPI.print();
                } else {
                    alert('打印功能不可用');
                }
            } catch (error) {
                console.error('打印失败:', error);
                alert('打印功能出错: ' + error.message);
            }
        }
    </script>
</body>
</html>

总结

实现要点

  1. 双重保障:优先使用浏览器原生 window.print(),降级到 Electron API
  2. 错误处理:完善的错误处理和日志记录
  3. 样式优化:使用 @media print 优化打印效果
  4. 用户体验:友好的错误提示和加载状态

注意事项

  1. 平台兼容性:在鸿蒙平台上,浏览器原生 API 更可靠
  2. 权限配置:确保应用有打印权限
  3. 性能优化:禁用硬件加速可能有助于解决某些兼容性问题
  4. 样式控制:合理使用 CSS 媒体查询控制打印样式

扩展方向

  1. PDF 导出:使用 webContents.printToPDF() 导出 PDF
  2. 打印预览:实现自定义打印预览功能
  3. 批量打印:支持批量打印多个页面
  4. 打印模板:实现可配置的打印模板系统

参考资料


Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐