
文章目录前言药品模型的关键是 times 和 taken今日总剂次和已服剂次都是派生值标记服药只允许从未服用变成已服用剂次按钮承担了状态反馈这个案例适合继续补哪些能力完整代码最后总结前言用药提醒页最怕写成“看起来像提醒实际上只是列表”。真正有价值的部分是用户标记某一剂已经服用后今日进度、药品剩余量、剂次状态都能一起更新。这个案例没有接系统通知也没有做新增药品但它把剂次标记和进度统计这条主线写出来了。当前页面包含三块顶部今日服药进度下方固定的“下次服药提醒”以及药品列表。右上角没有绑定新增逻辑showAlert状态也声明了但没有参与任何渲染或交互。药品模型的关键是times和taken每个药品用Medication表示。字段不少但真正影响今日进度的是times和taken。interfaceMedication{id:numbername:stringdosage:stringfrequency:stringtimes:string[]remaining:numbertotal:numberemoji:stringtaken:boolean[]}times表示今天有哪些服药时间点taken表示这些时间点是否已经完成。两者靠下标对应比如阿莫西林有三个时间点08:00、14:00、20:00taken: [true, true, false]就表示前两次已服用晚上那次还没服。这种结构适合做演示因为它直接表达“一种药有多个剂次”。真实项目里我不会只用下标绑定至少会把每次服药拆成对象带上时间、状态、实际服用时间和提醒 ID不然后面补签、延迟服用、漏服统计都会比较难做。今日总剂次和已服剂次都是派生值顶部进度没有单独保存百分比而是从meds实时计算。gettodayDoses():number{returnthis.meds.reduce((s,m)sm.times.length,0)}gettakenDoses():number{returnthis.meds.reduce((s,m)sm.taken.filter(tt).length,0)}todayDoses统计所有药品今天一共有多少个服药槽位takenDoses统计这些槽位里已经完成了多少个。顶部文案、百分比和Progress都依赖这两个 getter。Text(今日服药进度${this.takenDoses}/${this.todayDoses})Text(${Math.round(this.takenDoses/this.todayDoses*100)}%)Progress({value:this.takenDoses,total:this.todayDoses})这里同样有一个边界如果没有任何药品todayDoses会是 0百分比计算就不安全。当前示例写死了 4 个药品所以不会出问题正式页面要补空状态。标记服药只允许从未服用变成已服用每个时间槽都绑定了点击事件但只有未服用时才会调用takeMed()。.onClick((){if(!med.taken[i])this.takeMed(med.id,i)})takeMed()做了两件事把对应剂次的taken[timeIdx]改成true同时把剩余药量减一。takeMed(medId:number,timeIdx:number){this.medsthis.meds.map(m{if(m.id!medId)returnmconsttakenm.taken.slice()taken[timeIdx]trueconstremainingMath.max(0,m.remaining-1)m.takentaken;m.remainingremaining;returnm})}Math.max(0, m.remaining - 1)保证剩余药量不会减成负数。这个细节挺必要尤其是用药这种场景数字显示错了会让用户非常不信任。不过当前逻辑不支持取消已服用也没有防止重复扣减之外的更多校验。由于 UI 层已经禁止已服用槽位再次调用takeMed()正常点击不会重复减库存但如果后续别处也调用这个函数最好在函数内部再判断一次taken[timeIdx]是否已经为true。剂次按钮承担了状态反馈药品卡片底部的每个时间点会根据med.taken[i]改变颜色和文字。Text(med.taken[i]?✓:)Text(time).fontColor(med.taken[i]?#FFF:#555).backgroundColor(med.taken[i]?#4CAF50:#F0F0F0)这个处理让状态反馈很直接已服用的剂次变成绿色并显示对勾未服用的保持灰色。列表里的剩余药量进度条也会跟着remaining变化。“下次服药提醒”这一块目前是静态文案维生素C 09:00、7分钟后都没有从meds里计算。正式做提醒功能时应该根据当前时间和未完成剂次动态找出最近的一次并接入系统通知或后台提醒能力。这个案例适合继续补哪些能力如果要把它往真实用药助手扩展我会先补三件事新增/编辑药品、动态下一次提醒、服药记录持久化。新增药品需要处理剂量、频次和时间列表动态提醒需要和系统时间、通知权限打交道持久化则决定用户关闭应用后记录还在不在。当前代码最值得保留的是状态来源清楚meds是主数据今日进度和剂次完成数都从它推导出来点击剂次只改对应药品的taken和remaining。这条线稳住以后再加提醒服务和历史记录会容易很多。完整代码下面保留案例完整代码方便你直接对照学习、复制和重构。import{router}fromkit.ArkUIinterfaceMedication{id:numbername:stringdosage:stringfrequency:stringtimes:string[]remaining:numbertotal:numberemoji:stringtaken:boolean[]}EntryComponentstruct MedicationReminderBoard{Statemeds:Medication[][{id:1,name:阿莫西林,dosage:500mg,frequency:每天3次,times:[08:00,14:00,20:00],remaining:24,total:30,emoji:,taken:[true,true,false]},{id:2,name:维生素C,dosage:200mg,frequency:每天1次,times:[09:00],remaining:18,total:30,emoji:,taken:[false]},{id:3,name:布洛芬,dosage:400mg,frequency:需要时,times:[随需],remaining:10,total:12,emoji:,taken:[false]},{id:4,name:叶酸,dosage:0.4mg,frequency:每天1次,times:[07:30],remaining:60,total:90,emoji:,taken:[true]},]StateshowAlert:stringtakeMed(medId:number,timeIdx:number){this.medsthis.meds.map(m{if(m.id!medId)returnmconsttakenm.taken.slice()taken[timeIdx]trueconstremainingMath.max(0,m.remaining-1)m.takentaken;m.remainingremaining;returnm})}gettodayDoses():number{returnthis.meds.reduce((s,m)sm.times.length,0)}gettakenDoses():number{returnthis.meds.reduce((s,m)sm.taken.filter(tt).length,0)}build(){Column(){Row(){Image($r(app.media.ic_back)).width(24).height(24).onClick(()router.back())Text(用药提醒).fontSize(18).fontWeight(FontWeight.Bold).layoutWeight(1).textAlign(TextAlign.Center)Text().fontSize(22).fontColor(#4CAF50)}.width(100%).padding({left:16,right:16,top:12,bottom:12})// Progress summaryColumn(){Row(){Text(\今日服药进度\${this.takenDoses}/\${this.todayDoses}\).fontSize(15).fontColor(#FFF).layoutWeight(1)Text(\\${Math.round(this.takenDoses/this.todayDoses*100)}%\).fontSize(18).fontWeight(FontWeight.Bold).fontColor(#FFF)}.width(100%).margin({bottom:8})Progress({value:this.takenDoses,total:this.todayDoses}).color(#FFF).backgroundColor(#FFFFFF40).height(8).borderRadius(4)}.width(calc(100% - 32vp)).backgroundColor(#4CAF50).borderRadius(16).padding(20).margin({bottom:16})// Next dose reminderRow(){Text(⏰).fontSize(20)Column(){Text(下次服药提醒).fontSize(13).fontWeight(FontWeight.Bold).fontColor(#333)Text(维生素C 09:00).fontSize(13).fontColor(#4CAF50)}.layoutWeight(1).alignItems(HorizontalAlign.Start).margin({left:12})Text(7分钟后).fontSize(13).fontColor(#FF9500).fontWeight(FontWeight.Bold)}.width(calc(100% - 32vp)).backgroundColor(#FFF).borderRadius(12).padding(14).margin({bottom:12}).border({width:1,color:#E8F5E9})// MedicationsList({space:10}){ForEach(this.meds,(med:Medication){ListItem(){Column(){Row(){Text(med.emoji).fontSize(36).margin({right:12})Column(){Row(){Text(med.name).fontSize(16).fontWeight(FontWeight.Bold).fontColor(#333)Text(med.dosage).fontSize(12).fontColor(#888).margin({left:8})}Text(med.frequency).fontSize(13).fontColor(#666).margin({top:4})Row(){Progress({value:med.remaining,total:med.total}).color(#4CAF50).backgroundColor(#EEE).height(4).width(80).borderRadius(2)Text(\ 剩余 \${med.remaining}/\${med.total}\).fontSize(11).fontColor(#AAA).margin({left:4})}.margin({top:6})}.layoutWeight(1).alignItems(HorizontalAlign.Start)}.width(100%).margin({bottom:10})// Dose slotsRow(){ForEach(med.times,(time:string,i:number){Row(){Text(med.taken[i]?✓:).fontSize(12).fontColor(#FFF)Text(time).fontSize(12).fontColor(med.taken[i]?#FFF:#555).margin({left:2})}.backgroundColor(med.taken[i]?#4CAF50:#F0F0F0).borderRadius(16).padding({left:10,right:10,top:5,bottom:5}).margin({right:8}).onClick((){if(!med.taken[i])this.takeMed(med.id,i)})})}}.width(100%).backgroundColor(#FFF).borderRadius(14).padding(14).shadow({radius:4,color:#0000001A,offsetY:2})}})}.layoutWeight(1).padding({left:16,right:16})}.width(100%).height(100%).backgroundColor(#F1F8F4)}}最后总结用药提醒面板 这个案例并不靠复杂技巧取胜它更像一个结构扎实、逻辑完整的业务页面样板。把这篇文章里的状态设计、函数组织和页面分层吃透之后你再去写同类型功能速度会明显快很多。