前言

在跨端开发领域,Electron 凭借 “一次编写、多端运行” 的特性占据重要地位,其基于 Chromium 和 Node.js 的架构可快速将 Web 应用打包为 Windows、macOS、Linux 桌面应用。而鸿蒙系统(HarmonyOS)作为面向全场景的分布式操作系统,正逐步完善桌面端(如鸿蒙 PC、智慧屏)生态。将 Electron 与鸿蒙结合,既能复用 Web 技术栈的海量资源,又能借助鸿蒙的分布式能力实现 “跨设备无缝流转”,为开发者提供更灵活的全场景应用开发方案。

本文面向有 Web 或桌面开发基础的开发者,从技术背景、环境搭建、核心功能开发到场景落地,全程附代码示例与官方文档链接,助力快速上手鸿蒙 Electron 开发。

一、鸿蒙与 Electron 结合的技术背景

1.1 为什么需要鸿蒙 + Electron?

  • 技术复用:Electron 基于 Web 技术栈(HTML/CSS/JavaScript),开发者无需学习全新语言即可开发鸿蒙桌面应用,降低迁移成本。
  • 跨端能力互补:Electron 擅长桌面端应用开发,鸿蒙擅长分布式跨设备协同,二者结合可实现 “桌面端功能 + 多设备流转” 的全场景体验(如 PC 端编辑文档,智慧屏端展示)。
  • 生态适配需求:鸿蒙桌面端(如华为 MateBook X Pro 2024 款鸿蒙版)需要更多优质应用,Electron 可将现有桌面应用快速适配鸿蒙,加速生态建设。

1.2 核心技术原理

鸿蒙系统通过方舟引擎(Ark Engine)支持 Electron 应用运行,其核心逻辑是:

  1. Electron 应用的 Chromium 内核与鸿蒙的图形渲染框架适配,确保界面正常显示;
  2. Electron 的 Node.js 层与鸿蒙的系统 API 桥接,实现文件操作、设备调用等原生能力;
  3. 借助鸿蒙的分布式软总线,Electron 应用可调用其他鸿蒙设备的资源(如摄像头、扬声器)。

1.3 官方资源参考

二、鸿蒙 Electron 开发环境搭建(附代码与操作步骤)

环境搭建分为 3 个核心步骤:基础环境(Node.js+Electron)、鸿蒙开发环境(DevEco Studio)、工程初始化与依赖配置。全程基于 Windows 11 系统演示,macOS/Linux 步骤类似。

2.1 基础环境准备(Node.js + Electron)

2.1.1 安装 Node.js

Electron 依赖 Node.js 环境,需安装Node.js 16.x LTS 版本(经测试与鸿蒙适配最佳,高版本可能存在兼容性问题)。

  1. 下载地址:Node.js 16.20.2 官网下载
  2. 安装后验证:打开命令行(CMD 或 PowerShell),执行以下命令,输出版本号即安装成功:

bash

node -v  # 需输出 v16.20.2
npm -v   # 需输出 8.19.4(Node.js 16.x 默认版本)
2.1.2 安装 Electron

通过 npm 全局安装 Electron,指定版本为22.3.2(与鸿蒙方舟引擎适配的稳定版本):

bash

# 全局安装Electron
npm install -g electron@22.3.2

# 验证安装
electron -v  # 需输出 v22.3.2

2.2 鸿蒙开发环境配置(DevEco Studio)

鸿蒙应用的编译、打包与设备调试需通过 DevEco Studio 完成,需安装DevEco Studio 4.0 及以上版本

  1. 下载地址:DevEco Studio 官网下载

  2. 安装步骤:

    • 双击安装包,选择 “自定义安装”,勾选 “HarmonyOS SDK for PC”(桌面端开发必需);
    • 安装完成后,打开 DevEco Studio,自动下载鸿蒙 PC 端 SDK(版本选择 3.2.100 及以上);
    • 配置 SDK 路径:进入「File > Settings > Appearance & Behavior > System Settings > HarmonyOS SDK」,确认 “PC” 选项卡下的 SDK 已全部下载。
  3. 验证环境:

    • 打开 DevEco Studio,创建一个 “Empty Ability” 项目,模板选择 “PC”;
    • 连接鸿蒙 PC 设备或启动鸿蒙 PC 模拟器(需在「Tools > Device Manager」中申请);
    • 点击 “Run” 按钮,若项目能正常运行(显示默认页面),则鸿蒙环境配置成功。

2.3 鸿蒙 Electron 工程初始化(附代码)

2.3.1 创建 Electron 基础工程
  1. 新建文件夹(如HarmonyElectronDemo),打开命令行进入该文件夹,执行以下命令初始化 Electron 工程:

