
鸿蒙跨端健康数据同步:BMI指数计算器与多设备数据共享 原创
鸿蒙跨端健康数据同步:BMI指数计算器与多设备数据共享
本文将基于HarmonyOS 5的分布式能力和ArkUI框架,实现一个跨设备的BMI指数计算器,能够在多个设备间同步健康数据,同时展示如何使用条件语句进行健康状态判断。
技术架构
数据计算层:计算BMI指数并评估健康状态
数据同步层:通过分布式数据管理实现多设备数据共享
UI展示层:响应式UI展示计算结果和历史记录
设备管理层:发现和连接同一账号下的其他设备
完整代码实现
健康数据模型定义
// model/HealthData.ts
export class HealthData {
userId: string = ‘’; // 用户ID
deviceId: string = ‘’; // 设备ID
timestamp: number = 0; // 记录时间戳
height: number = 0; // 身高(cm)
weight: number = 0; // 体重(kg)
bmiValue: number = 0; // BMI值
healthStatus: string = ‘’; // 健康状态
constructor(data?: any) {
if (data) {
this.userId = data.userId || ‘’;
this.deviceId = data.deviceId || ‘’;
this.timestamp = data.timestamp || Date.now();
this.height = data.height || 0;
this.weight = data.weight || 0;
this.calculateBMI();
}
// 计算BMI并评估健康状态
calculateBMI() {
if (this.height > 0 && this.weight > 0) {
const heightInM = this.height / 100;
this.bmiValue = parseFloat((this.weight / (heightInM * heightInM)).toFixed(1));
this.evaluateHealthStatus();
}
// 评估健康状态
private evaluateHealthStatus() {
if (this.bmiValue < 18.5) {
this.healthStatus = ‘偏瘦’;
else if (this.bmiValue >= 18.5 && this.bmiValue < 24) {
this.healthStatus = '正常';
else if (this.bmiValue >= 24 && this.bmiValue < 28) {
this.healthStatus = '过重';
else {
this.healthStatus = '肥胖';
}
分布式健康数据同步服务
// service/HealthSyncService.ts
import distributedData from ‘@ohos.data.distributedData’;
import deviceInfo from ‘@ohos.deviceInfo’;
import { HealthData } from ‘…/model/HealthData’;
const STORE_ID = ‘health_data_store’;
const HEALTH_KEY_PREFIX = ‘bmi_record_’;
export class HealthSyncService {
private kvManager: distributedData.KVManager;
private kvStore: distributedData.SingleKVStore;
private localDeviceId: string = deviceInfo.deviceId;
// 初始化分布式数据存储
async initialize() {
const config = {
bundleName: ‘com.example.healthapp’,
userInfo: {
userId: ‘health_user’,
userType: distributedData.UserType.SAME_USER_ID
};
this.kvManager = distributedData.createKVManager(config);
const options = {
createIfMissing: true,
encrypt: false,
backup: false,
autoSync: true,
kvStoreType: distributedData.KVStoreType.SINGLE_VERSION
};
this.kvStore = await this.kvManager.getKVStore(STORE_ID, options);
// 订阅数据变更
this.kvStore.on('dataChange', distributedData.SubscribeType.SUBSCRIBE_TYPE_ALL, (data) => {
this.handleDataChange(data);
});
// 处理数据变更
private handleDataChange(data: distributedData.ChangeNotification) {
if (data.insertEntries.length > 0) {
data.insertEntries.forEach(entry => {
if (entry.key.startsWith(HEALTH_KEY_PREFIX)) {
const recordId = entry.key.substring(HEALTH_KEY_PREFIX.length);
const healthData: HealthData = JSON.parse(entry.value.value);
// 更新AppStorage中的健康数据
const currentRecords: Map<string, HealthData> = AppStorage.get('healthRecords') || new Map();
currentRecords.set(recordId, healthData);
AppStorage.setOrCreate('healthRecords', currentRecords);
});
}
// 同步健康数据到所有设备
async syncHealthData(data: HealthData) {
const recordKey = {HEALTH_KEY_PREFIX}{data.timestamp};
await this.kvStore.put(recordKey, JSON.stringify(data));
// 获取远程健康数据
async getRemoteHealthData(userId: string): Promise<HealthData[]> {
const entries = await this.kvStore.getEntries(HEALTH_KEY_PREFIX);
const records: HealthData[] = [];
for (let i = 0; i < entries.length; i++) {
const entry = entries[i];
const data: HealthData = JSON.parse(entry.value.value);
if (data.userId === userId) {
records.push(data);
}
return records.sort((a, b) => b.timestamp - a.timestamp);
}
BMI计算器页面实现
// pages/BmiCalculatorPage.ets
import { HealthData } from ‘…/model/HealthData’;
import { HealthSyncService } from ‘…/service/HealthSyncService’;
@Entry
@Component
struct BmiCalculatorPage {
private syncService: HealthSyncService = new HealthSyncService();
@State height: string = ‘’;
@State weight: string = ‘’;
@State result: HealthData | null = null;
@StorageLink(‘healthRecords’) healthRecords: Map<string, HealthData> = new Map();
async aboutToAppear() {
await this.syncService.initialize();
// 加载历史记录
const userId = AppStorage.get('userId') || 'default_user';
const records = await this.syncService.getRemoteHealthData(userId);
const newRecords = new Map(this.healthRecords);
records.forEach(r => newRecords.set(r.timestamp.toString(), r));
AppStorage.setOrCreate('healthRecords', newRecords);
build() {
Column() {
// 标题
Text('BMI计算器')
.fontSize(24)
.fontWeight(FontWeight.Bold)
.margin({ top: 20, bottom: 30 })
// 输入表单
Column() {
Row() {
Text('身高(cm):')
.width(100)
.fontSize(18)
TextInput({ text: this.height })
.width('60%')
.type(InputType.Number)
.onChange((value: string) => {
this.height = value;
})
.margin({ bottom: 20 })
Row() {
Text('体重(kg):')
.width(100)
.fontSize(18)
TextInput({ text: this.weight })
.width('60%')
.type(InputType.Number)
.onChange((value: string) => {
this.weight = value;
})
}
.width('90%')
.padding(20)
.backgroundColor('#F5F5F5')
.borderRadius(12)
// 计算按钮
Button('计算BMI')
.width('80%')
.height(50)
.margin(20)
.onClick(() => {
this.calculateBMI();
})
// 结果显示
if (this.result) {
Column() {
Text(BMI值: ${this.result.bmiValue})
.fontSize(20)
.margin({ bottom: 10 })
Text(健康状态: ${this.result.healthStatus})
.fontSize(20)
.fontColor(this.getStatusColor(this.result.healthStatus))
// 健康建议
Text(this.getHealthAdvice(this.result.healthStatus))
.fontSize(16)
.margin({ top: 20 })
.fontColor('#666666')
.multilineTextAlignment(TextAlign.Center)
.width(‘90%’)
.padding(20)
.margin({ top: 20 })
.backgroundColor('#FFFFFF')
.borderRadius(12)
.shadow({ radius: 2, color: '#10000000', offsetX: 0, offsetY: 1 })
// 历史记录按钮
Button('查看历史记录')
.width('80%')
.margin(20)
.onClick(() => {
router.pushUrl({ url: 'pages/HealthHistoryPage' });
})
.width(‘100%’)
.height('100%')
.alignItems(HorizontalAlign.Center)
// 计算BMI
private calculateBMI() {
const height = parseFloat(this.height);
const weight = parseFloat(this.weight);
if (isNaN(height) |isNaN(weight)
height <= 0
| weight <= 0) {
prompt.showToast({ message: ‘请输入有效的身高和体重’, duration: 2000 });
return;
const healthData = new HealthData({
userId: AppStorage.get('userId') || 'default_user',
deviceId: this.syncService.getLocalDeviceId(),
height: height,
weight: weight
});
this.result = healthData;
// 同步到其他设备
this.syncService.syncHealthData(healthData);
// 获取健康状态对应的颜色
private getStatusColor(status: string): Color {
switch (status) {
case ‘偏瘦’: return Color.Blue;
case ‘正常’: return Color.Green;
case ‘过重’: return Color.Orange;
case ‘肥胖’: return Color.Red;
default: return Color.Black;
}
// 获取健康建议
private getHealthAdvice(status: string): string {
switch (status) {
case ‘偏瘦’: return ‘建议增加营养摄入,适当进行力量训练以增加肌肉质量’;
case ‘正常’: return ‘保持良好饮食习惯和规律运动,维持当前健康状态’;
case ‘过重’: return ‘建议控制饮食热量,增加有氧运动,每周至少150分钟中等强度运动’;
case ‘肥胖’: return ‘建议咨询营养师制定饮食计划,逐步增加运动量,定期体检’;
default: return ‘’;
}
健康历史记录页面
// pages/HealthHistoryPage.ets
@Entry
@Component
struct HealthHistoryPage {
@StorageLink(‘healthRecords’) healthRecords: Map<string, HealthData> = new Map();
build() {
Column() {
// 标题栏
Row() {
Button(‘返回’)
.onClick(() => {
router.back();
})
Text('健康历史记录')
.fontSize(20)
.fontWeight(FontWeight.Bold)
.layoutWeight(1)
.textAlign(TextAlign.Center)
.width(‘100%’)
.padding(12)
// 历史记录列表
if (this.healthRecords.size > 0) {
List() {
ForEach(Array.from(this.healthRecords.values()), (record: HealthData) => {
ListItem() {
HealthRecordItem({ record: record })
})
.layoutWeight(1)
.width('100%')
else {
Column() {
Image($r('app.media.ic_empty'))
.width(100)
.height(100)
.margin({ bottom: 20 })
Text('暂无历史记录')
.fontSize(18)
.fontColor('#888888')
.width(‘100%’)
.height('50%')
.justifyContent(FlexAlign.Center)
}
.width('100%')
.height('100%')
}
@Component
struct HealthRecordItem {
@Prop record: HealthData
build() {
Column() {
Row() {
Text(this.formatDate(this.record.timestamp))
.fontSize(16)
.fontColor(‘#333333’)
.layoutWeight(1)
Text(BMI: ${this.record.bmiValue})
.fontSize(16)
.fontColor(this.getStatusColor(this.record.healthStatus))
Row() {
Text(身高: ${this.record.height}cm)
.fontSize(14)
.fontColor('#666666')
.margin({ right: 20 })
Text(体重: ${this.record.weight}kg)
.fontSize(14)
.fontColor('#666666')
.margin({ top: 8, bottom: 8 })
Row() {
Text('健康状态:')
.fontSize(14)
.fontColor('#666666')
Text(this.record.healthStatus)
.fontSize(14)
.fontColor(this.getStatusColor(this.record.healthStatus))
.margin({ left: 8 })
}
.width('100%')
.padding(16)
.backgroundColor('#FFFFFF')
.borderRadius(8)
.margin({ bottom: 8 })
// 格式化日期
private formatDate(timestamp: number): string {
const date = new Date(timestamp);
return {date.getFullYear()}-{(date.getMonth() + 1).toString().padStart(2, ‘0’)}-{date.getDate().toString().padStart(2, ‘0’)} {date.getHours().toString().padStart(2, ‘0’)}:${date.getMinutes().toString().padStart(2, ‘0’)};
// 获取健康状态对应的颜色
private getStatusColor(status: string): Color {
switch (status) {
case ‘偏瘦’: return Color.Blue;
case ‘正常’: return Color.Green;
case ‘过重’: return Color.Orange;
case ‘肥胖’: return Color.Red;
default: return Color.Black;
}
实现原理详解
BMI计算逻辑:
BMI = 体重(kg) / (身高(m) × 身高(m))
根据计算结果判断健康状态:
BMI < 18.5:偏瘦
18.5 ≤ BMI < 24:正常
24 ≤ BMI < 28:过重
BMI ≥ 28:肥胖
数据同步机制:
使用分布式KVStore存储健康记录
每条记录以时间戳为唯一标识
通过dataChange事件监听远程数据变更
多设备协同流程:
设备A计算BMI并同步到分布式数据库
设备B收到数据变更通知后更新本地UI
所有设备保持健康记录一致
扩展功能建议
健康趋势图表:
// 使用图表库展示BMI变化趋势
import charts from ‘@ohos.charts’;
function renderTrendChart(records: HealthData[]) {
const chartData = records.map(r => ({
date: r.timestamp,
value: r.bmiValue
}));
// 渲染折线图…
健康目标设置:
// 设置体重目标并跟踪进度
async setWeightGoal(targetWeight: number) {
await this.kvStore.put(‘weight_goal’, targetWeight);
健康数据导出:
// 将健康记录导出为CSV文件
async exportToCSV() {
const csvContent = Array.from(this.healthRecords.values())
.map(r => {r.timestamp},{r.height},{r.weight},{r.bmiValue},${r.healthStatus})
.join(‘\n’);
await fileIO.writeText(‘health_records.csv’, csvContent);
总结
本文展示了如何利用HarmonyOS的分布式能力实现跨设备的BMI计算器。通过条件语句实现健康状态判断,再通过分布式数据服务实现健康记录的多设备同步。这种架构不仅适用于健康应用,也可以扩展到健身记录、饮食追踪等场景。
