AtomGit码道CodeArts从模板工具故障切换手动建Stage工程,ArkTS严格模式适配、42格日历算法与打卡持久化的完整实现
一键开通华为云码道 CodeArts 代码智能体:https://developer.huaweicloud.com/codeartsco.html?source=dmzntgwatomgit1&sourcead=dmzntgwatomgithd
项目地址:https://atomgit.com/yangqing88/harmonyos-checkin-app (public)
一、缘起:公司要打卡功能,我顺势学鸿蒙
公司最近想做一个打卡功能,需求不复杂:今天能不能打卡、打了多少天、本月打了几天、最近记录是什么——一个典型的小型移动端页面。正好我一直想找机会学学鸿蒙,ArkTS 这套语法看着眼熟又处处透着陌生,真上手才发现环境配置、工程结构、状态管理、持久化 API,每一步都有门槛。
于是我换了个思路:自己闷头啃文档是一周,不如先把需求丢给 AtomGit 上的华为云码道 CodeArts,全程盯着它干活,把它的思考链、踩坑过程、自我修复全部截图留档——既满足了公司需求,又等于旁听了一场鸿蒙开发实况课。
在对话框里,我敲下的需求一如既往地朴素:
帮我生成一个鸿蒙ArkTS项目的打卡页面,需要生成一个页面,不需要后端代码

前面刚刚围观过它生成围棋 HTML 游戏,这次换成鸿蒙原生开发,我很好奇它的表现会不会打折扣。事实证明,这一场的精彩程度不亚于上一场——因为这次它翻车了,还翻了不止一次。
二、第一阶段:一句话落地,CodeArts 先"勘地形"
老规矩,CodeArts 没有立刻写代码。深度思考给出的第一个判断是:这是 HarmonyOS 开发任务,可以用 harmonyos 插件或者 createHtmlTemplate 这类工具生成项目模板,或者干脆直接创建。但它没有急着动手,而是先执行了一条 ls -la /workspace——先看工作区长什么样。结果很干净:除了一个 codeartsdoer 工具目录,整个 workspace 空空如也。

这个动作看着不起眼,价值却很大。工作区是空的,意味着没有现成项目可以"寄生",CodeArts 据此把计划拆成了两步:第一步创建一个标准鸿蒙ArkTS项目模板,第二步在这个模板中添加打卡页面。如果是人类新手,很可能直接开写一个孤零零的 .ets 文件,等到编译时才发现缺 app.json5、缺 module.json5、缺 hvigor 配置,一头雾水。

三、第二阶段:模板工具翻车,教科书级的 Plan B
真正的戏剧性从这里开始。CodeArts 调用了 CreateHtmlTemplate 工具,参数是 {"filePath":"/workspace/checkin-app","mode":"application"}——结果直接报错:
Error: templates directory not found. Searched upwards from /$buns/root

模板目录不存在,工具彻底不可用。换个心理素质差点的模型,这里可能就开始编造输出或者反复重试同一条路了。但这位 CodeArts 的切换非常利落:既然模板不可用,我将手动创建HarmonyOS项目结构。它随即在思考链里铺开了一份标准 HarmonyOS Stage 工程的目录树:
AppScope/
├── app.json5
├── resources/
entry/
├── src/main/
│ ├── ets/
│ │ ├── entryability/
│ │ │ └── EntryAbility.ets
│ │ ├── pages/
│ │ │ └── Index.ets
│ ├── resources/
│ │ ├── base/
│ │ │ ├── element/
│ │ │ │ ├── string.json
│ │ │ │ └── color.json
│ │ │ └── media/
│ │ │ └── icon.png
│ │ └── profile/
│ │ └── main_pages.json
├── module.json5
├── build-profile.json5
├── hvigorfile.ts
└── oh-package.json5
build-profile.json5
hvigorfile.ts
oh-package.json5
更值得玩味的是这一段对话里的"犹豫"。CodeArts 先是想到用 harmonyos-developer 子代理来完成专业任务,转念一想——这个任务其实很简单,一个打卡页面而已,文件不多且结构固定,让我自己动手创建,也为了避免让子代理重复阅读环境。中间还穿插了一次 SkillSearch 工具查询和 Glob 搜索技能定义,结果 **/ohos/SKILL.md、**/hms-atomic-dev/SKILL.md 都返回 No files found,Bash 翻了一圈只在 /root/.codeartsdoer 找到工具目录。

