案例——饮食记录、通用DB工具、饮食记录业务层开发和数据持久化及页面交互

一、饮食记录

主要实现功能:

记录吃了什么,做了什么运动,需要有饮食记录的模型和记录分组模型,记录本身模型信息已经存在了,要引用一下之前的模型信息。

代码:

RecordType.ets

export default class RecordType{
  /**
   * 类型id
   */
  id: number
  //类型名称
  name: ResourceStr
  //图标
  icon: ResourceStr
  //类型推荐最小卡路里
  min: number

  max: number

  constructor(id: number, name: ResourceStr, icon: ResourceStr, min: number = 0, max: number = 0) {
    this.id = id
    this.name = name
    this.icon = icon
    this.min = min
    this.max = max
  }
}

 RecordVO.ets

import RecordItem from './RecordItem'

/**
 * 饮食记录的页面数据模型
 */
export default class RecordVO {
  /**
   * 记录id
   */
  id: number
  /**
   * 饮食记录类型,RecordType的类型
   */
  typeId: number

  /**
   * 卡路里总数
   */
  calorie: number
  //引用的信息
  recordItem: RecordItem
  //食物的数量
  amount: number = 0
}

 RecordPO.ets

//数据库对象
export default class RecordPO{
  /**
   * 记录id
   */
  id?: number
  /**
   * 饮食记录类型
   */
  typeId: number
  /**
   * 记录中的食物或运动信息
   */
  itemId: number

  /**
   * 食物数量或运动时长,如果是运动信息则无
   */
  amount: number
  /**
   * 记录的日期
   */
  createTime: number
}

 RecordTypeModel.ets

import RecordType from '../viewmodel/RecordType'

enum RecordTypeEnum {
  /**
   * 早餐
   */
  BREAKFAST,
  /**
   * 午餐
   */
  LUNCH,
  /**
   * 晚餐
   */
  DINNER,
  /**
   * 加餐
   */
  EXTRA_MEAL,
  /**
   * 运动
   */
  WORKOUT
}

/**
 * 记录类型常量,每个枚举对应的数据
 */
const RecordTypes: RecordType[] = [
  new RecordType(0, $r("app.string.breakfast"), $r("app.media.ic_breakfast"), 423, 592),
  new RecordType(1, $r("app.string.lunch"), $r("app.media.ic_lunch"), 592, 761),
  new RecordType(2, $r("app.string.dinner"), $r("app.media.ic_dinner"), 423, 592),
  new RecordType(3, $r("app.string.extra_meal"), $r("app.media.ic_extra_m"), 0, 169),
  new RecordType(4, $r("app.string.workout"), $r("app.media.ic_workout")),//运动无卡路里
]

export {RecordTypes, RecordTypeEnum}

import relationalStore from '@ohos.data.relationalStore'
import { ColumnInfo, ColumnType } from '../common/bean/ColumnInfo'
import RecordPO from '../common/bean/RecordPO'
import DbUtil from '../common/utils/DbUtil'


/**
 * 数据库建表语句
 */
//下划线形式,增删改查时,不能直接拿着数据库对象进行处理
const CREATE_TABLE_SQL: string = `
 CREATE TABLE IF NOT EXISTS record (
   id INTEGER PRIMARY KEY AUTOINCREMENT,
   type_id INTEGER NOT NULL,
   item_id INTEGER NOT NULL,
   amount DOUBLE NOT NULL,
   create_time INTEGER NOT NULL
 )
 `


class RecordModel {//饮食记录有增删改查的功能,其还需要按日期查询--持久化保存,关系型数据库
  
}

let recordModel = new RecordModel()

export default recordModel as RecordModel

编写时问题:

RecordModel操作 饮食记录有增删改查的功能,其还需要按日期查询--数据库 数据库的表结构什么样,对象什么样,不是跟RecordVO一样,因为RecordVO是页面展示的,存储不合适

 新建一个RecordPO模型,有id可以增删改查用到,typeId可以,需要知道类型,卡路里可以算不需保存否则增加数据库体积, 对象完整的不能存,只能存id,amount数量可以存,因为要根据数据库的日期查,需要存一下日期。

相关知识:

二、通用DB工具

主要实现功能:

关系型数据库的增删改查实现非常麻烦,业务复杂不止仅有饮食记录做增删改查操作,可能还有其他, 如何每一个都写,很麻烦,开发通用关系数据库的工具类简化其增删改查DbUtil