bash

# 初始化package.json(一路回车默认配置即可)
npm init -y

# 安装本地Electron依赖(确保与全局版本一致)
npm install electron@22.3.2 --save-dev
  1. 在工程根目录创建 3 个核心文件:
    • main.js:Electron 主进程文件(控制应用生命周期、创建窗口);
    • index.html:应用界面文件(Web 端页面);
    • preload.js:主进程与渲染进程的通信桥接文件。
2.3.2 核心文件代码实现
(1)main.js(Electron 主进程)

javascript

// 引入Electron核心模块
const { app, BrowserWindow } = require('electron');
const path = require('path');

// 避免垃圾回收导致窗口被销毁
let mainWindow;

// 创建窗口函数
function createWindow() {
  mainWindow = new BrowserWindow({
    width: 800,          // 窗口宽度
    height: 600,         // 窗口高度
    webPreferences: {
      // 配置预加载脚本(用于主进程与渲染进程通信)
      preload: path.join(__dirname, 'preload.js'),
      // 启用Node.js环境(鸿蒙端需开启)
      nodeIntegration: true,
      contextIsolation: false  // 关闭上下文隔离(适配鸿蒙API调用)
    }
  });

  // 加载本地HTML文件
  mainWindow.loadFile('index.html');

  // 打开开发者工具(调试用,发布时需注释)
  mainWindow.webContents.openDevTools();

  // 窗口关闭事件
  mainWindow.on('closed', () => {
    mainWindow = null;
  });
}

// 应用就绪后创建窗口
app.whenReady().then(() => {
  createWindow();

  // 适配macOS(窗口关闭后重新打开)
  app.on('activate', () => {
    if (BrowserWindow.getAllWindows().length === 0) createWindow();
  });
});

// 所有窗口关闭后退出应用(Windows/Linux)
app.on('window-all-closed', () => {
  if (process.platform !== 'darwin') app.quit();
});
(2)preload.js(通信桥接)

javascript

// 暴露Electron的ipcRenderer到渲染进程(用于跨进程通信)
window.ipcRenderer = require('electron').ipcRenderer;
(3)index.html(应用界面)

html

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>鸿蒙Electron Demo</title>
  <style>
    body { 
      font-family: Arial, sans-serif; 
      text-align: center; 
      padding-top: 50px; 
    }
    #deviceInfo { 
      margin-top: 30px; 
      color: #333; 
      font-size: 16px; 
    }
  </style>
</head>
<body>
  <h1>Hello 鸿蒙Electron!</h1>
  <button id="getDeviceBtn">获取鸿蒙设备信息</button>
  <div id="deviceInfo"></div>

  <script>
    // 监听按钮点击事件
    document.getElementById('getDeviceBtn').addEventListener('click', () => {
      // 后续章节将通过鸿蒙API获取设备信息,此处先占位
      document.getElementById('deviceInfo').innerText = '待获取设备信息...';
    });
  </script>
</body>
</html>
2.3.3 配置工程运行脚本

修改package.json,添加scripts字段,用于启动 Electron 应用:

json

{
  "name": "harmony-electron-demo",
  "version": "1.0.0",
  "main": "main.js",  // 指定主进程文件
  "scripts": {
    "start": "electron ."  // 启动命令
  },
  "devDependencies": {
    "electron": "^22.3.2"
  }
}
2.3.4 测试 Electron 工程

在命令行执行以下命令,若能弹出窗口并显示 “Hello 鸿蒙 Electron!”,则基础工程搭建成功:

bash

npm run start

2.4 鸿蒙端工程配置(对接 Electron)

  1. 打开 DevEco Studio,创建一个 “Empty Ability” 项目,项目名称为HarmonyElectronHost,模板选择 “PC”,包名自定义(如com.example.harmonyelectronhost)。
  2. 在鸿蒙工程的main_pages.json中添加 Electron 应用的启动页面配置:

json

{
  "src": [
    "main_pages/Index",
    {
      "src": "electron/ElectronPage",
      "window": {
        "designWidth": 800,
        "autoDesignWidth": true
      }
    }
  ]
}
  1. src/main/electron目录下创建ElectronPage.ets文件,用于加载 Electron 应用:

typescript

import web_webview from '@ohos.web.webview';
import router from '@ohos.router';