这一段"要不要外包给子代理"的内心戏,恰恰是很多 AI 产品里缺失的东西:它评估了任务复杂度、环境可用性、上下文传递成本,最后得出"我自己写更快"的结论。工具失败没有触发摆烂,反而触发了一连串降级方案评估——这就是工程判断力。

顺便,它把页面功能也想清楚了:打卡页面通常包含顶部标题(今天日期)、打卡按钮(今天没打卡显示"立即打卡",已打卡显示"今日已完成")、打卡历史记录列表、统计(连续打卡天数、累计打卡天数、本月打卡天数)。不用后端,数据用本地持久化方案存。
四、第三阶段:骨架先行,标准 Stage 工程从零搭起
方案落定后是一连串利落的落地动作。先 mkdir -p 把目录树整个建出来,然后并行写入工程配置文件:
AppScope/app.json5—— 应用级配置(bundleName 等)AppScope/resources/base/element/string.json—— 字符串资源build-profile.json5—— 工程构建配置hvigorfile.ts—— 构建入口,import { appTasks } from ...

中间还藏着一个细节值得单独拎出来:图标怎么办?鸿蒙工程的 module.json5 和 app.json5 都引用图标资源,没有图标文件编译会失败,而 CodeArts 没法凭空画一张正式图标。它的解法是直接用 python3 内联脚本,通过 base64、zlib、struct 手工构造 PNG 文件——输出一行 PNG icons created,问题解决。这种"用代码生成资源占位"的操作,新手大概率想不到,反而会卡在"找不到图标"上半天。

接着是 entry 模块的配置五连:module.json5(module 名、entry 类型)、build-profile.json5(appType: 'stageMode')、hvigorfile.ts(import { hapTasks } from '@ohos/hvigor-ohos-plugin')、oh-package.json5(模块名、版本、描述"打卡页面")、string.json(module_desc 等字符串资源)。一条 Write 接一条 Write,全部 Write file successfully。
五、第四阶段:设计深潜——写代码前的最后一步推演
配置齐了,该写 ArkTS 了。CodeArts 在这里又停下来做了一轮设计推演,列出打卡页面要用到的关键技术点:@Entry、@Component 装饰器,@State 状态管理,Column、Row、Stack 布局组件,List、Text、Button 基础组件,数据持久化用 @ohos.data.preferences,日期格式化用 Date。

它还想确认 EntryAbility 的标准写法和 Preferences API——毕竟是"确保使用正确的API",于是又发起了一次技能知识检索,两次 Glob 都扑空之后,它干脆释然了:我对HarmonyOS ArkTS开发比较熟悉,让我直接编写标准的代码。这种"查证失败就靠已验证的既有知识兜底"的路径,比硬撑着装作查到了要诚实得多。

是 Preferences 使用方案的推演。CodeArts 对比了两种写法——同步版:
import { preferences } from '@kit.ArkData';
let preferences: preferences.Preferences | null = null;
try {
preferences = getContext(this).getPreferencesSync({ name: 'checkin_store' });
} catch (err) {
// ...
}
和异步版:
import { preferences } from '@kit.ArkData';
const context = getContext(this);
preferences.getPreferences(context, 'store', (err, val) => { /* ... */ });

权衡之后选了同步版本——理由是"更直观"。数据结构也在这一步定型:
interface DayInfo {
day: number; // 日
dateStr: string; // yyyy-MM-dd
isCurrentMonth: boolean;
isToday: boolean; // 是否今天
isChecked: boolean; // 是否已打卡
}
interface CheckinRecord {
date: string;
time: string;
}