代码:

DbUtil .ts

//增删改查模型
import common from '@ohos.app.ability.common';
import relationalStore from '@ohos.data.relationalStore';
import { ColumnInfo, ColumnType } from '../bean/ColumnInfo';
import Logger from './Logger';

const DB_FILENAME: string = 'HeiMaHealthy.db'

class DbUtil {
  rdbStore: relationalStore.RdbStore
//数据库操作初始化,该函数在项目启动时调用
  initDB(context: common.UIAbilityContext): Promise<void> {
    let config: relationalStore.StoreConfig = {
      name: DB_FILENAME,//Db文件名称,整个应用采用一个库就可以
      securityLevel: relationalStore.SecurityLevel.S1//安全级别
    }
    return new Promise<void>((resolve, reject) => {//rdb加载时异步的
      relationalStore.getRdbStore(context, config)//获取操作对象
        .then(rdbStore => {
          this.rdbStore = rdbStore//保存到成员变量的位置
          Logger.debug('rdbStore 初始化完成!')
          resolve()
        })
        .catch(reason => {
          Logger.debug('rdbStore 初始化异常', JSON.stringify(reason))
          reject(reason)
        })
    })
  }
//创建表(获取建表语句),在entryability中初始化
  //有recordModel表创建
  createTable(createSQL: string): Promise<void> {//异步
    return new Promise((resolve, reject) => {
      this.rdbStore.executeSql(createSQL)//把语句传进来
        .then(() => {
          Logger.debug('创建表成功', createSQL)
          resolve()
        })
        .catch(err => {
          Logger.error('创建表失败,' + err.message, JSON.stringify(err))
          reject(err)
        })
    })
  }
//增加
  insert(tableName: string, obj: any, columns: ColumnInfo[]): Promise<number> {//新增的表名称,对象
    return new Promise((resolve, reject) => {//异步返回
      // 1.构建新增数据(obj转成value格式)value不能写死,因为不知道obj的具体内容(对象和数据库的名字不匹配)
      //obj数据库的映射关系告诉columnInfo(数据库字段信息)
      let value = this.buildValueBucket(obj, columns)
      // 2.新增
      this.rdbStore.insert(tableName, value, (err, id) => {//回调
        if (err) {
          Logger.error('新增失败!', JSON.stringify(err))
          reject(err)
        } else {
          Logger.debug('新增成功!新增id:', id.toString())
          resolve(id)
        }
      })
    })
  }
//删除
  delete(predicates: relationalStore.RdbPredicates): Promise<number> {//需要传入条件
    return new Promise((resolve, reject) => {
      this.rdbStore.delete(predicates, (err, rows) => {
        if (err) {
          Logger.error('删除失败!', JSON.stringify(err))
          reject(err)
        } else {
          Logger.debug('删除成功!删除行数:', rows.toString())
          resolve(rows)
        }
      })
    })
  }
//查询
  queryForList<T>(predicates: relationalStore.RdbPredicates, columns: ColumnInfo[]): Promise<T[]> {//需要字段
    return new Promise((resolve, reject) => {
      this.rdbStore.query(predicates, columns.map(info => info.columnName), (err, result) => {//需要的是字段数组,不是Info数组map映射
        if (err) {
          Logger.error('查询失败!', JSON.stringify(err))
          reject(err)
        } else {
          Logger.debug('查询成功!查询行数:', result.rowCount.toString())//行数
          resolve(this.parseResultSet(result, columns))//如果直接返回,就是一堆结果
        }
      })
    })
  }

  parseResultSet<T> (result: relationalStore.ResultSet, columns: ColumnInfo[]): T[] {//需要用到字段名和字段类型
    // 1.声明最终返回的结果(数组)
    let arr = []
    // 2.判断是否有结果
    if (result.rowCount <= 0) {
      return arr
    }
    // 3.处理结果
    while (!result.isAtLastRow) {//如果最后一行就不处理,不是最后才处理
      // 3.1.去下一行
      result.goToNextRow()
      // 3.2.解析这行数据,转为对象
      let obj = {}//column中有想要的字段
      columns.forEach(info => {
        let val = null
        switch (info.type) {
          case ColumnType.LONG:
            val = result.getLong(result.getColumnIndex(info.columnName))//脚标和字段名
            break
          case ColumnType.DOUBLE:
            val = result.getDouble(result.getColumnIndex(info.columnName))
            break
          case ColumnType.STRING:
            val = result.getString(result.getColumnIndex(info.columnName))
            break
          case ColumnType.BLOB:
            val = result.getBlob(result.getColumnIndex(info.columnName))
            break
        }
        obj[info.name] = val
      })
      // 3.3.将对象填入结果数组
      arr.push(obj)
      Logger.debug('查询到数据:', JSON.stringify(obj))
    }
    return arr
  }