@Entry
@Component
struct ElectronPage {
  build() {
    Column({ space: 10 }) {
      // 鸿蒙WebView组件:加载Electron应用的本地HTML
      Web({ 
        src: $rawfile('index.html'),  // 需将Electron工程的index.html放入鸿蒙工程的rawfile目录
        controller: new web_webview.WebViewController() 
      })
      .width('100%')
      .height('90%')

      // 返回按钮
      Button('返回首页')
        .width(150)
        .height(40)
        .onClick(() => {
          router.back();
        })
    }
    .width('100%')
    .height('100%')
  }
}
  1. 资源复制:将 Electron 工程中的index.htmlpreload.js复制到鸿蒙工程的src/main/rawfile目录(若没有rawfile目录,需手动创建)。

  2. 测试鸿蒙对接:

    • 启动鸿蒙 PC 模拟器或连接真实设备;
    • 点击 DevEco Studio 的 “Run” 按钮,进入ElectronPage页面,若能显示 Electron 应用的界面,则鸿蒙端配置成功。

2.5 环境搭建常见问题解决

问题现象 解决方案 参考链接
Electron 启动报错 “Cannot find module 'electron'” 1. 检查 Node.js 版本是否为 16.x;2. 执行npm install重新安装依赖;3. 全局安装 Electron:npm install -g electron@22.3.2 Electron 安装问题排查
鸿蒙 WebView 加载 HTML 报错 “File not found” 1. 确认 HTML 文件路径是否正确;2. 检查rawfile目录是否在src/main下;3. 清理工程缓存:「Build > Clean Project」 鸿蒙 WebView 资源加载指南
DevEco Studio 无法识别鸿蒙 PC 设备 1. 安装鸿蒙设备驱动:鸿蒙设备驱动下载;2. 开启设备 “开发者模式” 并允许 USB 调试 鸿蒙设备连接指南

三、鸿蒙 Electron 核心功能开发(多代码示例)

本节聚焦 3 个核心功能:鸿蒙设备信息获取、Electron 与鸿蒙的跨进程通信、分布式设备资源调用,每个功能均附完整代码与调试步骤。

3.1 功能 1:鸿蒙设备信息获取(Electron 调用鸿蒙 API)

通过鸿蒙的deviceInfo模块获取设备型号、系统版本等信息,并在 Electron 界面展示。

3.1.1 鸿蒙端代码(能力封装)

在鸿蒙工程的src/main/ets/utils目录下创建DeviceInfoUtil.ets,封装设备信息获取方法:

typescript

import deviceInfo from '@ohos.device.deviceInfo';

/**
 * 鸿蒙设备信息工具类
 */
export class DeviceInfoUtil {
  /**
   * 获取设备基本信息
   * @returns 设备信息对象
   */
  static getDeviceBasicInfo(): Record<string, string> {
    return {
      deviceName: deviceInfo.deviceName,    // 设备名称
      deviceModel: deviceInfo.deviceModel,  // 设备型号
      osVersion: deviceInfo.osVersion,      // 系统版本
      manufacturer: deviceInfo.manufacturer  // 设备厂商
    };
  }

  /**
   * 获取设备唯一标识(需申请权限)
   * @returns 设备唯一标识
   */
  static getDeviceId(): string {
    return deviceInfo.deviceId;
  }
}
3.1.2 跨进程通信配置(Electron ↔ 鸿蒙)
  1. 鸿蒙端:在ElectronPage.ets中添加通信逻辑,通过WebViewonMessage监听 Electron 的请求,并返回设备信息:

typescript

import web_webview from '@ohos.web.webview';
import router from '@ohos.router';
import { DeviceInfoUtil } from '../utils/DeviceInfoUtil';

@Entry
@Component
struct ElectronPage {
  // WebView控制器
  private webViewController: web_webview.WebViewController = new web_webview.WebViewController();

  build() {
    Column({ space: 10 })
      .width('100%')
      .height('100%') {
        // WebView组件
        Web({
          src: $rawfile('index.html'),
          controller: this.webViewController
        })
        .width('100%')
        .height('90%')
        // 监听Electron发送的消息
        .onMessage((event) => {
          const message = event.message;
          // 处理“获取设备信息”请求
          if (message === 'getDeviceInfo') {
            const deviceInfo = DeviceInfoUtil.getDeviceBasicInfo();
            // 将设备信息发送给Electron
            this.webViewController.postMessage({
              type: 'deviceInfoResult',
              data: deviceInfo
            });
          }
        })

        Button('返回首页')
          .width(150)
          .height(40)
          .onClick(() => {
            router.back();
          })
      }
  }
}
  1. Electron 端:修改index.html的脚本,发送请求并接收设备信息:

html

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>鸿蒙Electron Demo</title>
  <style>
    body { 
      font-family: Arial, sans-serif; 
      text-align: center; 
      padding-top: 50px; 
    }
    #deviceInfo { 
      margin-top: 30px; 
      color: #333; 
      font-size: 16px; 
      line-height: 1.5; 
    }
    button { 
      padding: 10px 20px; 
      font-size: 14px; 
      cursor: pointer; 
    }
  </style>
