实现数据持久化和页面交互
经过上几次实践,我们对实现页面交互所需要的数据模型全部都已经都建好了,对可以对数据模型进行增删改查操作的业务层也已经定义好了,接下来我们要通过业务层的方法,实现数据持久化实现与页面的交互。
如图所示,我们需要实现上图所示统计卡片、记录列表两部分的数据持久化与页面交互。因为这两个组件都在RecordIndex文件中,所以我们在RecordIndex使用RecordService中的查询方法,做到一次查询,两个组件都可以使用。
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 {
//接收RecordVO[]数据
@Provide records:RecordVO[] =[]
//获取所选择日期
@StorageProp('selectedDate')
@Watch('aboutToAppear') //监控,当日期变更时,重新查询
selectedDate:number =DateUtil.beginTimeOfDay(new Date()) //从应用存储中提取选择日期的毫秒值
@Prop @Watch('handelPageShow') isPageShow:boolean
handelPageShow(){
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'))
}
}
这部分需要注意两点,第一是因为查询需要用到所选日期,所以我们从应用存储中提取选择日期的毫秒值,并监控该值,当其变化时,执行aboutToAppear生命周期钩子,根据变化的日期重新查询数据。
第二点,如下两图,在index页,我们定义了状态变量,和onPageShow,onPageHide方法,每次进入饮食记录页,将状态传给RecordIndex页,在RecordIndex接收该状态变量,并监控该值,一旦状态表示进入饮食记录页面,说明数据有可能改变,调用生命周期钩子重新查询数据,重新渲染。
统计卡片组件数据渲染
在获得查询到的数据后,可以看到查询结果被@Provide修饰,所以,在统计卡片组件中通过@Consume获取查到的数据,并进行监控,当数据变化时,重新获得变化后统计卡片所需数据。
@Consume
@Watch('handleRecordsChange')
records:RecordVO[] //获得父亲提供的数组
@State info:StatsInfo=new StatsInfo()
//当记录变更时,获取他
handleRecordsChange(){
this.info=RecordService.calculateStatsInfo(this.records)
}
在获得到需要的数据info后,即可将统计卡片中原本的死数据用info中包含的信息代替
//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%')
可以看出,我们将获得的info数据传递给了热量统计和营养素组件,这两部分获得了info中其需要的部分。
注意点:
以热量统计举例,在热量统计组件中我们封装了@Builder StatsBuilder组件,然后再在build()中调用该方法,传递数据,实现组件和数据的渲染,在这里,如果我们传递值给StatsBuilder组件,在数据变化时其不会重新渲染
,所以我们应该传递其引用。这样才能在数据变化时重新渲染,在营养素统计中也是一样。如下:
@Builder StatsBuilder($$:{label:string,value:number,tips?:string}){
Column({space:CommonConstants.SPACE_6}){
Text($$.label)
.fontColor($r('app.color.gray'))
.fontWeight(CommonConstants.FONT_WEIGHT_600)
Text($$.value.toFixed(0))
.fontSize(20)
.fontWeight(CommonConstants.FONT_WEIGHT_700)
if ($$.tips){
Text($$.tips)
.fontSize(12)
.fontColor($r('app.color.light_gray'))
}
}
}
在build()中调用该方法,传递数据时,如下:
//1.饮食摄入
this.StatsBuilder({label:'饮食摄入',value:this.intake})
Stack(){ //层叠
//2.还可以吃
//2.1 进度条
Progress({
value:this.intake, //达到值
total:this.recommend, //总共值
type:ProgressType.Ring //环形进度条
})
.width(120)
.style({strokeWidth:CommonConstants.DEFAULT_10}) //进度环的粗细
.color($r('app.color.primary_color')) //进度环的颜色
//2。2统计数据
this.StatsBuilder({label:'还可以吃',value:this.remainCalorie(),tips:`推荐${this.recommend}`})
}
//3.运动消耗
this.StatsBuilder({label:'运动消耗',value:this.expend})
记录列表组件数据渲染
在纪录列表中,同样在记录列表组件中通过@Consume获取查到的数据,并进行监控,当数据变化时,重新获得变化后记录列表所需数据。
@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(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.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_6}){
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%')
}.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)
}
}
注意点:
在上述代码中如下图部分,我们在点击分组标题时,会进入ItemIndex页,同时将分组的类型传递给ItemIndex页
在ItemIndex页
onPageShow(){
//获取跳转时的参数
let params:any=router.getParams()
//获取点击的饮食记录类型
this.type=params['type']
this.iSFood=this.type.id!==RecordTypeEnum.WORKOUT
}
当页面展示时,获得路由参数,确定饮食记录类型,从而确定显示运动列表还是饮食列表。
至此,饮食记录实现效果如下:
屏幕录制 2024-06-18 212733