欢迎加入开源鸿蒙PC社区:
https://harmonypc.csdn.net/

仓库源码地址:
https://gitcode.com/feng8403000/Heartratemonitoring

效果演示

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

1. 项目背景与目标

随着健康意识的提高,心率监测成为人们关注的焦点。在鸿蒙PC平台上开发一款简易的心率监测工具,不仅可以帮助用户了解自己的心率状况,还可以展示如何在鸿蒙平台上使用Electron框架和Canvas技术开发实用工具。

1.1 项目概述

本项目基于 Electron 和鸿蒙原生能力开发的一款心率监测工具,具有以下特点:

  • 基于 HTML5 + CSS3 + JavaScript 开发
  • 运行在 Electron (HarmonyOS 定制版) 上
  • 支持中文界面和中文菜单栏
  • 完整的心率监测功能,包括心率数据模拟、波形绘制、数据统计等
  • 使用 Canvas 技术绘制实时心率波形图

1.2 技术栈

技术 版本 用途
HTML5 - 页面结构
CSS3 - 样式设计
JavaScript ES6+ 游戏逻辑
Electron HarmonyOS 定制版 运行时环境
Canvas - 绘制心率波形图
ArkTS - 鸿蒙原生能力

2. 项目结构设计

2.1 整体架构

ohos_hap/
├── electron/                    # HAP 入口模块
├── web_engine/                  # 核心引擎模块
│   └── src/main/
│       ├── ets/                 # 鸿蒙原生代码
│       └── resources/resfile/resources/app/
│           ├── main.js          # Electron 主进程
│           ├── preload.js       # 桥接脚本
│           ├── index.html       # 主页面
│           └── heartrate.html   # 心率监测工具页面
└── docs/                        # 文档资料

2.2 核心文件说明

文件 功能
main.js Electron 主进程,负责创建窗口、菜单栏等
preload.js 桥接脚本,用于主进程和渲染进程之间的通信
index.html 应用主页面
heartrate.html 心率监测工具页面

3. 核心功能实现

3.1 中文菜单栏实现

main.js 文件中,我们实现了中文菜单栏,包括文件、编辑、视图、工具和帮助五个主要菜单项:

// 创建中文菜单栏
const template = [
    {
        label: '文件',
        submenu: [
            {
                label: '新建',
                accelerator: 'CmdOrCtrl+N',
                click: () => {
                    console.log('新建');
                }
            },
            {
                label: '打开',
                accelerator: 'CmdOrCtrl+O',
                click: () => {
                    console.log('打开');
                }
            },
            {
                label: '保存',
                accelerator: 'CmdOrCtrl+S',
                click: () => {
                    console.log('保存');
                }
            },
            {
                type: 'separator'
            },
            {
                label: '退出',
                accelerator: 'CmdOrCtrl+Q',
                click: () => {
                    app.quit();
                }
            }
        ]
    },
    // 其他菜单项...
    {
        label: '工具',
        submenu: [
            {
                label: '心率监测',
                click: () => {
                    mainWindow?.loadFile(path.join(__dirname, 'heartrate.html'));
                }
            }
        ]
    },
    // 帮助菜单项...
];

const menu = Menu.buildFromTemplate(template);
Menu.setApplicationMenu(menu);

3.2 心率数据模拟

我们实现了一个更加符合人体心率波动特点的模拟算法:

// 更新心率数据
function updateHeartrate() {
    // 模拟心率数据 - 更加符合人体心率波动特点
    
    // 缓慢调整基础心率(每10秒左右有一个小的趋势变化)
    if (Math.random() < 0.1) { // 10%的概率调整基础心率
        heartrateData.baseRate += (Math.random() - 0.5) * 4; // 基础心率变化范围±2
        // 保持基础心率在正常范围内
        heartrateData.baseRate = Math.max(60, Math.min(100, heartrateData.baseRate));
    }
    
    // 添加小幅度的随机波动(相邻数据变化不超过3次/分钟)
    const maxChange = 3;
    const fluctuation = (Math.random() - 0.5) * 2 * maxChange;
    
    // 计算当前心率
    let rate = Math.round(heartrateData.baseRate + fluctuation);
    
    // 限制心率范围
    rate = Math.max(50, Math.min(110, rate));
    
    // 确保相邻数据变化不太大
    if (heartrateData.currentRate > 0) {
        const diff = Math.abs(rate - heartrateData.currentRate);
        if (diff > maxChange) {
            // 限制变化幅度
            rate = heartrateData.currentRate + Math.sign(rate - heartrateData.currentRate) * maxChange;
        }
    }
    
    // 更新数据
    heartrateData.currentRate = rate;
    heartrateData.values.push(rate);
    heartrateData.times.push(Date.now());
    
    // 保持数据长度
    if (heartrateData.values.length > config.dataLength) {
        heartrateData.values.shift();
        heartrateData.times.shift();
    }
    
    // 更新统计数据
    heartrateData.totalRate += rate;
    heartrateData.count++;
    heartrateData.averageRate = Math.round(heartrateData.totalRate / heartrateData.count);
    heartrateData.maxRate = Math.max(heartrateData.maxRate, rate);
    heartrateData.minRate = heartrateData.count === 1 ? rate : Math.min(heartrateData.minRate, rate);
}

3.3 Canvas 绘制心率波形图

使用 Canvas 技术绘制实时心率波形图:

// 绘制心率波形
function drawHeartrateWave() {
    if (heartrateData.values.length === 0) return;
    
    ctx.strokeStyle = '#007AFF';
    ctx.lineWidth = 2;
    ctx.beginPath();
    
    // 计算心率值的范围
    const minValue = Math.min(...heartrateData.values);
    const maxValue = Math.max(...heartrateData.values);
    const valueRange = maxValue - minValue || 1;
    
    // 绘制波形
    for (let i = 0; i < heartrateData.values.length; i++) {
        const x = (i / (heartrateData.values.length - 1)) * width;
        // 归一化心率值到 0-1 范围
        const normalizedValue = (heartrateData.values[i] - minValue) / valueRange;
        // 映射到画布高度
        const y = height - (normalizedValue * (height - 40) + 20);
        
        if (i === 0) {
            ctx.moveTo(x, y);
        } else {
            ctx.lineTo(x, y);
        }
    }
    
    ctx.stroke();
}

3.4 心率数据统计和状态判断

实现了心率数据的统计和状态判断功能:

// 更新 UI
function updateUI() {
    // 更新心率数据
    document.getElementById('currentRate').textContent = heartrateData.currentRate;
    document.getElementById('averageRate').textContent = heartrateData.averageRate;
    document.getElementById('maxRate').textContent = heartrateData.maxRate;
    document.getElementById('minRate').textContent = heartrateData.minRate;
    
    // 更新心率状态
    const statusElement = document.getElementById('heartrateStatus');
    if (heartrateData.currentRate > config.highThreshold) {
        statusElement.textContent = '偏高';
        statusElement.className = 'status status-high';
    } else if (heartrateData.currentRate < config.lowThreshold) {
        statusElement.textContent = '偏低';
        statusElement.className = 'status status-low';
    } else {
        statusElement.textContent = '正常';
        statusElement.className = 'status status-normal';
    }
}

4. 技术实现细节

4.1 Electron 主进程配置

main.js 文件中,我们配置了 Electron 主进程,包括创建窗口、添加菜单栏等:

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

    mainWindow.setWindowButtonVisibility(true);

    // 创建中文菜单栏
    // ... 菜单栏代码 ...

    // 加载默认的HTML文件
    mainWindow.loadFile(path.join(__dirname, 'index.html'));

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

4.2 心率监测配置

我们使用一个 config 对象来存储心率监测的配置参数:

// 心率监测配置
const config = {
    samplingRate: 1000, // 采样率 (ms)
    dataLength: 60,   // 数据点数量
    normalRange: { min: 60, max: 100 }, // 正常心率范围
    highThreshold: 100, // 高心率阈值
    lowThreshold: 60,   // 低心率阈值
    baseAmplitude: 50,  // 基础振幅
    maxAmplitude: 100,  // 最大振幅
    noiseLevel: 5      // 噪声水平
};

4.3 心率数据管理

使用一个 heartrateData 对象来管理心率数据:

// 心率数据
let heartrateData = {
    values: [],         // 心率值
    times: [],          // 时间戳
    isMonitoring: false, // 是否正在监测
    timer: null,        // 定时器
    currentRate: 0,     // 当前心率
    averageRate: 0,     // 平均心率
    maxRate: 0,         // 最大心率
    minRate: 0,         // 最小心率
    totalRate: 0,       // 总心率值
    count: 0,           // 数据点数量
    baseRate: 75        // 基础心率
};

4.4 响应式设计

实现了响应式的 Canvas 绘制:

// 调整 Canvas 尺寸
function resizeCanvas() {
    const container = canvas.parentElement;
    canvas.width = container.clientWidth;
    canvas.height = container.clientHeight;
    width = canvas.width;
    height = canvas.height;
    drawChart();
}

// 监听窗口大小变化
window.addEventListener('resize', resizeCanvas);

5. 开发过程中的挑战与解决方案

5.1 心率数据模拟的真实性

挑战:如何模拟出更加真实的心率数据,符合人体心率的波动特点。

解决方案

  • 实现了基础心率调整机制,每10秒左右有一个小的趋势变化
  • 限制相邻数据的波动幅度,确保变化平滑自然
  • 保持心率在正常范围内(50-110次/分钟)

5.2 Canvas 绘制的性能

挑战:如何确保 Canvas 绘制的性能,避免卡顿。

解决方案

  • 使用 requestAnimationFrame 的替代方案,通过定时器控制绘制频率
  • 优化绘制逻辑,减少不必要的计算
  • 合理设置数据点数量,平衡视觉效果和性能

5.3 界面响应式设计

挑战:如何确保界面在不同屏幕尺寸下都能正常显示。

解决方案

  • 使用 CSS Grid 和 Flexbox 布局
  • 实现 Canvas 尺寸的动态调整
  • 使用相对单位,确保界面元素的比例协调

5.4 心率状态判断的准确性

挑战:如何准确判断心率状态(正常、偏高、偏低)。

解决方案

  • 参考医学标准,设置合理的心率阈值
  • 实时更新心率状态,并通过颜色变化直观显示
  • 提供详细的心率数据统计,帮助用户了解自己的心率状况

6. 功能测试

6.1 心率监测功能测试

测试项 预期结果 实际结果
开始监测 开始显示心率数据和波形图
停止监测 停止更新心率数据和波形图
重置 重置所有数据和界面
心率波动 心率数据平滑波动,符合人体实际情况
数据统计 正确计算平均心率、最大心率和最小心率
状态判断 正确判断心率状态(正常、偏高、偏低)

6.2 界面测试

测试项 预期结果 实际结果
中文菜单栏 显示中文菜单项
响应式设计 在不同屏幕尺寸下正常显示
波形图绘制 平滑绘制心率波形图
数据显示 清晰显示心率数据和统计信息

7. 未来扩展计划

7.1 功能扩展

  1. 数据持久化:添加心率数据的本地存储功能,记录用户的心率历史。
  2. 数据分析:添加心率数据分析功能,生成日报、周报和月报。
  3. 告警功能:当心率异常时,提供声音或通知告警。
  4. 导出功能:支持导出心率数据为 CSV 或 PDF 格式。
  5. 主题切换:添加深色模式和浅色模式切换功能。

7.2 技术优化

  1. 性能优化:进一步优化 Canvas 绘制性能,支持更高的采样率。
  2. 代码重构:使用模块化的方式重构代码,提高代码的可维护性。
  3. 测试覆盖:添加单元测试,确保代码的质量。
  4. 国际化:添加多语言支持,使工具能够在不同语言环境下运行。
  5. 与硬件集成:支持与心率监测硬件设备的集成,获取真实的心率数据。

8. 总结

通过本项目的开发,我们成功在鸿蒙平台上实现了一款功能完整、界面美观的心率监测工具。项目使用了 HTML5 + CSS3 + JavaScript 技术栈,运行在 Electron (HarmonyOS 定制版) 上,支持中文界面和中文菜单栏。

本项目的开发过程中,我们遇到了一些挑战,如心率数据模拟的真实性、Canvas 绘制的性能、界面响应式设计等,但通过合理的设计和实现,我们成功解决了这些问题。