</head>
<body>
  <h1>Hello 鸿蒙Electron!</h1>
  <button id="getDeviceBtn">获取鸿蒙设备信息</button>
  <div id="deviceInfo"></div>

  <script>
    // 获取DOM元素
    const getDeviceBtn = document.getElementById('getDeviceBtn');
    const deviceInfoDiv = document.getElementById('deviceInfo');

    // 监听按钮点击:发送“获取设备信息”请求到鸿蒙端
    getDeviceBtn.addEventListener('click', () => {
      deviceInfoDiv.innerText = '正在获取设备信息...';
      // 通过WebView的postMessage发送请求(鸿蒙端监听该事件)
      if (window.webkit && window.webkit.messageHandlers) {
        // 鸿蒙WebView适配:通过webkit.messageHandlers发送消息
        window.webkit.messageHandlers.postMessage.postMessage('getDeviceInfo');
      }
    });

    // 监听鸿蒙端返回的消息
    window.addEventListener('message', (event) => {
      const { type, data } = event.data;
      if (type === 'deviceInfoResult') {
        // 渲染设备信息到页面
        deviceInfoDiv.innerText = `
          设备名称:${data.deviceName}
          设备型号:${data.deviceModel}
          系统版本:${data.osVersion}
          设备厂商:${data.manufacturer}
        `;
      }
    });
  </script>
</body>
</html>
3.1.3 调试步骤
  1. 将修改后的index.html复制到鸿蒙工程的rawfile目录;
  2. 启动鸿蒙设备,运行鸿蒙工程;
  3. 点击 “获取鸿蒙设备信息” 按钮,若页面显示设备型号、系统版本等信息,则功能实现成功。

3.2 功能 2:Electron 与鸿蒙的文件交互(读写本地文件)

Electron 可通过 Node.js 的fs模块操作文件,鸿蒙端可通过fileIo模块读写文件,二者结合实现跨端文件共享。

3.2.1 Electron 端代码(文件写入)

修改main.js,添加文件写入功能,并通过 IPC 通信接收渲染进程的文件内容:

javascript

const { app, BrowserWindow, ipcMain } = require('electron');
const path = require('path');
const fs = require('fs');  // 引入Node.js文件模块

let mainWindow;

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

  mainWindow.loadFile('index.html');
  mainWindow.webContents.openDevTools();

  mainWindow.on('closed', () => {
    mainWindow = null;
  });
}

// 监听渲染进程的“写入文件”请求
ipcMain.on('writeFile', (event, { filePath, content }) => {
  try {
    // 写入文件(鸿蒙PC端路径示例:/home/user/Documents/)
    const fullPath = path.join('/home/user/Documents/', filePath);
    fs.writeFileSync(fullPath, content, 'utf8');
    // 发送成功消息给渲染进程
    event.reply('writeFileResult', { success: true, message: `文件已保存至:${fullPath}` });
  } catch (error) {
    // 发送失败消息
    event.reply('writeFileResult', { success: false, message: error.message });
  }
});

app.whenReady().then(createWindow);
app.on('activate', () => {
  if (BrowserWindow.getAllWindows().length === 0) createWindow();
});
app.on('window-all-closed', () => {
  if (process.platform !== 'darwin') app.quit();
});
3.2.2 Electron 渲染端代码(文件输入界面)

修改index.html,添加文件内容输入框和保存按钮:

html

<!-- 新增文件操作区域 -->
<div style="margin-top: 40px; padding: 0 20px;">
  <h3>文件写入测试(保存到鸿蒙PC)</h3>
  <input type="text" id="fileNameInput" placeholder="输入文件名(如test.txt)" style="width: 300px; padding: 8px; margin-right: 10px;">
  <textarea id="fileContentInput" placeholder="输入文件内容..." style="width: 100%; height: 150px; margin-top: 10px; padding: 8px;"></textarea>
  <button id="saveFileBtn" style="margin-top: 10px;">保存文件到鸿蒙PC</button>
  <div id="fileResult" style="margin-top: 10px; color: #666;"></div>
</div>

