鸿蒙HarmonyOS开发实战(完结-5)

根据黑马程序员(https://www.bilibili.com/video/BV1Sa4y1Z7B1?p=36&vd_source=756db0e287d884b2979ab5b54a59c305)编写

目录

实现效果图:

饮食记录业务层开发

实现需求

实现代码

StatsInfo.ets:

GroupInfo.ets:

ItemList.ets

RecordService.ets

实现数据持久化和页面交互

实现需求:

实现代码

StatsCard.ets:

RecordList.ets:

RecordIndex.ets:

CalorieStats.ets:

NutrientStats.ets:

ItemIndex.ets:

Index.ets:

ItemCard.ets

小结


本次实现饮食记录业务层开发和实现数据持久化和网页交互

最终实现效果描述:进入记录页面,如下方图(1)所示,在点击绿色添加按钮之后弹出添加界面图(2)图(3),然后再次点击添加按钮弹出图(4),此时在下方会弹出数字键盘,点击数字之后上方会实时更新数字键盘数据,点击提交以后图(1)会实时更新数据如图(5)和图(6)所示。

实现效果图:


记录页面和添加

图1图2图3

实现实时交互

图4图5图6

饮食记录业务层开发

实现需求

1.为了实现图(5)和图(6)的实时交互我们需要一个数据模型(StatsInfo)将其数据对应起来。
2.为了实现图1中饮食记录的分组展示,我们需要将数据库中查询到的数据需要先分组(GroupInfo)处理之后再渲染页面,而改动GroupInfo之后之前使用其的页面(ItemList)也需要跟随改变。
3.将数据库中查出的数据转化成需要的数据,需要封装业务逻辑(RecordService)。

实现代码

StatsInfo.ets:
export default class StatsInfo{
  //当日摄入卡路里总量
  intake: number = 0
  //当日运动消耗能量
  expend: number = 0
  //当日摄入碳水总量
  carbon: number = 0
  //当日摄入蛋白总量
  protein: number = 0
  //当日摄入脂肪总量
  fat: number = 0

  constructor(intake: number = 0, expend: number = 0, carbon: number = 0, protein: number = 0, fat: number = 0) {
    this.intake = intake
    this.expend = expend
    this.carbon = carbon
    this.protein = protein
    this.fat = fat
  }
}
GroupInfo.ets:
export default class GroupInfo<TYPE, ELEMENT> {
  //分组类型
  type: TYPE
  //组内数据集合
  items: ELEMENT[]
 //组内记录的总热量
  calorie: number = 0
  constructor(type: TYPE, items: ELEMENT[]) {
    this.type = type
    this.items = items
  }
}
ItemList.ets

将ForEach循环中的group: GroupInfo改为group: GroupInfo<ItemCategory,RecordItem>

RecordService.ets
import DateUtil from '../common/utils/DateUtil'
import ItemModel from '../model/ItemModel'
import RecordModel from '../model/RecordModel'
import { RecordTypeEnum, RecordTypes } from '../model/RecordTypeModel'
import GroupInfo from '../viewmodel/GroupInfo'
import RecordType from '../viewmodel/RecordType'
import RecordVO from '../viewmodel/RecordVO'
import StatsInfo from '../viewmodel/StatsInfo'
class RecordService {
  /**
   * 新增饮食记录
   * @param typeId 记录类型id
   * @param itemId 记录项id
   * @param amount 记录项数量(食物量、运动时长)
   * @returns 新增数量
   */
  insert(typeId: number, itemId: number, amount: number): Promise<number>{
    // 1.获取时间
    let createTime = (AppStorage.Get('selectedDate') || DateUtil.beginTimeOfDay(new Date())) as number
    // 2.新增
    return RecordModel.insert({typeId, itemId, amount, createTime})
  }

  /**
   * 根据id删除饮食记录
   * @param id 记录id
   * @returns 删除条数
   */
  deleteById(id: number): Promise<number>{
    return RecordModel.deleteById(id)
  }

  /**
   * 根据日期查询饮食记录列表
   * @param date 要查询的日期
   * @returns 记录列表
   */
  async queryRecordByDate(date: number): Promise<RecordVO[]>{
    // 1.查询数据库的RecordPO
    let rps = await RecordModel.listByDate(date)
    // 2.将RecordPO转为RecordVO
    return rps.map(rp => {
      // 2.1.获取po中的基本属性
      let rv = {id: rp.id, typeId: rp.typeId, amount: rp.amount} as RecordVO
      // 2.2.查询记录项
      rv.recordItem = ItemModel.getById(rp.itemId, rp.typeId !== RecordTypeEnum.WORKOUT)
      // 2.3.计算热量
      rv.calorie = rp.amount * rv.recordItem.calorie
      return rv
    })
  }
  /**
   * 根据记录列表信息统计出热量、营养素信息
   * @param records 饮食记录列表
   * @returns 热量、营养素信息
   */
  calculateStatsInfo(records: RecordVO[]): StatsInfo{
    // 1.准备结果
    let info = new StatsInfo()
    if(!records || records.length <= 0){
      return info
    }
    // 2.计算统计数据
    records.forEach(r => {
      if(r.typeId === RecordTypeEnum.WORKOUT){
        // 运动,累加消耗热量
        info.expend += r.calorie
      }else{
        // 食物,累加摄入热量、蛋白质、碳水、脂肪
        info.intake += r.calorie
        info.carbon += r.recordItem.carbon
        info.protein += r.recordItem.protein
        info.fat += r.recordItem.fat
      }
    })
    // 3.返回
    return info
  }
  /**
   * 将记录列表按照记录类型分组
   * @param records 记录列表
   * @returns 分组记录信息
   */
  calculateGroupInfo(records: RecordVO[]): GroupInfo<RecordType, RecordVO>[]{
    // 1.创建空的记录类型分组
    let groups = RecordTypes.map(recordType => new GroupInfo(recordType, []))
    if(!records || records.length <= 0){
      return groups
    }
    // 2.遍历所有饮食记录
    records.forEach(record => {
      // 2.1.把每个记录存入其对应类型的分组中
      groups[record.typeId].items.push(record)
      // 2.2.计算该组的总热量
      groups[record.typeId].calorie += record.calorie
    })
    return groups
  }
}

let recordService = new RecordService()

export default recordService as RecordService

实现数据持久化和页面交互

实现需求:

1.为了实现饮食记录界面交互的持久化保存统计卡片(StatsCard)和记录列表(RecordList)都需要用到饮食记录列表,所以为了方便在其共同父组件(RecordIndex)中设置@provide供子组件使用。

2.子组件再进行渲染数据,在渲染时需要在热量统计(CalorieStats)和营养素统计(NutrientStats)还有纪录列表(RecordList)三张卡片中渲染,所以其数据要改为状态变量(@Prop)。

3.完成点击饮食界面的标题完成跳转页面(ItemIndex)功能,然后在其内完成持久化保存功能

4.给主页面(Index)增加状态变数通知RecordIndex,渲染页面显示状态

5.因为@Builder函数不会触发视图的重新渲染,需要给其传递引用值(运用$$:{}),再用引用值改变其数据。所以需要更改(ItemCard.ets、CalorieStats.ets、NutrientStats.ets)中的数据

实现代码

StatsCard.ets:
 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 './CalorieState'
import DatePickDialog from './DatePickDialog'
import NutrientStats from './NutrientState'


@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(CommonConstants.THOUSANDTH_940)
    .backgroundColor($r('app.color.stats_title_bgc'))
    .borderRadius(CommonConstants.DEFAULT_18)
  }

}
RecordList.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'