存储方案则定了一个很聪明的"极简主义":只存一个日期字符串数组,形如 ["2026-09-01", "2026-09-02"],存进 Preferences 时序列化成 JSON。统计、日历、记录列表全部由这一个数组推导——records 不单独存,直接由 checkinDates 派生。一个数据源,多处消费,杜绝了两份数据不同步的隐患。这是数据库设计里最朴素的范式思想,被它自然地用进了一个页面级应用里。

六、第五阶段:42 格日历——整个项目最硬的骨头
如果说打卡按钮是"面子",日历就是这场生成的"里子"。 完整推演 buildCalendar 时铺开了完整细节:

buildCalendar(): void {
const year: number = this.viewMonth.getFullYear();
const month: number = this.viewMonth.getMonth();
const firstDay = new Date(year, month, 1);
const startWeek = firstDay.getDay();
const daysInMonth = new Date(year, month + 1, 0).getDate();
const cells: DayInfo[] = [];
const today = this.todayStr;
// 前置补空(上月尾巴)
for (let i = 0; i < startWeek; i++) {
const prev = new Date(year, month, 1 - (startWeek - i));
cells.push({
day: prev.getDate(),
dateStr: formatDate(prev),
isCurrentMonth: false,
isToday: false,
isChecked: false
});
}
// 当月每一天
for (let d = 1; d <= daysInMonth; d++) {
const dateStr = formatDate(new Date(year, month, d));
cells.push({
day: d,
dateStr: dateStr,
isCurrentMonth: true,
isToday: dateStr === today,
isChecked: this.checkinDates.includes(dateStr)
});
}
// 末尾补足对齐总数 42 格
while (cells.length % 7 !== 0) {
const tailDate = new Date(year, month + 1, 1 + (cells.length % 7));
cells.push({ /* ... */ });
}
this.monthDays = cells;
}

42 格网格、startWeek 偏移补空、月末补位、new Date(year, month + 1, 0) 取当月天数的惯用法——全都是日历组件开发里真实会踩的点。 还有一段关于布局方案的自问自答:Grid 加 columnsTemplate('1fr 1fr 1fr 1fr 1fr 1fr 1fr') 加 aspectRatio 的组合行不行?行数不定时怎么办?最终它权衡后选了更灵活的方案:
Flex({ wrap: FlexWrap.Wrap }) {
ForEach(this.monthDays, (item: DayInfo) => {
Text(item.day.toString())
.width('14.28%') // 每行7个,7 × 14.28% ≈ 99.96%
.aspectRatio(1)
// ...
})
}

更硬核的是 的 ArkTS 严格模式专题。ArkTS 不是 TypeScript 的简单子集,JSON.parse 的返回值处理就是经典的坑。CodeArts 给出的稳妥写法:
let list: string[] = [];
const raw = (await pref.get(KEY_CHECKIN, '[]')) as string;
try {
const parsed: ESObject = JSON.parse(raw);
if (parsed) {
parsed.forEach((item: ESObject) => {
list.push(item as string);
});
}
} catch (e) {
list = [];
}
this.checkinDates = list;

甚至细到 @kit.ArkData 与 @ohos.data.preferences 两种导入该怎么选——前者是 API 12 的归一化导入,后者从 API 9 一直兼容到 12,考虑到兼容性教程与社区示例的覆盖面,最终敲定:
import dataPreferences from '@ohos.data.preferences';

打卡动作与持久化的主逻辑倒是干净利落:
doCheckin(): void {
if (this.todayChecked) return;
if (this.pref === null) {
this.toast('数据未就绪');
return;
}
const newList = this.checkinDates.concat([this.todayStr]);
this.checkinDates = newList;
this.todayChecked = true;
this.buildCalendar();
this.refreshStats();
this.saveData();
}
saveData(): void {
if (this.pref === null) return;
const json = JSON.stringify(this.checkinDates);
this.pref.put(KEY_CHECKIN_DATES, json).then(() => this.pref!.flush());
}
还有工具函数级别的讲究:
function pad(n: number): string {
return n < 10 ? '0' + n : '' + n;
}
function formatDate(d: Date): string {
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
}