<script>
  // 新增:文件保存逻辑
  const fileNameInput = document.getElementById('fileNameInput');
  const fileContentInput = document.getElementById('fileContentInput');
  const saveFileBtn = document.getElementById('saveFileBtn');
  const fileResultDiv = document.getElementById('fileResult');

  // 监听保存按钮点击
  saveFileBtn.addEventListener('click', () => {
    const fileName = fileNameInput.value.trim();
    const content = fileContentInput.value.trim();

    if (!fileName) {
      fileResultDiv.innerText = '请输入文件名!';
      return;
    }

    // 通过ipcRenderer发送文件写入请求到主进程
    window.ipcRenderer.send('writeFile', {
      filePath: fileName,
      content: content
    });
  });

  // 监听主进程返回的文件写入结果
  window.ipcRenderer.on('writeFileResult', (event, result) => {
    if (result.success) {
      fileResultDiv.innerText = `成功:${result.message}`;
      fileResultDiv.style.color = 'green';
    } else {
      fileResultDiv.innerText = `失败:${result.message}`;
      fileResultDiv.style.color = 'red';
    }
  });
</script>
3.2.3 鸿蒙端代码(文件读取)

DeviceInfoUtil.ets中添加文件读取方法:

typescript

import fileIo from '@ohos.file.fs';
import { BusinessError } from '@ohos.base';

export class DeviceInfoUtil {
  // ... 原有设备信息方法 ...

  /**
   * 读取鸿蒙本地文件
   * @param filePath 文件路径(如:/home/user/Documents/test.txt)
   * @returns 文件内容
   */
  static async readFile(filePath: string): Promise<string> {
    try {
      // 打开文件
      const file = await fileIo.open(filePath, fileIo.OpenMode.READ_ONLY);
      // 读取文件内容
      const buffer = new ArrayBuffer(4096);
      const readResult = await fileIo.read(file.fd, buffer);
      // 关闭文件
      await fileIo.close(file.fd);
      // 转换buffer为字符串
      return String.fromCharCode.apply(null, new Uint8Array(buffer.slice(0, readResult.bytesRead)));
    } catch (error) {
      const err = error as BusinessError;
      throw new Error(`文件读取失败:${err.message}`);
    }
  }
}

ElectronPage.ets中添加文件读取的通信逻辑:

typescript

// 新增:监听Electron的文件读取请求
.webView.onMessage((event) => {
  const message = event.message;
  if (message.type === 'getDeviceInfo') {
    // ... 原有设备信息逻辑 ...
  } else if (message.type === 'readFile') {
    // 处理文件读取请求
    DeviceInfoUtil.readFile(message.filePath)
      .then(content => {
        this.webViewController.postMessage({
          type: 'readFileResult',
          success: true,
          content: content
        });
      })
      .catch(error => {
        this.webViewController.postMessage({
          type: 'readFileResult',
          success: false,
          message: error.message
        });
      });
  }
})
3.2.4 测试文件交互功能
  1. 在 Electron 界面输入文件名(如test.txt)和内容(如Hello HarmonyOS Electron!);
  2. 点击 “保存文件到鸿蒙 PC”,若提示 “文件已保存至:/home/user/Documents/test.txt”,则写入成功;
  3. 在鸿蒙端调用readFile方法(可添加按钮触发),若能读取到文件内容,则文件交互功能实现成功。

3.3 功能 3:分布式设备资源调用(鸿蒙特色)

借助鸿蒙的分布式软总线,Electron 应用可调用其他鸿蒙设备的资源(如智慧屏的摄像头、手机的麦克风)。本节以 “调用智慧屏摄像头拍照” 为例,演示分布式功能。

3.3.1 鸿蒙端代码(分布式能力配置)
  1. 配置分布式权限:在module.json5中添加分布式相关权限:

json

{
  "module": {
    "abilities": [
      {
        // ... 原有配置 ...
        "permissions": [
          "ohos.permission.DISTRIBUTED_DATASYNC",  // 分布式数据同步权限
          "ohos.permission.CAMERA",                // 摄像头权限
          "ohos.permission.MICROPHONE"             // 麦克风权限(可选)
        ]
      }
    ]
  }
}
  1. 封装分布式摄像头工具类:在src/main/ets/utils目录下创建DistributedCameraUtil.ets

typescript

import distributedDevice from '@ohos.distributedDevice';
import camera from '@ohos.multimedia.camera';
import image from '@ohos.multimedia.image';
import { BusinessError } from '@ohos.base';

/**
 * 分布式摄像头工具类
 */
export class DistributedCameraUtil {
  private static cameraManager: camera.CameraManager | null = null;
  private static currentDeviceId: string = '';  // 分布式设备ID

  /**
   * 发现周边鸿蒙设备
   * @returns 设备列表(设备ID + 设备名称)
   */
  static async discoverDevices(): Promise<Array<{ deviceId: string; deviceName: string }>> {
    try {
      // 获取分布式设备列表
      const deviceList = await distributedDevice.getTrustedDeviceListSync();
      return deviceList.map(device => ({
        deviceId: device.deviceId,
        deviceName: device.deviceName
      }));
    } catch (error) {
      const err = error as BusinessError;
      throw new Error(`设备发现失败:${err.message}`);
    }
  }

