黑马健康期末项目6

目录

 前言

一、项目介绍

二、应用运行

1.饮食记录业务层开发

(1)页面整体架构分析

(2)详细代码及分析

2.实现数据持久化和页面交互

(1)页面整体架构分析

(2)详细代码及分析

总结


 前言

综合运用本学期所学内容及个人自学知识,使用HarmonyOS 4.0及以上版本开发一款具有实用性和创新性的移动应用软件。


一、项目介绍

黑马健康应用程序是一个综合性的健康监控和管理工具,它具备一系列定制化功能,旨在帮助用户轻松地跟踪和管理自己的健康状况。通过个性化的饮食追踪记录和全面的健康评估机制,该应用能够根据用户的具体情况提供针对性的建议和指导。无论是对于追求体型改善和体重管理的用户,还是那些希望优化日常饮食以满足营养需求的用户,黑马健康都能提供一套量身打造的解决方案。它通过简化健康管理流程,使用户能够以一种更加直观和易于操作的方式,实现健康目标的达成。此外,黑马健康应用程序还强调了用户日常生活习惯的改善,通过持续的健康教育和行为指导,鼓励用户形成更加健康的生活方式。通过这种方式,该应用程序不仅帮助用户在短期内实现健康目标,更致力于长期促进用户的身体健康和生活质量的提升。

二、应用运行

1.饮食记录业务层开发

通过整合饮食记录管理系统的功能,实现了一个能够支持用户与页面进行实时互动的系统。该系统通过运用模型(Model)层中的增删改查操作,确保用户的饮食记录能够被持久化保存并随时更新。具体来说,就是采用了饮食记录model作为核心数据操作对象,用来支持用户在界面上创建新的饮食记录、修改现有记录、删除不再需要的记录以及查询特定时间段内的记录。

为了提升数据管理的效率引入了groupInfo来实现数据的分组功能。通过分组,用户能够更直观地查看和管理自己的饮食记录,如按餐次、食品类别或日期等方式进行分类。此外,还需要利用StatsInfo模块来统计和展示用户当日的卡路里摄入、运动消耗等关键信息,帮助用户更好地监控和调整自己的饮食习惯,以达到健康管理的目的。

(1)页面整体架构分析

(2)详细代码及分析

GroupInfo.ets:

定义了一个泛型类GroupInfo,它包含分组类型、组内数据集合和总热量信息,并在构造函数中初始化这些属性。


export default class GroupInfo<TYPE,ELEMENT>{//泛型 可读性会更好一点
  /**
   * 分组类型
   */
  type:TYPE
  /**
   * 组内数据集合
   */
  items:ELEMENT[]
  /**
   * 组内记录的总热量
   */
  calorie:number=0

  constructor(type:TYPE,items:ELEMENT[]) {
    this.type=type
    this.items=items
  }
}

RecordService.ets:

创建了一个RecordService类,它提供了一系列方法来处理饮食和运动记录,包括新增记录、删除记录、按日期查询记录列表、统计热量和营养素信息以及将记录按类型分组,并通过实例化提供了一个默认的服务对象。

import RecordPO from '../common/bean/RecordPO'
import DateUtil from '../common/utils/DateUtil'
import ItemModel from '../model/ItemModel'
import RecordModel from '../model/RecordModel'
import { RecordTypeEnum, RecordTypes } from '../model/RecordTypeModel'
import GroupInfo from '../viewmodel/GroupInfo'
import RecordType from '../viewmodel/RecordType'
import RecordVO from '../viewmodel/RecordVO'
import StatsInfo from '../viewmodel/StatsInfo'
class RecordService {
  /**
   * 新增饮食记录
   * @param typeId 记录类型id
   * @param itemId 记录项id
   * @param amount 记录项数量(食物量、运动时长)
   * @returns 新增数量
   */
  insert(typeId: number, itemId: number, amount: number): Promise<number>{
    // 1.获取时间
    let createTime = (AppStorage.Get('selectedDate') || DateUtil.beginTimeOfDay(new Date())) as number//numbe类型
    // 2.新增
    return RecordModel.insert({typeId, itemId, amount, createTime})//id可选,自增长
  }

  /**
   * 根据id删除饮食记录
   * @param id 记录id
   * @returns 删除条数
   */
  deleteById(id: number): Promise<number>{
    return RecordModel.deleteById(id)
  }

