鸿蒙期末个人项目“黑马健康”——项目阶段六:数据模型

目录

项目介绍:

步骤:

数据模型-记录项

数据模型-饮食记录&通用DB工具

页面实现效果:

数据模型-记录项

数据模型-饮食记录&通用DB工具

重要步骤与出现的问题:

阶段项目代码:

数据模型-记录项主要代码:

数据模型-饮食记录&通用DB工具主要代码:


项目介绍:

 黑马健康软件是一款基于全民健康的软件,主要有三个页面组成,分别是欢迎页面,统计记录页面,食物列表页面。

步骤:

数据模型-记录项

                当点击饮食记录页面的加号(可以是食物也可以是运动)之后,会自动跳转到选择食物等数据项的界面,所以首先应该加载食物等数据量的数据模型和分类的模型。将RecordItem[]作为参数传递给加载食物等数据项的函数时,在ItemList.ets页面可以使用数组加载这些数据。将TabContent写成动态的,在数组中不仅要加载tabBar的信息,还要加加载该tabBar下面的数据项的信息。在接口中生成下面这种格式的数据,在viewmodel中定义一个数据模型来盛放这些数据,并定义成一个类型。

在加载食物等列表的数据项时直接调用类型中的数据就可以,因为已经封装划分好了。

数据模型-饮食记录&通用DB工具

和上面的流程类似,建立饮食记录的类型和需要用到的数据模型设计,这里需要用到数据持久化操作来存储过去的某一天所记录的表项。

准备工作:

数据库操作:建表、初始化、数据的增删改查;

页面实现效果:

数据模型-记录项

分类查询渲染:

Panel效果:

结论:实现了动态查询。

数据模型-饮食记录&通用DB工具

重要步骤与出现的问题:

1.在渲染食物和运动的Panel样式时,因为展现的内容不一样,所以要增加一个判断来区分,第一种就是可以将isFood的参数传递到页面渲染的组件中,第二个就是将运动的数据项的下标设计的和食物不一样,让运动的下标>=10000,增加一个判断

渲染的效果如上图

2.向EntryAbility中导RecordModel的包时,发现导不进去

因为EntryAbility是ts类型,而要导的包是ets类型,需要将EntryAbility的类型改为ets

阶段项目代码:

数据模型-记录项主要代码:
import RecordItem from '../viewmodel/RecordItem'
import ItemCategory from '../viewmodel/ItemCategory'
import { FoodCategories, FoodCategoryEnum, WorkoutCategories, WorkoutCategoryEnum } from './ItemCategoryModel'
import GroupInfo from '../viewmodel/GroupInfo'