  /**
   * 连接分布式设备的摄像头
   * @param deviceId 目标设备ID
   */
  static async connectCamera(deviceId: string): Promise<void> {
    try {
      // 保存目标设备ID
      this.currentDeviceId = deviceId;
      // 获取摄像头管理器(指定分布式设备ID)
      this.cameraManager = await camera.getCameraManager(deviceId);
      if (!this.cameraManager) {
        throw new Error('获取摄像头管理器失败');
      }
    } catch (error) {
      const err = error as BusinessError;
      throw new Error(`摄像头连接失败:${err.message}`);
    }
  }

  /**
   * 拍照(返回图片Base64)
   * @returns 图片Base64字符串
   */
  static async takePhoto(): Promise<string> {
    if (!this.cameraManager || !this.currentDeviceId) {
      throw new Error('未连接分布式摄像头');
    }

    try {
      // 1. 获取摄像头列表
      const cameraList = await this.cameraManager.getCameraList();
      if (cameraList.length === 0) {
        throw new Error('目标设备无可用摄像头');
      }
      const cameraDevice = cameraList[0];  // 选择第一个摄像头

      // 2. 创建拍照会话
      const photoSession = await this.cameraManager.createPhotoSession({
        cameraDevice: cameraDevice,
        // 配置图片输出格式(JPEG)
        outputConfigs: [{
          format: image.ImageFormat.JPEG,
          size: { width: 1280, height: 720 }
        }]
      });

      // 3. 开始拍照
      const photoResult = await photoSession.takePhoto();
      // 4. 关闭会话
      await photoSession.close();

      // 5. 将图片转换为Base64
      const imageBuffer = await photoResult.image.getBuffer();
      const base64Str = Buffer.from(imageBuffer).toString('base64');
      return base64Str;
    } catch (error) {
      const err = error as BusinessError;
      throw new Error(`拍照失败:${err.message}`);
    }
  }
}
3.3.2 Electron 端代码(分布式功能界面)

index.html中添加分布式设备选择和拍照功能:

html

<!-- 新增分布式摄像头区域 -->
<div style="margin-top: 40px; padding: 0 20px; border-top: 1px solid #eee; padding-top: 20px;">
  <h3>分布式摄像头调用(鸿蒙特色)</h3>
  <button id="discoverDevicesBtn">发现周边鸿蒙设备</button>
  <select id="deviceSelect" style="margin-left: 10px; padding: 8px; display: none;"></select>
  <button id="connectCameraBtn" style="margin-left: 10px; display: none;">连接摄像头</button>
  <button id="takePhotoBtn" style="margin-left: 10px; display: none;">拍照</button>
  <div id="cameraResult" style="margin-top: 20px;"></div>
  <img id="photoImg" style="margin-top: 10px; max-width: 100%; display: none;" alt="拍照结果">
</div>

<script>
  // 新增:分布式摄像头逻辑
  const discoverDevicesBtn = document.getElementById('discoverDevicesBtn');
  const deviceSelect = document.getElementById('deviceSelect');
  const connectCameraBtn = document.getElementById('connectCameraBtn');
  const takePhotoBtn = document.getElementById('takePhotoBtn');
  const cameraResultDiv = document.getElementById('cameraResult');
  const photoImg = document.getElementById('photoImg');

  // 1. 发现周边设备
  discoverDevicesBtn.addEventListener('click', () => {
    cameraResultDiv.innerText = '正在发现周边鸿蒙设备...';
    // 发送设备发现请求到鸿蒙端
    window.webkit.messageHandlers.postMessage.postMessage({
      type: 'discoverDevices'
    });
  });

  // 2. 连接摄像头
  connectCameraBtn.addEventListener('click', () => {
    const selectedDeviceId = deviceSelect.value;
    if (!selectedDeviceId) {
      cameraResultDiv.innerText = '请选择目标设备!';
      return;
    }

    cameraResultDiv.innerText = '正在连接目标设备摄像头...';
    // 发送连接请求到鸿蒙端
    window.webkit.messageHandlers.postMessage.postMessage({
      type: 'connectCamera',
      deviceId: selectedDeviceId
    });
  });

  // 3. 拍照
  takePhotoBtn.addEventListener('click', () => {
    cameraResultDiv.innerText = '正在拍照...';
    // 发送拍照请求到鸿蒙端
    window.webkit.messageHandlers.postMessage.postMessage({
      type: 'takePhoto'
    });
  });

  // 监听鸿蒙端返回的分布式消息
  window.addEventListener('message', (event) => {
    const { type, data, success, message, base64Str } = event.data;

    switch (type) {
      case 'discoverDevicesResult':
        if (success) {
          // 渲染设备列表
          deviceSelect.innerHTML = '';
          data.forEach(device => {
            const option = document.createElement('option');
            option.value = device.deviceId;
            option.text = device.deviceName;
            deviceSelect.appendChild(option);
          });
          deviceSelect.style.display = 'inline-block';
          connectCameraBtn.style.display = 'inline-block';
          cameraResultDiv.innerText = `发现${data.length}台鸿蒙设备`;
        } else {
          cameraResultDiv.innerText = `设备发现失败:${message}`;
        }
        break;

      case 'connectCameraResult':
        if (success) {
          takePhotoBtn.style.display = 'inline-block';
          cameraResultDiv.innerText = '摄像头连接成功,可点击拍照';
        } else {
          cameraResultDiv.innerText = `连接失败:${message}`;
        }
        break;

      case 'takePhotoResult':
        if (success) {
          // 显示拍照结果(Base64转图片)
          photoImg.src = `data:image/jpeg;base64,${base64Str}`;
          photoImg.style.display = 'block';
          cameraResultDiv.innerText = '拍照成功';
        } else {
          cameraResultDiv.innerText = `拍照失败:${message}`;
        }
        break;
    }
  });
