飲食管理應(yīng)用架構(gòu)設(shè)計(jì)文檔
一、核心數(shù)據(jù)模型設(shè)計(jì)
1. 營養(yǎng)元素模型 (footItem)interface footItem { name: string; // 營養(yǎng)名稱(蛋白質(zhì)/碳水/脂肪) weight: number; // 重量(克) }2. 食物模型 (DietItem)
interface DietItem { name: string; // 食物名稱 image: string; // 圖片路徑(如app.media.mantou) weight: number; // 單份重量(克) calorie: number; // 單份卡路里 count: number; // 食用數(shù)量 nengliang: string; // 主要營養(yǎng)素類型 }
設(shè)計(jì)說明:
footItem 將蛋白質(zhì)、碳水、脂肪抽象為基礎(chǔ)單元DietItem 包含物理屬性+營養(yǎng)屬性,nengliang字段建立食物與營養(yǎng)素的映射關(guān)系二、主組件架構(gòu):Index組件
狀態(tài)管理@State progressIndex: number = 0 // 總卡路里 @State dbzIndex: number = 0 // 總蛋白質(zhì) @State tsIndex: number = 0 // 總碳水 @State zfIndex: number = 0 // 總脂肪頭部統(tǒng)計(jì)組件 (toubu)
@Builder toubu() { Column({ space: 15 }) { // 環(huán)形卡路里進(jìn)度條 Stack() { Progress({ value: this.progressIndex, total: 20000, type: ProgressType.Ring }) .width(90).height(90).style({ strokeWidth: 10 }) Text(`${this.progressIndex} kcal`).fontSize(14).fontWeight(FontWeight.Bold) } // 營養(yǎng)素統(tǒng)計(jì)行 Row() { ForEach(footData, (item) => { Column() { Text(this.getItemWeight(item).toString()).fontSize(18) Text(item.name).fontSize(14) }.width('30%') }) } } }
功能特點(diǎn):
環(huán)形進(jìn)度條目標(biāo)值20000kcal營養(yǎng)素統(tǒng)計(jì)行實(shí)時(shí)顯示三類營養(yǎng)素?cái)z入量三、可復(fù)用組件:foods組件
核心屬性@State ifjiajian: boolean = false // 操作類型(增減) @Prop item: DietItem // 食物對(duì)象(只讀) @Link progressIndex: number // 雙向綁定總卡路里 @Link dbzIndex: number // 雙向綁定蛋白質(zhì)關(guān)鍵方法
// 卡路里計(jì)算 calorieNUm() { const num = this.ifjiajian ? this.item.calorie * this.item.count : -this.item.calorie * (this.item.count + 1) this.progressIndex += num } // 營養(yǎng)素計(jì)算 weightNUm() { const amount = this.ifjiajian ? this.item.count : -(this.item.count + 1) const weightChange = 13 * amount switch(this.item.nengliang) { case '蛋白質(zhì)': this.dbzIndex += weightChange case '碳水': this.tsIndex += weightChange case '脂肪': this.zfIndex += weightChange } }
四、數(shù)據(jù)流轉(zhuǎn)閉環(huán)
用戶操作 → 點(diǎn)擊"+"按鈕
item.count++ifjiajian = true數(shù)據(jù)計(jì)算:
calorieNUm()計(jì)算新增卡路里weightNUm()更新對(duì)應(yīng)營養(yǎng)素界面更新:
環(huán)形進(jìn)度條自動(dòng)刷新營養(yǎng)素?cái)?shù)值實(shí)時(shí)更新五、完整代碼
<details>
<summary>點(diǎn)擊查看完整實(shí)現(xiàn)</summary>
interface footItem { name: string; // 營養(yǎng)名稱 weight: number; // 重量 } interface DietItem { name: string; // 食物名稱 image: string; // 食物圖片路徑(本地或網(wǎng)絡(luò),這里用占位示意) weight: number; // 重量 calorie: number; // 卡路里 count: number; // 食用數(shù)量 nengliang: string; // 營養(yǎng)名稱(蛋白質(zhì)、脂肪、碳水) } const footData: footItem[] = [ { name: '蛋白質(zhì)', weight: 0 }, { name: '碳水', weight: 0 }, { name: '脂肪', weight: 0 }, ]; const dietData: DietItem[] = [ { name: '饅頭', image: 'app.media.mantou', weight: 13, calorie: 100, count: 0, nengliang: '蛋白質(zhì)' }, { name: '油條', image: 'app.media.youtiao', weight: 13, calorie: 200, count: 0, nengliang: '脂肪' }, { name: '豆?jié){', image: 'app.media.doujiang', weight: 13, calorie: 300, count: 0, nengliang: '碳水' }, { name: '稀飯', image: 'app.media.xifan', weight: 13, calorie: 300, count: 0, nengliang: '碳水' }, { name: '雞蛋', image: 'app.media.egg', weight: 13, calorie: 200, count: 0, nengliang: '蛋白質(zhì)' }, ]; @Entry @Component export struct Index { @State progressIndex: number = 0 // 進(jìn)度條進(jìn)度(總大卡數(shù)) @State dbzIndex: number = 0 // 總蛋白質(zhì) @State tsIndex: number = 0 // 總碳水 @State zfIndex: number = 0 // 總脂肪 // 頭部組件 @Builder toubu() { Column({ space: 15 }) { Stack() { Progress({ value: this.progressIndex, total: 20000, type: ProgressType.Ring }) .width(90) .height(90) .style({ strokeWidth: 10 }) .color('#4CD964') .backgroundColor('#e0e0e0'); Text(`${this.progressIndex} kcal`) .fontSize(14) .fontWeight(FontWeight.Bold) .margin({ top: 5 }) } Row() { ForEach(footData, (item: footItem) => { Column() { Text(this.getItemWeight(item).toString()) .fontSize(18) .fontWeight(FontWeight.Bold) .fontColor('#333') Text(item.name) .fontSize(14) .fontColor('#666') } .width('30%') }, (item: footItem) => JSON.stringify(item)) } } .padding({ top: 20, bottom: 15 }) .width('100%') .alignItems(HorizontalAlign.Center) } // 獲取對(duì)應(yīng)的營養(yǎng)值 private getItemWeight(item: footItem): number { switch (item.name) { case '蛋白質(zhì)': return this.dbzIndex; case '碳水': return this.tsIndex; case '脂肪': return this.zfIndex; default: return 0; } } build() { Column({ space: 15 }) { this.toubu() Text('飲食內(nèi)容') .fontSize(20) .fontColor('#555') .width('100%') .margin({ left: 20 }) List({ space: 10 }) { ForEach(dietData, (item: DietItem) => { ListItem() { foods({ item: item, progressIndex: this.progressIndex, dbzIndex: this.dbzIndex, tsIndex: this.tsIndex, zfIndex: this.zfIndex }) } }, (item: DietItem) => JSON.stringify(item)) } } .width('100%') .padding({ left: 10,right: 10 }) } } // 飲食內(nèi)容組件 @Reusable @Component export struct foods { @State ifjiajian: boolean = false @Prop item: DietItem @Link progressIndex: number @Link dbzIndex: number @Link tsIndex: number @Link zfIndex: number // 統(tǒng)計(jì)大卡數(shù) calorieNUm() { let num = this.ifjiajian ? this.item.calorie * this.item.count : -this.item.calorie * (this.item.count + 1); this.progressIndex += num; } // 統(tǒng)計(jì)能量 weightNUm() { const amount = this.ifjiajian ? this.item.count : -(this.item.count + 1); const weightChange = 13 * amount; switch (this.item.nengliang) { case '蛋白質(zhì)': this.dbzIndex += weightChange; break; case '碳水': this.tsIndex += weightChange; break; case '脂肪': this.zfIndex += weightChange; break; } } build() { Row() { Image($r(this.item.image)) .width(60) .height(60) .borderRadius(8) Column({ space: 6 }) { Text(this.item.name) .fontSize(16) .fontWeight(FontWeight.Bold) Text(`${this.item.weight} 克`) .fontSize(14) .fontColor('#777') } .width('40%') .alignItems(HorizontalAlign.Start) Column({ space: 6 }) { Text(`${this.item.calorie * this.item.count} 卡`) .fontSize(16) .fontColor('#555') Row() { Text('-') .fontSize(20) .width(25) .height(25) .textAlign(TextAlign.Center) .borderRadius(4) .border({ width: 1, color: '#ccc' }) .onClick(() => { if (this.item.count > 0) { this.item.count--; this.ifjiajian = false; this.calorieNUm(); this.weightNUm(); } }) Text(`${this.item.count}`) .fontSize(16) .width(30) .textAlign(TextAlign.Center) Text('+') .fontSize(20) .width(25) .height(25) .textAlign(TextAlign.Center) .borderRadius(4) .border({ width: 1, color: '#ccc' }) .onClick(() => { this.item.count++; this.ifjiajian = true; this.calorieNUm(); this.weightNUm(); }) } .justifyContent(FlexAlign.SpaceAround) .width(90) } .width('40%') .alignItems(HorizontalAlign.Center) } .width('100%') .padding({ left: 10, right: 10 }) .justifyContent(FlexAlign.SpaceBetween) } }
</details>