「宝贝日程表」App 的开发初衷,源于孩子的日常课程安排。孩子的校内课程按节次排布,各类兴趣班有固定的具体上课时间,而老师临时通知的带物要求,又零散混杂在群消息里。每天出门前,我都需要在多个地方反复核对,十分麻烦。为此,我专门做了这款鸿蒙 App,打开就能一目了然看清孩子当日的全部日程与注意事项。
在实际开发落地的过程中,我陆续完成了响应式布局适配、安全区适配、悬浮底栏设计、跨进程服务卡片开发等功能优化,同时做好了应用签名、验包校验、三端商店上架素材筹备等全套配套工作。下文将按照真实开发过程,逐一记录各项功能的实现思路,并具体说明 DevEco CLI、MCP、Skills 在项目中各自的用途与落地方式。

先把每天真正会看的内容留下
一开始如果直接列功能,很容易得到一张越来越长的表:课程、提醒、日历、打卡、统计、云同步、家庭成员、AI 排课......每一个都像是"以后肯定用得上"。
我换了一个问法:用户会在什么时候打开它?
- 早上出门前:想快速看今天的课程和事项。
- 周末安排下周:需要看一张完整周课表。
- 收到兴趣班通知:希望几步内记下日期、时间和地点。
- 临时想到要带东西:需要新增一条一次性提醒。
- 不想打开 App:最好从桌面卡片就能扫一眼。
这样一拆,首版要做什么就清楚多了。
"今天"负责把当天课程和提醒放在一起;"课表"提供按日与按周两种视图;"事项"负责一次、每天和每周重复的提醒;"我的"承接勋章与应用信息。课程区分校内课和兴趣班:前者更适合按节次记录,后者更适合直接填写起止时间。
我也给产品划了几条边界:先服务单孩子家庭;不要求登录;不做自己的云端;课程和事项使用同一套本地数据。拍照识别课表、多人家庭和 AI 自动排课都没有塞进首版。
这几个决定看起来像"少做功能",其实是为了尽快回答最开始的问题:打开以后,能不能马上知道今天有什么?

视觉稿定气质,ArkUI 解决使用问题
课程表很容易做成教务系统的样子:规整、密集、功能一眼就能看懂。但这个 App 面向家庭日常,我希望它更温和一点,让人愿意每天打开。
我先用 GPT Image 2 做视觉方向探索,描述方向是"儿童手账式日程管理",而不是直接要求它生成一张可开发的最终 UI。提示词里放入奶油纸张、深紫色文字、低饱和彩色卡片、轻微手作感和课程插图,也会告诉模型页面里大概有哪些内容。
第一轮方向稿是这样的:

这张图确定了整体气质,但没有原样进入代码。早期稿里的装饰较多,部分信息重复,页面命名也和最终版本不同。如果逐像素照搬,视觉会很满,需要看的课程和提醒反而不够突出。
我最后只保留了四条视觉规则:
- 页面像一张温暖的纸,不用冷冰冰的大面积纯白。
- 标题使用深紫色,能够压住彩色插图,又不会像纯黑那么硬。
- 课程和事项用低饱和彩色区分,不让颜色抢过内容。
- 插图只放在课程、空状态和场景背景中,不盖住需要阅读的文字。
落到 ArkUI 时,信息结构、点击区域、长文字、深浅色、安全区和不同窗口尺寸都要重新处理。视觉稿先确定方向,代码再解决每天用起来顺不顺手。
图标先做质感,再按 HarmonyOS 规则拆层
应用图标也走过类似过程。
我给 GPT Image 2 生成过一批台历、日程本和星星主题的候选图标,最后选择了珊瑚色台历、彩色方格和笑脸星星这个组合。它缩小后仍然能认出"日程",颜色也和应用里的奶油纸张、紫色及柔和彩卡相接。
图标生成最容易踩的坑,是只给模型一句"帮我做个鸿蒙 App 图标"。图像模型未必知道当前的 HarmonyOS 图标资源规范,经常直接给出一张已经裁成圆角、四周透明,甚至带设备桌面背景的展示图。这类图看起来像成品,放进工程以后却可能被系统再次裁切。
我后来比较常用两种做法。
第一种是把华为提供的图标模板、网格或合格示例作为参考图交给模型,明确告诉它哪些只是规范参考,哪些是风格参考。第二种是用模型更熟悉的视觉语言描述质感。比如可以写"参考 iOS 27 风格的细腻拟物、柔和高光和陶瓷质感",但后面要马上补一句:只参考材质和光影,不照搬平台外形,不生成圆角底板。
我会在提示词里把下面几条写死:
生成图只是视觉候选。选定以后还要按 HarmonyOS 规则准备前景和背景分层资源:两层都是 1024×1024 正方形,背景不能含透明像素,系统会根据桌面、设置等场景自动生成遮罩。前景主体也不能贴近四角,否则换一种遮罩就可能被切掉。
「宝贝日程表」最终通过 layered_image_coral_star 引用背景层和前景层,而不是把一张已经带圆角的图片直接塞进工程。华为当前的应用图标规范也明确要求正方形、1024×1024、PNG、分层资源和不透明背景。
这一步最好再到真实桌面、设置列表、通知等小尺寸场景里看一遍。原图很精致,不代表缩到几十像素以后仍然清楚。
四个入口,共用一份本地数据
从工程结构看,「宝贝日程表」是一个单模块、轻量分层的 ArkUI 应用。
我没有为了显得"架构完整"硬套一套很重的 Clean Architecture,也没有独立 ViewModel 层。页面组件负责交互,Repository 负责把课程、事项和勋章状态写进 ArkData Preferences;系统日历能力封装在独立 Helper 中,服务卡片相关逻辑集中在 entry 模块的 forms 目录,并由 EntryFormAbility 承接系统回调。

主要的数据对象有三组:
CourseItem:记录校内课程或兴趣班的星期、节次或时间、地点、老师、备注和图标。KidReminderItem:记录一次、每天或每周事项,以及日期、时间和可选的系统日历事件 ID;每天的完成状态按事项和日期另行保存。- 勋章状态:记录活跃日期、已点亮勋章、点亮时间和等待展示的反馈。
课程、事项、完成记录、课表视图选择和勋章状态都放在 baby_schedule_prefs_v1 这个 Preferences 存储中。它没有 RDB,也没有业务后端。准确点说,用户的课表、事项和完成记录不会上传到开发者自己的云端;应用检查更新和评分仍会调用相应的平台能力。
页面共享同一份数据以后,麻烦的是刷新。
今天页、课表页、编辑页和桌面卡片不一定同时在前台。保存完成后,只改当前页面的一个状态变量并不够。我会先确保 Preferences 已经 flush,再增加 revision、派发数据变化事件,让相关页面重新读取。用户从课程编辑页返回、重新切到 Tab 或页面恢复可见时,还会补一次刷新。
这些动作看上去有些重复,但解决的是实际遇到的问题:Tab 子树可能一直保留,路由页面会覆盖在主页面上方,几次异步读取的返回顺序也未必和发起顺序一致。最终目标很朴素------保存以后,几个入口看到的必须是同一份落盘数据。
系统日历只服务"事项",课程不会自动写入日历。用户打开同步开关并保存时,应用才申请权限;本地事项会先保存,再尝试创建一个 30 分钟的日历事件。编辑已经同步的事项时,会先移除旧事件,再重新创建并保存新的事件 ID。日历写入失败不会抹掉已经保存的本地事项,而是给出明确提示。这里采用的是"本地保存优先,系统集成允许部分失败"。
沉浸式浏览
在窄屏首页,顶部会铺一张大幅场景插图。手机和平板窗口会开启全屏布局,让页面背景延伸到系统栏区域;到了较宽窗口,页面会减少这类大面积装饰,把空间留给课程和事项。
这里并不是把系统栏简单隐藏掉。EntryAbility 会读取状态栏和底部导航指示区域高度,存入 AppStorage;场景背景可以继续铺到屏幕边缘,标题、加号和编辑内容则按安全区下移。深色模式变化时,状态栏文字颜色也会跟着调整。2in1 窗口不套用移动端的全屏设置。

