饮食页面的业务记录:
作用
实现数据的增删改查,把在数据库中查到的数据变成页面上的数据,实现新增饮食记录,根据Id删除饮食记录,根据id查询饮食记录列表的功能。将查到的饮食录列表转换成统计信息和分组记录信息。
代码
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
{
//1.新增
insert(typeId:number,itemId:number,amount:number){
//1.1获取时间
let createTime = (AppStorage.Get('selectedDate') || DateUtil.beginTimeOfDay(new Date())) as number
//1.2
return RecordModel.insert({typeId,itemId,amount,createTime})
}
deleteById(id:number)
{
return RecordModel.deleteById(id)
}
//查询 先查询出 然后交给两个不同的部分去封装成他们想要的格式
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) //PO里面的item查到 就能查到recordItem 赋值vo
//2.3计算热量
rv.calorie = rp.amount * rv.recordItem.calorie
return rv
})
}
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)
{//运动累加消耗热量
info.expend += r.calorie
}else
{//食物,累加摄入热量,蛋白质,碳水,脂肪
info.protein+=r.calorie
info.carbon+=r.recordItem.carbon
info.protein+=r.recordItem.protein
info.fat+=r.recordItem.fat
}
})
//3.返回
return info
}
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
RecordPO.ets:
//记录数据持久化的一个数据库字段
export default class RecordPO{
/**
* 记录id
*/
id?: number
/**
* 饮食记录类型
*/
typeId: number
/**
* 记录中的食物或运动信息
*/
itemId: number
/**
* 食物数量或运动时长,如果是运动信息则无
*/
amount: number
/**
* 记录的日期
*/
createTime: number//页面变量要用基本类型不能用对象类型
}
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
}
}
StatsInfo.ets
export default class StatsInfo{
/**
* 当日摄入卡路里总量
*/
intake: number = 0
/**
* 当日运动消耗能量
*/
expend: number = 0
/**
* 当日摄入碳水总量
*/
carbon: number = 0
/**
* 当日摄入蛋白总量
*/
protein: number = 0
/**
* 当日摄入脂肪总量
*/
fat: number = 0
constructor(intake: number = 0, expend: number = 0, carbon: number = 0, protein: number = 0, fat: number = 0) {
this.intake = intake
this.expend = expend
this.carbon = carbon
this.protein = protein
this.fat = fat
}
}
运行截图:
总结
引入了groupInfo来实现数据的分组功能。通过分组,用户能够更直观地查看和管理自己的饮食记录,引用StatsInfo模块来统计和展示用户当日的卡路里摄入、运动消耗。实现 了饮食列表中业务的记录。
数据持久化和页面交互
作用
实现数据的可持久化,实现页面交互
代码
RecordIndex.ets
import DateUtil from '../../common/utils/DateUtil'
import RecordService from '../../RecordService/RecordService'
import RecordVO from '../../viewmodel/RecordVO'
import RecordList from './RecordList'
import SearchHeader from './SearchHeader'
import StatsCard from './StatsCard'
@Component
export default struct RecordIndex {
@StorageProp('selectedDate')
@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'))
}
}
StatsCard.ets
import BreakpointType from '../../common/bean/BreanpointType'
import BreakpointConstants from '../../common/constants/BreakpointConstants'
import { CommonConstants } from '../../common/constants/CommonConstants'
import DateUtil from '../../common/utils/DateUtil'
import RecordService from '../../RecordService/RecordService'
import RecordVO from '../../viewmodel/RecordVO'
import StatsInfo from '../../viewmodel/StatsInfo'
import CalorieStats from './CalorieStats'
import DatePickDialog from './DatePickDialog'
import NutrientStats from './NutrientStats'
@Component
export default struct StatsCard {
//读取日期
@StorageProp('selectedDate')selectedDate:number = DateUtil.beginTimeOfDay(new Date())
@StorageProp('currentBreakpoint')currentBreakpoint:string = BreakpointConstants.BREAKPOINT_SM
@Consume @Watch('handleRecordsChange') records:RecordVO[]
@State info:StatsInfo = new StatsInfo()
handleRecordsChange(){
this.info = RecordService.calculateStatsInfo(this.records)
}
controller:CustomDialogController = new CustomDialogController(
{builder:DatePickDialog({selectedDate:new Date(this.selectedDate)})
}
)
build() {
Column()
{
// 1.日期信息
Row()
{
Text(DateUtil.formatDate(this.selectedDate))
.fontColor($r('app.color.secondary_color'))
Image($r('app.media.ic_public_spinner'))
.fillColor($r('app.color.secondary_color'))
.width(20)//不添加的话就会铺满整个屏幕
.padding(CommonConstants.SPACE_12)//内边距
.onClick(()=>this.controller.open())
}
// 2.统计信息
Swiper()
{
//2.1热量统计
CalorieStats({intake:this.info.intake,expend:this.info.expend})
//2.2营养素统计
NutrientStats({carbon:this.info.carbon,protein:this.info.protein,fat:this.info.fat})
}
.width('100%')
.backgroundColor(Color.White)
.borderRadius(CommonConstants.DEFAULT_18)
.indicatorStyle({selectedColor:$r('app.color.primary_color')})
.displayCount(new BreakpointType({
sm:1,
md:1,
lg:2
}).getValue(this.currentBreakpoint))
}
.width(CommonConstants.THOUSANDTH_940)
.backgroundColor($r('app.color.stats_title_bgc'))
.borderRadius(CommonConstants.DEFAULT_18)
}
}
RecordList.ets
import router from '@ohos.router'
import { CommonConstants } from '../../common/constants/CommonConstants'
import RecordService from '../../RecordService/RecordService'
import GroupInfo from '../../viewmodel/GroupInfo'
import RecordType from '../../viewmodel/RecordType'
import RecordVO from '../../viewmodel/RecordVO'
@Extend(Text) function grayText(){
.fontSize(14)
.fontColor($r('app.color.light_gray'))
}
@Component
//列表LIST
export default struct RecordList {
@Consume @Watch('handleRecordsChange') records:RecordVO[]
@State groups:GroupInfo<RecordType,RecordVO>[] = []
handleRecordsChange(){
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(16).fontWeight(CommonConstants.FONT_WEIGHT_700)
Text(`建议${group.type.min}-${group.type.max}千卡`).grayText()
Blank()
Text(group.calorie.toFixed(0)).fontSize(12).fontColor($r('app.color.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()
{/*记录项的名字*/
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%')
}.width('100%')
.backgroundColor(Color.White)
.borderRadius(CommonConstants.DEFAULT_18)
.padding(CommonConstants.SPACE_12)
}
})
}
.height('100%')
.width(CommonConstants.THOUSANDTH_940)//宽度 加一个百分之94
.margin({top:10})
}
@Builder deleteButton(){//侧滑产生的图标是啥样式
Image($r('app.media.ic_public_delete_filled'))
.width(20)
.fillColor(Color.Red)
.margin(5)
}
}
Index.ets
import BreakpointType from '../common/bean/BreanpointType'
import BreakpointConstants from '../common/constants/BreakpointConstants'
import { CommonConstants } from '../common/constants/CommonConstants'
import BreakpointSystem from '../common/utils/BreakpointSystem'
import RecordIndex from '../view/record/RecordIndex'
@Entry
@Component
struct Index {
@State currentIndex:number = 0//状态变量 记录切换到谁
private breakpointSystem:BreakpointSystem = new BreakpointSystem()
//因为@所以必须初始化
@StorageProp('currentBreakpoint')currentBreakpoint:string = BreakpointConstants.BREAKPOINT_SM
@State isPageShow:boolean = false
onPageShow(){this.isPageShow = true}
onPageHide(){this.isPageShow = false}
@Builder TabBarBuilder(title:ResourceStr,image:ResourceStr,index:number){//自定义文本样式 builder
Column({space:CommonConstants.SPACE_8}){
Image(image)
.width(22)
.fillColor(this.selectColor(index))//图片必须是svg格式
Text(title)
.fontSize(14)
.fontColor(this.selectColor(index))
}
}
aboutToAppear(){
this.breakpointSystem.register()//完成回调函数的注册
}
aboutToDisappear(){
this.breakpointSystem.unregister()//取消注册
}
selectColor(index:number)
{
return this.currentIndex===index?$r('app.color.primary_color') :$r('app.color.light_gray' )
}
chooseBarPosition(){//为了适应手机 平板
return new BreakpointType(
{
sm:BarPosition.End,
md:BarPosition.Start,
lg:BarPosition.Start,
}
).getValue(this.currentBreakpoint)
}
build() {
Tabs({barPosition:BreakpointConstants.BAR_POSITION.getValue(this.currentBreakpoint)})
{
TabContent(){
RecordIndex({isPageShow:this.isPageShow})
}
.tabBar(this.TabBarBuilder($r('app.string.tab_record'),$r('app.media.ic_calendar'),0))
TabContent(){
Text('发现页面')
}
.tabBar(this.TabBarBuilder($r('app.string.tab_discover'),$r('app.media.discover'),1))
TabContent(){
Text('我的主页')
}
.tabBar(this.TabBarBuilder($r('app.string.tab_user'),$r('app.media.ic_user_portrait'),2))
}
.width('100%')
.height('100%')
.onChange(index=>this.currentIndex = index)
.vertical(new BreakpointType(
{
sm:false,
md:true,
lg:true
}
).getValue(this.currentBreakpoint)
)
}
}
总结
实现了一个具有良好结构和功能的应用程序界面,包括数据展示、用户交互和适配不同设备的布局。并且遵循了HarmonyOS应用开发的最佳实践,使用了组件化和响应式设计原则。