一键开通华为云码道 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.json5app.json5 都引用图标资源,没有图标文件编译会失败,而 CodeArts 没法凭空画一张正式图标。它的解法是直接用 python3 内联脚本,通过 base64、zlib、struct 手工构造 PNG 文件——输出一行 PNG icons created,问题解决。这种"用代码生成资源占位"的操作,新手大概率想不到,反而会卡在"找不到图标"上半天。
在这里插入图片描述

接着是 entry 模块的配置五连:module.json5(module 名、entry 类型)、build-profile.json5appType: 'stageMode')、hvigorfile.tsimport { hapTasks } from '@ohos/hvigor-ohos-plugin')、oh-package.json5(模块名、版本、描述"打卡页面")、string.json(module_desc 等字符串资源)。一条 Write 接一条 Write,全部 Write file successfully。

五、第四阶段:设计深潜——写代码前的最后一步推演

配置齐了,该写 ArkTS 了。CodeArts 在这里又停下来做了一轮设计推演,列出打卡页面要用到的关键技术点:@Entry@Component 装饰器,@State 状态管理,ColumnRowStack 布局组件,ListTextButton 基础组件,数据持久化用 @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 自己复查代码,发现了两个问题

  1. 月份切换按钮被设置成 Visibility.None 并且 height(0),用户根本无法切换月份;
  2. Grid 后面多了一个多余的 Row,导致布局冗余。

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

在这里插入图片描述

同时还能看到它对 AppScope 的 app_icon.pngapp_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.json5build-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/.hvigoroh_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 个文件已推送,本地与远端关联完毕。从"帮我初始化"到仓库就绪,全程不到两分钟。

全链路用一张流程图收拢:

用户: 生成鸿蒙ArkTS打卡页面
不需要后端

深度思考: HarmonyOS开发任务

ls -la /workspace
确认空工作区

尝试 CreateHtmlTemplate 模板工具

❌ Error: templates directory not found

Plan B 评估

harmonyos-developer 子代理?

SkillSearch / Glob 查技能

结论: 任务简单, 自己动手

mkdir 目录树 + 生成PNG图标
Write 全套工程配置

设计推演: Preferences方案
DayInfo结构/只存日期数组

ArkTS严格模式适配
JSON.parse/ESObject/API选型

Write Index.ets
❌ 输出截断 → 继续 → 成功

自检发现2个问题
Edit修复月份按钮/多余Row

等价JS逻辑验证算法
清理临时文件

交付: 6m6s / 输入51.4K / 输出41.9K

用户: 初始化到atomgit仓库+生成readme

ag-cli: 验证登录/查帮助/API确认账号

Write README.md + git init

交互确认: 仓库名/可见性

ag repo create --public + push

验证: 22文件/main分支/ArkTS/public

十、几点技术解读

流水账讲完,把值得琢磨的技术点单独拎出来说透。

1. 工具失败的降级路径,是这场生成最值钱的部分。 CreateHtmlTemplatetemplates directory not found 之后,CodeArts 做了完整的 Plan B 评估:子代理能不能干?技能库有没有现成的?检索扑空后判断"任务简单、文件不多且结构固定",自己动手。这个决策链里最难得的是成本意识——它没有为了"显得专业"硬套子代理流程,而是按任务实际复杂度选择了最短路径。新手手写项目时恰恰相反:要么死磕一个报错半天,要么绕一个大圈子做不必要的事。

2. “只存一个日期数组"的极简数据设计,胜过很多新手的"建表冲动”。 打卡场景里最容易过度设计的就是数据结构:打卡表、统计表、记录表……而 CodeArts 只持久化了一个 ["2026-09-01", ...] 的 JSON 数组,连续天数、累计次数、月度统计、日历高亮、记录列表全部实时推导。数据源唯一,视图即函数。这个设计在数据量小(打卡记录一年几百条)的场景下是最优解,也侧面说明它对"页面级应用"的定位拿捏得很准——没给它后端,它就没假装自己需要后端。

3. ArkTS 严格模式的适配,是纯经验活。 JSON.parse 返回值转 ESObject 再逐项 as stringgetPreferencesSync 与异步版的选择、@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,学习曲线会被压得又陡又稳。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