用ArkTS做了个精力管理App:delta累加模型+快捷动作条实现5秒记录
起因我想记录精力消耗但试过的工具都太重了做这个 App 的起点很简单——我自己是独立开发者每天的精力分配直接决定产出质量。之前用过「Daylio」记心情每次要选五个表情里的一个再勾标签操作完要十几秒感觉像在填问卷。用了大概一周某天下午忙完抬头已经晚上八点想补记下午的状态发现根本记不清三点和五点分别是什么心情了。那之后就再没打开过。我想要的东西很具体打开 App点一下-20 开会消耗或30 跑步补能然后关掉。整个过程不超过 5 秒。所以我做了「元気手账」核心概念就一个把精力当成手机电量来管理。每天从 50% 开始消耗就减恢复就加一天下来看曲线就知道自己的能量都去哪了。iOS 版和鸿蒙版都已上架鸿蒙版是今年重点投入的方向。这篇主要聊鸿蒙端的实现。数据模型delta 累加算电量精力记录的核心字段一个 title 描述事件一个 delta 表示变化量正数是补能负数是消耗再关联到具体的 project。我不存当前电量这个绝对值只存每次变化的 delta。当天的实时电量 当天基准值 sum(所有delta)。好处是记录之间完全独立删改任何一条都不影响其他条目的数据一致性。鸿蒙端用ohos.data.relationalStore实现查询当前电量的核心逻辑长这样async function getCurrentEnergy(rdbStore: relationalStore.RdbStore, dayStart: number): Promisenumber { const baseline 50 const predicates new relationalStore.RdbPredicates(energy_record) predicates.greaterThanOrEqualTo(timestamp, dayStart) .and() .equalTo(is_deleted, 0) const result await rdbStore.query(predicates, [delta]) let totalDelta 0 while (result.goToNextRow()) { totalDelta result.getLong(result.getColumnIndex(delta)) } result.close() return Math.max(0, Math.min(100, baseline totalDelta)) } 这里有个细节电量做了 0-100 的 clamp。试过不限制范围结果某天测试时一口气加了几个 30电量飙到 160%显示上很违和。人的精力确实有上限100 封顶合理。 ## 快捷动作条把记录成本压到 5 秒内 降低记录门槛是这个 App 活下去的关键。 鸿蒙端我做了一个快捷动作条放在首页顶部。用户可以自定义常用动作比如-15 刷手机、20 午睡、-25 写文档点一下就完成记录。 arkts Component struct QuickActionStrip { State actions: QuickAction[] [] onRecord: (action: QuickAction) void () {} build() { Scroll() { Row({ space: 8 }) { ForEach(this.actions, (action: QuickAction) { Column() { Image($r(app.media.${action.icon})) .width(24).height(24) Text(action.title).fontSize(12) Text(${action.delta 0 ? : }${action.delta}) .fontSize(10) .fontColor(action.delta 0 ? #4CAF50 : #FF5722) } .padding(8) .borderRadius(12) .backgroundColor(#1A1A1A) .onClick(() this.onRecord(action)) }) } }.scrollable(ScrollDirection.Horizontal) } } 说实话这部分在 ArkUI 里写比 SwiftUI 还顺手一些Scroll 组件的横向滚动配置很直白不用像 iOS 那样套 ScrollView LazyHGrid 再调 spacing。 ## 方舟项目给精力消耗加一个去向 1.2.0 版本加的功能。我发现光记录精力变化不够我想知道精力都花在哪些事情上了。 所以加了方舟项目维度。每个项目有等级1-10和经验值你往一个项目投入精力它就涨经验升级。到 10 级变成传说状态。 经验值的计算规则**exp sum(正delta)**只有正向投入才计入经验负 delta消耗类事件不涨经验。这样设计的原因是——开会消耗了你 25 点精力不应该算你对这个项目的投入它是一种被动损耗。只有主动投入比如30 写了两小时代码才值得被项目经验系统认可。 我自己用了一个月写代码项目升到了 7 级开会卡在 3 级。这个数据让我更有意识地减少低效会议——不是因为开会少了而是开会根本不给我的项目涨经验它只消耗电量。 ## 鸿蒙端踩过的坑 ### 日期处理和时区 iOS 端用 Calendar.current.startOfDay(for:) 一行搞定。鸿蒙端没有直接对应的 API得自己处理取当天 0 点时间戳 arkts function getLocalDayStart(timestamp: number): number { // 用 Date 对象直接归零时分秒避免手动算时区偏移出错 const date new Date(timestamp) date.setHours(0, 0, 0, 0) return date.getTime() } 看起来简单但跨时区场景容易翻车。比如用户在东八区记了一条晚上 11:50 的记录飞到西五区后再看如果用当前时区重新算 dayStart这条记录会跑到第二天去。我最终的方案是记录时同时存一份当时的 new Date().getTimezoneOffset() 值展示时用记录自带的时区偏移来归属日期。 ### 深色模式适配 这个反而比预想的省心。ArkUI 的资源目录结构天然支持 dark/light 切换我在 resources/base/element/color.json 定义浅色值在 resources/dark/element/color.json 放深色值组件里用 $r(app.color.card_bg) 引用就行系统自动切换。 目录结构大概是这样resources/├── base/│ └── element/│ └── color.json → { “card_bg”: “#FFFFFF”, “text_primary”: “#1A1A1A” }├── dark/│ └── element/│ └── color.json → { “card_bg”: “#1E1E1E”, “text_primary”: “#F0F0F0” }比 iOS 手动写 UIColor { traitCollection in ... } 或者在 Assets Catalog 里逐个配 Any/Dark Appearance 要简洁。当然代价是颜色 token 要提前规划好后期加新颜色得两个文件都改。 ## 为什么选择做鸿蒙版 我的判断是HarmonyOS NEXT 的纯血鸿蒙应用市场里工具类 App 供给明显不足。我上个月在华为应用市场搜精力管理结果只有两个 App其中一个还是套壳网页。搜能量记录直接零结果。同样的关键词在 App Store 能搜出来十几个。 对独立开发者来说这是个时间窗口。ArkTS 学习成本不高TypeScript 底子够用ArkUI 的声明式语法和 SwiftUI 思路接近。我 iOS 端的代码逻辑迁移到鸿蒙大概花了三周主要时间其实花在熟悉 relationalStore 的 API 和调试 hvigor 构建配置上。 ## 当前状态和后续计划 鸿蒙端刚上架不久属于早期阶段欢迎尝鲜反馈。两端数据目前是独立的没做云同步。纯本地存储也是产品定位的一部分——精力数据属于比较私密的信息我不想碰服务器。 后面打算做的 - **桌面卡片**让用户不打开 App 就能看到当前电量和快速记录 - - **周/月趋势图优化**自动标记持续低耗能时段 - - **数据导出**CSV 格式方便有量化需求的用户二次分析 --- 关于桌面卡片这块我还在调研。鸿蒙的 FormExtensionAbility 和主 App 之间的数据共享我目前看到的方案是通过 preferences 或者 dataShare但没找到太多实际案例。如果有做过鸿蒙桌面卡片 relationalStore 联动的朋友想聊聊你们是怎么处理数据同步时机的。App 在华为应用市场搜「元気手账」可以找到。