</script>
3.3.3 鸿蒙端代码(分布式通信逻辑)

ElectronPage.ets中添加分布式功能的通信处理:

typescript

import { DistributedCameraUtil } from '../utils/DistributedCameraUtil';

// 修改onMessage监听逻辑
.webView.onMessage((event) => {
  const message = event.message;
  switch (message.type) {
    case 'getDeviceInfo':
      // ... 原有设备信息逻辑 ...
      break;

    case 'readFile':
      // ... 原有文件读取逻辑 ...
      break;

    case 'discoverDevices':
      // 处理设备发现请求
      DistributedCameraUtil.discoverDevices()
        .then(devices => {
          this.webViewController.postMessage({
            type: 'discoverDevicesResult',
            success: true,
            data: devices
          });
        })
        .catch(error => {
          this.webViewController.postMessage({
            type: 'discoverDevicesResult',
            success: false,
            message: error.message
          });
        });
      break;

    case 'connectCamera':
      // 处理摄像头连接请求
      DistributedCameraUtil.connectCamera(message.deviceId)
        .then(() => {
          this.webViewController.postMessage({
            type: 'connectCameraResult',
            success: true
          });
        })
        .catch(error => {
          this.webViewController.postMessage({
            type: 'connectCameraResult',
            success: false,
            message: error.message
          });
        });
      break;

    case 'takePhoto':
      // 处理拍照请求
      DistributedCameraUtil.takePhoto()
        .then(base64Str => {
          this.webViewController.postMessage({
            type: 'takePhotoResult',
            success: true,
            base64Str: base64Str
          });
        })
        .catch(error => {
          this.webViewController.postMessage({
            type: 'takePhotoResult',
            success: false,
            message: error.message
          });
        });
      break;
  }
})
3.3.4 测试分布式功能
  1. 确保鸿蒙 PC 与智慧屏(或其他鸿蒙设备)处于同一局域网,并已登录同一华为账号(信任设备);
  2. 运行鸿蒙工程,点击 “发现周边鸿蒙设备”,若能显示智慧屏设备,则发现功能成功;
  3. 选择智慧屏,点击 “连接摄像头”,若提示 “摄像头连接成功”,则连接功能成功;
  4. 点击 “拍照”,若能显示智慧屏摄像头拍摄的图片,则分布式摄像头功能实现成功。

3.4 核心功能开发常见问题解决

问题现象 解决方案 参考链接
分布式设备发现为空 1. 确认设备已登录同一华为账号;2. 开启设备 “分布式网络” 功能;3. 检查权限是否添加(DISTRIBUTED_DATASYNC) 鸿蒙分布式设备管理
摄像头调用报错 “Permission denied” 1. 在鸿蒙设备的 “设置> 应用 > 权限管理” 中授予应用摄像头权限;2. 确保module.json5中已添加 CAMERA 权限 鸿蒙应用权限申请
图片 Base64 显示空白 1. 检查 Base64 字符串是否完整;2. 确认图片格式(JPEG/PNG)与data:image/xxx匹配;3. 缩小图片尺寸(避免 Base64 过长) 鸿蒙图片处理指南

四、鸿蒙 Electron 应用场景与进阶实践

4.1 典型应用场景