连续打卡的算法(从今天或昨天起向前回溯连续有记录的天数)、星期一为一周起始的周对齐、月份左右切换时自动处理跨年——这些散落在推演里的细节,合起来就是一份"鸿蒙日历打卡组件"的迷你设计文档。
七、第六阶段:两次"自愈"——AI 的不完美,与它的坦诚
写核心页面 Index.ets 的时候,翻车了。第一次 Write 尝试直接 Tool execution aborted——因为前面的思考内容太长,达到了模型输出长度上限,回答被截断,系统提示"请发送’继续’以获取完整回复"。我发了个"继续",第二次 Write 成功。

这还没完。写完之后 CodeArts 自己复查代码,发现了两个问题:
- 月份切换按钮被设置成
Visibility.None并且height(0),用户根本无法切换月份; - Grid 后面多了一个多余的 Row,导致布局冗余。

于是连续两次 Edit:一次让月份切换按钮重新显示,一次清理多余的 Row。整个过程没有遮掩、没有"假装没事",修正记录全都在对话流里。这对应了上一场围棋实验里我总结过的那句话——AI 的失败是透明的,而透明本身就是一种可靠性。

同时还能看到它对 AppScope 的 app_icon.png、app_icon_navigation.png 占位图的处理,以及对"这个工程结构是不是标准的"的自我审视——毕竟环境里没有 DevEco Studio,无法直接编译,它能做的最后防线是用等价 JS 逻辑在本地验证核心算法:连续打卡计算、日历 42 格生成、月份切换、记录倒序,全部跑通后清理临时文件(rm -rf /tmp/check.js ...),然后如实写下一句:当前环境无鸿蒙 SDK,未做实际编译,建议在 DevEco Studio 中运行确认。
八、交付时刻:6 分 6 秒的成绩单
交付总结信息量很足。工程位于 /workspace/checkin-app,标准 Stage 模型、API 12:核心是 entry/src/main/ets/pages/Index.ets(打卡页面,纯前端、无后端)和 entry/src/main/ets/entryability/EntryAbility.ets(入口 Ability),其余为 module.json5、build-profile.json5 等工程配置。

功能清单四条:今日打卡(渐变头部卡片 + 圆形打卡按钮,点击后本地持久化,再次点击提示"已打卡");数据统计(连续打卡天数 / 累计打卡次数 / 本月打卡次数,实时计算);本月日历(42 格日历网格,已打卡日期高亮,今天蓝底标记,支持左右切换月份,自动处理跨年);最近打卡记录(倒序展示最近 6 条,日期 + 时间)。

底部一行小字是这场生成的完整成本:2026-09-21 21:58:10 · 耗时 6m6s · 网络命中 100% · 输入 51.4K tok · 输出 41.9K tok。对比围棋那一场(输出 17.3K tok),这次输出量翻了近两倍半——ArkTS 的样板代码量确实比单文件 HTML 大得多。
九、第七阶段:一句话,推上 AtomGit
页面交付完,我追加了第二句话:
帮我初始化到atomgit仓库,并且自动生成readme文件
CodeArts 立刻加载 ag-cli 技能——一个专门管理 AtomGit / GiteCode 仓库的命令行技能,然后流程走得比上次围棋还稳:

第一步,验明正身。ag auth status 确认已登录 atomgit(yangqing88),ag api /user 拿到当前账号信息,还顺手 ag repo create --help 把命令用法过了一遍——注意到默认可见性是 private,需要显式传 --public。