未来,我们计划进一步扩展功能,优化技术实现,使工具更加完善和实用。同时,我们也希望通过本项目的开发,为鸿蒙平台的应用开发提供一些参考和借鉴。

8.1 项目亮点

  1. 真实的心率数据模拟:实现了更加符合人体心率波动特点的模拟算法。
  2. 实时波形绘制:使用 Canvas 技术绘制实时心率波形图,视觉效果直观。
  3. 完整的数据统计:提供当前心率、平均心率、最大心率和最小心率的统计。
  4. 智能状态判断:自动判断心率状态,并通过颜色变化直观显示。
  5. 响应式设计:界面在不同屏幕尺寸下都能正常显示。
  6. 中文用户界面:所有界面元素都是中文的,符合中文用户的使用习惯。

8.2 技术价值

  1. 鸿蒙平台应用开发:展示了如何在鸿蒙平台上开发桌面应用。
  2. Electron 应用开发:展示了如何使用 Electron 开发跨平台应用。
  3. Canvas 绘图技术:展示了如何使用 Canvas 绘制实时波形图。
  4. 数据模拟:展示了如何模拟真实的生物数据。
  5. 响应式设计:展示了如何实现响应式的界面设计。

9. 附录

9.1 完整代码

9.1.1 main.js
const { app, BrowserWindow, ipcMain, Menu } = require('electron');
const path = require('path');

let mainWindow;
app.disableHardwareAcceleration();

// 是否为开发模式
const isDev = process.env.NODE_ENV === 'development';

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

    mainWindow.setWindowButtonVisibility(true);

    // 创建中文菜单栏
    const template = [
        {
            label: '文件',
            submenu: [
                {
                    label: '新建',
                    accelerator: 'CmdOrCtrl+N',
                    click: () => {
                        console.log('新建');
                    }
                },
                {
                    label: '打开',
                    accelerator: 'CmdOrCtrl+O',
                    click: () => {
                        console.log('打开');
                    }
                },
                {
                    label: '保存',
                    accelerator: 'CmdOrCtrl+S',
                    click: () => {
                        console.log('保存');
                    }
                },
                {
                    type: 'separator'
                },
                {
                    label: '退出',
                    accelerator: 'CmdOrCtrl+Q',
                    click: () => {
                        app.quit();
                    }
                }
            ]
        },
        {
            label: '编辑',
            submenu: [
                {
                    label: '撤销',
                    accelerator: 'CmdOrCtrl+Z',
                    click: () => {
                        console.log('撤销');
                    }
                },
                {
                    label: '重做',
                    accelerator: 'CmdOrCtrl+Y',
                    click: () => {
                        console.log('重做');
                    }
                },
                {
                    type: 'separator'
                },
                {
                    label: '剪切',
                    accelerator: 'CmdOrCtrl+X',
                    click: () => {
                        console.log('剪切');
                    }
                },
                {
                    label: '复制',
                    accelerator: 'CmdOrCtrl+C',
                    click: () => {
                        console.log('复制');
                    }
                },
                {
                    label: '粘贴',
                    accelerator: 'CmdOrCtrl+V',
                    click: () => {
                        console.log('粘贴');
                    }
                },
                {
                    label: '全选',
                    accelerator: 'CmdOrCtrl+A',
                    click: () => {
                        console.log('全选');
                    }
                }
            ]
        },
        {
            label: '视图',
            submenu: [
                {
                    label: '刷新',
                    accelerator: 'CmdOrCtrl+R',
                    click: () => {
                        mainWindow?.reload();
                    }
                },
                {
                    label: '切换全屏',
                    accelerator: 'F11',
                    click: () => {
                        mainWindow?.setFullScreen(!mainWindow?.isFullScreen());
                    }
                },
                {
                    type: 'separator'
                },
                {
                    label: '开发者工具',
                    accelerator: 'CmdOrCtrl+Shift+I',
                    click: () => {
                        mainWindow?.webContents.openDevTools();
                    }
                }
            ]
        },
        {
            label: '工具',
            submenu: [
                {
                    label: '心率监测',
                    click: () => {
                        mainWindow?.loadFile(path.join(__dirname, 'heartrate.html'));
                    }
                }
            ]
        },
        {
            label: '帮助',
            submenu: [
                {
                    label: '关于',
                    click: () => {
                        console.log('关于');
                    }
                },
                {
                    label: '使用帮助',
                    click: () => {
                        console.log('使用帮助');
                    }
                }
            ]
        }
    ];

    const menu = Menu.buildFromTemplate(template);
    Menu.setApplicationMenu(menu);

    // 加载默认的HTML文件
    mainWindow.loadFile(path.join(__dirname, 'index.html'));

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

