请添加图片描述

项目概述

本项目是一个基于Electron框架开发的仪表盘组件应用,为鸿蒙PC平台提供直观的仪表盘可视化功能。这是Electron初学者学习仪表盘可视化的理想示例,展示了如何使用Canvas API实现交互式仪表盘。

功能特点

  • 仪表盘渲染:使用Canvas API绘制精美的仪表盘
  • 数值展示:支持实时数值显示和动画过渡效果
  • 交互功能:支持鼠标拖拽调整数值、点击设置等交互操作
  • 颜色渐变:内置颜色渐变方案,根据数值范围显示不同颜色
  • 多指针支持:支持单个或多个指针同时显示
  • 自定义配置:支持自定义角度范围、刻度、标签等
  • 响应式设计:适配不同窗口大小的界面布局
  • 鸿蒙PC适配:针对鸿蒙PC平台优化的用户体验

技术架构

核心架构

  • 主进程 (Main Process):由main.js实现,负责应用程序生命周期管理和窗口创建
  • 渲染进程 (Renderer Process):由renderer.js实现,负责仪表盘渲染和用户交互
  • 预加载脚本 (Preload Script):由preload.js实现,提供安全的进程间通信桥接

文件结构

96-gauge-chart/
├── main.js         # 主进程文件,应用入口
├── preload.js      # 预加载脚本,进程通信
├── index.html      # 应用界面
├── style.css       # 样式文件
├── renderer.js     # 渲染进程逻辑
└── README.md       # 项目说明文档

核心代码解析

主进程 (main.js)

主进程负责创建和管理应用窗口,处理文件系统操作:

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

function createWindow() {
  // 创建浏览器窗口并配置安全选项
  const win = new BrowserWindow({
    width: 800,
    height: 600,
    title: '仪表盘组件 - Electron for 鸿蒙PC项目实战案例',
    webPreferences: {
      preload: path.join(__dirname, 'preload.js'),
      nodeIntegration: false,
      contextIsolation: true,
      sandbox: true
    }
  });
  
  win.loadFile('index.html');
}

// 应用生命周期管理
app.whenReady().then(() => {
  createWindow();
  
  app.on('activate', () => {
    if (BrowserWindow.getAllWindows().length === 0) createWindow();
  });
});

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

渲染进程 (renderer.js)

渲染进程负责仪表盘的核心渲染逻辑:

class GaugeChart {
  constructor(canvasId, options = {}) {
    this.canvas = document.getElementById(canvasId);
    this.ctx = this.canvas.getContext('2d');
    this.value = options.value || 0;
    this.min = options.min || 0;
    this.max = options.max || 100;
    this.title = options.title || '仪表盘';
    this.startAngle = options.startAngle || -135; // 开始角度(度)
    this.endAngle = options.endAngle || 135;     // 结束角度(度)
    this.colors = options.colors || ['#e74c3c', '#f39c12', '#2ecc71'];
    this.init();
  }
  
  // 初始化图表
  init() {
    this.calculateDimensions();
    this.render();
    this.bindEvents();
  }
  
  // 绘制仪表盘
  render() {
    // 清空画布
    this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
    
    const centerX = this.canvas.width / 2;
    const centerY = this.canvas.height * 0.7; // 底部留出空间
    const radius = Math.min(centerX, centerY) - 50;
    
    // 绘制背景弧
    this.drawBackgroundArc(centerX, centerY, radius);
    
    // 绘制渐变弧
    this.drawGradientArc(centerX, centerY, radius);
    
    // 绘制刻度和标签
    this.drawTicksAndLabels(centerX, centerY, radius);
    
    // 绘制指针
    this.drawPointer(centerX, centerY, radius);
    
    // 绘制中心圆
    this.drawCenterCircle(centerX, centerY);
    
    // 绘制标题和数值
    this.drawTitleAndValue(centerX, centerY);
  }
  
  // 绘制背景弧
  drawBackgroundArc(centerX, centerY, radius) {
    const startRad = (this.startAngle * Math.PI) / 180;
    const endRad = (this.endAngle * Math.PI) / 180;
    
    this.ctx.beginPath();
    this.ctx.arc(centerX, centerY, radius, startRad, endRad);
    this.ctx.lineWidth = 20;
    this.ctx.strokeStyle = '#e0e0e0';
    this.ctx.stroke();
  }
  
  // 绘制渐变弧
  drawGradientArc(centerX, centerY, radius) {
    const startRad = (this.startAngle * Math.PI) / 180;
    const endRad = (this.endAngle * Math.PI) / 180;
    const angleRange = endRad - startRad;
    
    // 创建渐变
    const gradient = this.ctx.createLinearGradient(centerX - radius, centerY, centerX + radius, centerY);
    for (let i = 0; i < this.colors.length; i++) {
      gradient.addColorStop(i / (this.colors.length - 1), this.colors[i]);
    }
    
    // 计算当前值对应的角度
    const valueRatio = (this.value - this.min) / (this.max - this.min);
    const currentEndRad = startRad + (angleRange * valueRatio);
    
    // 绘制渐变弧
    this.ctx.beginPath();
    this.ctx.arc(centerX, centerY, radius, startRad, currentEndRad);
    this.ctx.lineWidth = 20;
    this.ctx.strokeStyle = gradient;
    this.ctx.stroke();
  }
  
