目录
前言
综合运用本学期所学内容及个人自学知识,使用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.实现数据持久化和页面交互
通过本节课的学习,黑马健康程序已经可以正常运行。
黑马健康程序可以准确记录用户每天的饮食和锻炼,通过计算来得出每天消耗及食用的热量,对用户管理自己的身体健康起到了良好的作用。