HarmonyOS 鸿蒙PC开源electron框架——简易心率监测工具实战开发
开源鸿蒙PC心率监测工具 本项目是基于Electron和鸿蒙原生能力开发的心率监测工具,具有以下特点: 技术架构:采用HTML5+CSS3+JavaScript开发,运行在Electron(HarmonyOS定制版)上,使用Canvas绘制实时心率波形图。 功能亮点: 完整的心率监测功能,包括数据模拟、波形绘制和统计分析 实现中文菜单栏界面 采用符合人体心率波动特点的模拟算法 实现细节: 通过El
欢迎加入开源鸿蒙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 功能扩展
- 数据持久化:添加心率数据的本地存储功能,记录用户的心率历史。
- 数据分析:添加心率数据分析功能,生成日报、周报和月报。
- 告警功能:当心率异常时,提供声音或通知告警。
- 导出功能:支持导出心率数据为 CSV 或 PDF 格式。
- 主题切换:添加深色模式和浅色模式切换功能。
7.2 技术优化
- 性能优化:进一步优化 Canvas 绘制性能,支持更高的采样率。
- 代码重构:使用模块化的方式重构代码,提高代码的可维护性。
- 测试覆盖:添加单元测试,确保代码的质量。
- 国际化:添加多语言支持,使工具能够在不同语言环境下运行。
- 与硬件集成:支持与心率监测硬件设备的集成,获取真实的心率数据。
8. 总结
通过本项目的开发,我们成功在鸿蒙平台上实现了一款功能完整、界面美观的心率监测工具。项目使用了 HTML5 + CSS3 + JavaScript 技术栈,运行在 Electron (HarmonyOS 定制版) 上,支持中文界面和中文菜单栏。
本项目的开发过程中,我们遇到了一些挑战,如心率数据模拟的真实性、Canvas 绘制的性能、界面响应式设计等,但通过合理的设计和实现,我们成功解决了这些问题。
未来,我们计划进一步扩展功能,优化技术实现,使工具更加完善和实用。同时,我们也希望通过本项目的开发,为鸿蒙平台的应用开发提供一些参考和借鉴。
8.1 项目亮点
- 真实的心率数据模拟:实现了更加符合人体心率波动特点的模拟算法。
- 实时波形绘制:使用 Canvas 技术绘制实时心率波形图,视觉效果直观。
- 完整的数据统计:提供当前心率、平均心率、最大心率和最小心率的统计。
- 智能状态判断:自动判断心率状态,并通过颜色变化直观显示。
- 响应式设计:界面在不同屏幕尺寸下都能正常显示。
- 中文用户界面:所有界面元素都是中文的,符合中文用户的使用习惯。
8.2 技术价值
- 鸿蒙平台应用开发:展示了如何在鸿蒙平台上开发桌面应用。
- Electron 应用开发:展示了如何使用 Electron 开发跨平台应用。
- Canvas 绘图技术:展示了如何使用 Canvas 绘制实时波形图。
- 数据模拟:展示了如何模拟真实的生物数据。
- 响应式设计:展示了如何实现响应式的界面设计。
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 开发环境搭建
- 安装 Node.js:下载并安装 Node.js 18+ 版本
- 安装 DevEco Studio:下载并安装 DevEco Studio 5.0+ 版本
- 安装 HarmonyOS SDK:在 DevEco Studio 中安装 HarmonyOS SDK
- 克隆项目:使用 git 克隆项目到本地
- 运行项目:在 DevEco Studio 中打开项目并运行
9.3 构建与部署
- 构建 HAP:在
ohos_hap目录下运行hvigorw assembleHap - 部署应用:将构建生成的 HAP 文件部署到 HarmonyOS 设备上
10. 结语
本项目成功实现了一款在鸿蒙平台上运行的心率监测工具,展示了如何使用 Electron 框架和 Canvas 技术开发实用工具。通过本项目的开发,我们不仅学习了心率监测的基本原理和方法,还了解了如何在鸿蒙平台上构建和部署应用。
心率监测工具作为一款健康监测工具,具有广泛的应用场景。通过添加中文界面和中文菜单栏,我们使工具更加符合中文用户的使用习惯。同时,我们也实现了完整的心率监测功能,包括心率数据模拟、波形绘制、数据统计等。
未来,我们将继续优化工具功能,添加更多的健康监测功能,使工具更加完善和实用。同时,我们也希望通过本项目的开发,为鸿蒙平台的应用开发提供一些参考和借鉴,促进鸿蒙生态的发展。
欢迎加入开源鸿蒙PC社区,一起探索鸿蒙应用开发的无限可能!
更多推荐



所有评论(0)