期末项目黑马健康2

期末项目黑马健康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 方法为列表项添加了左滑显示删除按钮的功能

  • 32
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
根据提供的引用内容,我无法直接提供完整的JavaScript期末项目的源码。但是,我可以为您提供一些关于JavaScript期末项目的一般介绍和示例。 JavaScript期末项目通常要求学生设计和开发一个具有交互性和动态功能的网页。这些项目可以涉及使用HTML、CSS和JavaScript来创建各种功能,例如表单验证、动画效果、数据展示等。 以下是一个简单的JavaScript期末项目示例,用于创建一个基本的网页表单验证功能: ```html <!DOCTYPE html> <html> <head> <title>表单验证</title> <script> function validateForm() { var name = document.forms["myForm"]["name"].value; var email = document.forms["myForm"]["email"].value; if (name == "") { alert("请输入姓名"); return false; } if (email == "") { alert("请输入电子邮件"); return false; } } </script> </head> <body> <h1>表单验证</h1> <form name="myForm" onsubmit="return validateForm()"> <label for="name">姓名:</label> <input type="text" id="name" name="name"><br><br> <label for="email">电子邮件:</label> <input type="email" id="email" name="email"><br><br> <input type="submit" value="提交"> </form> </body> </html> ``` 在这个示例中,我们创建了一个简单的表单,要求用户输入姓名和电子邮件。然后,使用JavaScript编写了一个函数`validateForm()`来验证表单数据。如果姓名或电子邮件为空,将弹出警告框并阻止表单提交。 这只是一个简单的示例,JavaScript期末项目可以更加复杂和有创意。您可以根据自己的兴趣和要求来设计和开发一个独特的项目

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值