HarmonyOS5 运动健康app(一):健康饮食(附代码)

2025-06-28 19:42:37
108次阅读
0个评论

image.png

饮食管理应用架构设计文档

一、核心数据模型设计

1. 营养元素模型 (footItem)

interface footItem {
  name: string; // 营养名称(蛋白质/碳水/脂肪)
  weight: number; // 重量(克)
}

### 2. 食物模型 (`DietItem`)
```typescript
interface DietItem {
  name: string; // 食物名称
  image: string; // 图片路径(如app.media.mantou)
  weight: number; // 单份重量(克)
  calorie: number; // 单份卡路里
  count: number; // 食用数量
  nengliang: string; // 主要营养素类型
}

设计说明

  • footItem 将蛋白质、碳水、脂肪抽象为基础单元
  • DietItem 包含物理属性+营养属性,nengliang字段建立食物与营养素的映射关系

二、主组件架构:Index组件

状态管理

@State progressIndex: number = 0 // 总卡路里
@State dbzIndex: number = 0 // 总蛋白质
@State tsIndex: number = 0 // 总碳水
@State zfIndex: number = 0 // 总脂肪

头部统计组件 (toubu)

@Builder
toubu() {
  Column({ space: 15 }) {
    // 环形卡路里进度条
    Stack() {
      Progress({ value: this.progressIndex, total: 20000, type: ProgressType.Ring })
        .width(90).height(90).style({ strokeWidth: 10 })
      Text(`${this.progressIndex} kcal`).fontSize(14).fontWeight(FontWeight.Bold)
    }
    // 营养素统计行
    Row() {
      ForEach(footData, (item) => {
        Column() {
          Text(this.getItemWeight(item).toString()).fontSize(18)
          Text(item.name).fontSize(14)
        }.width('30%')
      })
    }
  }
}

功能特点

  • 环形进度条目标值20000kcal
  • 营养素统计行实时显示三类营养素摄入量

三、可复用组件:foods组件

核心属性

@State ifjiajian: boolean = false // 操作类型(增减)
@Prop item: DietItem // 食物对象(只读)
@Link progressIndex: number // 双向绑定总卡路里
@Link dbzIndex: number // 双向绑定蛋白质

关键方法

// 卡路里计算
calorieNUm() {
  const num = this.ifjiajian 
    ? this.item.calorie * this.item.count 
    : -this.item.calorie * (this.item.count + 1)
  this.progressIndex += num
}

// 营养素计算
weightNUm() {
  const amount = this.ifjiajian ? this.item.count : -(this.item.count + 1)
  const weightChange = 13 * amount
  switch(this.item.nengliang) {
    case '蛋白质': this.dbzIndex += weightChange
    case '碳水': this.tsIndex += weightChange
    case '脂肪': this.zfIndex += weightChange
  }
}

四、数据流转闭环

  1. 用户操作 → 点击"+"按钮

    • item.count++
    • ifjiajian = true
  2. 数据计算

    • calorieNUm()计算新增卡路里
    • weightNUm()更新对应营养素
  3. 界面更新

    • 环形进度条自动刷新
    • 营养素数值实时更新

五、完整代码

点击查看完整实现


interface footItem {
  name: string; // 营养名称
  weight: number; // 重量
}

interface DietItem {
  name: string; // 食物名称
  image: string; // 食物图片路径(本地或网络,这里用占位示意)
  weight: number; // 重量
  calorie: number; // 卡路里
  count: number; // 食用数量
  nengliang: string; // 营养名称(蛋白质、脂肪、碳水)
}

const footData: footItem[] = [
  { name: '蛋白质', weight: 0 },
  { name: '碳水', weight: 0 },
  { name: '脂肪', weight: 0 },
];

const dietData: DietItem[] = [
  { name: '馒头', image: 'app.media.mantou', weight: 13, calorie: 100, count: 0, nengliang: '蛋白质' },
  { name: '油条', image: 'app.media.youtiao', weight: 13, calorie: 200, count: 0, nengliang: '脂肪' },
  { name: '豆浆', image: 'app.media.doujiang', weight: 13, calorie: 300, count: 0, nengliang: '碳水' },
  { name: '稀饭', image: 'app.media.xifan', weight: 13, calorie: 300, count: 0, nengliang: '碳水' },
  { name: '鸡蛋', image: 'app.media.egg', weight: 13, calorie: 200, count: 0, nengliang: '蛋白质' },
];

@Entry
@Component
export struct Index {

  @State progressIndex: number = 0 // 进度条进度(总大卡数)
  @State dbzIndex: number = 0 // 总蛋白质
  @State tsIndex: number = 0 // 总碳水
  @State zfIndex: number = 0 // 总脂肪

  // 头部组件
  @Builder
  toubu() {
    Column({ space: 15 }) {
      Stack() {
        Progress({
          value: this.progressIndex,
          total: 20000,
          type: ProgressType.Ring
        })
          .width(90)
          .height(90)
          .style({ strokeWidth: 10 })
          .color('#4CD964')
          .backgroundColor('#e0e0e0');
        Text(`${this.progressIndex} kcal`)
          .fontSize(14)
          .fontWeight(FontWeight.Bold)
          .margin({ top: 5 })
      }

      Row() {
        ForEach(footData, (item: footItem) => {
          Column() {
            Text(this.getItemWeight(item).toString())
              .fontSize(18)
              .fontWeight(FontWeight.Bold)
              .fontColor('#333')
            Text(item.name)
              .fontSize(14)
              .fontColor('#666')
          }
          .width('30%')
        }, (item: footItem) => JSON.stringify(item))
      }
    }
    .padding({ top: 20, bottom: 15 })
    .width('100%')
    .alignItems(HorizontalAlign.Center)
  }

  // 获取对应的营养值
  private getItemWeight(item: footItem): number {
    switch (item.name) {
      case '蛋白质':
        return this.dbzIndex;
      case '碳水':
        return this.tsIndex;
      case '脂肪':
        return this.zfIndex;
      default:
        return 0;
    }
  }

  build() {
    Column({ space: 15 }) {
      this.toubu()
      Text('饮食内容')
        .fontSize(20)
        .fontColor('#555')
        .width('100%')
        .margin({ left: 20 })
      List({ space: 10 }) {
        ForEach(dietData, (item: DietItem) => {
          ListItem() {
            foods({
              item: item,
              progressIndex: this.progressIndex,
              dbzIndex: this.dbzIndex,
              tsIndex: this.tsIndex,
              zfIndex: this.zfIndex
            })
          }
        }, (item: DietItem) => JSON.stringify(item))
      }
    }
    .width('100%')
    .padding({ left: 10,right: 10 })
  }
}

// 饮食内容组件
@Reusable
@Component
export struct foods {
  @State ifjiajian: boolean = false
  @Prop item: DietItem
  @Link progressIndex: number
  @Link dbzIndex: number
  @Link tsIndex: number
  @Link zfIndex: number

  // 统计大卡数
  calorieNUm() {
    let num = this.ifjiajian ?
      this.item.calorie * this.item.count :
      -this.item.calorie * (this.item.count + 1);
    this.progressIndex += num;
  }

  // 统计能量
  weightNUm() {
    const amount = this.ifjiajian ? this.item.count : -(this.item.count + 1);
    const weightChange = 13 * amount;

    switch (this.item.nengliang) {
      case '蛋白质':
        this.dbzIndex += weightChange;
        break;
      case '碳水':
        this.tsIndex += weightChange;
        break;
      case '脂肪':
        this.zfIndex += weightChange;
        break;
    }
  }

  build() {
    Row() {
      Image($r(this.item.image))
        .width(60)
        .height(60)
        .borderRadius(8)
      Column({ space: 6 }) {
        Text(this.item.name)
          .fontSize(16)
          .fontWeight(FontWeight.Bold)
        Text(`${this.item.weight} 克`)
          .fontSize(14)
          .fontColor('#777')
      }
      .width('40%')
      .alignItems(HorizontalAlign.Start)

      Column({ space: 6 }) {
        Text(`${this.item.calorie * this.item.count} 卡`)
          .fontSize(16)
          .fontColor('#555')

        Row() {
          Text('-')
            .fontSize(20)
            .width(25)
            .height(25)
            .textAlign(TextAlign.Center)
            .borderRadius(4)
            .border({ width: 1, color: '#ccc' })
            .onClick(() => {
              if (this.item.count > 0) {
                this.item.count--;
                this.ifjiajian = false;
                this.calorieNUm();
                this.weightNUm();
              }
            })
          Text(`${this.item.count}`)
            .fontSize(16)
            .width(30)
            .textAlign(TextAlign.Center)
          Text('+')
            .fontSize(20)
            .width(25)
            .height(25)
            .textAlign(TextAlign.Center)
            .borderRadius(4)
            .border({ width: 1, color: '#ccc' })
            .onClick(() => {
              this.item.count++;
              this.ifjiajian = true;
              this.calorieNUm();
              this.weightNUm();
            })
        }
        .justifyContent(FlexAlign.SpaceAround)
        .width(90)
      }
      .width('40%')
      .alignItems(HorizontalAlign.Center)
    }
    .width('100%')
    .padding({ left: 10, right: 10 })
    .justifyContent(FlexAlign.SpaceBetween)
  }
}

```
收藏00

登录 后评论。没有帐号? 注册 一个。