通过 @Link 或 @Prop 装饰器将父组件或外部传入的数据绑定到子组件。底部导航栏,通常使用 Tabs 组件,允许用户在不同的功能页面间切换。 router 模块来处理页面间的导航和跳转,例如 router.pushUrl 或 router.replaceUrl。利用生命周期方法,如 aboutToAppear 和 aboutToDisappear,来处理页面显示和隐藏时的逻辑。:使用 @StorageProp 装饰器或 AppStorage API 来存储和读取用户的应用状态。使用 List 或 ForEach 组件遍历项目列表,为每个项目项创建 ListItem的组件。
这一个页面分为三块小部分 build() { Column(){ // 1.头部搜索栏 SearchHeader() // 2.统计卡片 StatsCard() // 3.记录列表 RecordList() .layoutWeight(1) } } }
又分成了三个不同的页面来引用
import BreakpointType from '../common/bean/BreakanpointType'
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装饰器定义当前选中的标签页索引,默认为0
@State currentIndex: number = 0
// 创建BreakpointSystem实例,用于处理断点逻辑
private breakpointSystem: BreakpointSystem = new BreakpointSystem()
// 使用@StorageProp装饰器定义当前断点的存储属性,默认为小型设备断点
@StorageProp('currentBreakpoint') currentBreakpoint: string = BreakpointConstants.BREAKPOINT_SM
// 使用@State装饰器定义页面是否显示的状态
@State isPageShow: boolean = false
// 页面显示时调用的方法,设置页面显示状态为true
onPageShow(){
this.isPageShow = true
}
// 页面隐藏时调用的方法,设置页面显示状态为false
onPageHide(){
this.isPageShow = false
}
// 定义TabBarBuilder方法,用于构建标签栏的每一项
@Builder TabBarBuilder(title: ResourceStr, image: ResourceStr, index: number) {
Column({ space: CommonConstants.SPACE_8 }) {
// 设置图标,根据当前选中项改变颜色
Image(image)
.width(22)
.fillColor(this.selectColor(index))
// 设置标题,根据当前选中项改变颜色
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.gray')
}
// 构建UI界面的方法
build() {
// 使用Tabs组件创建标签页,设置标签栏位置
Tabs({ barPosition: BreakpointConstants.BAR_POSITION.getValue(this.currentBreakpoint) }) {
// 定义每个标签页的内容和对应的标签栏项
TabContent() {
RecordIndex({isPageShow: this.isPageShow})
}
// 使用TabBarBuilder构建标签栏的第一项
.tabBar(this.TabBarBuilder($r('app.string.tab_record'), $r('app.media.ic_calendar'), 0))
TabContent() {
// 第二个标签页的内容,这里只是一个示例文本
Text('发现页面')
}
// 使用TabBarBuilder构建标签栏的第二项
.tabBar(this.TabBarBuilder($r('app.string.tab_discover'), $r('app.media.discover'), 1))
TabContent() {
// 第三个标签页的内容,这里只是一个示例文本
Text('我的主页')
}
// 使用TabBarBuilder构建标签栏的第三项
.tabBar(this.TabBarBuilder($r('app.string.tab_user'), $r('app.media.ic_user_portrait'), 2))
}
// 设置Tabs组件的宽度和高度
.width('100%')
.height('100%')
// 设置选项卡变化时的回调,更新当前选中项索引
.onChange(index => this.currentIndex = index)
// 设置Tabs组件的布局方向,根据断点类型变化
.vertical(new BreakpointType({
sm: false,
md: true,
lg: true
}).getValue(this.currentBreakpoint))
}
}
头部搜索栏
import { CommonConstants } from '../../common/constants/CommonConstants'
@Component
export default struct SearchHeader {
build() {
// 使用Row组件创建水平布局,space属性定义子组件之间的间距
Row({space: CommonConstants.SPACE_6}){
// 在Row中添加Search组件用于搜索功能,placeholder属性定义搜索框的占位符文本
Search({placeholder: '搜索饮食或运动信息'})
// 使用textFont方法设置搜索框内文字的样式,这里设置了文字大小为18
.textFont({size: 18})
// 使用layoutWeight方法设置搜索组件在Row中的权重,值为1表示占据大部分空间
.layoutWeight(1)
// 在Row中添加Badge组件,用于显示角标
Badge({count: 1, position: BadgePosition.RightTop, style: {fontSize: 12}})
// 在Badge内部添加Image组件,用于显示图标
Image($r('app.media.ic_public_email'))
// 设置图标的宽度为24
.width(24)
}
// 设置Row组件的宽度为CommonConstants.THOUSANDTH_940定义的值
.width(CommonConstants.THOUSANDTH_940)
}
}
统计卡片
import BreakpointType from '../../common/bean/BreakpointType'
import BreakpointConstants from '../../common/constants/BreakpointConstants'
import { CommonConstants } from '../../common/constants/CommonConstants'
import DateUtil from '../../common/utils/DateUtil'
import RecordService from '../../service/RecordService'
import RecordVO from '../../viewmodel/RecordVO'
import StatsInfo from '../../viewmodel/StatsInfo'
import CalorieStats from './CalorieStats'
import DatePickDialog from './DatePickDialog'
import NutrientStats from './NutrientStats'
@Preview
@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)})
})
// 构建组件UI界面的方法
build() {
Column(){
// 1.日期信息
Row(){
// 显示格式化的日期
Text(DateUtil.formatDate(this.selectedDate))
.fontColor($r('app.color.secondary_color'))
// 添加一个可点击的图标,用于打开日期选择对话框
Image($r('app.media.ic_public_spinner'))
.width(20)
.fillColor($r('app.color.secondary_color'))
}
.padding(CommonConstants.SPACE_8)
.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)
// 设置Swiper的指示器样式
.indicatorStyle({selectedColor: $r('app.color.primary_color')})
// 根据断点类型设置Swiper的显示数量
.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)
}
}
记录列表
import router from '@ohos.router'
import { CommonConstants } from '../../common/constants/CommonConstants'
import RecordService from '../../service/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
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({space: CommonConstants.SPACE_8}){
// 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)
.height('100%')
.margin({top: 10})
}
@Builder deleteButton(){
Image($r('app.media.ic_public_delete_filled'))
.width(20)
.fillColor(Color.Red)
.margin(5)
}
}