底部导航在 API 23 及以上使用 HdsTabs:
它不是把普通 TabBar 往上挪几个像素。开启内容重叠以后,底栏会悬浮在页面内容之上,每个 Tab 的滚动区域末尾都要额外加上底栏高度、导航指示条高度和一段留白。否则底栏看起来是悬浮了,最后一张课程卡片却会被它挡住。
低版本也要有退路。代码会检查系统 API:API 23 及以上加载 NewIndex 和 HdsTabs,低于 API 23 时进入旧的 Index,继续使用原生 Tabs。这条兜底路径已经写进代码,但最低兼容版本仍需补真机回归,因此我不会把它写成"所有兼容版本均已验证"。
"一多"适配
华为把这套思路称为"一次开发,多端部署",常简称"一多"。官方文档给出的目标是"一套代码工程,一次开发上架,多端按需部署"。不过这个"一套"不是说所有设备都得显示成一模一样。它其实同时覆盖工程组织、设备能力兼容和界面适配;页面层仍然要根据窗口形态做响应式或差异化布局。可以参考一次开发,多端部署概览。
「宝贝日程表」在 module.json5 中声明支持 phone、tablet 和 2in1。我没有直接根据设备名称写三套页面,而是先看当前窗口有多宽。
EntryAbility 监听 windowSizeChange,再用屏幕密度把 px 换算成 vp。宽度会进入五档断点:
这些断点写入 AppStorage,具体页面通过 @StorageProp 读取。当前业务并没有为了展示"适配很多档"而强行做五套布局,主要归并成两种策略:sm 及以下走窄屏,md 及以上走中大窗口。lg、xl 先保留扩展空间。

实际变化包括:
- "今天""课表""事项"的大幅场景图只在窄屏展示;中大窗口把空间还给内容。
- 今天页的课程和提醒由单列变为双列,日课表中的课程卡片也采用同样策略。
- 事项页的卡片在窄屏占满一行,在中大窗口使用约 49% 宽度,两张并排。
- 新增课程、课程详情和事项编辑,在窄屏使用底部 Sheet;进入
md后改为居中 Sheet。 - 2in1 不直接套用移动端全屏逻辑,避免应用内容与桌面窗口标题栏、系统区域打架。
这里有个容易踩的坑:不要只在启动时判断一次"这是手机还是平板"。平板和 2in1 都可能改变窗口大小;同一台折叠设备也会经历不同窗口宽度。响应式布局真正应该观察的是当前可用窗口,而不是设备标签。
官方一多文档也没有要求所有复杂页面只靠几个比例值硬撑。页面复杂时,可以在断点处明确切换单列、双列,甚至采用单独布局。关键是数据和业务逻辑继续复用,界面则按照设备场景重新安排。
这条原则也延续到了应用市场截图:手机、平板和 PC/2in1 必须分别从目标设备采集原始画面。宣传图可以共享视觉规则,但不能把一张手机版成品拉伸成另外两个尺寸。
服务卡片
桌面服务卡片是这个 App 里最容易低估的一部分。页面里显示一份"今日课程"很简单;把同一份内容放到桌面,并且保证修改课程后能及时刷新,就不只是多画一张卡片。
目前有"今日课程"和"今日提醒"两类,每类提供 1×2、2×2、2×4 三种尺寸,一共六个卡片配置。卡片做的是只读摘要:课程卡显示当天日期、课程数量和前几项课程;提醒卡额外显示完成数量和完成状态。点击卡片后,通过 tabKey 拉起应用并进入"课表"或"事项",不在桌面上直接编辑数据。