const foods: RecordItem[] = [
  new RecordItem(0, '米饭',$r('app.media.rice'),FoodCategoryEnum.STAPLE, '碗',  209, 46.6, 4.7, 0.5),
  new RecordItem(1, '馒头',$r('app.media.steamed_bun'),FoodCategoryEnum.STAPLE, '个',  114, 24.0, 3.6, 0.6),
  new RecordItem(2, '面包',$r('app.media.bun'),FoodCategoryEnum.STAPLE, '个',  188, 35.2, 5.0, 3.1),
  new RecordItem(3, '全麦吐司',$r('app.media.toast'),FoodCategoryEnum.STAPLE, '片',  91, 15.5, 4.4, 1.3),
  new RecordItem(4, '紫薯',$r('app.media.purple_potato'),FoodCategoryEnum.STAPLE, '个',  163, 42.0, 1.6, 0.4),
  new RecordItem(5, '煮玉米',$r('app.media.corn'),FoodCategoryEnum.STAPLE, '根',  111, 22.6, 4.0, 1.2),
  new RecordItem(6, '黄瓜',$r('app.media.cucumber'),FoodCategoryEnum.FRUIT, '根',  29, 5.3, 1.5, 0.4),
  new RecordItem(7, '蓝莓',$r('app.media.blueberry'),FoodCategoryEnum.FRUIT, '盒',  71, 18.1, 0.9, 0.4),
  new RecordItem(8, '草莓',$r('app.media.strawberry'),FoodCategoryEnum.FRUIT, '颗',  14, 3.1, 0.4, 0.1),
  new RecordItem(9, '火龙果',$r('app.media.pitaya'),FoodCategoryEnum.FRUIT, '个',  100, 24.6, 2.2, 0.5),
  new RecordItem(10, '奇异果',$r('app.media.kiwi'),FoodCategoryEnum.FRUIT, '个',  25, 8.4, 0.5, 0.3),
  new RecordItem(11, '煮鸡蛋',$r('app.media.egg'),FoodCategoryEnum.MEAT, '个',  74, 0.1, 6.2, 5.4),
  new RecordItem(12, '煮鸡胸肉',$r('app.media.chicken_breast'),FoodCategoryEnum.MEAT, '克',  1.15, 0.011, 0.236, 0.018),
  new RecordItem(13, '煮鸡腿肉',$r('app.media.chicken_leg'),FoodCategoryEnum.MEAT, '克',  1.87, 0.0, 0.243, 0.092),
  new RecordItem(14, '牛肉',$r('app.media.beef'),FoodCategoryEnum.MEAT, '克',  1.22, 0.0, 0.23, 0.033),
  new RecordItem(15, '鱼肉',$r("app.media.fish"),FoodCategoryEnum.MEAT, '克',  1.04, 0.0, 0.206, 0.024),
  new RecordItem(16, '牛奶',$r("app.media.milk"),FoodCategoryEnum.MEAT, '毫升',  0.66, 0.05, 0.03, 0.038),
  new RecordItem(17, '酸奶',$r("app.media.yogurt"),FoodCategoryEnum.MEAT, '毫升',  0.7, 0.10, 0.032, 0.019),
  new RecordItem(18, '核桃',$r("app.media.walnut"),FoodCategoryEnum.NUT, '颗',  42, 1.2, 1.0, 3.8),
  new RecordItem(19, '花生',$r("app.media.peanut"),FoodCategoryEnum.NUT, '克',  3.13, 0.13, 0.12, 0.254),
  new RecordItem(20, '腰果',$r("app.media.cashew"),FoodCategoryEnum.NUT, '克',  5.59, 0.416, 0.173, 0.367),
  new RecordItem(21, '无糖拿铁',$r("app.media.coffee"),FoodCategoryEnum.OTHER, '毫升',  0.43, 0.044, 0.028, 0.016),
  new RecordItem(22, '豆浆',$r("app.media.soybean_milk"),FoodCategoryEnum.OTHER, '毫升',  0.31, 0.012, 0.030, 0.016),
]

const workouts: RecordItem[] = [
  new RecordItem(10000, '散步',$r('app.media.ic_walk'), WorkoutCategoryEnum.WALKING, '小时', 111),
  new RecordItem(10001, '快走',$r('app.media.ic_walk'), WorkoutCategoryEnum.WALKING, '小时',  343),
  new RecordItem(10002, '慢跑',$r('app.media.ic_running'), WorkoutCategoryEnum.RUNNING, '小时',  472),
  new RecordItem(10003, '快跑',$r('app.media.ic_running'), WorkoutCategoryEnum.RUNNING, '小时',  652),
  new RecordItem(10004, '自行车',$r('app.media.ic_ridding'), WorkoutCategoryEnum.RIDING, '小时',  497),
  new RecordItem(10005, '动感单车',$r('app.media.ic_ridding'), WorkoutCategoryEnum.RIDING, '小时',  587),
  new RecordItem(10006, '瑜伽',$r('app.media.ic_aerobics'), WorkoutCategoryEnum.AEROBICS, '小时',  172),
  new RecordItem(10007, '健身操',$r('app.media.ic_aerobics'), WorkoutCategoryEnum.AEROBICS, '小时',  429),
  new RecordItem(10008, '游泳',$r('app.media.ic_swimming'), WorkoutCategoryEnum.SWIMMING, '小时',  472),
  new RecordItem(10009, '冲浪',$r('app.media.ic_swimming'), WorkoutCategoryEnum.SWIMMING, '小时',  429),
  new RecordItem(10010, '篮球',$r('app.media.ic_basketball'), WorkoutCategoryEnum.BALLGAME, '小时',  472),
  new RecordItem(10011, '足球',$r('app.media.ic_football'), WorkoutCategoryEnum.BALLGAME, '小时',  515),
  new RecordItem(10012, '排球',$r("app.media.ic_volleyball"), WorkoutCategoryEnum.BALLGAME, '小时',  403),
  new RecordItem(10013, '羽毛球',$r("app.media.ic_badminton"), WorkoutCategoryEnum.BALLGAME, '小时',  386),
  new RecordItem(10014, '乒乓球',$r("app.media.ic_table_tennis"), WorkoutCategoryEnum.BALLGAME, '小时',  257),
  new RecordItem(10015, '哑铃飞鸟',$r("app.media.ic_dumbbell"), WorkoutCategoryEnum.STRENGTH, '小时',  343),
  new RecordItem(10016, '哑铃卧推',$r("app.media.ic_dumbbell"), WorkoutCategoryEnum.STRENGTH, '小时',  429),
  new RecordItem(10017, '仰卧起坐',$r("app.media.ic_sit_up"), WorkoutCategoryEnum.STRENGTH, '小时',  515),
]