  /**
   * 根据日期查询饮食记录列表
   * @param date 要查询的日期
   * @returns 记录列表
   */
  async queryRecordByDate(date: number): Promise<RecordVO[]>{
    // 1.查询数据库的RecordPO
    let rps = await RecordModel.listByDate(date)
    // 2.将RecordPO转为RecordVO
    return rps.map(rp => {
      // 2.1.获取po中的基本属性
      let rv = {id: rp.id, typeId: rp.typeId, amount: rp.amount} as RecordVO
      // 2.2.查询记录项
      rv.recordItem = ItemModel.getById(rp.itemId, rp.typeId !== RecordTypeEnum.WORKOUT)
      // 2.3.计算热量
      rv.calorie = rp.amount * rv.recordItem.calorie
      return rv
    })
  }

  /**
   * 根据记录列表信息统计出热量、营养素信息
   * @param records 饮食记录列表
   * @returns 热量、营养素信息
   */
  calculateStatsInfo(records: RecordVO[]): StatsInfo{
    // 1.准备结果
    let info = new StatsInfo()
    if(!records || records.length <= 0){//没有值或长度小于零
      return info
    }
    // 2.计算统计数据
    records.forEach(r => {
      if(r.typeId === RecordTypeEnum.WORKOUT){//判断是什么类型 枚举WORKOUT代表运动
        // 运动,累加消耗热量
        info.expend += r.calorie
      }else{
        // 食物,累加摄入热量、蛋白质、碳水、脂肪
        info.intake += r.calorie
        info.carbon += r.recordItem.carbon
        info.protein += r.recordItem.protein
        info.fat += r.recordItem.fat
      }
    })
    // 3.返回
    return info
  }

  /**
   * 将记录列表按照记录类型分组
   * @param records 记录列表
   * @returns 分组记录信息
   */
  calculateGroupInfo(records: RecordVO[]): GroupInfo<RecordType, RecordVO>[]{//分组
    // 1.创建空的记录类型分组
    let groups = RecordTypes.map(recordType => new GroupInfo(recordType, []))
    if(!records || records.length <= 0){
      return groups
    }
    // 2.遍历所有饮食记录,
    records.forEach(record => {
      // 2.1.把每个记录存入其对应类型的分组中
      groups[record.typeId].items.push(record)
      // 2.2.计算该组的总热量
      groups[record.typeId].calorie += record.calorie
    })
    return groups
  }
}

let recordService = new RecordService()

export default recordService as RecordService

//数据库查询的数据转换成页面需要的数据的接口全部写好
//新增 删除已全部完成

2.实现数据持久化和页面交互

(1)页面整体架构分析

利用前面页面的内容,实现饮食记录页面的用户交互,从而确保数据的持久化保存。这些功能包括通过增删改查操作对饮食记录进行实时管理,使用户能够轻松地实现相应功能。同时,我们还引入了数据分组(如通过groupInfo)的功能,使得用户能够更加清晰地组织和管理他们的饮食和运动记录。此外,利用StatsInfo进行卡路里摄入和运动消耗的统计,为用户提供了一个直观的数据概览,帮助他们更好地理解和控制自己的饮食习惯。

(2)详细代码及分析

RecordIndex.ets

定义了一个名为RecordIndex的组件,它使用@StorageProp@Watch装饰器来管理选中的日期,并在页面显示时通过RecordService查询并展示当天的饮食记录。同时,该组件构建了一个包含搜索头部、统计卡片和记录列表的UI布局。

import DateUtil from '../../common/utils/DateUtil'
import RecordService from '../../service/RecordService'
import RecordVO from '../../viewmodel/RecordVO'
import RecordList from './RecordList'
import SearchHeader from './SearchHeader'
import StatsCard from './StatsCard'
@Component
export default struct RecordIndex {

  @StorageProp('selectDate')
  @Watch('aboutToAppear')//监控日期变更
  selectedDate:number=DateUtil.beginTimeOfDay(new Date())

  @Provide records:RecordVO[]=[]//父

  @Prop @Watch('handlePageShow') isPageShow:boolean

  handlePageShow(){
    if(this.isPageShow){
      this.aboutToAppear()
    }
  }


//页面加载时会查询 变更时也会查询
 async aboutToAppear(){
    this.records=await RecordService.queryRecordByDate(this.selectedDate)//默认今天 若用户选择日期,利用上方内容
  }