  // 绘制指针
  drawPointer(centerX, centerY, radius) {
    const startRad = (this.startAngle * Math.PI) / 180;
    const endRad = (this.endAngle * Math.PI) / 180;
    const angleRange = endRad - startRad;
    
    // 计算当前值对应的角度
    const valueRatio = (this.value - this.min) / (this.max - this.min);
    const pointerAngle = startRad + (angleRange * valueRatio);
    
    // 指针长度
    const pointerLength = radius * 0.8;
    
    // 绘制指针
    this.ctx.save();
    this.ctx.translate(centerX, centerY);
    this.ctx.rotate(pointerAngle);
    
    this.ctx.beginPath();
    this.ctx.moveTo(0, -5);
    this.ctx.lineTo(pointerLength, 0);
    this.ctx.lineTo(0, 5);
    this.ctx.closePath();
    
    this.ctx.fillStyle = '#34495e';
    this.ctx.fill();
    
    this.ctx.restore();
  }
  
  // 设置新值(带动画)
  setValue(newValue, animate = true) {
    if (animate) {
      const startValue = this.value;
      const change = newValue - startValue;
      const duration = 500; // 动画持续时间
      const startTime = Date.now();
      
      const animateValue = () => {
        const elapsed = Date.now() - startTime;
        const progress = Math.min(elapsed / duration, 1);
        
        // 使用缓动函数
        const easedProgress = this.easeInOutCubic(progress);
        this.value = startValue + (change * easedProgress);
        
        this.render();
        
        if (progress < 1) {
          requestAnimationFrame(animateValue);
        }
      };
      
      requestAnimationFrame(animateValue);
    } else {
      this.value = newValue;
      this.render();
    }
  }
  
  // 缓动函数
  easeInOutCubic(t) {
    return t < 0.5 ? 4 * t * t * t : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1;
  }
  
  // 其他方法:绘制刻度、标签、中心圆、处理事件等
}

// 应用初始化
window.addEventListener('DOMContentLoaded', () => {
  // 创建仪表盘实例
  const gaugeChart = new GaugeChart('gaugeChart', {
    value: 75,
    min: 0,
    max: 100,
    title: '速度仪表盘',
    colors: ['#e74c3c', '#f39c12', '#2ecc71']
  });
  
  // 绑定控制事件
  document.getElementById('updateButton').addEventListener('click', () => {
    const newValue = parseFloat(document.getElementById('valueInput').value);
    if (!isNaN(newValue)) {
      gaugeChart.setValue(newValue);
    }
  });
  
  // 添加随机值按钮
  document.getElementById('randomButton').addEventListener('click', () => {
    const randomValue = Math.random() * 100;
    gaugeChart.setValue(randomValue);
  });
});

如何运行

  1. 克隆本项目
  2. 安装依赖:npm install
  3. 启动应用:npm start

鸿蒙PC适配改造指南

1. 环境准备

  • 系统要求:Windows 10/11、8GB RAM以上、20GB可用空间

  • 工具安装
    DevEco Studio 5.0+(安装鸿蒙SDK API 20+)

  • Node.js 18.x+

2. 获取Electron鸿蒙编译产物

  1. 登录Electron 鸿蒙官方仓库

  2. 下载Electron 34+版本的Release包(.zip格式)

  3. 解压到项目目录,确认electron/libs/arm64-v8a/下包含核心.so库

3. 部署应用代码

将Electron应用代码按以下目录结构放置:
在这里插入图片描述


web_engine/src/main/resources/resfile/resources/app/
├── main.js
├── package.json
└── src/
    ├── index.html
    ├── preload.js
    ├── renderer.js
    └── style.css

4. 配置与运行

  1. 打开项目:在DevEco Studio中打开ohos_hap目录

  2. 配置签名
    进入File → Project Structure → Signing Configs

  3. 自动生成调试签名或导入已有签名

  4. 连接设备
    启用鸿蒙设备开发者模式和USB调试

  5. 通过USB Type-C连接电脑

  6. 编译运行:点击Run按钮或按Shift+F10

5. 验证检查项

  • ✅ 应用窗口正常显示

  • ✅ 窗口大小可调整,响应式布局生效

  • ✅ 控制台无"SysCap不匹配"或"找不到.so文件"错误

  • ✅ 动画效果正常播放

跨平台兼容性

平台 适配策略 特殊处理
Windows 标准Electron运行 无特殊配置
macOS 标准Electron运行 保留dock图标激活逻辑
Linux 标准Electron运行 确保系统依赖库完整
鸿蒙PC 通过Electron鸿蒙适配层 禁用硬件加速,使用特定目录结构

鸿蒙开发调试技巧

1. 日志查看

在DevEco Studio的Log面板中过滤"Electron"关键词,查看应用运行日志和错误信息。

2. 常见问题解决

  • "SysCap不匹配"错误:检查module.json5中的reqSysCapabilities,只保留必要系统能力

  • "找不到.so文件"错误:确认arm64-v8a目录下四个核心库文件完整

  • 窗口不显示:在main.js中添加app.disableHardwareAcceleration()

  • 动画卡顿:简化CSS动画效果,减少重绘频率

Logo

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

更多推荐