一款面向鸿蒙(HarmonyOS)的每日诗词 + 英语单词学习应用,支持语音朗诵、背诵打卡、收藏复习、进度持久化。本文复盘从 0 到上线的完整实现思路与关键踩坑——全程由 WorkBuddy 辅助完成:从项目结构梳理、ArkTS 代码生成、TTS 报错排查到复盘文档撰写,AI 搭档贯穿了开发各环节。
"一筏载诗笔,日日打卡渡新知。"
做这款小应用的出发点很简单:想每天背一首诗、记一个单词,但市面上的 App 太重。于是自己动手,做一个干净、能朗诵、能打卡、关掉再打开数据还在的小工具。
作为非专业鸿蒙开发者,我选择把 WorkBuddy 当成贴身搭档:不会的 API 直接问、报错直接贴、文档草稿让它理。下面所有代码与排错结论,都经过它和我一起打磨。
核心功能清单:
工具 | 版本 / 说明 |
|---|---|
DevEco Studio | 自带 hvigor 构建、SDK、JBR(Java) |
HarmonyOS SDK | default 版本 |
开发语言 | ArkTS(声明式 UI) |
关键 Kit | @kit.CoreSpeechKit(TTS)、@kit.ArkData(preferences 持久化) |
真机调试 | hdc.exe 安装到设备 |
AI 搭档 | WorkBuddy(代码生成 / 排错 / 文档) |
构建命令(PowerShell,需设置 JAVA_HOME 与 SDK 路径):
$env:JAVA_HOME = "D:\DevEcoStudio\jbr"
$env:DEVECO_SDK_HOME = "D:\DevEcoStudio\sdk"
$env:Path = "D:\DevEcoStudio\jbr\bin;" + $env:Path
cd 你的项目目录\HarmonyPoetryWords
node "D:\DevEcoStudio\tools\hvigor\bin\hvigorw.js" --mode module `
-p module=entry@default -p product=default assembleHap安装到真机:
& "D:\DevEcoStudio\sdk\default\openharmony\toolchains\hdc.exe" `
-t <设备SN> install entry\build\default\outputs\default\entry-default-signed.hap项目核心文件分布如下:
所有逻辑(页面 + 详情 + 学习)集中在 Index.ets 里用多个 @Component 组织,方便状态共享。最初的目录划分就是 WorkBuddy 根据我的需求描述建议的,再按实际迭代微调。
古诗词按朝代、作者、分类组织;英语单词按"由简到难、按构词规律"分阶(前缀 / 后缀 / 词根 / 高阶词汇),更符合记忆曲线。下面的 Poetry 类模板由 WorkBuddy 根据鸿蒙 ArkTS 习惯一键生成,我再补字段:
// PoetryData.ets
export class Poetry {
id: number = 0;
title: string = '';
dynasty: string = '';
author: string = '';
content: string = '';
translation: string = ''; // 译文
appreciation: string = ''; // 赏析
category: string = '';
}这是整个项目踩坑最多的地方。鸿蒙 textToSpeech 的正确使用流程是:查询语音 → (必要时下载)→ 创建引擎 → 设置监听 → 朗读。
最初我照搬示例代码,朗读始终失败。把报错贴给 WorkBuddy 后,它让我重点核对语言代码格式和参数类型,才定位到下面三个坑:
import { textToSpeech } from '@kit.CoreSpeechKit';
// 1. 查询设备可用语音
const voices = await textToSpeech.listVoices({ requestId: 'q_' + Date.now(), online: 1 });
// 2. 选已安装语音;没有就尝试下载 GA 语音
let voice = voices.find(v => v.language === 'zh_CN' && v.status === 'INSTALLED');
if (!voice) {
const gv = voices.find(v => v.language === 'zh_CN');
// downloadVoice 只有 callback 版本,需包成 Promise
await new Promise((resolve, reject) => {
textToSpeech.downloadVoice(
{ requestId: 'dl', language: gv.language, person: gv.person, style: gv.style },
(err, resp) => { err ? reject(err) : resolve(resp); }
);
});
}
// 3. 创建引擎
this.engine = await textToSpeech.createEngine({ language: voice.language, person: voice.person, online: 1 });
this.engine.setListener({ onComplete: () => { this.isSpeaking = false; }, /* ... */ });
// 4. 朗读(重点:音量/语速要放在 SpeakParams.extraParams,且是数字不是字符串)
this.engine.speak(text, {
requestId: 'p_' + Date.now(),
extraParams: { 'volume': 2.0, 'speed': 1.0, 'pitch': 1.0 } as Record<string, Object>
});三个关键踩坑(详见第七节):
用 AppStorage 保存每个条目的标记状态(key 形如 mem_<id>、known_<id>)。列表里已完成的项显示绿色高亮 + ✓。
// 列表中判断是否完成(引用 statsTrigger 触发刷新)
private isPoemDone(id: number): boolean {
let _ = this.statsTrigger; // 引用响应式变量
return AppStorage.get('mem_' + id) === '1';
}详情页"收藏"按钮写 fav_<id>;"我的"页用 getFavPoems() 汇总所有收藏并支持点击跳转复习:
private getFavPoems(): Poetry[] {
let _ = this.statsTrigger;
return poetryList.filter(p => AppStorage.get('fav_' + p.id) === '1');
}收藏后必须递增 statsTrigger,否则"我的"页不会重新计算列表(这是最初"收藏了但不显示"的根因,也是 WorkBuddy 帮我定位的响应式刷新问题)。
AppStorage 只在内存中,退出即丢。改用 @kit.ArkData 的 preferences 落盘:
import { preferences } from '@kit.ArkData';
private async persistMarks(): Promise<void> {
const ctx = getContext(this);
const prefs = await preferences.getPreferences(ctx, 'learning_progress');
const ids: number[] = [];
for (const p of poetryList) if (AppStorage.get('mem_' + p.id) === '1') ids.push(p.id);
await prefs.put('memorized_poems', JSON.stringify(ids));
await prefs.flush();
}启动时反向恢复:
const poemJson = await prefs.get('memorized_poems', '') as string;
if (poemJson) JSON.parse(poemJson).forEach((id: number) => AppStorage.setOrCreate('mem_' + id, '1'));AppStorage 不是响应式的——值变了 UI 不会自动重建。解法:用一个 @StorageLink('statsTrigger') 作为"刷新触发器",任何标记变化都让它 +1,所有依赖它的 getXxx() 方法随之重算。
@StorageLink('statsTrigger') statsTrigger: number = 0;App 名称与图标在 AppScope/resources/base/element/string.json 与 media/app_icon.png 配置。
见第二节命令。签名 HAP 通过 hdc install 推到真机即可运行。本文档对应的应用版本为 V28。
问题 | 现象 | 解决 |
|---|---|---|
语言代码格式 | 朗读初始化失败 | zh-CN → zh_CN(下划线) |
en_US 语音缺失 | 单词初始化失败 | 先 downloadVoice 下载 GA 语音再建引擎 |
音量无效 | 声音偏小 | volume 传数字 2.0,放 SpeakParams.extraParams |
进度条不刷新 | 背了不显示 | 用 @StorageLink('statsTrigger') 触发重算 |
退出归零 | 重开标记全丢 | 改用 preferences 持久化 |
收藏不显示 | 收藏了"我的"页无反应 | 收藏按钮也要递增 statsTrigger |
嵌套滚动冲突 | 收藏列表不渲染 | Scroll 内用 Column+ForEach,不用嵌套 List |
把 AI 当搭档,不等于让它替我写全部代码,而是把"重复、易错、文档化"的部分交给它,我专注判断与真机验证:
如果你也想用 WorkBuddy 上手鸿蒙 ArkTS 开发,建议从"把报错原样贴给它"开始——它比搜索引擎更懂你当前的工程上下文。
本文基于真实鸿蒙 ArkTS 项目「古诗词单词通」整理,开发过程由 WorkBuddy 全程辅助。欢迎在评论区交流鸿蒙 TTS、声明式 UI 与 AI 辅助开发的经验。 #WorkBuddy
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。