一、食物列表主页
主要功能:
前面记录页面完成,记录有早餐、午餐、晚餐和运动的分类,当点击某个分组时会跳转到食物列表案例,本模块实现的是食物列表页。
代码:
,当点击饮食记录里面的分组时就会跳转到响应的页record是之前的页面,Item Index的页面组件应记录在条目文件记录的里面。
ItemIndex.ets:
import router from '@ohos.router'
import { CommonConstants } from '../common/constants/CommonConstants'
import ItemCard from '../view/item/ItemCard'
import ItemList from '../view/item/ItemList'
import ItemPanelHeader from '../view/item/ItemPanelHeader'
import NumberKeyboard from '../view/item/NumberKeyboard'
@Entry
@Component
struct ItemIndex {
@State amount:number=1//状态变量
@State value:string=''//用户按键的内容记录在value内 amount最终结果
@State showPanel:boolean=false//默认不展示
onPanelShow(){
this.amount=1//每次弹出,需要初始化为原始状态
this.value=''//每次弹出,初始化为原始状态
this.showPanel=true
}
build() {
Column() {
//1.头部导航
this.Header()
//2.列表
ItemList({showPanel:this.onPanelShow.bind(this)})
.layoutWeight(1)//除头部外 剩下都被列表占用 这样高度固定
}
.width('100%')
.height('100%')
}
@Builder Header(){
Row(){
Image($r('app.media.ic_public_back'))
.width(24)
.onClick(() => router.back())
Blank()
Text('早餐').fontWeight(CommonConstants.FONT_WEIGHT_500)
}
.width(CommonConstants.THOUSANDTH_940)
.height(32)
}
}
ItemList.ets:
import { CommonConstants } from '../../common/constants/CommonConstants'
@Component
export default struct ItemList {
build() {
Tabs() {
TabContent() {
this.TabContentBuilder()
}
.tabBar('全部')
TabContent() {
this.TabContentBuilder()
}
.tabBar('主食')
TabContent() {
this.TabContentBuilder()
}
.tabBar('果蔬')
}
.width(CommonConstants.THOUSANDTH_940)
.height('100%')
.barMode(BarMode.Scrollable)
}
@Builder TabContentBuilder(){
List({space:CommonConstants.SPACE_10}){
ForEach([1,2,3,4,5],(item)=>{
ListItem(){
Row({space:CommonConstants.SPACE_6}){
Image($r('app.media.toast'))
.width(50)
Column({space:CommonConstants.SPACE_4}){
Text('全麦吐司')
.fontWeight(CommonConstants.FONT_WEIGHT_500)
Text('91千卡/片')
.fontSize(14)
.fontColor($r('app.color.light_gray'))
}
Blank()
Image($r('app.media.ic_public_add_norm_filled'))
.width(18)
.fillColor($r('app.color.primary_color'))
}
.width('100%')
.padding(CommonConstants.SPACE_6)
}
})
}
.width('100%')
.height('100%')
}
}
效果:
编写时问题:
代码有很多重复的,想要抽出来,用Builder函数调用,但是抽出来 TabContent() 是不行的。
把TabContent()的内容抽出来可以,把里面的List封装到Builder中。
二、底部Panel
主要功能:
当点击列表的每一项时,会弹出食物详细信息,用户可以选择详细的数量。用户可以输入相应数量,然后保存添加。该部分主要实现日期,卡片样式。
代码:
ItemIndex.ets
import router from '@ohos.router'
import { CommonConstants } from '../common/constants/CommonConstants'
import ItemList from '../view/item/ItemList'
@Entry
@Component
struct ItemIndex {
@State message: string = 'Hello World'
@State showPanel: boolean = false
onPanelShow(){
this.showPanel=true
}
build() {
Column(){
//导航
this.Header()
//列表
ItemList({showPanel:this.onPanelShow.bind(this)})
//底部面板
Panel(this.showPanel) {//点击加号是弹出
// 3.1.顶部日期
ItemPanelHeader()
// 3.2.记录项卡片
if(this.item){
ItemCard({amount: this.amount, item: $item})
}
// 3.4.按钮
this.PanelButton()
Button('关闭')
.onClick(()=>this.showPanel=false)
}
.mode(PanelMode.Full)
.dragBar(false)
.backgroundMask($r('app.color.light_gray'))
.backgroundColor(Color.White)
}
.width('100%')
.height('100%')
}
@Builder Header() {
Row() {
Image($r('app.media.ic_public_back'))
.width(24)
.onClick(() => router.back())
Blank()
Text('早餐').fontSize(18).fontWeight(CommonConstants.FONT_WEIGHT_600)
}
.width(CommonConstants.THOUSANDTH_940)
.height(32)
}
}
ItemList.ets
import { CommonConstants } from '../../common/constants/CommonConstants'
@Component
export default struct ItemList {
showPanel:()=>void//实际有参
build() {
Tabs() {
TabContent() {
this.TabContentBuilder()
}
.tabBar('全部')
TabContent() {
this.TabContentBuilder()
}
.tabBar('主食')
TabContent() {
this.TabContentBuilder()
}
.tabBar('果蔬')
}
.width(CommonConstants.THOUSANDTH_940)
.height('100%')
.barMode(BarMode.Scrollable)
}
@Builder TabContentBuilder(){
List({space:CommonConstants.SPACE_10}){
ForEach([1,2,3,4,5],(item)=>{
ListItem(){
Row({space:CommonConstants.SPACE_6}){
Image($r('app.media.toast'))
.width(50)
Column({space:CommonConstants.SPACE_4}){
Text('全麦吐司')
.fontWeight(CommonConstants.FONT_WEIGHT_500)
Text('91千卡/片')
.fontSize(14)
.fontColor($r('app.color.light_gray'))
}
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()
})
})
}
.width('100%')
.height('100%')
}
}
ItemPanelHeader.ets
import { CommonConstants } from '../../common/constants/CommonConstants'
@Component
export default struct ItemPanelHeader {
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)
}
}
}
ItemCard.ets
import { CommonConstants } from '../../common/constants/CommonConstants'
@Component
export default struct ItemCard {
@Prop amount: number//状态变量 Prop不能初始化
build() {
Column({space:CommonConstants.SPACE_8}){
//1.图片
Image($r('app.media.toast')).width(150)
//2.名称
Row(){
Text('全麦吐司') .fontWeight(CommonConstants.FONT_WEIGHT_700)//为了添加颜色 将其放入Row容器中
}
.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('热量(千卡)',91.0)
this.NutrientInfo('碳水(克)',15.5)
this.NutrientInfo('蛋白质(克)',4.4)
this.NutrientInfo('脂肪(克)',1.3)
}
Divider()//下划线
.width(CommonConstants.THOUSANDTH_940)
.opacity(0.6)//透明度
//4.数量
Row(){
Column({space:CommonConstants.SPACE_4}){
Text(this.amount.toFixed(1))//1位小数
.fontSize(50).fontColor($r('app.color.primary_color'))
.fontWeight(CommonConstants.FONT_WEIGHT_600)
Divider().color($r('app.color.primary_color'))
}
.width(150)
Text('片')
.fontColor($r('app.color.primary_color'))
.fontWeight(CommonConstants.FONT_WEIGHT_600)
}
}
}
@Builder NutrientInfo(label:string,value:number){//其他传过来
Column({space:CommonConstants.SPACE_8}){
Text(label).fontSize(14).fontColor($r('app.color.light_gray'))
Text((value+this.amount).toFixed(1))//一位小数
.fontSize(18).fontWeight(CommonConstants.FONT_WEIGHT_700)
}
}
}
效果:
编写时问题:
1.触发Panel的事件,当点击加号是弹出面板,在ItemList中ListItem加点击事件不对
原因ItemList无法控制父组件ItemIndex的弹出,showPanel用函数控制,函数声明出来,有父组件调用我们时去覆盖我们的声明。
2.不弹面板
面板不占高度的,浮在表面的, 但是他要求所在的容器高度和所包含的元素是固定的,ItemList是动态的,不固定,被挤到最下面去了 ItemList({showPanel:this.onPanelShow.bind(this)}) .layoutWeight(1)。
相关知识:
Panel
可滑动面板,提供一种轻量的内容展示窗口,方便在不同尺寸中
Panel(show: boolean)
参数名 | 参数类型 | 必填 | 参数描述 |
---|---|---|---|
show | boolean | 是 | 控制Panel显示或隐藏。 说明: 如果设置为false时,则不占位隐藏。Visible.None或者show之间有一个生效时,都会生效不占位隐藏。 |
属性
除支持通用属性外,还支持以下属性:
名称 | 参数类型 | 描述 |
---|---|---|
type | 设置可滑动面板的类型。 默认值:PanelType.Foldable | |
mode | 设置可滑动面板的初始状态。 Minibar类型默认值:PanelMode.Mini;其余类型默认值:PanelMode.Half | |
dragBar | boolean | 设置是否存在dragbar,true表示存在,false表示不存在。 默认值:true |
fullHeight | string | number | 指定PanelMode.Full状态下的高度。 默认值:当前组件主轴大小减去8vp空白区 说明: 不支持设置百分比。 |
halfHeight | string | number | 指定PanelMode.Half状态下的高度。 默认值:当前组件主轴大小的一半。 说明: 不支持设置百分比。 |
miniHeight | string | number | 指定PanelMode.Mini状态下的高度。 默认值:48 单位:vp 说明: 不支持设置百分比。 |
show | boolean | 当滑动面板弹出时调用。 |
backgroundMask9+ | 指定Panel的背景蒙层。 |
PanelType枚举说明
名称 | 描述 |
---|---|
Minibar | 提供minibar和类全屏展示切换效果。 |
Foldable | 内容永久展示类,提供大(类全屏)、中(类半屏)、小三种尺寸展示切换效果。 |
Temporary | 内容临时展示区,提供大(类全屏)、中(类半屏)两种尺寸展示切换效果。 |
PanelMode枚举说明
名称 | 描述 |
---|---|
Mini | 类型为minibar和foldable时,为最小状态;类型为temporary,则不生效。 |
Half | 类型为foldable和temporary时,为类半屏状态;类型为minibar,则不生效。 |
Full | 类全屏状态。 |
事件
除支持通用事件外,还支持以下事件:
名称 | 功能描述 |
---|---|
onChange(event: (width: number, height: number, mode: PanelMode) => void) | 当可滑动面板发生状态变化时触发, 返回的height值为内容区高度值,当dragbar属性为true时,panel本身的高度值为dragbar高度加上内容区高度。 |
onHeightChange(callback: (value: number) => void)9+ | 当可滑动面板发生高度变化时触发,返回的height值为内容区高度值,默认返回值单位为px。当dragbar属性为true时,panel本身的高度值为dragbar高度加上内容区高度。因用户体验设计原因,panel最高只能滑到 fullHeight-8vp。 |
三、数字键盘
主要功能:
实现键盘的格式和输入以及按钮等功能,完成键盘键入,达到用户根据实际按钮实现变化。
代码:
ItemIndex.ets
import router from '@ohos.router'
import { CommonConstants } from '../common/constants/CommonConstants'
import ItemCard from '../view/item/ItemCard'
import ItemList from '../view/item/ItemList'
import ItemPanelHeader from '../view/item/ItemPanelHeader'
import NumberKeyboard from '../view/item/NumberKeyboard'
@Entry
@Component
struct ItemIndex {
@State amount:number=1
@State value:string=''//记录按键
@State showPanel: boolean = false
onPanelShow(){
//在每次展开时要做一个初始化
this.amount=1
this.value=''
this.showPanel=true
}
build() {
Column(){
//导航
this.Header()
//列表
ItemList({showPanel:this.onPanelShow.bind(this)})
.layoutWeight(1)
//底部面板
Panel(this.showPanel) {//点击加号是弹出
// 3.1.顶部日期
ItemPanelHeader()
// 3.2.记录项卡片
ItemCard({amount:this.amount})
// 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%')
}
@Builder Header() {
Row() {
Image($r('app.media.ic_public_back'))
.width(24)
.onClick(() => router.back())
Blank()
Text('早餐').fontSize(18).fontWeight(CommonConstants.FONT_WEIGHT_600)
}
.width(CommonConstants.THOUSANDTH_940)
.height(32)
}
}
NumderKeyboard.ets
import { CommonConstants } from '../../common/constants/CommonConstants'
@Component
export default struct NumberKeyboard {
numbers: string[] = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '.']
@Link amount: number
@Link value: string
@Styles keyBoxStyle(){//样式函数
.backgroundColor(Color.White)
.borderRadius(8)
.height(60)
}
build() {
Grid(){
ForEach(this.numbers, num => {
GridItem(){//每一个键都是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')//1:1:1
.columnsGap(8)//列间距
.rowsGap(8)//行间距
.padding(8)
.margin({top: 10})
}
//点击数字
clickNumber(num: string){
// 1.拼接用户输入的内容
let val = this.value + num
// 2.校验输入格式是否正确
let firstIndex = val.indexOf('.')//从前向后记录小数点的脚标
let lastIndex = val.lastIndexOf('.')//从后往前记录小数点的脚标
if(firstIndex !== lastIndex || (lastIndex != -1 && lastIndex < val.length - 2)){//一个小数点或小数位不超两位
// 非法输入
return
}
// 3.将字符串转为数值
//不能直接ParseFloat转,会报错,要单独定义一个函数
let amount = this.parseFloat(val)
// 4.保存
if(amount >= 999.9){//值有上限
this.amount = 999.0
this.value = '999'
}else{
this.amount = amount
this.value = val
}
}
//点击删除
clickDelete(){
if(this.value.length <= 0){
//删空了
this.value = ''
this.amount = 0
return
}
this.value = this.value.substring(0, this.value.length - 1)
this.amount = this.parseFloat(this.value)
}
parseFloat(str: string){
if(!str){
return 0
}
if(str.endsWith('.')){
str = str.substring(0, str.length - 1)//去除,从0开始去,去到字符串最后一位减一,把小数点去除了
}
return parseFloat(str)
}
}
键盘Button:默写成两个按钮都关闭
Row({space: CommonConstants.SPACE_6}){
Button('取消')
.width(120)
.type(ButtonType.Normal)
.borderRadius(6)
.backgroundColor($r('app.color.light_gray'))
.onClick(() => this.showPanel = false)
Button('提交')
.width(120)
.type(ButtonType.Normal)
.borderRadius(6)
.backgroundColor($r('app.color.primary_color'))
.onClick(() => this.showPanel = false)
}
.margin({top: 10})
效果:
编写时问题:
1.由于按键和数字可能对不起来:
用value记录,amount才是最终结果。
2.其碳水化合物之类的会随着片数增加二受影响
@Builder NutrientInfo(label: string, value: number){
Column({space: CommonConstants.SPACE_8}){
Text(label)
.fontSize(14)
.fontColor($r('app.color.light_gray'))
Text((value*this.amount).toFixed(1))//一位小数
.fontSize(18)
.fontWeight(CommonConstants.FONT_WEIGHT_700)
}
}
相关知识:
Grid
网格容器,由“行”和“列”分割的单元格所组成,通过指定“项目”所在的单元格做出各种各样的布局。
Grid子组件的索引值计算规则:
- 按子组件的顺序依次递增。
- if/else语句中,只有条件成立分支内的子组件会参与索引值计算,条件不成立分支内的子组件不计算索引值。
- ForEach/LazyForEach语句中,会计算展开所有子节点索引值。
- if/else/ForEach/LazyForEach发生变化以后,会更新子节点索引值。
- Grid子组件的visibility属性设置为Hidden或None时依然会计算索引值。
- Grid子组件的visibility属性设置为None时不显示,但依然会占用子组件对应的网格。
接口
Grid(scroller?: Scroller)
参数名 | 参数类型 | 必填 | 参数描述 |
---|---|---|---|
scroller | 否 | 可滚动组件的控制器。用于与可滚动组件进行绑定。 说明: 不允许和其他滚动类组件绑定同一个滚动控制对象。 |
属性
除支持通用属性外,还支持以下属性:
名称 | 参数类型 | 描述 |
---|---|---|
columnsTemplate | string | 设置当前网格布局列的数量,不设置时默认1列。 例如, '1fr 1fr 2fr' 是将父组件分3列,将父组件允许的宽分为4等份,第一列占1份,第二列占1份,第三列占2份。 说明: 设置为'0fr'时,该列的列宽为0,不显示GridItem。设置为其他非法值时,GridItem显示为固定1列。 |
rowsTemplate | string | 设置当前网格布局行的数量,不设置时默认1行。 例如,'1fr 1fr 2fr'是将父组件分三行,将父组件允许的高分为4等份,第一行占1份,第二行占一份,第三行占2份。 说明: 设置为'0fr',则这一行的行宽为0,这一行GridItem不显示。设置为其他非法值,按固定1行处理。 |
columnsGap | Length | 设置列与列的间距。 默认值:0 说明: 设置为小于0的值时,按默认值显示。 |
rowsGap | Length | 设置行与行的间距。 默认值:0 说明: 设置为小于0的值时,按默认值显示。 |
scrollBar | 设置滚动条状态。 默认值:BarState.Off | |
scrollBarColor | string | number | Color | 设置滚动条的颜色。 |
scrollBarWidth | string | number | 设置滚动条的宽度。宽度设置后,滚动条正常状态和按压状态宽度均为滚动条的宽度值。 默认值:4 单位:vp |
cachedCount | number | 设置预加载的GridItem的数量,只在LazyForEach中生效。具体使用可参考减少应用白块说明。 默认值:1 说明: 设置缓存后会在Grid显示区域上下各缓存cachedCount*列数个GridItem。LazyForEach超出显示和缓存范围的GridItem会被释放。 设置为小于0的值时,按默认值显示。 |
editMode8+ | boolean | 设置Grid是否进入编辑模式,进入编辑模式可以拖拽Grid组件内部GridItem。 默认值:false |
layoutDirection8+ | 设置布局的主轴方向。 默认值:GridDirection.Row | |
maxCount8+ | number | 当layoutDirection是Row/RowReverse时,表示可显示的最大列数 当layoutDirection是Column/ColumnReverse时,表示可显示的最大行数。 默认值:Infinity 说明: 当maxCount小于minCount时,maxCount和minCount都按默认值处理。 设置为小于0的值时,按默认值显示。 |
minCount8+ | number | 当layoutDirection是Row/RowReverse时,表示可显示的最小列数。 当layoutDirection是Column/ColumnReverse时,表示可显示的最小行数。 默认值:1 说明: 设置为小于0的值时,按默认值显示。 |
cellLength8+ | number | 当layoutDirection是Row/RowReverse时,表示一行的高度。 当layoutDirection是Column/ColumnReverse时,表示一列的宽度。 默认值:第一个元素的大小 |
multiSelectable8+ | boolean | 是否开启鼠标框选。 默认值:false - false:关闭框选。 - true:开启框选。 |
supportAnimation8+ | boolean | 是否支持动画。当前支持GridItem拖拽动画。 默认值:false |
Grid组件根据rowsTemplate、columnsTemplate属性的设置情况,可分为以下三种布局模式:
- rowsTemplate、columnsTemplate同时设置:
- Grid只展示固定行列数的元素,其余元素不展示,且Grid不可滚动。
- 此模式下以下属性不生效:layoutDirection、maxCount、minCount、cellLength。
- Grid的宽高没有设置时,默认适应父组件尺寸。
- Gird网格列大小按照Gird自身内容区域大小减去所有行列Gap后按各个行列所占比重分配。
- GridItem默认填满网格大小。
- 此模式下GridItem同时设置了rowStart、columnStart,会用设置的rowStart、columnStart所在位置摆放GridItem。如果这个位置已经有GridItem则会发生重叠。
- 如果GridItem设置了rowStart、columnStart其中一个,会从上一个GridItem布局位置开始遍历寻找满足rowStart或columnStart的空闲位置摆放,如果无满足条件的空闲位置,则不布局该GridItem。
- 如果GridItem的rowStart、columnStart属性都没有设置,会从上一个GridItem布局位置开始遍历寻找空闲位置摆放,如果没有空闲位置,则不布局该GridItem。
- 如果GridItem的rowEnd有设置,但是rowStart没有设置,当做rowStart已经设置,并且和rowEnd设置为相同值。如果GridItem的columnEnd有设置,但是columnStart没有设置,当做columnStart已经设置,并且和columnEnd设置为相同值。
- rowsTemplate、columnsTemplate仅设置其中的一个:
- 元素按照设置的方向进行排布,超出Grid显示区域后,Grid可通过滚动的方式展示。
- 如果设置了columnsTemplate,Gird滚动方向为垂直方向,主轴方向为垂直方向,交叉轴方向为水平方向。
- 如果设置了rowsTemplate,Gird滚动方向为水平方向,主轴方向为水平方向,交叉轴方向为垂直方向。
- 此模式下以下属性不生效:layoutDirection、maxCount、minCount、cellLength。
- 网格交叉轴方向尺寸根据Gird自身内容区域交叉轴尺寸减去交叉轴方向所有Gap后按所占比重分配。
- 网格主轴方向尺寸取当前网格交叉轴方向所有GridItem高度最大值。
- 此模式下GridItem同时设置了rowStart、columnStart,会用设置的rowStart、columnStart所在位置摆放GridItem。如果这个位置已经有GridItem则会发生重叠。
- 如果GridItem设置了rowStart、columnStart其中一个,会从上一个GridItem布局位置开始遍历寻找满足rowStart或columnStart的空闲位置摆放。
- 如果GridItem的rowStart、columnStart属性都没有设置,会从上一个GridItem布局位置开始遍历寻找空闲位置摆放。
- 如果GridItem的rowEnd有设置,但是rowStart没有设置,当做rowStart已经设置,并且和rowEnd设置为相同值。如果GridItem的columnEnd有设置,但是columnStart没有设置,当做columnStart已经设置,并且和columnEnd设置为相同值。
- rowsTemplate、columnsTemplate都不设置:
- 元素在layoutDirection方向上排布,列数由Grid的宽度、首个元素的宽度、minCount、maxCount、columnsGap共同决定。
- 行数由Grid高度、首个元素高度、cellLength、rowsGap共同决定。超出行列容纳范围的元素不显示,也不能通过滚动进行展示。
- 此模式下仅生效以下属性:layoutDirection、maxCount、minCount、cellLength、editMode、columnsGap、rowsGap。
- 当前layoutDirection设置为Row时,先从左到右排列,排满一行再排一下一列。剩余高度不足时不再布局,整体内容顶部居中。
- 当前layoutDirection设置为Column时,先从上到下排列,排满一列再排一下一列,剩余宽度度不足时不再。整体内容顶部居中。
- 此模式下GridItem的rowStart、columnStart不生效。
总结:
一、食物列表
首先,定义了一个具有头部导航和列表的组件,列表可以通过点击头部的返回图标或某种方式触发显示一个面板。面板显示时,会重置数量和输入内容的状态。
其次,使用 Tabs 组件创建标签页布局,TabContent 用于展示所有项目列表。使用 ForEach 遍历分组信息,并为每个分类创建一个 TabContent 和 tabBar。对于每个项目,使用 ListItem、Row、Image、Column 和 Text 组件创建列表项布局。为每个列表项添加 onClick 事件处理器,当点击时调用 showPanel 方法,并传递被点击的项目项。
二、底部Panel
在前面的基础上,ItemIndex 导入了路由模块、通用常量和自定义组件。定义了一个名为ItemIndex的结构体,它是一个组件,具有两个状态变量message和showPanel。onPanelShow方法用于设置showPanel为true,表示面板应该显示。build方法构建了组件的UI布局,包括导航、列表和面板。面板使用了Panel组件,并且可以自定义面板的样式和行为。Header 定义了组件的头部布局,包括返回按钮和标题文本。ItemList 组件定义了ItemList组件,它接收一个showPanel回调函数作为属性。build方法构建了一个带标签页的列表界面,每个标签页都调用TabContentBuilder方法来生成内容。TabContentBuilder方法使用ForEach遍历一个数组,并为每个项生成一个列表项,列表项中包含图片、文本和添加按钮。点击列表项时,会调用showPanel回调函数。ItemPanelHeader 定义了ItemPanelHeader组件,用于显示日期和可能的下拉箭头图标。ItemCard 组件定义了ItemCard组件,它接收一个amount属性。build方法构建了一个卡片布局,显示了图片、名称、营养素信息和数量。NutrientInfo是一个辅助构建器,用于显示营养素的标签和值。
三、数字键盘
在前面的基础上,Grid 组件创建网格布局,用于展示数字键盘的按钮。用 ForEach 遍历 numbers 数组,并为每个数字创建 GridItem 和 Text 组件作为按钮。clickDelete 方法处理删除按钮的点击事件,从当前值中移除最后一个字符,并更新数值。parseFloat 方法用于将字符串转换为浮点数,去除字符串末尾的小数点。在 clickNumber 中,检查输入的字符串是否符合预期的数值格式,确保不会输入超过两个小数点的数字。