//对Text组件提出,方便以后调用,减少代码冗余
@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循环,用以循环渲染页面,此处1,2,3,4是暂时先渲染一样的,后面方便进行更改
      ForEach(this.groups, (groups: GroupInfo<RecordType, RecordVO>) => {
        ListItem() {
          Column({ space: CommonConstants.SPACE_8 }) {
            // 1.分组的标题
            Row({ space: CommonConstants.SPACE_4 }) {
              Image(groups.type.icon).width(24)
              Text(groups.type.name).fontSize(18).fontWeight(CommonConstants.FONT_WEIGHT_700)
              Text(`建议${groups.type.min}~${groups.type.max}千卡`).grayText()
              //布局
              Blank()
              Text(groups.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:groups.type}
              })
            })
            // 2.组内记录列表
            List() {
              //此处ForEach同上
              ForEach(groups.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)
  }
}
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{
  @Prop @Watch('handlePageShow') isPageShow :boolean
  @Provide records:RecordVO[]=[]
  //监控日期变更
  @Watch('aboutToAppear')
  @StorageProp('selectedDate') selectedDate: number = DateUtil.beginTimeOfDay(new Date())

  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'))

  }

}
CalorieStats.ets:
import { CommonConstants } from '../../common/constants/CommonConstants'
@Component
export default struct CalorieStats {
  @Prop intake: number
  @Prop expend: number
  recommend: number = CommonConstants.RECOMMEND_CALORIE

  remainCalorie(){
    return this.recommend - this.intake + this.expend
  }