  build() {
    Column(){
      //1.头部搜索栏
      SearchHeader()//定义单独组件写
      //2.统计卡片
      StatsCard()
      //3.记录列表
      RecordList()
        .layoutWeight(1)//除上面 剩下的内容
    }
    .width('100%')
    .height('100%')
    .backgroundColor($r('app.color.index_page_background'))
  }
}

RecordList.ets

实现了一个饮食记录列表页面,它通过分组展示记录,并提供了交互功能,如点击跳转和滑动删除,同时展示了每组的总热量和建议热量摄入。

import router from '@ohos.router'
import { CommonConstants } from '../../common/constants/CommonConstants'
import { RecordTypeEnum } from '../../model/RecordTypeModel'
import RecordService from '../../service/RecordService'
import GroupInfo from '../../viewmodel/GroupInfo'
import RecordType from '../../viewmodel/RecordType'
import RecordVO from '../../viewmodel/RecordVO'

@Extend(Text) function grayText(){//1.分组的标题 中 Text单独样式
  .fontSize(14)
  .fontColor($r('app.color.light_gray'))

}

@Component
export default struct RecordList {

  @Consume @Watch('handleRecordsChanges')records:RecordVO[]//接收父提供的数据
  @State groups:GroupInfo<RecordType,RecordVO>[]=[]

  handleRecordsChanges(){
    this.groups=RecordService.calculateGroupInfo(this.records)
  }


  build() {
    List({space:CommonConstants.SPACE_10}){//上下有间隔
      ForEach(this.groups,(group:GroupInfo<RecordType,RecordVO>)=>{
        ListItem(){
          Column() {
            //1.分组的标题
            Row({ space: CommonConstants.SPACE_4 }) {
              Image(group.type.icon).width(24)
              Text(group.type.name).fontSize(18).fontWeight(CommonConstants.FONT_WEIGHT_700)
              Text(`建议${group.type.min}~${group.type.max}千卡`).grayText()
              Blank() //空白
              Text(group.calorie.toFixed(0)).fontSize(14).fontColor($r('app.color.light_primary_color'))
              Text('千卡').grayText()
              Image($r('app.media.ic_public_add_norm_filled'))
                .width(20)
                .fillColor($r('app.color.primary_color'))
            }
            .width('100%')
            .onClick(()=>{
              router.pushUrl({
                url:'pages/ItemIndex',
                params:{type:group.type}

              })
            })

            //2.组内记录列表
            List() {
              ForEach(group.items, (item:RecordVO) => {
                ListItem(){
                  Row({space:CommonConstants.SPACE_4}){
                    Image(item.recordItem.image).width(50)
                    Column({space:CommonConstants.SPACE_4}){
                      Text(item.recordItem.name).fontWeight(CommonConstants.FONT_WEIGHT_500)
                      Text(`${item.amount}${item.recordItem.unit}`).grayText()
                    }
                    Blank()
                    Text(`${item.calorie.toFixed(0)}千卡`).grayText()
                  }
                  .width('100%')
                  .padding(CommonConstants.SPACE_6)
                }.swipeAction({end:this.deleteButton.bind(this)})//左滑显示删除按钮


              })
            }
          }
          .width('100%')
          .backgroundColor(Color.White)
          .borderRadius(CommonConstants.DEFAULT_18)//边框弧度
          .padding(CommonConstants.SPACE_12)//边距 内容不贴边
        }
      })
    }
    .width(CommonConstants.THOUSANDTH_940)
    .margin({top:10})
    .height('100%')
  }
  @Builder deleteButton(){
    Image($r('app.media.ic_public_delete_filled'))
      .width(20)
      .fillColor(Color.Red)
      .margin(5)
  }
}


总结

1.饮食记录业务层开发
本次我们来看两种数据之间的差异,并对此进行分析,以及怎么把数据库中的数据变成页面中所需的数据。页面中需要的数据分为两部分。上半部分为统计信息,下半部分为饮食记录的列表信息。
统计信息中可滑动的卡片分为两部分。第一部分为对热量的统计,分为饮食热量、还可以吃和运动消耗。第二部分为营养素的统计信息,分为碳水、蛋白和脂肪。
饮食记录的列表信息分为早餐、午餐、晚餐、加餐和运动五部分,每一部分都有自己的记录项。

2.实现数据持久化和页面交互
通过本节课的学习,黑马健康程序已经可以正常运行。
黑马健康程序可以准确记录用户每天的饮食和锻炼,通过计算来得出每天消耗及食用的热量,对用户管理自己的身体健康起到了良好的作用。

  • 26
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值