期末实战——黑马健康6(完结撒花)


前言

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


一、项目介绍

黑马健康是一款功能全面的健康管理应用,它通过提供个性化的饮食记录、健康评估等功能,帮助用户轻松管理健康,改善饮食和生活习惯。无论是需要减肥塑形,还是关注日常营养摄入,黑马健康都能为用户提供定制化的服务,让健康管理变得简单而有效。

二、应用运行

1.饮食记录业务层开发

(1)页面整体分析

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

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

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

(2)代码如下(示例)

GroupInfo.ets


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

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

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)RecordList.ets
有了此处代码
在这里插入图片描述
下面红线代码就可以修改
在这里插入图片描述
改为:
在这里插入图片描述
继续修改
下面
在这里插入图片描述
修改为
在这里插入图片描述
此处
在这里插入图片描述
修改为
在这里插入图片描述
(2)RecordList页面中,在分组标题下面添加跳转事件,跳转到ItemIndex页面

在这里插入图片描述
继续优化,ItemList页面中,此处代码优化
在这里插入图片描述

优化为
在这里插入图片描述

(3)信息持久化保存到数据库
修改下面代码
在这里插入图片描述

修改为
在这里插入图片描述
从而实现持久化保存
(4)在虚拟机中添加相关项目,主页面信息不变
解决方法:在Index中定义一个@State isPageShow:boolean=false
添加此代码在这里插入图片描述
接着对首页RecordIndex组件修改
在这里插入图片描述
改为
在这里插入图片描述
接着从RecordIndex中定义一个@Prop接收对象并定义一个handlePageShow函数
在这里插入图片描述
(5)主页面上方信息不变,未触发视图的重新渲染,小数未取整

在这里插入图片描述
优化:
添加取整
在这里插入图片描述
信息未变的原因:
Bulider函数不会触发视图重新渲染
在CalorieStats
修改下面代码
在这里插入图片描述
修改为

在这里插入图片描述
这样里面内容就变成引用,就可以触发渲染
同时,上方传值也进行修改
在这里插入图片描述
改为
在这里插入图片描述以此类推,此页面下方类似代码也进行相关修改
同样,NutrientStats页面也进行相关操作
(6)食物页面热量一样
在这里插入图片描述
在这里插入图片描述
在ItemCard里面也是因为Builder
对ItemCard页面进行修改

四、运行效果截图

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

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


总结

问题修改

(1)在这里插入图片描述
修改:拼写错误 StatsInfo
(2)在虚拟机运行出现闪退
在这里插入图片描述
修改:未对calorie初始化,将其初始化为零。

  • 28
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
以下是一个关于电影主题的HTML网页设计作业的示例,以千与千寻为例: ```html <!DOCTYPE html> <html> <head> <title>千与千寻</title> <style> /* CSS样式 */ body { font-family: Arial, sans-serif; background-color: #f2f2f2; } h1 { color: #333333; text-align: center; } .container { max-width: 800px; margin: 0 auto; padding: 20px; background-color: #ffffff; border-radius: 5px; box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1); } .movie-info { display: flex; align-items: center; margin-bottom: 20px; } .movie-info img { width: 150px; height: auto; margin-right: 20px; } .movie-info h2 { margin: 0; } .movie-info p { margin: 0; } </style> </head> <body> <div class="container"> <h1>千与千寻</h1> <div class="movie-info"> <img src="千与千寻海报.jpg" alt="千与千寻海报"> <div> <h2>电影信息</h2> <p>导演:宫崎骏</p> <p>主演:柊瑠美、入野自由</p> <p>上映日期:2001年7月20日</p> <p>片长:125分钟</p> </div> </div> <h2>剧情简介</h2> <p>《千与千寻》是宫崎骏执导的一部动画电影,讲述了一个小女孩千寻在神秘的世界中寻找父母的故事。影片以细腻的画面和深刻的寓意赢得了广大观众的喜爱。</p> <h2>影评</h2> <p>这是一部非常有意义的电影,它通过一个小女孩的冒险故事,探讨了成长、勇气和爱的主题。宫崎骏的动画技巧和故事情节都非常出色,让人流连忘返。</p> </div> </body> </html> ``` 这个示例包含了一个简单的电影主题网页,其中包括电影的基本信息、剧情简介和影评。你可以根据自己的需求进行修改和扩展。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值