4.1.1 企业办公软件
  • 场景描述:开发跨端办公套件(如文档编辑、会议软件),PC 端用 Electron 实现复杂编辑功能,智慧屏端实现投屏演示,手机端实现移动审批。
  • 技术优势:复用 Web 技术栈的办公组件(如 Quill、TinyMCE),借助鸿蒙分布式能力实现 “编辑 - 演示 - 审批” 无缝流转。
  • 案例参考:华为鸿蒙 PC 端的 “华为文档”,支持与手机、智慧屏同步编辑。
4.1.2 工具类应用
  • 场景描述:开发代码编辑器、思维导图工具,PC 端用 Electron 实现高效编辑,平板端用鸿蒙实现手写批注,手机端实现文件管理。
  • 技术优势:Electron 支持 Node.js 后端能力(如代码编译、文件解析),鸿蒙支持手写笔、触摸屏等硬件适配。
  • 开发建议:使用 Monaco Editor(VS Code 核心)作为代码编辑组件,集成鸿蒙的手写笔 API 实现批注功能。
4.1.3 多媒体应用
  • 场景描述:开发视频剪辑、直播工具,PC 端用 Electron 实现视频编辑,智慧屏端实现预览,手机端实现远程控制。
  • 技术优势:Electron 可集成 FFmpeg(视频处理)、WebRTC(直播),鸿蒙可调用多设备的音视频资源(如 PC 麦克风、智慧屏扬声器)。
  • 资源推荐Electron-FFmpeg 集成文档鸿蒙 WebRTC 开发指南

4.2 进阶实践:应用打包与发布

4.2.1 Electron 端打包(鸿蒙 PC)

使用electron-packager将 Electron 应用打包为鸿蒙 PC 端可执行文件:

  1. 安装打包工具:

bash

npm install electron-packager --save-dev
  1. 添加打包脚本(package.json):

json

"scripts": {
  "start": "electron .",
  "package:harmony": "electron-packager . HarmonyElectronApp --platform linux --arch x64 --out dist --overwrite"
}
  1. 执行打包命令:

bash

npm run package:harmony
  • 打包结果位于dist/HarmonyElectronApp-linux-x64目录,可直接在鸿蒙 PC 端运行。
4.2.2 鸿蒙端打包(HAP 文件)

通过 DevEco Studio 打包鸿蒙应用为 HAP 文件(可发布到鸿蒙应用市场):

  1. 配置签名:进入「Project Structure > Project > Signing Configs」,创建签名证书(需申请鸿蒙开发者账号);
  2. 打包 HAP:点击「Build > Build HAP (s)」,生成的 HAP 文件位于build/outputs/hap目录;
  3. 发布渠道:通过鸿蒙应用市场开发者平台提交 HAP 文件审核。

4.3 性能优化建议

  1. 减少 Electron 窗口渲染压力

    • 使用webPreferences.plugins = false禁用插件;
    • 避免大量 DOM 操作,使用虚拟列表(如 Vue Virtual Scroller)处理长列表。
  2. 优化鸿蒙分布式通信

    • 减少跨设备数据传输量(如压缩图片、分块传输);
    • 使用鸿蒙的DistributedData模块缓存常用数据,避免重复请求。
  3. 内存管理

    • Electron 主进程避免创建过多窗口(每个窗口占用独立内存);
    • 鸿蒙端及时释放不再使用的资源(如关闭摄像头会话、释放文件句柄)。

五、总结与资源推荐

5.1 开发总结

鸿蒙与 Electron 结合,为全场景应用开发提供了 “Web 技术复用 + 分布式能力” 的新路径。核心要点总结如下:

  1. 环境搭建:需同步配置 Node.js(16.x)、Electron(22.3.2)、DevEco Studio(4.0+),确保版本兼容;
  2. 核心通信:通过 WebView 的onMessagepostMessage实现 Electron 与鸿蒙的跨进程通信;
  3. 分布式能力:借助鸿蒙的distributedDevicecamera等模块,实现多设备资源调用;
  4. 场景落地:适合企业办公、工具类、多媒体等场景,可快速复用 Web 生态资源。

5.2 官方资源推荐

5.3 学习路径建议

  1. 入门阶段:掌握 Electron 基础(窗口创建、IPC 通信)、鸿蒙 DevEco Studio 使用;
  2. 进阶阶段:学习鸿蒙分布式 API、Electron 与鸿蒙的通信适配;
  3. 实战阶段:开发小型工具应用(如记事本、图片查看器),熟悉打包与发布流程;
  4. 深入阶段:优化性能、集成复杂功能(如视频处理、数据库),参与开源项目。

通过本文的代码示例与指南,希望能帮助开发者快速上手鸿蒙 Electron 开发,共同推动鸿蒙桌面端生态的完善。若在实践中遇到问题,可通过鸿蒙开发者论坛(HarmonyOS 开发者论坛)或 Electron 社区(Electron Community)寻求帮助。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