OpenHarmony 英语学习 App 实战:自定义生词本、持久化存储与学习数据管理
OpenHarmony 英语学习 App 实战:自定义生词本、持久化存储与学习数据管理
摘要
英语学习 App 不能只提供固定词库。真实学习中,用户会从课本、阅读、听力、影视台词里遇到自己的生词,因此“自定义生词本”是非常重要的功能。本文以「英语视界 YingYu」项目为例,分享如何在 OpenHarmony/HarmonyOS 中实现自定义生词本、详情弹窗、添加表单、搜索过滤和本地持久化。📘
相关文件包括:
entry/src/main/ets/pages/CustomWordBook.ets
entry/src/main/ets/utils/StorageService.ts
entry/src/main/ets/model/DataModels.ts
entry/src/main/ets/utils/YingYuPreferences.ts
一、自定义生词本要解决什么问题?
固定词库适合系统学习,但用户自己的生词更有价值:
- 课外阅读遇到的词;
- 老师补充的重点词;
- 听力材料里的陌生词;
- 错题中反复出现的词;
- 考试作文常用表达。
所以自定义生词本需要具备:
- 添加单词;
- 保存音标、释义、例句、标签;
- 查看详情;
- 搜索过滤;
- 参与复习系统;
- 本地持久化保存。
二、数据模型:CustomWord
项目中使用 CustomWord 表示一个自定义单词:
export interface CustomWord {
id: string
word: string
phonetic?: string
translation: string
example?: string
tags: string[]
createdAt: string
reviewCount: number
mastered: boolean
}
字段设计比较完整:
id:唯一标识;word:英文单词;phonetic:可选音标;translation:中文释义;example:可选例句;tags:标签;createdAt:添加时间;reviewCount:复习次数;mastered:是否掌握。
这些字段不仅能展示单词详情,也能和复习系统、统计系统结合。
三、存储 Key 设计
StorageService.ts 中集中管理存储 Key:
const STORAGE_KEY_CUSTOM_WORDS = 'yingyu_custom_words'
const STORAGE_KEY_REVIEW_RECORDS = 'yingyu_review_records'
const STORAGE_KEY_DAILY_TASKS = 'yingyu_daily_tasks'
const STORAGE_KEY_LEARNING_GOALS = 'yingyu_learning_goals'
把 Key 放在统一服务里有两个好处:
- 避免页面里到处写字符串;
- 后续迁移数据结构更方便。
四、生成唯一 ID
添加单词时需要生成唯一 ID:
function generateId(): string {
return Date.now().toString(36) + Math.random().toString(36).substring(2, 9)
}
这是一个轻量方案,适合本地数据。它结合时间戳和随机字符串,在单机使用场景下足够可靠。
五、读取自定义单词
读取逻辑封装在 getCustomWords() 中:
export function getCustomWords(): CustomWord[] {
try {
const stored = AppStorage.get<string>(STORAGE_KEY_CUSTOM_WORDS)
if (stored) {
return JSON.parse(stored) as CustomWord[]
}
} catch (e) {
console.error('Failed to load custom words:', e)
}
return []
}
这里有一个值得保留的习惯:读取失败时返回空数组,而不是让页面崩掉。学习类应用要优先保证可用性。
六、保存自定义单词
保存时把数组序列化成 JSON:
export function saveCustomWords(words: CustomWord[]): void {
try {
AppStorage.setOrCreate(STORAGE_KEY_CUSTOM_WORDS, JSON.stringify(words))
} catch (e) {
console.error('Failed to save custom words:', e)
}
}
对于小规模本地生词本,JSON 数组足够简单。后续如果数据量上升,可以迁移到数据库或云端同步。
七、添加单词:补齐系统字段
页面提交的只是用户输入字段,真正保存前由服务层补齐 ID、创建时间、复习次数和掌握状态:
export function addCustomWord(
word: Omit<CustomWord, 'id' | 'createdAt' | 'reviewCount' | 'mastered'>
): CustomWord {
const newWord: CustomWord = {
...word,
id: generateId(),
createdAt: new Date().toISOString(),
reviewCount: 0,
mastered: false
}
const words = getCustomWords()
words.push(newWord)
saveCustomWords(words)
return newWord
}
这样页面只关心用户输入,服务层负责数据完整性。
八、更新和删除
更新单词:
export function updateCustomWord(id: string, updates: Partial<CustomWord>): boolean {
const words = getCustomWords()
const index = words.findIndex(w => w.id === id)
if (index !== -1) {
words[index] = { ...words[index], ...updates }
saveCustomWords(words)
return true
}
return false
}
删除单词:
export function deleteCustomWord(id: string): boolean {
const words = getCustomWords()
const filtered = words.filter(w => w.id !== id)
if (filtered.length !== words.length) {
saveCustomWords(filtered)
return true
}
return false
}
返回 boolean 可以让页面判断操作是否成功,并显示 Toast 或刷新列表。
九、搜索过滤
搜索逻辑同时支持英文、中文释义和标签:
export function searchCustomWords(keyword: string): CustomWord[] {
const words = getCustomWords()
const lower = keyword.toLowerCase()
return words.filter(w =>
w.word.toLowerCase().includes(lower) ||
w.translation.toLowerCase().includes(lower) ||
w.tags.some(t => t.toLowerCase().includes(lower))
)
}
这种搜索方式对用户很友好:
- 输入英文可以找到单词;
- 输入中文可以找到释义;
- 输入标签可以找到专题词。
十、添加单词表单
CustomWordBook.ets 中定义了 AddWordSheet 子组件,用于处理添加表单。
@Component
struct AddWordSheet {
@Link visible: boolean
onWordAdded: () => void = () => {}
@State wordText: string = ''
@State phonetic: string = ''
@State translation: string = ''
@State example: string = ''
@State tagsInput: string = ''
}
这里把表单做成独立子组件,而不是直接放在页面里,可以减少整页重建,也更利于维护。
提交时处理标签:
const tags = this.tagsInput
.split(',')
.map(t => t.trim())
.filter(t => t.length > 0)
标签支持逗号分隔,适合用户输入“考试, 写作, 高频词”这样的内容。
十一、详情弹窗:WordDetailSheet
项目还实现了单词详情弹窗:
@Component
struct WordDetailSheet {
@Link visible: boolean
@Prop word: CustomWord = {
id: '',
word: '',
translation: '',
tags: [],
createdAt: '',
reviewCount: 0,
mastered: false
}
}
弹窗进入时有缩放和透明度动画:
aboutToAppear() {
this.dialogScale = 0.88
this.dialogOpacity = 0
animateTo({
duration: 260,
curve: Curve.EaseOut
}, () => {
this.dialogScale = 1
this.dialogOpacity = 1
})
}
展示内容包括:
- 单词;
- 音标;
- 中文释义;
- 例句;
- 标签;
- 添加时间;
- 复习次数或掌握状态。
十二、本地持久化的另一层:Preferences 封装
项目中还有 YingYuPreferences.ts,用于 Preferences 和 AppStorage 的兜底封装。
export function initYingYuUserData(context: common.UIAbilityContext): void {
if (pref) {
return
}
try {
pref = preferences.getPreferencesSync(context, { name: 'yingyu_user_data_v1' })
} catch (e) {
console.error('initYingYuUserData failed:', JSON.stringify(e))
}
}
读取时优先 Preferences,失败则回退 AppStorage:
export function yingyuPrefGet(key: string): string | undefined {
if (pref) {
const v = pref.getSync(key, '') as string
return v.length > 0 ? v : undefined
}
const fallback = AppStorage.get<string>(key)
if (fallback !== undefined && fallback.length > 0) {
return fallback
}
return undefined
}
这种封装让数据层更稳,也便于后续统一替换存储方案。
十三、小结
本文结合「英语视界 YingYu」项目,拆解了自定义生词本的完整实现:
- 使用
CustomWord描述用户单词; - 使用
StorageService封装增删改查; - 使用 JSON + AppStorage 保存轻量数据;
- 使用
searchCustomWords()支持英文、中文、标签搜索; - 使用
AddWordSheet管理添加表单; - 使用
WordDetailSheet展示单词详情; - 使用 Preferences 封装增强持久化可靠性。
自定义生词本让学习 App 从“固定内容工具”变成“用户自己的学习资料库”。这类功能虽然不 flashy,但非常能提升长期使用价值。📖

更多推荐



所有评论(0)