期末项目黑马健康2
文章目录
前言
这篇用来展示如何实现 饮食记录-顶部搜索栏、 饮食记录-统计卡片和 饮食记录-记录列表
一、项目名称:黑马健康
黑马健康app是一款健康减肥服务软件,具备工具、社区、电商等多种属性。
提供体重/饮食/运动/习惯/睡眠/围度管理、食物热量查询、健身课程推荐、周边产品电商渠道等多功能为一体的健康类应用。
二、应用运行过程
1.饮食记录-顶部搜索栏
运行截图
代码
SearchHeader.ests
是一个用于构建搜索头部组件的模型。
代码如下(示例):
import { CommonConstants } from '../../common/constants/CommonConstants'
@Component
export default struct SearchHeader {
build() {
Row({space: CommonConstants.SPACE_6}){
Search({placeholder: '搜索饮食或运动信息'})
.textFont({size: 18})
.layoutWeight(1)
Badge({count: 1, position: BadgePosition.RightTop, style: {fontSize: 12}}){
Image($r('app.media.ic_public_email'))
.width(24)
}
}
.width(CommonConstants.THOUSANDTH_940)
}
}
RecordIndex.ets
是一个页面或组件的模型,用于展示和查询记录。
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 {
@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'))
}
}
2.饮食记录-统计卡片
运行截图
代码
DatePickDialog代码
定义了一个名为 DatePickDialog 的自定义对话框组件,用于选择日期。
import { CommonConstants } from '../../common/constants/CommonConstants'
@CustomDialog
export default struct DatePickDialog {
controller: CustomDialogController
selectedDate: Date = new Date()
build() {
Column({space: CommonConstants.SPACE_12}){
// 1.日期选择器
DatePicker({
start: new Date('2020-01-01'),
end: new Date(),
selected: this.selectedDate
})
.onChange((value: DatePickerResult) => {
this.selectedDate.setFullYear(value.year, value.month, value.day)
})
// 2.按钮
Row({space:CommonConstants.SPACE_12}){
Button('取消')
.width(120)
.backgroundColor($r('app.color.light_gray'))
.onClick(() => this.controller.close())
Button('确定')
.width(120)
.backgroundColor($r('app.color.primary_color'))
.onClick(() => {
// 1.保存日期到全局存储
AppStorage.SetOrCreate('selectedDate', this.selectedDate.getTime())
// 2.关闭窗口
this.controller.close()
})
}
}
.padding(CommonConstants.SPACE_12)
}
}
StatsCard.ets
定义了一个名为 StatsCard 的组件,用于显示统计信息。这个组件使用了装饰器和特定的属性来管理状态、事件监听和UI构建。
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 '../../service/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'))
.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)
.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)
}
}
3.饮食记录-记录列表
运行截图
代码
RecordIdex.ets
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)
}
}
RecordList.ets
用于展示一个包含分组标题和组内记录列表的列表结构。
import { CommonConstants } from '../../common/constants/CommonConstants'
@Extend(Text) function grayText(){//1.分组的标题 中 Text单独样式
.fontSize(14)
.fontColor($r('app.color.light_gray'))
}
@Component
export default struct RecordList {
build() {
List({space:CommonConstants.SPACE_10}){//上下有间隔
ForEach([1,2,3,4,5],(item)=>{
ListItem(){
Column() {
//1.分组的标题
Row({ space: CommonConstants.SPACE_4 }) {
Image($r('app.media.ic_breakfast')).width(24)
Text('早餐').fontSize(18).fontWeight(CommonConstants.FONT_WEIGHT_700)
Text('建议423-592千卡').grayText()
Blank() //空白
Text('190').fontSize(14).fontColor($r('app.color.light_primary_color'))
Text('千卡').grayText()
Image($r('app.media.ic_public_add_norm_filled'))
.width(20)
.fillColor($r('app.color.primary_color'))
}
.width('100%')
//2.组内记录列表
List() {
ForEach([1, 2], (item) => {
ListItem(){
Row({space:CommonConstants.SPACE_4}){
Image($r('app.media.toast')).width(50)
Column({space:CommonConstants.SPACE_4}){
Text('全麦吐司').fontWeight(CommonConstants.FONT_WEIGHT_500)
Text('1片').grayText()
}
Blank()
Text('91千克').grayText()
}
.width('100%')
.padding(CommonConstants.SPACE_6)
}.swipeAction({end:this.delectButton.bind(this)})//左滑显示删除按钮
})
}
}
.width('100%')
.backgroundColor(Color.White)
.borderRadius(CommonConstants.DEFAULT_18)//边框弧度
.padding(CommonConstants.SPACE_12)//边距 内容不贴边
}
})
}
.width(CommonConstants.THOUSANDTH_940)
.margin({top:10})
.height('100%')
}
@Builder delectButton(){
Image($r('app.media.ic_public_delete_filled'))
.width(20)
.fillColor(Color.Red)
.margin(5)
}
}
总结
1.饮食记录-顶部搜索栏
通过这个框架,我们来实现顶部搜索栏
build方法:用来构建和渲染组件UI的。
UI结构:
使用Row组件来创建一个水平排列的子组件容器,
在Row组件内部,有两个子组件:Search组件和Badge组件。
2. 饮食记录-统计卡片
通过饮食记录UI设计来实现饮食记录-统计卡片
3.饮食记录-记录列表
1.自定义函数 grayText
2.在 build 方法中,定义了组件的渲染结构。
1.外部列表:使用 List 组件创建了一个外部列表,列表项之间有间距
列表项结构:每个列表项(ListItem)内部包含了一个 Column 组件来组织内容。
分组标题部分:使用 Row 组件和 Image、Text 组件展示了早餐的图标、标题、建议的卡路里值等信息。
组内记录列表:嵌套了一个 List 组件来展示组内记录。
3.使用 ForEach 组件遍历数组 [1, 2]
4.每个内部列表项(ListItem)内部使用了 Row 和 Column 组件来组织内容,包括食物图片、名称、数量、卡路里等信息。
5.使用了 swipeAction 方法为列表项添加了左滑显示删除按钮的功能