class ItemModel{


  // 判断是查询食物还是查询运动,返回的是数组
  list(isFood: boolean = true): RecordItem[]{
    return isFood ? foods : workouts
  }

  getById(id: number, isFood: boolean = true){
    return isFood ? foods[id] : workouts[id - 10000]
  }
  // 分组
  listItemGroupByCategory(isFood: boolean = true){
    // 1.判断要处理的是食物数组还是运动数组
    let categories = isFood ? FoodCategories : WorkoutCategories
    let items = isFood ? foods: workouts

    // 2.创建空的分组
    //map映射,遍历前面的数组中的每一个元素,转成另外一种形式
    let groups = categories.map(itemCategory => new GroupInfo(itemCategory, []))

    // 3.遍历记录项列表,将食物添加到对应的分组
    // item.categoryId就是类型枚举的角标,这样遍历完之后,每一个食物都进入自己的那个数据中
    items.forEach(item => groups[item.categoryId].items.push(item))
    // 4.返回结果
    return groups
  }
}


let itemModel = new ItemModel()

export default itemModel as ItemModel

import { CommonConstants } from '../../common/constants/CommonConstants'
import RecordItem from '../../viewmodel/RecordItem'
/**
 * Panel信息部分
 */
@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:10,left:12,right:12})
      Divider().width(CommonConstants.THOUSANDTH_940).opacity(0.6)
      // 3.营养素信息
      Row({space:CommonConstants.SPACE_8}){
        this.NutrientInfo('热量(千卡)',this.item.calorie)
        if(this.item.id<10000){
          this.NutrientInfo('碳水(克)',this.item.carbon)
          this.NutrientInfo('蛋白质(克)',this.item.protein)
          this.NutrientInfo('脂肪(克)',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'))
         }.width(150)
       Text(this.item.unit).fontColor($r('app.color.light_gray')).fontWeight(CommonConstants.FONT_WEIGHT_600)
     }



    }
  }
  @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)
    }
  }
}
import { CommonConstants } from '../../common/constants/CommonConstants'
import ItemModel from '../../model/ItemModel'
import GroupInfo from '../../viewmodel/GroupInfo'
import ItemCategory from '../../viewmodel/ItemCategory'
import RecordItem from '../../viewmodel/RecordItem'
/**
 * 列表展示Tabs
 * 注意这里控制父组件的状态改变的话,不能直接用,需要用一个函数就覆盖
 */
@Extend(Text) function grayText(){
  .fontSize(14)
  .fontColor($r('app.color.light_gray'))
}
@Component
export default struct ItemList {

  showPanel:(item:RecordItem) => void
  @State isFood:boolean = true

  //tabBar的信息数组
  //不仅要把tabBar的不同信息封装进去,还要把不同tabBar的数据项封装进去
  // data = [
  //   new GroupInfo(new ItemCategory(0,'主食'),[]),
  //   new GroupInfo(new ItemCategory(1,'果蔬'),[]),
  //   new GroupInfo(new ItemCategory(2,'肉蛋奶'),[]),
  //   new GroupInfo(new ItemCategory(3,'坚果'),[]),
  //   new GroupInfo(new ItemCategory(4,'其它'),[]),
  //   // {type: new ItemCategory(0,'主食'), items:[{},{},{}]},
  //   // {type: new ItemCategory(1,'果蔬'), items:[{},{},{}]},
  //   // {type: new ItemCategory(2,'肉蛋奶'), items:[{},{},{}]},
  //   // {type: new ItemCategory(3,'坚果'), items:[{},{},{}]},
  //   // {type: new ItemCategory(4,'其它'), items:[{},{},{}]},
  //
  //
  // ]
  build() {

    Tabs(){
      TabContent(){
          this.TabContentBuilder(ItemModel.list(this.isFood))
      }.tabBar('全部')
      ForEach(ItemModel.listItemGroupByCategory(this.isFood),(group: GroupInfo)=>{
        TabContent(){
          this.TabContentBuilder(group.items)
        }.tabBar(group.type.name)
      })
    }
    .barMode(BarMode.Scrollable)
    .barWidth(700)
    .width('100%')
    .height('100%')
  }
  @Builder TabContentBuilder(items:RecordItem[]){
    List({space:CommonConstants.SPACE_10}){
      ForEach(items,(item:RecordItem)=>{
        ListItem(){
          Row({space:CommonConstants.SPACE_6}){
            Image(item.image).width(50)
            Column(){
              Text(item.name).fontWeight(CommonConstants.FONT_WEIGHT_500)
              Text(`${item.calorie}千卡/${item.unit}`).grayText()
            }
            Blank()
            Image($r('app.media.ic_public_add_norm_filled')).width(18).fillColor($r('app.color.primary_color'))

          }.width('100%')
          .padding(CommonConstants.SPACE_6)
        }
        .onClick(()=>this.showPanel(item))
      })
    }
    .width('100%')
    .height('100%')
  }
}