// ============ IPC 处理器 - 桥接鸿蒙原生能力 ============

// 系统信息
ipcMain.handle('ohos:getSystemInfo', async () => {
    // 这些会通过 JsBinding 调用鸿蒙原生 API
    return {
        platform: 'HarmonyOS',
        version: '5.0',
        deviceType: 'tablet'
    };
});

// 文件操作
ipcMain.handle('ohos:showOpenDialog', async (event, options) => {
    const { dialog } = require('electron');
    return await dialog.showOpenDialog(mainWindow, options);
});

ipcMain.handle('ohos:showSaveDialog', async (event, options) => {
    const { dialog } = require('electron');
    return await dialog.showSaveDialog(mainWindow, options);
});

// 通知
ipcMain.handle('ohos:showNotification', async (event, { title, body }) => {
    const { Notification } = require('electron');
    new Notification({ title, body }).show();
    return true;
});

// 剪贴板
ipcMain.handle('ohos:clipboard:read', async () => {
    const { clipboard } = require('electron');
    return clipboard.readText();
});

ipcMain.handle('ohos:clipboard:write', async (event, text) => {
    const { clipboard } = require('electron');
    clipboard.writeText(text);
    return true;
});

// 窗口控制
ipcMain.handle('ohos:window:minimize', async () => {
    mainWindow?.minimize();
});

ipcMain.handle('ohos:window:maximize', async () => {
    if (mainWindow?.isMaximized()) {
        mainWindow.unmaximize();
    } else {
        mainWindow?.maximize();
    }
});

ipcMain.handle('ohos:window:close', async () => {
    mainWindow?.close();
});

ipcMain.handle('ohos:window:setTitle', async (event, title) => {
    mainWindow?.setTitle(title);
});

ipcMain.handle('ohos:window:setSize', async (event, { width, height }) => {
    mainWindow?.setSize(width, height);
});

// 应用生命周期
app.whenReady().then(createWindow);

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

app.on('activate', () => {
    if (mainWindow === null) {
        createWindow();
    }
});
9.1.2 heartrate.html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>心率监测工具</title>
    <style>
        body {
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
            margin: 0;
            padding: 20px;
            background-color: #f5f5f5;
            color: #333;
        }
        .container {
            max-width: 1000px;
            margin: 0 auto;
            background-color: white;
            border-radius: 8px;
            box-shadow: 0 2px 10px rgba(0,0,0,0.1);
            padding: 30px;
        }
        h1 {
            text-align: center;
            color: #007AFF;
            margin-bottom: 30px;
        }
        .controls {
            display: flex;
            justify-content: center;
            gap: 15px;
            margin-bottom: 30px;
        }
        button {
            padding: 12px 24px;
            font-size: 16px;
            border: none;
            border-radius: 4px;
            cursor: pointer;
            background-color: #007AFF;
            color: white;
            transition: background-color 0.2s ease;
        }
        button:hover {
            background-color: #0056b3;
        }
        button:active {
            transform: scale(0.98);
        }
        button:disabled {
            background-color: #cccccc;
            cursor: not-allowed;
        }
        .chart-container {
            width: 100%;
            height: 300px;
            margin-bottom: 30px;
            background-color: #f9f9f9;
            border-radius: 8px;
            padding: 20px;
            box-shadow: 0 2px 4px rgba(0,0,0,0.1);
        }
        canvas {
            width: 100%;
            height: 100%;
        }
        .stats-container {
            display: grid;
            grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
            gap: 20px;
            margin-bottom: 30px;
        }
        .stat-card {
            background-color: #f9f9f9;
            padding: 20px;
            border-radius: 8px;
            box-shadow: 0 2px 4px rgba(0,0,0,0.1);
            text-align: center;
        }
        .stat-card h3 {
            margin: 0 0 10px 0;
            color: #666;
            font-size: 14px;
        }
        .stat-card .value {
            font-size: 24px;
            font-weight: bold;
            color: #007AFF;
        }
        .status-card {
            background-color: #f9f9f9;
            padding: 20px;
            border-radius: 8px;
            box-shadow: 0 2px 4px rgba(0,0,0,0.1);
            text-align: center;
            margin-bottom: 30px;
        }
        .status-card h3 {
            margin: 0 0 10px 0;
            color: #666;
            font-size: 14px;
        }
        .status-card .status {
            font-size: 24px;
            font-weight: bold;
        }
        .status-normal {
            color: #28a745;
        }
        .status-high {
            color: #dc3545;
        }
        .status-low {
            color: #ffc107;
        }
        .back-button {
            display: block;
            margin: 0 auto;
            text-align: center;
        }
    </style>
