ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

OpenHarmony 英语学习 App 实战:自定义生词本、持久化存储与学习数据管理

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:stringword:stringphonetic?:stringtranslation:stringexample?:stringtags:string[] createdAt:stringreviewCount:numbermastered:boolean}

字段设计比较完整:

  • id:唯一标识;
  • word:英文单词;
  • phonetic:可选音标;
  • translation:中文释义;
  • example:可选例句;
  • tags:标签;
  • createdAt:添加时间;
  • reviewCount:复习次数;
  • mastered:是否掌握。

这些字段不仅能展示单词详情,也能和复习系统、统计系统结合。

三、存储 Key 设计

StorageService.ts中集中管理存储 Key:

constSTORAGE_KEY_CUSTOM_WORDS ='yingyu_custom_words'constSTORAGE_KEY_REVIEW_RECORDS ='yingyu_review_records'constSTORAGE_KEY_DAILY_TASKS ='yingyu_daily_tasks'constSTORAGE_KEY_LEARNING_GOALS ='yingyu_learning_goals'

把 Key 放在统一服务里有两个好处:

  • 避免页面里到处写字符串;
  • 后续迁移数据结构更方便。

四、生成唯一 ID

添加单词时需要生成唯一 ID:

functiongenerateId():string{ returnDate.now().toString(36)+Math.random().toString(36).substring(2,9) }

这是一个轻量方案,适合本地数据。它结合时间戳和随机字符串,在单机使用场景下足够可靠。

五、读取自定义单词

读取逻辑封装在getCustomWords()中:

exportfunctiongetCustomWords():CustomWord[] {try{conststored =AppStorage.get<string>(STORAGE_KEY_CUSTOM_WORDS)if(stored) {returnJSON.parse(stored)asCustomWord[] } }catch(e) {console.error('Failed to load custom words:', e) }return[] }

这里有一个值得保留的习惯:读取失败时返回空数组,而不是让页面崩掉。学习类应用要优先保证可用性。

六、保存自定义单词

保存时把数组序列化成 JSON:

exportfunctionsaveCustomWords(words: CustomWord[]): void {try{AppStorage.setOrCreate(STORAGE_KEY_CUSTOM_WORDS, JSON.stringify(words)) } catch (e) { console.error('Failedtosave custom words:', e) } }

对于小规模本地生词本,JSON 数组足够简单。后续如果数据量上升,可以迁移到数据库或云端同步。

七、添加单词:补齐系统字段

页面提交的只是用户输入字段,真正保存前由服务层补齐 ID、创建时间、复习次数和掌握状态:

exportfunctionaddCustomWord(word: Omit<CustomWord,'id'|'createdAt'|'reviewCount'|'mastered'>):CustomWord{constnewWord:CustomWord= { ...word,id:generateId(),createdAt:newDate().toISOString(),reviewCount:0,mastered:false}constwords =getCustomWords() words.push(newWord)saveCustomWords(words)returnnewWord }

这样页面只关心用户输入,服务层负责数据完整性。

八、更新和删除

更新单词:

exportfunctionupdateCustomWord(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)returntrue} returnfalse}

删除单词:

exportfunctiondeleteCustomWord(id:string):boolean{constwords =getCustomWords()constfiltered = words.filter(w=>w.id!== id)if(filtered.length!== words.length) {saveCustomWords(filtered)returntrue}returnfalse}

返回boolean可以让页面判断操作是否成功,并显示 Toast 或刷新列表。

九、搜索过滤

搜索逻辑同时支持英文、中文释义和标签:

exportfunctionsearchCustomWords(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子组件,用于处理添加表单。

@Componentstruct AddWordSheet {@Linkvisible: booleanonWordAdded: () => void = () => {}@StatewordText: string =''@Statephonetic: string =''@Statetranslation: string =''@Stateexample: string =''@StatetagsInput: string =''}

这里把表单做成独立子组件,而不是直接放在页面里,可以减少整页重建,也更利于维护。

提交时处理标签:

const tags = this.tagsInput.split(',').map(t => t.trim()).filter(t => t.length>0)

标签支持逗号分隔,适合用户输入“考试, 写作, 高频词”这样的内容。

十一、详情弹窗:WordDetailSheet

项目还实现了单词详情弹窗:

@Componentstruct WordDetailSheet {@Linkvisible: boolean@Propword: CustomWord = { id: '', word: '', translation: '', tags: [], createdAt: '', reviewCount: 0, mastered: false } }

弹窗进入时有缩放和透明度动画:

aboutToAppear() {this.dialogScale =0.88this.dialogOpacity =0animateTo({ duration:260, curve: Curve.EaseOut }, () => {this.dialogScale =1this.dialogOpacity =1}) }

展示内容包括:

  • 单词;
  • 音标;
  • 中文释义;
  • 例句;
  • 标签;
  • 添加时间;
  • 复习次数或掌握状态。

十二、本地持久化的另一层:Preferences 封装

项目中还有YingYuPreferences.ts,用于 Preferences 和 AppStorage 的兜底封装。

exportfunctioninitYingYuUserData(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:

exportfunctionyingyuPrefGet(key:string):string|undefined{if(pref) {constv = pref.getSync(key,'')asstringreturnv.length>0? v :undefined}constfallback =AppStorage.get<string>(key)if(fallback !==undefined&& fallback.length>0) {returnfallback }returnundefined}

这种封装让数据层更稳,也便于后续统一替换存储方案。

十三、小结

本文结合「英语视界 YingYu」项目,拆解了自定义生词本的完整实现:

  • 使用CustomWord描述用户单词;
  • 使用StorageService封装增删改查;
  • 使用 JSON + AppStorage 保存轻量数据;
  • 使用searchCustomWords()支持英文、中文、标签搜索;
  • 使用AddWordSheet管理添加表单;
  • 使用WordDetailSheet展示单词详情;
  • 使用 Preferences 封装增强持久化可靠性。

自定义生词本让学习 App 从“固定内容工具”变成“用户自己的学习资料库”。这类功能虽然不 flashy,但非常能提升长期使用价值。📖

返回列表