食物列表页
食物列表页,当用户添加食物时,显示一个食物列表页,该页面上方有若干栏目,点击对应栏目,会显示该分类的食物列表,便于用户快速查询并添加食物,点击添加食物时,会弹出面板,输入数字,决定要添加的食物数量是多少,并根据一系列计算,得到卡路里值。
要实现的组件如下:
食物分类栏
食物卡片以及由食物卡组成的列表
便于控制添加食物数量所用到的数字面板
这里先优先创建食物卡片类,ps:后续会添加运动栏,但本质所需用到的属性与食物一致,只不过在最后计算卡路里的时候有所区别,所以也可以使用该类表示运动,不过要添加一个分类属性加以区别
食物分类栏
食物分类栏与制作首页下方导航栏一致,不过没有用到自定义tabs组件,仅使用了文字进行描述,数据模型部分稍后再做解释
Tabs() {
TabContent() {
this.TabContentBuilder(ItemModel.list(this.isFood))
}
.tabBar('全部')
食物卡片以及组成列表
1.食物卡片
食物卡片布局简单,但是涉及到较多不同单位的食物,单位无法统一,这提醒我们,在建立数据模型的时候,添加一个食物单位的属性
@Builder TabContentBuilder(items: RecordItem[]) {
List({ space: CommonConstants.SPACE_10 }) {
ForEach(items, (item: RecordItem) => {
ListItem() {
Row({ space: CommonConstants.SPACE_6 }) {
Image(item.image).width(50)
Column({ space: CommonConstants.SPACE_4 }) {
Text(item.name).fontWeight(CommonConstants.FONT_WEIGHT_500)
Text(`${item.calorie}千卡/${item.unit}`).fontSize(14).fontColor($r('app.color.light_gray'))
}.alignItems(HorizontalAlign.Start)
Blank()
Image($r('app.media.ic_public_add_norm_filled'))
.width(18)
.fillColor($r('app.color.primary_color'))
}
.width('100%')
.padding(CommonConstants.SPACE_6)
}
.onClick(() => this.showPanel(item))//食物添加面板
})
}
.width('100%')
.height('100%')
}
2.食物列表
ForEach(
ItemModel.listItemGroupByCategory(this.isFood),
(group: GroupInfo<ItemCategory, RecordItem>) => {
TabContent() {
this.TabContentBuilder(group.items)//食物卡片
}
.tabBar(group.type.name)
})
数字面板
数字面板上半部分为日期以及食物内容, 下半部分是数字键盘以及取消和提交按钮
首先编写上半部分ui
build() {
Row(){
Text('2024年1月25日 早餐')
.fontSize(18).fontWeight(CommonConstants.FONT_WEIGHT_600)
Image($r('app.media.ic_public_spinner'))
.width(20)
.fillColor(Color.Black)
}
}
这里的日期当然也可以使用一些方法调用到系统当前日期
剩下的部分为食物内容
build() {
Column({space: CommonConstants.SPACE_8}){
// 1.图片
Image(this.item.image).width(150)
// 2.名称
Row(){
Text(this.item.name).fontWeight(CommonConstants.FONT_WEIGHT_700)
}
.backgroundColor($r('app.color.lightest_primary_color'))
.padding({top: 5, bottom: 5, left: 12, right: 12})
Divider().width(CommonConstants.THOUSANDTH_940).opacity(0.6)
// 3.营养素
Row({space: CommonConstants.SPACE_8}){
this.NutrientInfo({label: '热量(千卡)', value: this.item.calorie})
if(this.item.id < 10000){
this.NutrientInfo({label: '碳水(千卡)', value: this.item.carbon})
this.NutrientInfo({label: '蛋白质(千卡)', value: this.item.protein})
this.NutrientInfo({label: '脂肪(千卡)', value: this.item.fat})//这里是自定义组件简化一下代码,与之前操作类似,就不做过多展示了
}
}
Divider().width(CommonConstants.THOUSANDTH_940).opacity(0.6)
// 4.数量
Row(){
Column({space: CommonConstants.SPACE_4}){
Text(this.amount.toFixed(1))
.fontSize(50).fontColor($r('app.color.primary_color'))
.fontWeight(CommonConstants.FONT_WEIGHT_600)
Divider().color($r('app.color.primary_color'))
}
.width(150)
Text(this.item.unit)
.fontColor($r('app.color.light_gray'))
.fontWeight(CommonConstants.FONT_WEIGHT_600)
}
}
}
然后是键盘部分
build() {
Grid(){//众所周知,一般数字面板有很多键,这里使用网格布局再适合不过
ForEach(this.numbers, num => {
GridItem(){
Text(num).fontSize(20).fontWeight(CommonConstants.FONT_WEIGHT_900)
}
.keyBoxStyle()
.onClick(() => this.clickNumber(num))
})
GridItem(){
Text('删除').fontSize(20).fontWeight(CommonConstants.FONT_WEIGHT_900)
}
.keyBoxStyle()
.onClick(() => this.clickDelete())
}
.width('100%')
.height(280)
.backgroundColor($r('app.color.index_page_background'))
.columnsTemplate('1fr 1fr 1fr')
.columnsGap(8)
.rowsGap(8)
.padding(8)
.margin({top: 10})
}
至此面板所需要的组件已全部完成,开始组装!
build() {
Column() {
// 1.头部导航
this.Header()
// 2.列表
ItemList({ showPanel: this.onPanelShow.bind(this), isFood: this.isFood })
.layoutWeight(1)
// 3.底部面板
Panel(this.showPanel) {
// 3.1.顶部日期
ItemPanelHeader()
// 3.2.记录项卡片
if(this.item){
ItemCard({amount: this.amount, item: $item})
}
// 3.3.数字键盘
NumberKeyboard({amount: $amount, value: $value})
// 3.4.按钮
this.PanelButton()
}
.mode(PanelMode.Full)
.dragBar(false)
.backgroundMask($r('app.color.light_gray'))
.backgroundColor(Color.White)
}
.width('100%')
.height('100%')
}
下面解说一下数字面板所要实现的一些业务逻辑,第一部分为通过提交数据实现数据持久化保存
Row({space: CommonConstants.SPACE_6}){
Button('取消')
.panelButtonStyle()
.backgroundColor($r('app.color.light_gray'))
.onClick(() => this.showPanel = false)//showPanel为一个布尔类型值,用于控制页面是否出现
Button('提交')
.panelButtonStyle()
.backgroundColor($r('app.color.primary_color'))
.onClick(() => {
// 1.持久化保存
RecordService.insert(this.type.id, this.item.id, this.amount)//将数据保存到关系型数据库
.then(() => {
// 2.关闭弹窗
this.showPanel = false
})
})
}
.margin({top: 10})
}
第二,与页面路由有关,点击返回图片,使用router.back的方法,跳转回先前的页面
@Builder Header() {
Row() {
Image($r('app.media.ic_public_back'))
.width(24)
.onClick(() => router.back())
Blank()
Text(this.type.name).fontSize(18).fontWeight(CommonConstants.FONT_WEIGHT_600)
}
.width(CommonConstants.THOUSANDTH_940)
.height(32)
}