</head>
<body>
    <div class="container">
        <h1>心率监测工具</h1>
        
        <div class="controls">
            <button id="startBtn">开始监测</button>
            <button id="stopBtn" disabled>停止监测</button>
            <button id="resetBtn" disabled>重置</button>
        </div>
        
        <div class="chart-container">
            <canvas id="heartrateChart"></canvas>
        </div>
        
        <div class="stats-container">
            <div class="stat-card">
                <h3>当前心率</h3>
                <div class="value" id="currentRate">0</div>
                <div>次/分钟</div>
            </div>
            <div class="stat-card">
                <h3>平均心率</h3>
                <div class="value" id="averageRate">0</div>
                <div>次/分钟</div>
            </div>
            <div class="stat-card">
                <h3>最大心率</h3>
                <div class="value" id="maxRate">0</div>
                <div>次/分钟</div>
            </div>
            <div class="stat-card">
                <h3>最小心率</h3>
                <div class="value" id="minRate">0</div>
                <div>次/分钟</div>
            </div>
        </div>
        
        <div class="status-card">
            <h3>心率状态</h3>
            <div class="status status-normal" id="heartrateStatus">正常</div>
        </div>
        
        <button class="back-button" onclick="window.location.href='index.html'">返回主界面</button>
    </div>
    
    <script>
        // 心率监测配置
        const config = {
            samplingRate: 1000, // 采样率 (ms)
            dataLength: 60,   // 数据点数量
            normalRange: { min: 60, max: 100 }, // 正常心率范围
            highThreshold: 100, // 高心率阈值
            lowThreshold: 60,   // 低心率阈值
            baseAmplitude: 50,  // 基础振幅
            maxAmplitude: 100,  // 最大振幅
            noiseLevel: 5      // 噪声水平
        };
        
        // 心率数据
        let heartrateData = {
            values: [],         // 心率值
            times: [],          // 时间戳
            isMonitoring: false, // 是否正在监测
            timer: null,        // 定时器
            currentRate: 0,     // 当前心率
            averageRate: 0,     // 平均心率
            maxRate: 0,         // 最大心率
            minRate: 0,         // 最小心率
            totalRate: 0,       // 总心率值
            count: 0,           // 数据点数量
            baseRate: 75        // 基础心率
        };
        
        // Canvas 相关
        let canvas, ctx, width, height;
        
        // 初始化
        function init() {
            // 获取 Canvas 元素
            canvas = document.getElementById('heartrateChart');
            ctx = canvas.getContext('2d');
            
            // 设置 Canvas 尺寸
            resizeCanvas();
            window.addEventListener('resize', resizeCanvas);
            
            // 初始化数据
            resetData();
            
            // 绑定事件
            document.getElementById('startBtn').addEventListener('click', startMonitoring);
            document.getElementById('stopBtn').addEventListener('click', stopMonitoring);
            document.getElementById('resetBtn').addEventListener('click', resetMonitoring);
            
            // 绘制初始图表
            drawChart();
        }
        
        // 调整 Canvas 尺寸
        function resizeCanvas() {
            const container = canvas.parentElement;
            canvas.width = container.clientWidth;
            canvas.height = container.clientHeight;
            width = canvas.width;
            height = canvas.height;
            drawChart();
        }
        
        // 重置数据
        function resetData() {
            heartrateData.values = [];
            heartrateData.times = [];
            heartrateData.currentRate = 75;
            heartrateData.averageRate = 75;
            heartrateData.maxRate = 75;
            heartrateData.minRate = 75;
            heartrateData.totalRate = 0;
            heartrateData.count = 0;
            heartrateData.baseRate = 75;
            
            // 初始化数据数组
            const now = Date.now();
            for (let i = 0; i < config.dataLength; i++) {
                heartrateData.values.push(75);
                heartrateData.times.push(now - (config.dataLength - i) * config.samplingRate);
            }
            
            // 更新 UI
            updateUI();
        }
        
        // 开始监测
        function startMonitoring() {
            if (heartrateData.isMonitoring) return;
            
            heartrateData.isMonitoring = true;
            document.getElementById('startBtn').disabled = true;
            document.getElementById('stopBtn').disabled = false;
            document.getElementById('resetBtn').disabled = false;
            
            // 开始定时器
            heartrateData.timer = setInterval(() => {
                updateHeartrate();
                drawChart();
                updateUI();
            }, config.samplingRate);
        }
        
        // 停止监测
        function stopMonitoring() {
            if (!heartrateData.isMonitoring) return;
            
            heartrateData.isMonitoring = false;
            clearInterval(heartrateData.timer);
            document.getElementById('startBtn').disabled = false;
            document.getElementById('stopBtn').disabled = true;
        }
        
        // 重置监测
        function resetMonitoring() {
            stopMonitoring();
            resetData();
            drawChart();
            document.getElementById('resetBtn').disabled = true;
        }
        
        // 更新心率数据
        function updateHeartrate() {
            // 模拟心率数据 - 更加符合人体心率波动特点
            
            // 缓慢调整基础心率(每10秒左右有一个小的趋势变化)
            if (Math.random() < 0.1) { // 10%的概率调整基础心率
                heartrateData.baseRate += (Math.random() - 0.5) * 4; // 基础心率变化范围±2
                // 保持基础心率在正常范围内
                heartrateData.baseRate = Math.max(60, Math.min(100, heartrateData.baseRate));
            }
            
            // 添加小幅度的随机波动(相邻数据变化不超过3次/分钟)
            const maxChange = 3;
            const fluctuation = (Math.random() - 0.5) * 2 * maxChange;
            
            // 计算当前心率
            let rate = Math.round(heartrateData.baseRate + fluctuation);
            
            // 限制心率范围
            rate = Math.max(50, Math.min(110, rate));
            
            // 确保相邻数据变化不太大
            if (heartrateData.currentRate > 0) {
                const diff = Math.abs(rate - heartrateData.currentRate);
                if (diff > maxChange) {
                    // 限制变化幅度
                    rate = heartrateData.currentRate + Math.sign(rate - heartrateData.currentRate) * maxChange;
                }
            }
            
            // 更新数据
            heartrateData.currentRate = rate;
            heartrateData.values.push(rate);
            heartrateData.times.push(Date.now());
            
            // 保持数据长度
            if (heartrateData.values.length > config.dataLength) {
                heartrateData.values.shift();
                heartrateData.times.shift();
            }
            
            // 更新统计数据
            heartrateData.totalRate += rate;
            heartrateData.count++;
            heartrateData.averageRate = Math.round(heartrateData.totalRate / heartrateData.count);
            heartrateData.maxRate = Math.max(heartrateData.maxRate, rate);
            heartrateData.minRate = heartrateData.count === 1 ? rate : Math.min(heartrateData.minRate, rate);
        }
        
        // 绘制心率图表
        function drawChart() {
            if (!ctx) return;
            
            // 清空画布
            ctx.clearRect(0, 0, width, height);
            
            // 绘制网格
            drawGrid();
            
            // 绘制心率波形
            drawHeartrateWave();
        }
        
        // 绘制网格
        function drawGrid() {
            ctx.strokeStyle = '#e0e0e0';
            ctx.lineWidth = 1;
            
            // 水平网格线
            const gridHeight = height / 5;
            for (let i = 0; i <= 5; i++) {
                const y = i * gridHeight;
                ctx.beginPath();
                ctx.moveTo(0, y);
                ctx.lineTo(width, y);
                ctx.stroke();
            }
            
            // 垂直网格线
            const gridWidth = width / 10;
            for (let i = 0; i <= 10; i++) {
                const x = i * gridWidth;
                ctx.beginPath();
                ctx.moveTo(x, 0);
                ctx.lineTo(x, height);
                ctx.stroke();
            }
        }
        
        // 绘制心率波形
        function drawHeartrateWave() {
            if (heartrateData.values.length === 0) return;
            
            ctx.strokeStyle = '#007AFF';
            ctx.lineWidth = 2;
            ctx.beginPath();
            
            // 计算心率值的范围
            const minValue = Math.min(...heartrateData.values);
            const maxValue = Math.max(...heartrateData.values);
            const valueRange = maxValue - minValue || 1;
            
            // 绘制波形
            for (let i = 0; i < heartrateData.values.length; i++) {
                const x = (i / (heartrateData.values.length - 1)) * width;
                // 归一化心率值到 0-1 范围
                const normalizedValue = (heartrateData.values[i] - minValue) / valueRange;
                // 映射到画布高度
                const y = height - (normalizedValue * (height - 40) + 20);
                
                if (i === 0) {
                    ctx.moveTo(x, y);
                } else {
                    ctx.lineTo(x, y);
                }
            }
            
            ctx.stroke();
        }
        
        // 更新 UI
        function updateUI() {
            // 更新心率数据
            document.getElementById('currentRate').textContent = heartrateData.currentRate;
            document.getElementById('averageRate').textContent = heartrateData.averageRate;
            document.getElementById('maxRate').textContent = heartrateData.maxRate;
            document.getElementById('minRate').textContent = heartrateData.minRate;
            
            // 更新心率状态
            const statusElement = document.getElementById('heartrateStatus');
            if (heartrateData.currentRate > config.highThreshold) {
                statusElement.textContent = '偏高';
                statusElement.className = 'status status-high';
            } else if (heartrateData.currentRate < config.lowThreshold) {
                statusElement.textContent = '偏低';
                statusElement.className = 'status status-low';
            } else {
                statusElement.textContent = '正常';
                statusElement.className = 'status status-normal';
            }
        }
        
        // 初始化应用
        window.addEventListener('DOMContentLoaded', init);
    </script>