官方文档里,formProvider 是卡片提供方与卡片管理服务之间的桥梁,通过 IPC 与 FormExtension 通信。卡片提供方可以调用 updateForm 主动更新内容,并配合 onAddForm、onUpdateForm、onFormEvent 等生命周期工作。对应说明见ArkTS 卡片主动刷新和formProvider API。
这也是为什么 FormExtensionAbility 不能依赖主界面里的 @State、AppStorage 或某个页面数组。它和 UIAbility 运行在不同进程,任何"主页面刚刚已经改过"的内存假设都不可靠。
我把卡片链路拆成了四层:
FormPreference持久化formId ↔ formName,知道桌面上实际存在什么卡片。BabyScheduleFormUtils负责读取数据、排队刷新和调用 Form Kit。BabyScheduleSnapshot把课程、事项、日期和完成状态压成一份当天快照。EntryFormAbility接收系统生命周期回调,不直接承载业务数据。
formId 不能只放在内存中。卡片被添加后,系统会为每个实例分配 ID;同一种 2×2 卡片被加到桌面两次,也会得到两个不同 ID。因此工程单独使用 baby_schedule_form_ids_v1 保存全部 ID、卡片名称以及对应关系,并监听 multiProcessChange。其他进程修改 Preferences 后,旧句柄会失效,下次读取时重新打开。
几个生命周期回调,我是这样处理的:
onAddForm:登记formId和formName,生成第一份快照并主动推送。由于回调需要同步返回,先返回空的绑定数据,再异步完成读取和更新。onUpdateForm:系统请求刷新时,重新读取持久化数据,只更新对应卡片。onRemoveForm:从本地映射中移除已经删除的formId。onConfigurationUpdate:主题或系统配置变化后刷新全部卡片。onAcquireFormState:系统查询卡片状态时补记 ID,并触发一次单卡更新。
应用会持久化 formId 和卡片名称。每次刷新时,BabyScheduleFormUtils 重新读取本机课程、事项和完成状态,由 BabyScheduleSnapshot 整理出当天快照,再根据卡片类型生成绑定数据,通过 formProvider.updateForm 推送给已经添加到桌面的卡片。
读取前会先清理 Preferences 缓存,确保 FormExtension 进程拿到的是刚刚落盘的数据。保存、删除、清空、改变事项完成状态、应用回到前台,以及卡片添加、更新、状态获取和配置变化等系统回调,都会进入这条刷新链路。
快速连续修改时还有一个异步顺序问题。假设用户连续保存两次,第一次读取较慢、第二次较快,如果两个刷新任务完全并行,旧快照可能最后才调用 updateForm,反而把新数据覆盖掉。这里用 updateChain 把刷新串行化:上一轮完成以后,下一轮再读取和推送。
项目同时调用 setFormNextRefreshTime(formId, 5) 安排下一次系统刷新。官方 API 规定这个间隔不能小于 5 分钟,所以它只承担兜底;用户刚保存课程时,仍然走主动 updateForm,不让用户等到下一轮定时刷新。
如果 updateForm 返回 16501001,说明这个卡片 ID 已不存在,工程会顺手清理本地的过期映射。卡片更新失败也不会反过来阻断课程和事项保存。
这也是为什么 FormRefreshBus 只传递一句"本机数据已经落盘,可以刷新了"。它不是跨进程数据总线,也不把页面数组传给卡片;真正的跨进程更新仍由持久化数据和 Form Kit 完成。
DevEco CLI 的使用
「宝贝日程表」早期版本的构建和验证,主要直接使用 hvigorw、hdc 和工程脚本。后来接入 DevEco CLI,我才逐步把查文档、检查工程、构建、装机、读取日志和调用 Skills 收进一个更适合 Agent 使用的入口。底层工具没有消失,只是不用每次都让 Agent 猜它们安装在哪里、参数应该怎样拼。
华为对 DevEco CLI 的定位也不是"把 DevEco Studio 做成纯命令行版"。官方工具概述强调,它面向各类 AI Agent,把 HarmonyOS 工具集、HarmonyOS 知识库和精品 Skills 封装为可调用的命令行接口。其实就是补通用 Agent 对鸿蒙领域知识和本机开发工具不熟的问题。
按照快速入门文档,使用前需要安装 DevEco Studio 6.0.0 及以上版本和 Node.js,官方推荐 Node.js 22 及以上。稳定版可以通过 npm 安装:
它实际帮我接起了哪些步骤
目前我用 DevEco CLI 1.3.1 跑这套流程。对我来说,它的价值很实在:官方文档、工程检查、构建、装机、截图和日志都有了 Agent 能直接调用的入口。过去需要在 IDE、终端和文档站之间来回切换的步骤,现在可以沿着同一条任务链继续做下去。
但工具入口和验收标准是两回事。DevEco CLI 负责把文档、构建、设备、UI 与日志能力交给 Agent;项目 Skill 再规定改完要检查哪些页面、怎样回读设备,以及 Release 包里哪些字段必须核对。这样 Agent 才不是"改到编译通过就结束",而是沿着项目自己的标准留下真机和包体证据。
它对这个项目最直接的帮助,是把原本容易断开的步骤接了起来。以服务卡片为例,我可以先用 docs search 和 docs read 找到 updateForm、刷新周期与生命周期的官方说明,再通过 MCP 查定义、引用和调用关系;改完以后继续构建、选择设备、安装启动、截取页面并跟踪指定包名的日志。哪一步失败,错误和前面的上下文都还在同一个任务里,不用重新向另一个工具解释工程结构。
做"一多"适配时也是如此。Agent 不只看 ArkUI 源码,还可以读取当前设备和窗口信息,用 UI 工具截图、点击、滑动或输入,再把结果与断点规则对照。它当然不会凭空替我判断界面好不好看,但能把"改一处、跑一次、留一张证据"变成连续动作。对个人开发者来说,这比单纯生成一段代码更有价值。
我常用的几组命令
安装后,可以用下面两个命令确认版本和完整命令列表;已经安装旧版本时,执行 devecocli update 即可升级:
从零创建工程时,可以指定目录、应用名称、包名和 API Level。当前 create 只支持 Empty Ability 模板:
我现在更常用的是下面几组:
DevEco CLI 里也不全是只读命令。signature generate 会生成签名材料并写入工程配置,check lint --fix 会改代码,run --uninstall 会动设备上的安装状态,init 与 Skills 的增删也会修改项目或 Agent 配置。让 Agent 使用这些能力时,应该先分清哪些只是检查,哪些会留下改动。
即使 build 和 run 都成功,Release 发版仍然要单独核验签名、Profile、包名与包体。证书、Profile、密码和本机绝对路径也不应该出现在公开文章或仓库中。命令细节如果和网上的旧示例不一致,我会先看本机 devecocli help 与子命令帮助,再对照DevEco CLI 命令文档。
MCP 到底放在什么位置
MCP 其实就是 Agent 与外部工具之间的一层标准连接。模型本身只会生成文字;接上 MCP 后,它才能用结构化参数去查文档、检查代码、找定义或调用本机工具,并拿回可以继续判断的结果。
在这套流程里,我实际区分两类 MCP 能力:
- HarmonyOS 开发者知识 MCP:用来搜索和读取最新官方文档。本文补充"一多"、Form Kit 和 DevEco CLI 时,查询的就是官方文档内容,而不是让模型凭印象补 API。
- DevEco CLI 本地 MCP:把工程检查、类型悬浮信息、定义、引用、实现、符号搜索和调用关系等能力开放给 Agent。官方文档建议用
devecocli init --mcp自动配置。
工程里我是这样配的:
需要注意,--mcp 和 --skill 是两种独立配置,不能在同一条 init 命令里同时使用。按照当前文档,本地 MCP 要求 DevEco Studio 26.0.0 Release 及以上、DevEco CLI 1.3.0 及以上;遇到版本不满足时,应该先升级,而不是在 Agent 配置里反复重试。
Skill 和 MCP 不是一回事
我自己的理解是:MCP 给 Agent 一双能操作工具的手,Skill 则告诉它遇到某类任务时先做什么、怎样检查、什么结果才算完成。
官方Skills 配置文档把 Skill 定义为包含 SKILL.md 的文件夹。里面用 YAML Frontmatter 描述名称和触发条件,再用 Markdown 写步骤与注意事项。它的价值不是多一段提示词,而是把重复流程做成"一次定义,长期复用"。
DevEco CLI 可以查询并安装官方 HarmonyOS Skills:
我写这篇文章时,devecocli skills list --long 能列出 35 个官方 HarmonyOS Skills。它们不是一个什么都做的"大 Skill",而是按 ArkUI、ArkTS、测试、多设备、安全区等任务拆开;Agent 在合适的阶段加载对应规则,结果通常比把所有要求塞进一段超长提示词更稳定。
这次实际查到并适合本工程的 Skill 包括:
hmos-arkui-develop-skill:ArkUI 页面、状态管理、列表网格、路由和模态交互。hmos-arkts-knowledge-retriever:检索 ArkTS 语言与 API 资料。hmos-arkts-syntax-checker:检查语法、构建并围绕编译错误修复。hmos-multidevice-screen-window-size:窗口尺寸、断点、响应式布局和多设备适配。hmos-multidevice-avoid-areas:状态栏、导航指示条、刘海和沉浸式安全区。hmos-local-test:执行 ArkTS/JS Local Test。
这些官方 Skills 解决通用鸿蒙问题;项目自己的 Skill 则负责把团队约束和验收标准补进去。例如本工程要求保留独立包名、独立签名,UI 改动后验证今天、课表、事项、设置和编辑页,这些规则不会凭空出现在通用 Skill 里。