  //抽取函数方便调用
  @Builder StatsBuilder($$:{label: string, value: number, tips?: string}){
    Column({space: CommonConstants.SPACE_6}){
      Text($$.label)
        .fontColor($r('app.color.gray'))
        .fontWeight(CommonConstants.FONT_WEIGHT_600)
      Text($$.value.toFixed(0))
        .fontSize(20)
        .fontWeight(CommonConstants.FONT_WEIGHT_700)
      if($$.tips){
        Text($$.tips)
          .fontSize(12)
          .fontColor($r('app.color.light_gray'))
      }
    }
  }
  build() {
    Row({space: CommonConstants.SPACE_6}){
      // 1.饮食摄入
      this.StatsBuilder({label:'饮食摄入', value:this.intake})
      // 2.还可以吃
      Stack(){
        // 2.1.进度条
        Progress({
          value: this.intake,
          total: this.recommend,
          type: ProgressType.Ring
        })
          .width(120)
          .style({strokeWidth: CommonConstants.DEFAULT_10})
          .color($r('app.color.primary_color'))
        // 2.2.统计数据
        this.StatsBuilder({label:'还可以吃', value:this.remainCalorie(),tips:`推荐${this.recommend}`})
      }
      // 3.运动消耗
      this.StatsBuilder({label:'运动消耗', value:this.expend})
    }
    .width('100%')
    .justifyContent(FlexAlign.SpaceEvenly)
    //设置边距
    .padding({top: 30, bottom: 35})
  }

}
NutrientStats.ets:
import { CommonConstants } from '../../common/constants/CommonConstants'

@Component
export default struct NutrientStats {
  @Prop carbon: number
  @Prop protein: number
  @Prop fat: number
  recommendCarbon: number = CommonConstants.RECOMMEND_CARBON
  recommendProtein: number = CommonConstants.RECOMMEND_PROTEIN
  recommendFat: number = CommonConstants.RECOMMEND_FAT
  //抽取组件方便调用
  @Builder StatsBuilder($$: {
    label: string,
    value: number,
    recommend: number,
    color: ResourceStr
  }) {
    Column({ space: CommonConstants.SPACE_6 }) {
      Stack() {
        Progress({
          value: $$.value,
          total: $$.recommend,
          type: ProgressType.Ring
        })
          .width(95)
          .style({ strokeWidth: CommonConstants.DEFAULT_6 })
          .color($$.color)
        Column({ space: CommonConstants.SPACE_6 }) {
          Text('摄入推荐')
            .fontSize(12)
            .fontColor($r('app.color.gray'))
          Text(`${$$.value.toFixed(0)}/${$$.recommend.toFixed(0)}`)
            .fontSize(18)
            .fontWeight(CommonConstants.FONT_WEIGHT_600)
        }
      }

      Text('(克)')
        .fontSize(12)
        .fontColor($r('app.color.light_gray'))
    }
  }

  build() {
    Row({ space: CommonConstants.SPACE_6 }) {
      this.StatsBuilder({
        label: '碳水化合物',
        value: this.carbon,
        recommend: this.recommendCarbon,
        color: $r('app.color.carbon_color')
      })
      this.StatsBuilder({
        label: '蛋白质',
        value: this.protein,
        recommend: this.recommendProtein,
        color: $r('app.color.protein_color')
      })
      this.StatsBuilder({
        label: '脂肪',
        value: this.fat,
        recommend: this.recommendFat,
        color: $r('app.color.fat_color')
      })
    }
    .width('100%')
    .justifyContent(FlexAlign.SpaceEvenly)
    .padding({ top: 30, bottom: 35 })
  }
}
ItemIndex.ets:
import router from '@ohos.router'
import { CommonConstants } from '../common/constants/CommonConstants'
import { RecordTypeEnum, RecordTypes } from '../model/RecordTypeModel'
import RecordService from '../service/RecordService'
import ItemCard from '../view/item/ItemCard'
import ItemList from '../view/item/ItemList'
import ItemPanelHeader from '../view/item/ItemPanelHeader'
import NumberKeyboard from '../view/item/NumberKeyboard'
import RecordItem from '../viewmodel/RecordItem'
import RecordType from '../viewmodel/RecordType'
@Entry
@Component
struct ItemIndex {
  @State showPanel:boolean = false
  @State amount:number=1
  @State value: string = ''
  //item用于接受点击的数据
  @State item:RecordItem=null
  @State type:RecordType=RecordTypes[0]
  @State isFood:boolean = true
  onPanelShow(item:RecordItem){
    //控制面板展示
    this.amount=1
    this.value=''
    this.item=item
    this.showPanel=true
  }
  onPageShow(){
    //1.获取跳转时的参数
    let params:any=router.getParams()
    //2.获取点击的饮食记录类型
    this.type=params.type
    //3.判断是否是食物
    this.isFood =this.type.id!==RecordTypeEnum.WORKOUT
  }
  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按钮
        Row({space: CommonConstants.SPACE_6}){
          Button('取消')
            .width(120)
            .backgroundColor($r('app.color.light_gray'))
            .type(ButtonType.Normal)
            .borderRadius(6)
            .onClick(() => this.showPanel = false)
          Button('提交')
            .width(120)
            .type(ButtonType.Normal)
            .borderRadius(6)
            .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})
      }
      .mode(PanelMode.Full)
      .dragBar(false)
      .backgroundMask($r('app.color.light_gray'))
      .backgroundColor(Color.White)
    }
    .height('100%')
  }




  //头部