</body>
</html>

9.2 开发环境搭建

  1. 安装 Node.js:下载并安装 Node.js 18+ 版本
  2. 安装 DevEco Studio:下载并安装 DevEco Studio 5.0+ 版本
  3. 安装 HarmonyOS SDK:在 DevEco Studio 中安装 HarmonyOS SDK
  4. 克隆项目:使用 git 克隆项目到本地
  5. 运行项目:在 DevEco Studio 中打开项目并运行

9.3 构建与部署

  1. 构建 HAP:在 ohos_hap 目录下运行 hvigorw assembleHap
  2. 部署应用:将构建生成的 HAP 文件部署到 HarmonyOS 设备上

10. 结语

本项目成功实现了一款在鸿蒙平台上运行的心率监测工具,展示了如何使用 Electron 框架和 Canvas 技术开发实用工具。通过本项目的开发,我们不仅学习了心率监测的基本原理和方法,还了解了如何在鸿蒙平台上构建和部署应用。

心率监测工具作为一款健康监测工具,具有广泛的应用场景。通过添加中文界面和中文菜单栏,我们使工具更加符合中文用户的使用习惯。同时,我们也实现了完整的心率监测功能,包括心率数据模拟、波形绘制、数据统计等。

未来,我们将继续优化工具功能,添加更多的健康监测功能,使工具更加完善和实用。同时,我们也希望通过本项目的开发,为鸿蒙平台的应用开发提供一些参考和借鉴,促进鸿蒙生态的发展。

欢迎加入开源鸿蒙PC社区,一起探索鸿蒙应用开发的无限可能!

Logo

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

更多推荐