项目做久以后,我又把几段容易漏步骤的流程写成自己的 Skill:
harmonyos-build-deploy
它负责识别工程、产品和模块,处理依赖、Debug/Release 构建、签名、设备安装、Ability 启动与日志。完成标准不只是看到 BUILD SUCCESSFUL,还要回读设备上的包名和版本,确认实际运行的是刚构建的产物。
harmonyos-appgallery-release
它负责正式发版。APP 是外层归档,里面还有 HAP,所以需要先核对外层 pack.info,再检查内层 HAP 的 module.json、API、设备类型、调试标志和签名。上传以后继续区分云端处理、提交审核和正式在架。
create-app-store-promo-images
这个 Skill 专门把真实截图做成手机、平板和 PC/2in1 的应用市场宣传图。

它最重要的规则是:App 截图属于产品证据,不能交给图像模型重画。AI 可以提供背景纹理或视觉方向,最终的中文标题、主题色、截图缩放、设备位置和留白全部由确定性脚本完成。
手机输出 1080×1920,平板输出 1280×1920,PC/2in1 输出 1920×1080。三个尺寸都从对应设备的原始截图独立合成。PC/2in1 只截取应用窗口本体,排除模拟器桌面、Dock、壁纸和外部阴影。
同一种设备规格下的一组宣传图共享标题坐标、截图顶边、目标高度和中心轴。文字先按更高分辨率渲染,再缩小保证边缘清晰;生成后还会机械检查图片数量、尺寸、比例、格式和文件体积,并逐张查看标题、底部留白和设备是否对齐。
DevEco CLI 的安装和能力范围可以参考华为开发者联盟 DevEco Studio 页面及DevEco CLI 文档。
上架到 AppGallery
应用写完以后,我开始准备商店文案和截图。
宣传图的第一行说功能,第二行说用户能得到什么。例如"今天怎么安排 / 打开就能看清""这周上什么课 / 提前看一眼就好"。它们没有写"高效赋能家庭日程管理",因为家长打开 App 时不会这么想。
下面这张是我从应用市场网页端截取的公开页面:

真正走发版流程,至少要过这几步:
- 更新版本号与版本名称。
- 使用 Release 配置构建 APP。
- 检查外层
pack.info。 - 解出内层 HAP,核对模块、API、设备类型和调试标志。
- 验证签名与摘要。
- 上传 AppGallery Connect,等待云端处理。
- 写版本说明并提交审核。
- 审核通过后,重新读取应用市场的在架版本。

写在最后
「宝贝日程表」没有复杂账号体系,也没有宏大的产品故事。它只是把校内课程、兴趣班和日常提醒放到一张更清楚的日程里。如果你也在做第一个鸿蒙应用,希望这份记录能帮你少漏掉几步,也欢迎在华为应用市场搜索「宝贝日程表」体验。