import ItemCategory from './ItemCategory'
import RecordItem from './RecordItem'

export default class GroupInfo{
  type: ItemCategory
  items:RecordItem[]

  constructor(type:ItemCategory,items:RecordItem[]) {
    this.type = type
    this.items = items
  }
}
数据模型-饮食记录&通用DB工具主要代码:
/**
 * 数据库建表语句
 */
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
 )
 `
//定义常量,常量类型是ColumnInfo[]
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 {
  //获取建表语句
  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
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,
      securityLevel: relationalStore.SecurityLevel.S1
    }
    return new Promise<void>((resolve, reject) => {
      relationalStore.getRdbStore(context, config)
        .then(rdbStore => {
          this.rdbStore = rdbStore
          Logger.debug('rdbStore 初始化完成!')
          resolve()
        })
        .catch(reason => {
          Logger.debug('rdbStore 初始化异常', JSON.stringify(reason))
          reject(reason)
        })
    })
  }

  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> {
    //resolve是成功处理,reject是失败处理
    return new Promise((resolve, reject) => {
      // 1.构建新增数据
      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) => {
      //                              使用map,将ColumnInfo的数组转换成ColumnName的数组
      this.rdbStore.query(predicates, columns.map(info => info.columnName), (err, result) => {
        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 = {}
      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') {
        // 只要不为空,就穿进去,防止像id没有传进去的情况,id自增
        value[info.columnName] = val
      }
    })
    return value
  }
}


let dbUtil: DbUtil = new DbUtil();

export default dbUtil as DbUtil

参考黑马课堂老师的讲解,欢迎大家的批评和指正。

嗨!对于Docker零基础入门精通,我可以给你一些指导。首先,让我们从Docker的基础开始。 1. 安装Docker:首先,你需要在你的机器上安装Docker。你可以去Docker官方网站上下载适合你操作系统的版本,并按照官方文档中的说明进行安装。 2. Docker镜像和容器:Docker使用镜像和容器来管理应用程序和服务。镜像是一个只读的模板,它包含了构建应用程序所需的所有文件和配置。容器是基于镜像创建的运行实例,它可以被启动、停止、删除等操作。 3. Dockerfile:Dockerfile是一个包含了一系列命令的文本文件,用于自动化构建Docker镜像。通过编写Dockerfile,你可以定义构建镜像所需的环境、依赖和配置。 4. Docker命令:学习一些常用的Docker命令是非常重要的。这些命令可以帮助你管理镜像、容器、网络等。一些常用的命令包括:`docker run`(运行容器),`docker build`(构建镜像),`docker stop`(停止容器)等。你可以通过查阅Docker官方文档来了解更多命令的详细用法。 5. Docker Compose:Docker Compose是一个用于定义和管理多个Docker容器的工具。它使用YAML文件来配置应用程序的服务、网络、卷等。通过使用Docker Compose,你可以轻松地启动、停止和管理多个容器。 6. Docker网络:Docker提供了不同类型的网络,用于连接和通信容器。你可以使用默认的桥接网络,也可以创建自定义网络。此外,Docker还支持外部网络和容器之间的链接。 7. Docker卷:Docker卷是用于持久化数据的机制。通过使用卷,你可以将容器内的数据存储到宿主机上的特定位置,从而实现数据的持久化和共享。 这些是Docker入门的一些基本概念和工具。一旦你熟悉了这些基础知识,你可以进一步学习Docker的高级特性,如Docker Swarm(用于集群管理)、Docker Registry(用于镜像存储和分发)等。 希望这些信息对你有所帮助!如果你有更具体的问题,我会尽力回答。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值