@Builder Header(){
  Row(){
    Image($r('app.media.ic_public_back'))
      .width(30)
      .onClick(()=>router.back())
    Blank()
    Text(this.type.name).fontSize(18).fontWeight(CommonConstants.FONT_WEIGHT_600)
  }.width(CommonConstants.THOUSANDTH_940)
  .height(32)
}
}
Index.ets:
import BreakpointType from '../common/bean/BreanpointType'
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 currentIndex: number = 0
  @State isPageShow: boolean = false
  private breakpointSystem:BreakpointSystem=new BreakpointSystem()
  @StorageProp ('currentBreakpoint') currentBreakpoint :string =BreakpointConstants.BREAKPOINT_SM

  //页面显示
  onPageShow(){
    this.isPageShow = true
  }
  //页面隐藏
  onPageHide(){
    this.isPageShow = false
  }
aboutToAppear(){
  this.breakpointSystem.register()
}
  aboutToDisappear(){
    this.breakpointSystem.unregister()
  }

  @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))
    }
  }

  //颜色选择
  selectColor(index: number) {
    return this.currentIndex === index ? $r('app.color.primary_color') : $r('app.color.gray')
  }

  build() {

    Tabs({ barPosition: BreakpointConstants.BAR_POSITION.getValue(this.currentBreakpoint) }) {
      TabContent() {
       RecordIndex({isPageShow:this.isPageShow})
      }
      .tabBar(this.TabBarBuilder($r('app.string.tab_record'), $r('app.media.ic_calendar'),0))

      TabContent() {
        Text('发现页面')
      }
      .tabBar(this.TabBarBuilder($r('app.string.tab_discover'), $r('app.media.discover'), 1))

      TabContent() {
        Text('我的主页')
      }
      .tabBar(this.TabBarBuilder($r('app.string.tab_user'), $r('app.media.ic_user_portrait'), 2))

    }
    .width('100%')
    .height('100%')
    .vertical(false)
    .onChange(index => this.currentIndex = index)
    .vertical(new BreakpointType({
      sm: false,
      md: true,
      lg: true
    }).getValue(this.currentBreakpoint))
  }
}
ItemCard.ets
import { CommonConstants } from '../../common/constants/CommonConstants'
import RecordItem from '../../viewmodel/RecordItem'

@Component
export default struct ItemCard {
  @Prop amount:number
  //接收数据
  @Link item:RecordItem
  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:15,right:12})
      //水平线
      Divider().width(CommonConstants.THOUSANDTH_940).opacity(0.6)
      //3.营养素信息
      Row({space:CommonConstants.SPACE_8}){
        this.NutrientInfo({label:'热量(千卡)',value:this.item.calorie})
        //判断ID是否小于10000,因为运动没有碳水等信息且我们将运动ID设置为10000以上
        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'))
      }
        Text(this.item.unit)
          .fontColor($r('app.color.light_gray'))
          .fontWeight(CommonConstants.FONT_WEIGHT_600)
        } .width(150)

    }
  }
  @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)
    }
  }
}

小结

本次开发任务中,我们成功实现了饮食记录业务层的关键功能,不仅涵盖了数据持久化,还深入到了页面交互的开发。通过精心设计和细致编程,我们顺利完成了记录页面的开发工作,并且确保了其具备实时交互和高效渲染的能力。

在业务层方面,我们针对饮食记录的需求,建立了清晰的数据模型和业务流程。通过业务逻辑的处理,用户能够方便地记录他们的饮食信息,包括食物的种类、数量、时间等关键数据。同时,我们也为食物和运动信息提供了增加、修改和删除的功能,使得用户能够根据自己的实际情况灵活调整记录内容。

在数据持久化方面,我们采用了高效稳定的数据存储方案,确保用户的数据能够安全、可靠地保存在服务器上。通过数据持久化,我们为用户提供了持久化的数据服务,即使在软件关闭或重启后,用户的饮食记录也能得以保留,方便用户随时查看和管理。

在页面交互方面,我们注重用户体验和交互设计。通过精心设计的用户界面和流畅的用户操作,我们为用户提供了便捷、直观的操作体验。用户可以在记录页面上轻松添加食物或运动信息,查看历史记录,以及进行各种查询和筛选操作。同时,我们也充分利用了现代前端技术,实现了实时交互和高效渲染,使得页面在加载和更新数据时能够迅速响应,极大地提升了用户体验。

最终,我们成功地将这些功能集成到了黑马健康APP中,为用户提供了一个全面、便捷的饮食记录工具。

  • 24
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值