提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
前言
综合运用本学期所学内容及个人自学知识,使用HarmonyOS 4.0 及以上版本开发一款具有实用性和创新性的移动应用软件。
一、项目名称:黑马健康运动
黑马健康是一款流行的运动健身软件,旨在帮助用户保持健康和锻炼,致力于提供健身教学、跑步、骑行、交友及健身饮食指导一站式运动解决方案。健身、跑步、骑行,运动品类更丰富,个性化推荐更加精细。
二、应用运行过程5
1.数据模型—通用DB工具
代码如下(示例):
EntryAbility.ets
import UIAbility from '@ohos.app.ability.UIAbility';
import hilog from '@ohos.hilog';
import window from '@ohos.window';
import { CommonConstants } from '../common/constants/CommonConstants';
import DateUtil from '../common/utils/DateUtil';
import DbUtil from '../common/utils/DbUtil';
import PreferenceUtil from '../common/utils/PreferenceUtil';
import RecordModel from '../model/RecordModel';
export default class EntryAbility extends UIAbility {
async onCreate(want, launchParam) {
//1.加载用户首选项
PreferenceUtil.loadPreference(this.context)
//2.初始化日期
AppStorage.SetOrCreate(CommonConstants.RECORD_DATE, DateUtil.beginTimeOfDay(new Date()))
//3.初始化RDB工具
await DbUtil.initDB(this.context)
//4.创建record表
DbUtil.createTable(RecordModel.getCreateTableSql())
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onCreate');
}
onDestroy() {
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onDestroy');
}
onWindowStageCreate(windowStage: window.WindowStage) {
// Main window is created, set main page for this ability
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageCreate');
//默认加载页面
windowStage.loadContent('pages/WelcomePage', (err, data) => {
if (err.code) {
hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? '');
return;
}
hilog.info(0x0000, 'testTag', 'Succeeded in loading the content. Data: %{public}s', JSON.stringify(data) ?? '');
});
}
onWindowStageDestroy() {
// Main window is destroyed, release UI related resources
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageDestroy');
}
onForeground() {
// Ability has brought to foreground
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onForeground');
}
onBackground() {
// Ability has back to background
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onBackground');
}
}
2.饮食记录业务层开发
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){
//1.获取时间
let createTime = (AppStorage.Get('selectedDate') || DateUtil.beginTimeOfDay(new Date())) as number
//2.新增
return RecordModel.insert({typeId, itemId, amount, createTime})
}
deleteById(id: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: RecordPO) => {
//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
})
}
//作用:把RecordVo数组转成StatsInfo和GroupInfo
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
该处使用的url网络请求的数据。
3.实现数据持久化和页面交互
运行截图:
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 amount:number = 1
@State value: string = ''//按键记录
@State showPanel: boolean = false
@State item: RecordItem = null
@State type: RecordType = RecordTypes[0]
@State isFood: boolean = true
onPanelShow(item: RecordItem){//定义函数,当panel要展示时做一些事情
this.amount = 1
this.value = ''
this.item = item
this.showPanel = true//控制弹窗展示
}
onPageShow(){
//1.获取跳转时的参数
let params:any = router.getParams()
//2.获取点击的饮食记录类型
this.type = params.type
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.按钮
this.PanelButton()
}
.mode(PanelMode.Full)
.dragBar(false)
.backgroundMask($r('app.color.light_gray'))
.backgroundColor(Color.White)
}
.width('100%')
.height('100%')
}
@Builder PanelButton(){
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)
.backgroundColor($r('app.color.primary_color'))
.type(ButtonType.Normal)//样式
.borderRadius(6)//边角
.onClick(() => {
//1.持久化保存
RecordService.insert(this.type.id, this.item.id, this.amount)
.then(() => {
//2.关闭弹窗
this.showPanel = false
})
})
}
.margin({top: 10})
}
@Builder Header(){
Row(){
Image($r('app.media.ic_public_back'))
.width(24)
.onClick(() => router.back())//点击事件 返回
Blank()
Text(this.type.name).fontSize(18).fontWeight(CommonConstants.FONT_WEIGHT_600)
}
.width(CommonConstants.THOUSANDTH_940)
.height(32)
}
}
总结
1.数据模型—通用DB工具
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
}
}
2.饮食记录业务层开发
- 新增饮食记录
@param typeId 记录类型id
@param itemId 记录项id
@param amount 记录项数量(食物量、运动时长)
@returns 新增数量
2.根据id删除饮食记录
@param id 记录id
@returns 删除条数
-
根据日期查询饮食记录列表
@param date 查询的日期
@returns 记录列表 -
根据记录列表信息统计出热量、营养素信息
@param records 饮食记录列表
@returns 热量、营养素信息
3.实现数据持久化和页面交互
实现页面交互时,添加饮食和运动为什么添加不进去
解决方案:在RecordIndex页面中的aboutToAppear在组件构建时build之前触发,在进入首页之前已经触发,这个时候没有数据,在index页面定义
@State isPagesShow: boolean = false
来通知RrcordIndex
在RecordIndex.ets写
@Prop @Watch('handlePageShow') isPageShow: boolean
handlePageShow(){
if (this.isPageShow) {
this.aboutToAppear()
}
}