欢迎加入开源鸿蒙PC社区https://harmonypc.csdn.net/源码仓库https://atomgit.com/qq_33247427/englishProject.git效果截图第2篇数据模型与单词仓库系列教程导航篇号标题状态01环境搭建与项目创建✅ 已完成02数据模型与单词仓库本篇03主入口页面与导航结构下一篇04极速划词页面实现05手写画布实现06百度 OCR 手写识别接入07答案比对与反馈 UI08单词切换与底部导航09词根分解与水印展示10项目总结与优化方向一、为什么要先设计数据模型在动手写 UI 之前先把数据结构想清楚有几个好处UI 和数据解耦— 页面只关心拿到什么数据不关心数据从哪来方便后续扩展— 今天用硬编码数据明天换数据库或网络接口UI 层不用改团队协作— 前端和后端可以基于同一份接口定义并行开发类型安全— ArkTS 是强类型语言定义好接口后编译器帮你检查我们的单词学习 App 需要管理以下信息单词本身英文、释义、音标词根词缀分解帮助记忆学习分组按日期唯一标识列表渲染需要二、定义 VocabularyWord 数据模型2.1 创建模型文件在electron/src/main/ets/models/目录下创建VocabularyWord.ets/** * 词根/词缀分解项 * 用于展示单词的构词法帮助用户理解和记忆 */ export interface WordPart { /** 词根/词缀文本如 electr */ text: string; /** 含义如 电 */ meaning: string; /** 类型prefix前缀| root词根| suffix后缀 */ type: string; } /** * 单词详情信息 * 包含词根分解等扩展信息 */ export interface WordDetail { /** 词根词缀分解列表 */ parts: WordPart[]; } /** * 单词数据模型 * 这是整个应用最核心的数据结构 */ export interface VocabularyWord { /** 唯一标识符用于列表渲染的 key */ id: string; /** 英文单词 */ english: string; /** 中文释义 */ meaning: string; /** 音标如 /ɪˈlektrɪkl/ */ phonetic: string; /** 音译可选如 伊莱克特瑞克 */ transliteration?: string; /** 词根分解详情可选 */ detail?: WordDetail; /** 所属日期分组如 3/12 */ date?: string; }2.2 设计思路详解为什么id用 string 而不是 numberArkUI 的ForEach和LazyForEach需要一个唯一的 key 来标识列表项。用 string 类型更灵活可以是数据库主键、UUID、或者简单的序号字符串。为什么detail和transliteration是可选的不是所有单词都有词根分解信息也不是所有单词都需要音译。用?标记为可选字段避免强制填充无意义的空值。为什么type用 string 而不是 enumArkTS 对 enum 的支持有一些限制特别是在 UI 描述中使用时。用 string 字面量prefix | root | suffix更简单直接也方便从 JSON 数据源加载。2.3 WordPart 的颜色编码设计在后续的 UI 中不同类型的词根词缀会用不同颜色展示type 值含义背景色文字色示例prefix前缀#FFEBEE浅红#E57373trans-跨越root词根#EEF1E4浅绿#8B9D6B-form形式suffix后缀#DBEAFE浅蓝#64B5F6-tion名词后缀这种颜色编码让用户一眼就能区分词根词缀的类型提升学习效率。三、创建单词数据仓库3.1 Repository 模式介绍Repository仓库模式是一种常见的数据层设计模式┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ UI 页面 │ ──→ │ Repository │ ──→ │ 数据源 │ │ (只管展示) │ │ (统一接口) │ │ (本地/网络) │ └──────────────┘ └──────────────┘ └──────────────┘好处UI 层不需要知道数据来自哪里切换数据源本地 → 网络只需修改 Repository 内部实现方便做缓存、数据转换等中间处理3.2 创建 SpeedWordRepository在electron/src/main/ets/data/目录下创建SpeedWordRepository.etsimport { VocabularyWord } from ../models/VocabularyWord; /** * 极速划词 默写单词的数据仓库 * 当前使用硬编码数据后续可替换为数据库或网络接口 */ export class SpeedWordRepository { private words: VocabularyWord[] [ { id: 1, english: electrical, meaning: adj. 电的与电有关的, phonetic: /ɪˈlektrɪkl/, date: 3/12, detail: { parts: [ { text: electr, meaning: 电, type: root }, { text: ical, meaning: 形容词后缀, type: suffix } ] } }, { id: 2, english: transform, meaning: v. 使转变使改观, phonetic: /trænsˈfɔːrm/, date: 3/12, detail: { parts: [ { text: trans, meaning: 跨越、转变, type: prefix }, { text: form, meaning: 形式、形状, type: root } ] } }, { id: 3, english: international, meaning: adj. 国际的, phonetic: /ˌɪntərˈnæʃənl/, date: 3/12, detail: { parts: [ { text: inter, meaning: 在…之间, type: prefix }, { text: nation, meaning: 国家, type: root }, { text: al, meaning: 形容词后缀, type: suffix } ] } }, { id: 4, english: uncomfortable, meaning: adj. 不舒服的不自在的, phonetic: /ʌnˈkʌmftəbl/, date: 3/12, detail: { parts: [ { text: un, meaning: 不、否定, type: prefix }, { text: comfort, meaning: 舒适, type: root }, { text: able, meaning: 能…的, type: suffix } ] } }, { id: 5, english: transportation, meaning: n. 运输交通工具, phonetic: /ˌtrænspɔːrˈteɪʃn/, date: 3/12, detail: { parts: [ { text: trans, meaning: 跨越, type: prefix }, { text: port, meaning: 搬运, type: root }, { text: ation, meaning: 名词后缀, type: suffix } ] } }, { id: 6, english: environment, meaning: n. 环境周围的事物, phonetic: /ɪnˈvaɪrənmənt/, date: 3/11, detail: { parts: [ { text: en, meaning: 使…, type: prefix }, { text: viron, meaning: 周围, type: root }, { text: ment, meaning: 名词后缀, type: suffix } ] } }, { id: 7, english: independent, meaning: adj. 独立的自主的, phonetic: /ˌɪndɪˈpendənt/, date: 3/11, detail: { parts: [ { text: in, meaning: 不、否定, type: prefix }, { text: depend, meaning: 依赖, type: root }, { text: ent, meaning: 形容词后缀, type: suffix } ] } }, // ... 更多单词数据 ]; /** * 按日期获取单词列表 * param date 日期字符串如 3/12 * returns 该日期下的所有单词 */ getWordsByDate(date: string): VocabularyWord[] { return this.words.filter((w: VocabularyWord) w.date date); } /** * 获取所有单词 * returns 完整单词列表 */ getAllWords(): VocabularyWord[] { return this.words; } /** * 根据 ID 获取单个单词 * param id 单词唯一标识 * returns 匹配的单词未找到返回 undefined */ getWordById(id: string): VocabularyWord | undefined { return this.words.find((w: VocabularyWord) w.id id); } /** * 获取所有可用的日期分组 * returns 去重后的日期列表 */ getAvailableDates(): string[] { const dateSet new Setstring(); for (const word of this.words) { if (word.date) { dateSet.add(word.date); } } return Array.from(dateSet).sort(); } }3.3 方法设计说明方法用途使用场景getWordsByDate(date)按日期筛选极速划词页面按天显示getAllWords()获取全部搜索功能、统计getWordById(id)精确查找跳转到单词详情getAvailableDates()获取日期列表日期选择器四、创建 HandwritingWordRepository除了极速划词的数据源手写练习页面也需要一个独立的数据仓库。创建electron/src/main/ets/data/HandwritingWordRepository.etsimport { VocabularyWord } from ../models/VocabularyWord; /** * 手写练习专用数据仓库 * 提供随机顺序的单词适合默写测试场景 */ export class HandwritingWordRepository { private words: VocabularyWord[] [ { id: hw-1, english: appreciate, meaning: v. 欣赏感激理解, phonetic: /əˈpriːʃieɪt/, transliteration: 阿普瑞希艾特 }, { id: hw-2, english: communicate, meaning: v. 交流传达, phonetic: /kəˈmjuːnɪkeɪt/, transliteration: 克缪尼凯特 }, { id: hw-3, english: demonstrate, meaning: v. 证明演示示威, phonetic: /ˈdemənstreɪt/, transliteration: 戴蒙斯特瑞特 }, // ... 更多单词 ]; /** * 获取所有手写练习单词 */ getAllWords(): VocabularyWord[] { return this.words; } /** * 获取单词总数 */ getWordCount(): number { return this.words.length; } }4.1 两个 Repository 的区别对比项SpeedWordRepositoryHandwritingWordRepository用途极速划词 默写独立手写练习分组方式按日期无分组词根分解有可选音译无有数据量每天 15-20 个全量词库五、在页面中使用数据仓库5.1 基本用法import { SpeedWordRepository } from ../data/SpeedWordRepository; import { VocabularyWord } from ../models/VocabularyWord; Entry Component struct MyPage { // 创建仓库实例私有不需要响应式 private repository: SpeedWordRepository new SpeedWordRepository(); // 状态变量UI 会响应变化 State words: VocabularyWord[] []; State selectedDate: string 3/12; aboutToAppear() { // 页面创建时加载数据 this.words this.repository.getWordsByDate(this.selectedDate); } build() { Column() { // 使用 ForEach 渲染列表 ForEach(this.words, (word: VocabularyWord) { Row() { Text(word.english) .fontSize(16) .fontWeight(FontWeight.Medium) Text(word.meaning) .fontSize(14) .fontColor(#6B7280) } .width(100%) .padding(12) }, (word: VocabularyWord) word.id) // key 函数 } } }5.2 关键点解析privatevsState// ❌ 不需要 State仓库实例不会变化 State repository: SpeedWordRepository new SpeedWordRepository(); // ✅ 正确仓库是私有的不触发 UI 刷新 private repository: SpeedWordRepository new SpeedWordRepository(); // ✅ 正确单词列表需要 State因为切换日期时会变化 State words: VocabularyWord[] [];ForEach 的 key 函数// ForEach 第三个参数是 key 生成函数 // 用于标识每个列表项帮助框架做最小化 DOM diff ForEach(this.words, (word: VocabularyWord) { // 渲染逻辑 }, (word: VocabularyWord) word.id) // ← 用 id 作为 key如果不提供 key 函数ArkUI 会用数组索引作为 key在数据变化时可能导致不必要的重渲染。5.3 切换日期加载数据selectDate(date: string) { this.selectedDate date; // 重新从仓库获取数据赋值给 State 变量触发 UI 刷新 this.words this.repository.getWordsByDate(date); }六、ArkTS 中数组状态的注意事项6.1 数组变更必须创建新引用ArkTS 的State检测的是引用变化不是内容变化// ❌ 错误push 不会触发 UI 刷新引用没变 this.words.push(newWord); // ✅ 正确创建新数组 this.words [...this.words, newWord]; // ❌ 错误splice 不会触发 UI 刷新 this.words.splice(index, 1); // ✅ 正确filter 返回新数组 this.words this.words.filter((w: VocabularyWord) w.id ! targetId);6.2 对象属性变更如果State是一个对象修改其属性也需要创建新对象State currentWord: VocabularyWord | null null; // ❌ 不会触发刷新 this.currentWord.meaning 新释义; // ✅ 创建新对象 this.currentWord { ...this.currentWord, meaning: 新释义 };七、数据层的后续扩展方向当前我们使用硬编码数据这在开发初期是最简单高效的方式。后续可以按需升级7.1 从 JSON 文件加载将单词数据放在resources/rawfile/words.json中import { resourceManager } from kit.LocalizationKit; async loadWordsFromFile(): PromiseVocabularyWord[] { const context getContext(this); const mgr context.resourceManager; const data await mgr.getRawFileContent(words.json); const text new TextDecoder().decode(data); return JSON.parse(text) as VocabularyWord[]; }7.2 使用 Preferences 持久化学习记录import { preferences } from kit.ArkData; // 保存已学习的单词 ID async saveLearnedWords(wordIds: string[]) { const store await preferences.getPreferences(getContext(this), learning); await store.put(learnedIds, JSON.stringify(wordIds)); await store.flush(); }7.3 接入网络 APIimport { http } from kit.NetworkKit; async fetchWordsFromServer(): PromiseVocabularyWord[] { const req http.createHttp(); try { const resp await req.request(https://api.example.com/words, { method: http.RequestMethod.GET }); return JSON.parse(resp.result as string) as VocabularyWord[]; } finally { req.destroy(); } }这些扩展都不需要修改 UI 层代码只需要修改 Repository 内部实现。八、本篇完整文件清单本篇新增的文件electron/src/main/ets/ ├── models/ │ └── VocabularyWord.ets ← 新增数据模型定义 └── data/ ├── SpeedWordRepository.ets ← 新增极速划词数据仓库 └── HandwritingWordRepository.ets ← 新增手写练习数据仓库九、本篇小结通过本篇教程我们完成了设计了VocabularyWord核心数据模型含词根分解理解了 Repository 模式的优势创建了SpeedWordRepository按日期分组创建了HandwritingWordRepository手写练习专用掌握了在页面中使用数据仓库的方法了解了 ArkTS 数组状态变更的注意事项规划了数据层的后续扩展方向下一篇预告第 3 篇主入口页面与导航结构— 我们将创建应用的主入口列表页NativeListPage实现功能入口卡片和页面路由跳转让用户能够进入极速划词和默写单词功能。
02数据模型与单词仓库-鸿蒙PC端Electron开发
欢迎加入开源鸿蒙PC社区https://harmonypc.csdn.net/源码仓库https://atomgit.com/qq_33247427/englishProject.git效果截图第2篇数据模型与单词仓库系列教程导航篇号标题状态01环境搭建与项目创建✅ 已完成02数据模型与单词仓库本篇03主入口页面与导航结构下一篇04极速划词页面实现05手写画布实现06百度 OCR 手写识别接入07答案比对与反馈 UI08单词切换与底部导航09词根分解与水印展示10项目总结与优化方向一、为什么要先设计数据模型在动手写 UI 之前先把数据结构想清楚有几个好处UI 和数据解耦— 页面只关心拿到什么数据不关心数据从哪来方便后续扩展— 今天用硬编码数据明天换数据库或网络接口UI 层不用改团队协作— 前端和后端可以基于同一份接口定义并行开发类型安全— ArkTS 是强类型语言定义好接口后编译器帮你检查我们的单词学习 App 需要管理以下信息单词本身英文、释义、音标词根词缀分解帮助记忆学习分组按日期唯一标识列表渲染需要二、定义 VocabularyWord 数据模型2.1 创建模型文件在electron/src/main/ets/models/目录下创建VocabularyWord.ets/** * 词根/词缀分解项 * 用于展示单词的构词法帮助用户理解和记忆 */ export interface WordPart { /** 词根/词缀文本如 electr */ text: string; /** 含义如 电 */ meaning: string; /** 类型prefix前缀| root词根| suffix后缀 */ type: string; } /** * 单词详情信息 * 包含词根分解等扩展信息 */ export interface WordDetail { /** 词根词缀分解列表 */ parts: WordPart[]; } /** * 单词数据模型 * 这是整个应用最核心的数据结构 */ export interface VocabularyWord { /** 唯一标识符用于列表渲染的 key */ id: string; /** 英文单词 */ english: string; /** 中文释义 */ meaning: string; /** 音标如 /ɪˈlektrɪkl/ */ phonetic: string; /** 音译可选如 伊莱克特瑞克 */ transliteration?: string; /** 词根分解详情可选 */ detail?: WordDetail; /** 所属日期分组如 3/12 */ date?: string; }2.2 设计思路详解为什么id用 string 而不是 numberArkUI 的ForEach和LazyForEach需要一个唯一的 key 来标识列表项。用 string 类型更灵活可以是数据库主键、UUID、或者简单的序号字符串。为什么detail和transliteration是可选的不是所有单词都有词根分解信息也不是所有单词都需要音译。用?标记为可选字段避免强制填充无意义的空值。为什么type用 string 而不是 enumArkTS 对 enum 的支持有一些限制特别是在 UI 描述中使用时。用 string 字面量prefix | root | suffix更简单直接也方便从 JSON 数据源加载。2.3 WordPart 的颜色编码设计在后续的 UI 中不同类型的词根词缀会用不同颜色展示type 值含义背景色文字色示例prefix前缀#FFEBEE浅红#E57373trans-跨越root词根#EEF1E4浅绿#8B9D6B-form形式suffix后缀#DBEAFE浅蓝#64B5F6-tion名词后缀这种颜色编码让用户一眼就能区分词根词缀的类型提升学习效率。三、创建单词数据仓库3.1 Repository 模式介绍Repository仓库模式是一种常见的数据层设计模式┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ UI 页面 │ ──→ │ Repository │ ──→ │ 数据源 │ │ (只管展示) │ │ (统一接口) │ │ (本地/网络) │ └──────────────┘ └──────────────┘ └──────────────┘好处UI 层不需要知道数据来自哪里切换数据源本地 → 网络只需修改 Repository 内部实现方便做缓存、数据转换等中间处理3.2 创建 SpeedWordRepository在electron/src/main/ets/data/目录下创建SpeedWordRepository.etsimport { VocabularyWord } from ../models/VocabularyWord; /** * 极速划词 默写单词的数据仓库 * 当前使用硬编码数据后续可替换为数据库或网络接口 */ export class SpeedWordRepository { private words: VocabularyWord[] [ { id: 1, english: electrical, meaning: adj. 电的与电有关的, phonetic: /ɪˈlektrɪkl/, date: 3/12, detail: { parts: [ { text: electr, meaning: 电, type: root }, { text: ical, meaning: 形容词后缀, type: suffix } ] } }, { id: 2, english: transform, meaning: v. 使转变使改观, phonetic: /trænsˈfɔːrm/, date: 3/12, detail: { parts: [ { text: trans, meaning: 跨越、转变, type: prefix }, { text: form, meaning: 形式、形状, type: root } ] } }, { id: 3, english: international, meaning: adj. 国际的, phonetic: /ˌɪntərˈnæʃənl/, date: 3/12, detail: { parts: [ { text: inter, meaning: 在…之间, type: prefix }, { text: nation, meaning: 国家, type: root }, { text: al, meaning: 形容词后缀, type: suffix } ] } }, { id: 4, english: uncomfortable, meaning: adj. 不舒服的不自在的, phonetic: /ʌnˈkʌmftəbl/, date: 3/12, detail: { parts: [ { text: un, meaning: 不、否定, type: prefix }, { text: comfort, meaning: 舒适, type: root }, { text: able, meaning: 能…的, type: suffix } ] } }, { id: 5, english: transportation, meaning: n. 运输交通工具, phonetic: /ˌtrænspɔːrˈteɪʃn/, date: 3/12, detail: { parts: [ { text: trans, meaning: 跨越, type: prefix }, { text: port, meaning: 搬运, type: root }, { text: ation, meaning: 名词后缀, type: suffix } ] } }, { id: 6, english: environment, meaning: n. 环境周围的事物, phonetic: /ɪnˈvaɪrənmənt/, date: 3/11, detail: { parts: [ { text: en, meaning: 使…, type: prefix }, { text: viron, meaning: 周围, type: root }, { text: ment, meaning: 名词后缀, type: suffix } ] } }, { id: 7, english: independent, meaning: adj. 独立的自主的, phonetic: /ˌɪndɪˈpendənt/, date: 3/11, detail: { parts: [ { text: in, meaning: 不、否定, type: prefix }, { text: depend, meaning: 依赖, type: root }, { text: ent, meaning: 形容词后缀, type: suffix } ] } }, // ... 更多单词数据 ]; /** * 按日期获取单词列表 * param date 日期字符串如 3/12 * returns 该日期下的所有单词 */ getWordsByDate(date: string): VocabularyWord[] { return this.words.filter((w: VocabularyWord) w.date date); } /** * 获取所有单词 * returns 完整单词列表 */ getAllWords(): VocabularyWord[] { return this.words; } /** * 根据 ID 获取单个单词 * param id 单词唯一标识 * returns 匹配的单词未找到返回 undefined */ getWordById(id: string): VocabularyWord | undefined { return this.words.find((w: VocabularyWord) w.id id); } /** * 获取所有可用的日期分组 * returns 去重后的日期列表 */ getAvailableDates(): string[] { const dateSet new Setstring(); for (const word of this.words) { if (word.date) { dateSet.add(word.date); } } return Array.from(dateSet).sort(); } }3.3 方法设计说明方法用途使用场景getWordsByDate(date)按日期筛选极速划词页面按天显示getAllWords()获取全部搜索功能、统计getWordById(id)精确查找跳转到单词详情getAvailableDates()获取日期列表日期选择器四、创建 HandwritingWordRepository除了极速划词的数据源手写练习页面也需要一个独立的数据仓库。创建electron/src/main/ets/data/HandwritingWordRepository.etsimport { VocabularyWord } from ../models/VocabularyWord; /** * 手写练习专用数据仓库 * 提供随机顺序的单词适合默写测试场景 */ export class HandwritingWordRepository { private words: VocabularyWord[] [ { id: hw-1, english: appreciate, meaning: v. 欣赏感激理解, phonetic: /əˈpriːʃieɪt/, transliteration: 阿普瑞希艾特 }, { id: hw-2, english: communicate, meaning: v. 交流传达, phonetic: /kəˈmjuːnɪkeɪt/, transliteration: 克缪尼凯特 }, { id: hw-3, english: demonstrate, meaning: v. 证明演示示威, phonetic: /ˈdemənstreɪt/, transliteration: 戴蒙斯特瑞特 }, // ... 更多单词 ]; /** * 获取所有手写练习单词 */ getAllWords(): VocabularyWord[] { return this.words; } /** * 获取单词总数 */ getWordCount(): number { return this.words.length; } }4.1 两个 Repository 的区别对比项SpeedWordRepositoryHandwritingWordRepository用途极速划词 默写独立手写练习分组方式按日期无分组词根分解有可选音译无有数据量每天 15-20 个全量词库五、在页面中使用数据仓库5.1 基本用法import { SpeedWordRepository } from ../data/SpeedWordRepository; import { VocabularyWord } from ../models/VocabularyWord; Entry Component struct MyPage { // 创建仓库实例私有不需要响应式 private repository: SpeedWordRepository new SpeedWordRepository(); // 状态变量UI 会响应变化 State words: VocabularyWord[] []; State selectedDate: string 3/12; aboutToAppear() { // 页面创建时加载数据 this.words this.repository.getWordsByDate(this.selectedDate); } build() { Column() { // 使用 ForEach 渲染列表 ForEach(this.words, (word: VocabularyWord) { Row() { Text(word.english) .fontSize(16) .fontWeight(FontWeight.Medium) Text(word.meaning) .fontSize(14) .fontColor(#6B7280) } .width(100%) .padding(12) }, (word: VocabularyWord) word.id) // key 函数 } } }5.2 关键点解析privatevsState// ❌ 不需要 State仓库实例不会变化 State repository: SpeedWordRepository new SpeedWordRepository(); // ✅ 正确仓库是私有的不触发 UI 刷新 private repository: SpeedWordRepository new SpeedWordRepository(); // ✅ 正确单词列表需要 State因为切换日期时会变化 State words: VocabularyWord[] [];ForEach 的 key 函数// ForEach 第三个参数是 key 生成函数 // 用于标识每个列表项帮助框架做最小化 DOM diff ForEach(this.words, (word: VocabularyWord) { // 渲染逻辑 }, (word: VocabularyWord) word.id) // ← 用 id 作为 key如果不提供 key 函数ArkUI 会用数组索引作为 key在数据变化时可能导致不必要的重渲染。5.3 切换日期加载数据selectDate(date: string) { this.selectedDate date; // 重新从仓库获取数据赋值给 State 变量触发 UI 刷新 this.words this.repository.getWordsByDate(date); }六、ArkTS 中数组状态的注意事项6.1 数组变更必须创建新引用ArkTS 的State检测的是引用变化不是内容变化// ❌ 错误push 不会触发 UI 刷新引用没变 this.words.push(newWord); // ✅ 正确创建新数组 this.words [...this.words, newWord]; // ❌ 错误splice 不会触发 UI 刷新 this.words.splice(index, 1); // ✅ 正确filter 返回新数组 this.words this.words.filter((w: VocabularyWord) w.id ! targetId);6.2 对象属性变更如果State是一个对象修改其属性也需要创建新对象State currentWord: VocabularyWord | null null; // ❌ 不会触发刷新 this.currentWord.meaning 新释义; // ✅ 创建新对象 this.currentWord { ...this.currentWord, meaning: 新释义 };七、数据层的后续扩展方向当前我们使用硬编码数据这在开发初期是最简单高效的方式。后续可以按需升级7.1 从 JSON 文件加载将单词数据放在resources/rawfile/words.json中import { resourceManager } from kit.LocalizationKit; async loadWordsFromFile(): PromiseVocabularyWord[] { const context getContext(this); const mgr context.resourceManager; const data await mgr.getRawFileContent(words.json); const text new TextDecoder().decode(data); return JSON.parse(text) as VocabularyWord[]; }7.2 使用 Preferences 持久化学习记录import { preferences } from kit.ArkData; // 保存已学习的单词 ID async saveLearnedWords(wordIds: string[]) { const store await preferences.getPreferences(getContext(this), learning); await store.put(learnedIds, JSON.stringify(wordIds)); await store.flush(); }7.3 接入网络 APIimport { http } from kit.NetworkKit; async fetchWordsFromServer(): PromiseVocabularyWord[] { const req http.createHttp(); try { const resp await req.request(https://api.example.com/words, { method: http.RequestMethod.GET }); return JSON.parse(resp.result as string) as VocabularyWord[]; } finally { req.destroy(); } }这些扩展都不需要修改 UI 层代码只需要修改 Repository 内部实现。八、本篇完整文件清单本篇新增的文件electron/src/main/ets/ ├── models/ │ └── VocabularyWord.ets ← 新增数据模型定义 └── data/ ├── SpeedWordRepository.ets ← 新增极速划词数据仓库 └── HandwritingWordRepository.ets ← 新增手写练习数据仓库九、本篇小结通过本篇教程我们完成了设计了VocabularyWord核心数据模型含词根分解理解了 Repository 模式的优势创建了SpeedWordRepository按日期分组创建了HandwritingWordRepository手写练习专用掌握了在页面中使用数据仓库的方法了解了 ArkTS 数组状态变更的注意事项规划了数据层的后续扩展方向下一篇预告第 3 篇主入口页面与导航结构— 我们将创建应用的主入口列表页NativeListPage实现功能入口卡片和页面路由跳转让用户能够进入极速划词和默写单词功能。