  buildValueBucket(obj: any, columns: ColumnInfo[]): relationalStore.ValuesBucket {
    let value = {}
    columns.forEach(info => {//循环遍历
      let val = obj[info.name]
      if (typeof val !== 'undefined') {//不是空才能匹配
        value[info.columnName] = val
      }
    })
    return value
  }
}

//初始化好了,导入就可以用
let dbUtil: DbUtil = new DbUtil();

export default dbUtil as DbUtil

 RecordModel.ets

import relationalStore from '@ohos.data.relationalStore'
import { ColumnInfo, ColumnType } from '../common/bean/ColumnInfo'
import RecordPO from '../common/bean/RecordPO'
import DbUtil from '../common/utils/DbUtil'


/**
 * 数据库建表语句
 */
//下划线形式,增删改查时,不能直接拿着数据库对象进行处理
const CREATE_TABLE_SQL: string = `
 CREATE TABLE IF NOT EXISTS record (
   id INTEGER PRIMARY KEY AUTOINCREMENT,
   type_id INTEGER NOT NULL,
   item_id INTEGER NOT NULL,
   amount DOUBLE NOT NULL,
   create_time INTEGER NOT NULL
 )
 `
//数据库列()映射关系
const COLUMNS: ColumnInfo[] = [
  {name: 'id', columnName: 'id', type: ColumnType.LONG},
  {name: 'typeId', columnName: 'type_id', type: ColumnType.LONG},
  {name: 'itemId', columnName: 'item_id', type: ColumnType.LONG},
  {name: 'amount', columnName: 'amount', type: ColumnType.DOUBLE},//可以是小数
  {name: 'createTime', columnName: 'create_time', type: ColumnType.LONG}
]

const TABLE_NAME = 'record'//表名
const ID_COLUMN = 'id'
const DATE_COLUMN = 'create_time'

class RecordModel {//饮食记录有增删改查的功能,其还需要按日期查询--持久化保存,关系型数据库
  //一调用Record Model就可以拿到建表语句
  getCreateTableSql(): string {//返回值是字符串
    return CREATE_TABLE_SQL
  }

  insert(record: RecordPO): Promise<number>{
    return DbUtil.insert(TABLE_NAME, record, COLUMNS)
  }

  deleteById(id: number): Promise<number>{
    // 1.删除条件
    let predicates = new relationalStore.RdbPredicates(TABLE_NAME)//删除知道表名
    predicates.equalTo(ID_COLUMN, id)
    // 2.删除
    return DbUtil.delete(predicates)
  }

  listByDate(date: number): Promise<RecordPO[]>{//分日期查询
    // 1.查询条件
    let predicates = new relationalStore.RdbPredicates(TABLE_NAME)
    predicates.equalTo(DATE_COLUMN, date)
    // 2.查询
    return DbUtil.queryForList(predicates, COLUMNS)
  }
}

let recordModel = new RecordModel()

export default recordModel as RecordModel

 ColumnInfo.ts

export interface ColumnInfo{
  name: string//实体名
  columnName: string//映射的名
  type: ColumnType//数据类型对应枚举,是否小数
}

//为什么是这四个(枚举)
export enum ColumnType{
  LONG,
  DOUBLE,
  STRING,
  BLOB
}
async onCreate(want, launchParam) {
    //加载用户首选项
    PreferenceUtil.loadPreference(this.context)
    // 2.初始化日期
    AppStorage.SetOrCreate(CommonConstants.RECORD_DATE, DateUtil.beginTimeOfDay(new Date()))
    //初始化RDB
    await DbUtil.initDB(this.context)
    //创建表,等待完后再创建表
    DbUtil.createTable(RecordModel.getCreateTableSql())

    hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onCreate');
  }

 编写时问题:

rdb加载时异步的,为什么用promise

promise对象就可以将异步操作以同步的流程表达出来,避免了层层嵌套导致的 

相关知识:

三、饮食记录业务层开发

主要实现功能:

饮食记录的数据库增删改查,但是数据库查的跟页面的有差异,实现把数据包括的转为页面所需要的。实现了新增饮食记录,根据Id删除饮食记录,根据id查询饮食记录列表的功能。将查到的饮食录列表转换成统计信息和分组记录信息。

代码:

RecordService.ets

//业务逻辑(实现数据库和页面的转化)
//完成增删改查的业务和封装
import RecordPO from '../common/bean/RecordPO'
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 {
  //新增饮食记录

  insert(typeId: number, itemId: number, amount: number): Promise<number>{//日期不用传,在全局变量中
    // 1.获取时间
    let createTime = (AppStorage.Get('selectedDate') || DateUtil.beginTimeOfDay(new Date())) as number//没取到就是今天(不一定number)
    // 2.新增
    return RecordModel.insert({typeId, itemId, amount, createTime})//给匿名对象
  }

  //根据id删除饮食记录
  deleteById(id: number): Promise<number>{
    return RecordModel.deleteById(id)
  }

  //根据日期查询饮食记录列表(给两不同的使用)
  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.查询记录项(处理1)
      rv.recordItem = ItemModel.getById(rp.itemId, rp.typeId !== RecordTypeEnum.WORKOUT)//从记录项的model找,运动无热量
      // 2.3.计算热量(处理2)
      rv.calorie = rp.amount * rv.recordItem.calorie
      return rv
    })
  }

  //卡片
  calculateStatsInfo(records: RecordVO[]): StatsInfo{
    // 1.准备结果
    let info = new StatsInfo()//默认0
    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
  }

  //分组的统计
  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

 GroupInfo.ets

//方便调用,循环写出bar,可点
export default class GroupInfo<TYPE, ELEMENT> {
  /**
   * 分组类型
   */
  type: TYPE
  /**
   * 组内数据集合
   */
  items: ELEMENT[]

  /**
   * 组内记录的总热量
   */
  calorie: number = 0

  constructor(type: TYPE, items: ELEMENT[]) {
    this.type = type
    this.items = items
  }
}

 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
  }
}
效果:

四、数据持久化和页面交互

主要实现功能:

利用上面功能,实现饮食记录和数据交互。

代码:

RecordIndex

@Provide records:RecordVO[]=[]

  @Prop @Watch('handlePageShow') isPageShow:boolean//true才aboutToAppear

  @StorageProp('selectedDate')
  @Watch('aboutToAppear')
  selectedDate: number = DateUtil.beginTimeOfDay(new Date())//获取这一天的毫秒值

  handlePageShow(){
    if (this.isPageShow) {
      this.aboutToAppear()
    }
  }

  //生命周期钩子中查询
  async aboutToAppear(){//查到共卡片和列表使用
    this.records=await RecordService.queryRecordByDate(this.selectedDate)//拿到选中的日期
  }

 StatsCard.ets

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'
@Component
export default struct StatsCard {
  //单项读取
  //DAteUtil获取当前日期的毫秒值
  @StorageProp('selectedDate') selectedDate: number = DateUtil.beginTimeOfDay(new Date())//获取这一天的毫秒值
  @StorageProp('currentBreakpoint') currentBreakpoint: string = BreakpointConstants.BREAKPOINT_SM

  @Consume @Watch('handleRecordChange') records:RecordVO[]//变量名与父亲的一直
  //拿到查询的,需要处理成Stats Info来渲染
  @State info:StatsInfo=new StatsInfo()
  //当record变更是处理
  handleRecordChange(){
    this.info=RecordService.calculateStatsInfo(this.records)
  }


  controller: CustomDialogController = new CustomDialogController({
    builder: DatePickDialog({selectedDate: new Date(this.selectedDate)})//下次打开要回显,不在初始,new date显示为日期
  })
  build() {
    Column(){
      //日期信息
      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()
      })
      //统计信息
      Swiper(){
        //热量统计
        CalorieStats({intake:this.info.intake,expend:this.info.expend})
        //营养素统计
        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)
  }
}

 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'

@Extend(Text) function grayText(){
  .fontSize(14)
  .fontColor($r('app.color.light_gray'))
}