第二步,内容先行。先把 README.md 写出来:一款基于 HarmonyOS ArkTS 开发的每日打卡应用,纯前端实现,包含功能特性、工程结构、环境要求、运行方式、核心实现说明(数据模型、连续打卡算法、日历生成)以及 MIT License。同时本地 git init -b main、准备 .gitignore(屏蔽 build/、.hvigor、oh_modules/ 等鸿蒙构建产物)。
第三步,交互确认。这次 CodeArts 学"乖"了——上次围棋仓库被我发现 private/public 不符,这次它直接把两个问题提前弹窗确认:
- 仓库名称(1/2):
checkin-app(推荐,用项目目录名)还是harmonyos-checkin-app(更明确标识鸿蒙打卡应用)?我选了后者。 - 可见性(2/2):公开 Public(推荐)还是私有 Private?我选了公开。

第四步,建仓推送:
$ ag repo create harmonyos-checkin-app --public
$ git remote add origin https://atomgit.com/yangqing88/harmonyos-checkin-app.git
$ git push -u origin main
remote: Start Git Hooks Checking
To https://atomgit.com/yangqing88/harmonyos-checkin-app.git
* [new branch] main -> main
branch 'main' set up to track 'origin/main'.
最后一步验证:git status 干净、ag repo view 确认——Name: yangqing88/harmonyos-checkin-app,Visibility: public,Default Branch: main,Language: ArkTS,22 个文件已推送,本地与远端关联完毕。从"帮我初始化"到仓库就绪,全程不到两分钟。
全链路用一张流程图收拢:
十、几点技术解读
流水账讲完,把值得琢磨的技术点单独拎出来说透。
1. 工具失败的降级路径,是这场生成最值钱的部分。 CreateHtmlTemplate 报 templates directory not found 之后,CodeArts 做了完整的 Plan B 评估:子代理能不能干?技能库有没有现成的?检索扑空后判断"任务简单、文件不多且结构固定",自己动手。这个决策链里最难得的是成本意识——它没有为了"显得专业"硬套子代理流程,而是按任务实际复杂度选择了最短路径。新手手写项目时恰恰相反:要么死磕一个报错半天,要么绕一个大圈子做不必要的事。
2. “只存一个日期数组"的极简数据设计,胜过很多新手的"建表冲动”。 打卡场景里最容易过度设计的就是数据结构:打卡表、统计表、记录表……而 CodeArts 只持久化了一个 ["2026-09-01", ...] 的 JSON 数组,连续天数、累计次数、月度统计、日历高亮、记录列表全部实时推导。数据源唯一,视图即函数。这个设计在数据量小(打卡记录一年几百条)的场景下是最优解,也侧面说明它对"页面级应用"的定位拿捏得很准——没给它后端,它就没假装自己需要后端。
3. ArkTS 严格模式的适配,是纯经验活。 JSON.parse 返回值转 ESObject 再逐项 as string、getPreferencesSync 与异步版的选择、@kit.ArkData(API 12 归一化导入)与 @ohos.data.preferences(API 9~12 全兼容)的兼容性权衡、@State 初始化时机与 aboutToAppear 的配合——这些不是看一眼文档就能写对的,每一条都对应着社区里真实的踩坑帖。AI 能一次到位,靠的是海量真实工程语料的沉淀,这也是它与"查文档写代码"的人类新手之间最本质的差距。
十一、新手手写鸿蒙 vs AI 生成:一张诚实的对比表
假设一个有前端基础、但没碰过鸿蒙的新手来做同样的打卡页面,把两条路线摆在一起(手工耗时按真实学习曲线估计):
| 环节 | 新手手工开发(估时) | AI CodeArts 生成(实测) | 差距说明 |
|---|---|---|---|
| 环境与工程搭建 | 3~6 小时(装 DevEco Studio、配 SDK、建 Stage 工程、踩 hvigor 坑) | 全套 Stage 工程 + 配置文件约 1 分钟,含 base64 生成占位图标 | 工程结构知识直接内置 |
| 模板/脚手架 | 找模板、抄示例项目 | 模板工具失败后自动切换手动创建 | 降级决策无需人工干预 |
| 打卡页面编码 | 6~12 小时(学 ArkTS 装饰器、状态管理、布局体系) | 约 6 分钟,含日历/统计/记录全部功能 | ArkTS 严格模式的坑全部提前规避 |
| 数据持久化 | 2~4 小时(啃 Preferences 文档、试同步异步 API) | 设计阶段一次定型(只存日期数组) | 数据模型极简且正确 |
| 算法验证 | 常被跳过,运行时才暴露 | 等价 JS 逻辑验证 + 自检修复 2 处 UI bug | 无 SDK 环境下的最大诚意验证 |
| README 与仓库 | 1~2 小时 | README 自动生成 + 交互确认建仓推送 | 仓库名/可见性提前弹窗,不再返工 |
| 总计 | 约 2~4 天 | 约 10 分钟(含两轮对话) | 效率差距 30 倍以上 |
| 产出质量 | 可用,但严格模式报错、日历边界 bug 高发 | API 12 标准 Stage 工程,22 个文件,结构规范 | AI 胜在工程完整度 |
| 学习收获 | 极高(DevEco、hvigor、ArkTS 每个坑都长在自己身上) | 中高(思考链 = 一份带决策理由的鸿蒙开发实录) | 对照思考链学,比啃文档快 |
CodeArts 交付时自己给出的功能与验证清单:
| 项目 | 状态 |
|---|---|
| 今日打卡 / 已打卡提示 / 本地持久化 | 完成 |
| 连续打卡 / 累计打卡 / 本月统计 | 完成(实时计算) |
| 42 格日历 / 打卡高亮 / 月份切换(跨年处理) | 完成 |
| 最近 6 条记录倒序展示 | 完成 |
| 连续打卡、日历生成等核心算法(等价 JS 验证) | 通过 |
| 实机编译验证 | 未做(环境无鸿蒙 SDK,建议 DevEco Studio 确认) |
照例把话说全:这张表的"30 倍效率"有三个前提——需求清晰且体量可控(一个纯前端页面恰好落在 CodeArts 的舒适区)、平台工具链可被自动调用(ag-cli、Write/Edit/Bash 全程在线)、用户能读懂思考链并在关键节点(仓库名、可见性)做决策。如果你的目标本来就是"学会鸿蒙",那手工路线的 2~4 天一天都不会白费;但如果目标是"周五之前给老板看个能跑的 demo",答案就不言自明了。
十二、总结
这次"学鸿蒙 + 交差"一石二鸟的小实验,让我看到了 CodeArts 处理"陌生且严肃"技术栈时的完整姿态:模板工具报错,它不慌,评估子代理、查技能库、算成本,最后手动搭出标准 Stage 工程;ArkTS 严格模式的重重限制,它靠经验积累一次写对;输出超长被截断,补一句"继续"就接上;写完自检抓出自己埋的两个 UI bug,当场 Edit 修复;没有鸿蒙 SDK 没法编译,就用等价 JS 验证算法,并老老实实告诉你"建议在 DevEco Studio 里跑一遍";最后连同 README、.gitignore 一起,两轮弹窗确认后推上 AtomGit,22 个文件,public,main 分支,语言一栏赫然写着 ArkTS。

给同样想入门鸿蒙的朋友三句话:第一,把 AI 当"随叫随到的鸿蒙老司机"而不是代码生成器,它思考链里那些"为什么选 @ohos.data.preferences 而不是 @kit.ArkData"的权衡,才是真正值得抄走的东西;第二,验收 AI 产出的鸿蒙工程,先看结构是不是标准 Stage 模型、API 版本对不对,再看它有没有诚实交代"哪些验证做了、哪些没做";第三,新手路线和 AI 路线从来不是单选题——用 AI 把能跑的 demo 立起来,再回头逐行读它的 ArkTS,学习曲线会被压得又陡又稳。
更多推荐

所有评论(0)