@Component
export default struct RecordList {

  @Consume @Watch('handleRecordChange') records:RecordVO[]//变量名与父亲的一直
  //拿到查询的,需要处理成Stats Info来渲染
  @State groups:GroupInfo<RecordType,RecordVO>[]=[]
  //当record变更是处理
  handleRecordChange(){
    this.groups=RecordService.calculateGroupInfo(this.records)
  }




  build() {
    List({space:CommonConstants.SPACE_10}){
      ForEach(this.groups,(group:GroupInfo<RecordType,RecordVO>)=>{
        ListItem(){
          Column(){
            //分组标题
            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'))
            }
            .onClick(()=>{
              router.pushUrl({
                url:'pages/ItemIndex',
                params:{type:group.type}

              })
            })
            .width('100%')
            //组内记录
            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)
    .margin({top:10})
    .height('100%')

  }
  @Builder deleteButton(){
    Image($r('app.media.ic_public_delete_filled'))
      .width(20)
      .fillColor(Color.Red)
      .margin(5)
  }
}

Index.ets 

import BreakpointType from '../common/bean/BreakpointType'
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//实现点击当前页面的效果
  //再次进行初始化,欢迎界面之后
  //因为不是静态的,要new出来
  private breakpointSystem:BreakpointSystem=new BreakpointSystem()
  //storageProp必须初始化
  @StorageProp('currentBreakpoint') currentBreakpoint: string=BreakpointConstants.BREAKPOINT_SM
//实现RecordIndex加载
  @State isPageShow: boolean = false

  onPageShow(){//显示
    this.isPageShow = true
  }
  onPageHide(){//隐藏
    this.isPageShow = false
  }


  @Builder TabBarBuilder(title: ResourceStr, image: ResourceStr, index: number) {
    Column({ space: CommonConstants.SPACE_8 }) {
      Image(image)//图片必须是svg格式
        .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')
  }



  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))
    }
    .onChange(index => this.currentIndex = index)
    .width('100%')
    .height('100%')
    .vertical(new BreakpointType({
      sm:false,
      md:true,
      lg:true
    }).getValue(this.currentBreakpoint))//布局模式
  }
}
效果:

编写时问题:

1.RecordIndex首页about Appear是一次查询,只有在组件构建时触发,我们是页面跳转,跳转再回来,其不会重新出发About。。,函数没有触发,没有重新查询。

页面每次加载查询,recordIndex是组件没有页面显示,首页有页面显示,每次显示时通知RIndex。

2.统计中没有渲染:

builder默认下是传的值,而不是引用,所以値变更不会重新渲染

相关知识:

@Builder声明实体,表示可以进行Builder方式初始化,@Value注解,表示只公开getter,对所有属性的setter都封闭,即private修饰,所以它不能和@Builder现起用。

总结:

一、饮食记录

定义了几个类和枚举,用于管理饮食记录的数据模型、视图对象、持久化对象以及数据库访问。

二、通用DB工具

通过RecordTypeRecordVORecordPO类定义了不同类型的数据模型,用于在内存中操作和数据库中存储饮食记录数据。使用RecordTypeEnum枚举和RecordTypes常量来管理饮食记录的类型,提供类型安全和易于维护的数据。通过SQL语句定义了数据库的表结构,为持久化存储做准备。RecordModel类可能包含对数据库的增删改查操作,以及数据查询功能。

三、饮食记录业务层开发

引入了groupInfo来实现数据的分组功能。通过分组,用户能够更直观地查看和管理自己的饮食记录,如按餐次、食品类别或日期等方式进行分类。此外,我们还利用StatsInfo模块来统计和展示用户当日的卡路里摄入、运动消耗等关键信息。

四、数据持久化和页面交互

通过生命周期方法和事件处理函数响应用户的交互操作。组件的builderer方法使用了链式调用,构建了组件的UI布局和样式。Index中onPageShowonPageHide方法用于处理页面显示和隐藏的事件。RecordIndex组件即将显示(isPageShow变为true)时,调用handlePageShow方法。handlePageShow方法触发aboutToAppear生命周期钩子。aboutToAppear方法通过异步调用服务层的queryRecordByDate方法,根据持久化的selectedDate获取记录数据,并更新到records数组中,供组件的其他部分使用。

  • 27
    点赞
  • 42
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值