欢迎加入开源鸿蒙 PC社区

https://harmonypc.csdn.net/

效果截图

在这里插入图片描述


第10篇:项目总结与优化方向

系列教程导航

篇号 标题 状态
01 环境搭建与项目创建 ✅ 已完成
02 数据模型与单词仓库 ✅ 已完成
03 主入口页面与导航结构 ✅ 已完成
04 极速划词页面实现 ✅ 已完成
05 手写画布实现 ✅ 已完成
06 百度OCR手写识别接入 ✅ 已完成
07 答案比对与反馈UI ✅ 已完成
08 单词切换与底部导航 ✅ 已完成
09 词根分解与水印展示 ✅ 已完成
10 项目总结与优化方向 📖 本篇(完结)

源码仓库https://gitcode.com/qq_33247427/englishProject


一、项目架构总览

1.1 目录结构
HarmonyElectronProject/libelectron/ohos_hap/
├── AppScope/                          # 应用级配置
│   ├── app.json5                     # 应用信息(包名、版本)
│   └── resources/base/media/         # 应用图标
├── electron/                          # 主模块
│   ├── src/main/ets/                 # ArkTS 业务源码
│   │   ├── entryability/            # Ability 入口
│   │   │   └── EntryAbility.ets
│   │   ├── models/                  # 数据模型层
│   │   │   └── VocabularyWord.ets
│   │   ├── data/                    # 数据仓库层
│   │   │   ├── SpeedWordRepository.ets
│   │   │   └── HandwritingWordRepository.ets
│   │   ├── services/                # 服务层
│   │   │   └── BaiduOCRService.ets
│   │   ├── components/              # 可复用组件
│   │   │   └── HandwritingCanvas.ets
│   │   ├── pages/                   # 页面层
│   │   │   ├── NativeListPage.ets
│   │   │   ├── SpeedVocabPage.ets
│   │   │   ├── DictationPage.ets
│   │   │   └── Index.ets
│   │   └── utils/                   # 工具类
│   │       └── CanvasManager.ets
│   ├── src/main/resources/          # 模块资源
│   └── src/main/module.json5        # 模块配置(权限声明)
├── build-profile.json5               # 构建配置
├── hvigorfile.ts                     # 构建脚本
└── oh-package.json5                  # 依赖管理
1.2 分层架构图
┌─────────────────────────────────────────────────────┐
│                    UI 层 (ArkUI)                     │
│                                                     │
│  pages/           components/         @Builder      │
│  NativeListPage   HandwritingCanvas   WordPartsRow  │
│  SpeedVocabPage                                     │
│  DictationPage                                      │
├─────────────────────────────────────────────────────┤
│                  业务逻辑层                           │
│                                                     │
│  状态管理          比对逻辑           切换逻辑        │
│  @State/@Link     normalize()        prevWord()     │
│  @Prop/@Watch     checkAnswer()      nextWord()     │
├─────────────────────────────────────────────────────┤
│                    数据层                             │
│                                                     │
│  data/                    services/                  │
│  SpeedWordRepository      BaiduOCRService           │
│  HandwritingWordRepository                          │
├─────────────────────────────────────────────────────┤
│                  系统能力层                           │
│                                                     │
│  Canvas 2D        componentSnapshot    @kit.NetworkKit │
│  TouchEvent       @kit.ImageKit        Preferences  │
└─────────────────────────────────────────────────────┘
1.3 数据流向
用户手写 → TouchEvent → Canvas 绘制笔迹
                              ↓
点击识别 → componentSnapshot 截图 → PixelMap
                              ↓
         BaiduOCRService → HTTP 请求 → 百度 API
                              ↓
         识别结果 → normalize() → checkAnswer()
                              ↓
         更新 @State → UI 自动刷新(反馈浮层)

二、技术栈总结表

技术领域 具体技术 用途 所在篇章
UI 框架 ArkUI (声明式) 页面布局、组件渲染 全系列
编程语言 ArkTS 业务逻辑、类型安全 全系列
画布绑图 Canvas 2D API 手写笔迹绘制 第 5 篇
组件截图 componentSnapshot 将画布导出为图片 第 6 篇
网络请求 @kit.NetworkKit (http) 调用百度 OCR API 第 6 篇
图片处理 @kit.ImageKit (image) PixelMap → Base64 转换 第 6 篇
OCR 识别 百度手写文字识别 API 手写内容转文字 第 6 篇
状态管理 @State / @Link / @Prop 响应式 UI 更新 第 7、8 篇
页面路由 @kit.ArkUI (router) 页面间导航跳转 第 3 篇
数据模式 Repository Pattern 数据层抽象 第 2 篇

三、核心设计决策回顾

3.1 为什么用 Stack 三层结构?
Stack({ alignContent: Alignment.TopStart }) {
  Column().backgroundColor('#FFFFFF')     // 白色底层
  Column() { /* 水印内容 */ }             // 水印层
  Canvas(this.canvasContext)              // 手写层
    .backgroundColor(Color.Transparent)
  // 反馈浮层、工具栏...
}

问题:如果 Canvas 有白色背景,会遮住水印;如果没有白底,OCR 截图是透明的,识别失败。

解决:三层分离——白底保证截图对比度,透明 Canvas 让水印可见,水印层提供书写参考。

3.2 为什么每次 touchMove 都 beginPath?
// 每次 Move 事件
this.canvasContext.beginPath();
this.canvasContext.moveTo(lastX, lastY);
this.canvasContext.lineTo(currentX, currentY);
this.canvasContext.stroke();

问题:如果不 beginPath,每次 stroke 会重绘整条路径,笔画越多帧率越低。

解决:每次只绘制最新一小段线条(从上一个点到当前点),保持恒定的绘制性能。

3.3 为什么选择百度 OCR 而不是端侧 CoreVisionKit?
对比项 百度手写 OCR 华为 CoreVisionKit
稳定性 ✅ 高(云端服务) ⚠️ 部分设备超时
识别准确率 ✅ 高(专门的手写模型) ⚠️ 通用 OCR,手写效果一般
网络依赖 ❌ 需要联网 ✅ 端侧离线
延迟 ~500ms ~200ms
免费额度 每天 500 次 无限制

决策:学习场景下网络通常可用,稳定性和准确率优先。百度手写 OCR 的免费额度(500次/天)对个人学习完全够用。

3.4 为什么用 Repository 模式?
// UI 层只依赖接口,不关心数据来源
private repository: SpeedWordRepository = new SpeedWordRepository();
this.words = this.repository.getWordsByDate('3/12');

好处

  • 当前用硬编码数据,开发快
  • 将来换数据库或网络 API,UI 层零修改
  • 方便单元测试(可以 mock Repository)

四、主题色体系

4.1 完整调色板
分类 色名 色值 用途
主题色 primary #8B9D6B 主按钮、开关、强调
primary-dark #6F7F52 按钮按下态
primary-light #B6C496 次要强调
primary-surface #EEF1E4 词根背景、卡片底色
中性色 text-primary #1A1A1A 标题文字
text-body #374151 正文文字
text-secondary #6B7280 辅助文字
text-muted #9CA3AF 弱化文字(音标)
text-disabled #D1D5DB 禁用文字
border #E5E7EB 边框线
border-light #F3F4F6 禁用边框
background #FAFAF7 页面背景
surface #FFFFFF 卡片/按钮背景
语义色 success #5B8A4A 正确反馈
error #B5533C 错误反馈
warning #C9A14A 警告提示
词根色 prefix-bg #FFEBEE 前缀背景
prefix-text #E57373 前缀文字
root-bg #EEF1E4 词根背景
root-text #8B9D6B 词根文字
suffix-bg #DBEAFE 后缀背景
suffix-text #64B5F6 后缀文字
水印色 watermark-word #E8EDE0 水印单词
watermark-phonetic #D0D8C8 水印音标
4.2 设计理念

整体色调以**鼠尾草绿(Sage Green)**为主题色,传达自然、舒适、专注的学习氛围。中性色采用暖灰色系(带微黄),避免冷灰的生硬感。背景色 #FAFAF7 是带暖调的近白色,长时间阅读不疲劳。


五、功能增强方向

以下是我们规划的功能增强方向。这些功能按照优先级排列,建议先实现对用户学习效果提升最大的功能(如错题本和艾宾浩斯复习),再逐步完善体验类功能(如动画和深色模式)。每个功能都可以作为一个独立的迭代版本来开发和发布。

5.1 TTS 语音播放

接入系统 TTS 能力,点击单词即可朗读:

import { textToSpeech } from '@kit.CoreSpeechKit';

async speakWord(word: string): Promise<void> {
  const ttsEngine = await textToSpeech.createEngine({
    language: 'en-US',
    person: 0,  // 默认发音人
    online: 1   // 在线模式(发音更自然)
  });
  await ttsEngine.speak(word);
}
5.2 错题本

记录每次识别错误的单词,支持重点复习:

interface MistakeRecord {
  wordId: string;
  wrongAnswer: string;    // 用户写错的内容
  timestamp: number;      // 错误时间
  reviewCount: number;    // 复习次数
}
5.3 艾宾浩斯复习曲线

根据遗忘曲线安排复习计划:

复习次数 间隔时间 记忆保持率
第 1 次 20 分钟后 ~58%
第 2 次 1 小时后 ~44%
第 3 次 1 天后 ~34%
第 4 次 2 天后 ~28%
第 5 次 7 天后 ~25%
第 6 次 30 天后 ~21%
5.4 键盘输入模式

除了手写,支持键盘直接输入:

TextInput({ placeholder: '请输入单词...' })
  .type(InputType.Normal)
  .fontSize(20)
  .fontColor('#374151')
  .onChange((value: string) => {
    this.typedText = value;
  })
  .onSubmit(() => {
    this.checkAnswer(this.typedText);
  })
5.5 学习统计图表

使用 ArkUI 的绘图能力展示学习数据:

  • 每日学习量柱状图
  • 正确率折线图
  • 连续学习天数
  • 累计掌握单词数

六、性能优化

性能优化是应用从"能用"到"好用"的关键一步。在单词学习应用中,手写画布的流畅度直接影响用户的书写体验,而网络请求的速度则影响识别反馈的及时性。以下是几个重要的性能优化方向,每一个都能显著提升用户体验。

6.1 OffscreenCanvas 离屏渲染

将笔迹绘制移到离屏 Canvas,减少主线程压力:

// 创建离屏 Canvas
const offscreen = new OffscreenCanvas(width, height);
const offCtx = offscreen.getContext('2d');

// 在离屏 Canvas 上绘制
offCtx.beginPath();
offCtx.moveTo(lastX, lastY);
offCtx.lineTo(currentX, currentY);
offCtx.stroke();

// 将离屏内容绘制到主 Canvas
this.canvasContext.drawImage(offscreen, 0, 0);
6.2 图片压缩

OCR 前压缩 PixelMap,减少上传时间和流量:

// 获取原始 PixelMap 信息
const imageInfo = await pixelMap.getImageInfo();
const originalWidth = imageInfo.size.width;
const originalHeight = imageInfo.size.height;

// 如果图片过大,缩放到合理尺寸
const maxSize = 1024;
if (originalWidth > maxSize || originalHeight > maxSize) {
  const scale = maxSize / Math.max(originalWidth, originalHeight);
  await pixelMap.scale(scale, scale);
}
6.3 Token 持久化(Preferences)

百度 OCR 的 Access Token 有效期 30 天,不需要每次都重新获取:

import { preferences } from '@kit.ArkData';

async getToken(): Promise<string> {
  const store = await preferences.getPreferences(getContext(this), 'ocr_config');
  const savedToken = await store.get('access_token', '') as string;
  const expireTime = await store.get('token_expire', 0) as number;

  // Token 未过期,直接使用
  if (savedToken.length > 0 && Date.now() < expireTime) {
    return savedToken;
  }

  // Token 过期或不存在,重新获取
  const newToken = await this.fetchTokenFromServer();
  await store.put('access_token', newToken);
  await store.put('token_expire', Date.now() + 29 * 24 * 3600 * 1000); // 29天后过期
  await store.flush();
  return newToken;
}
6.4 LazyForEach 懒加载

单词列表较长时,使用 LazyForEach 替代 ForEach,只渲染可见区域:

// 需要实现 IDataSource 接口
class WordDataSource implements IDataSource {
  private words: VocabularyWord[] = [];

  totalCount(): number {
    return this.words.length;
  }

  getData(index: number): VocabularyWord {
    return this.words[index];
  }

  // ... registerDataChangeListener, unregisterDataChangeListener
}

// 使用 LazyForEach
LazyForEach(this.wordDataSource, (word: VocabularyWord) => {
  ListItem() {
    WordCard({ word: word })
  }
}, (word: VocabularyWord) => word.id)

七、体验优化

7.1 手势滑动切换

左右滑动切换单词,比点击按钮更自然:

.gesture(
  PanGesture({ direction: PanDirection.Horizontal })
    .onActionEnd((event: GestureEvent) => {
      if (event.offsetX > 100) {
        this.prevWord();   // 右滑 → 上一个
      } else if (event.offsetX < -100) {
        this.nextWord();   // 左滑 → 下一个
      }
    })
)
7.2 动画反馈

正确/错误时添加缩放动画,增强反馈感:

@State feedbackScale: number = 1.0;

// 正确时的弹跳动画
animateTo({ duration: 200, curve: Curve.EaseOut }, () => {
  this.feedbackScale = 1.2;
});
animateTo({ duration: 150, curve: Curve.EaseIn, delay: 200 }, () => {
  this.feedbackScale = 1.0;
});
7.3 深色模式适配

响应系统深色模式切换:

// 获取当前颜色模式
const colorMode = getContext(this).config.colorMode;

// 根据模式选择颜色
const bgColor = colorMode === ColorMode.DARK ? '#1A1A1A' : '#FAFAF7';
const textColor = colorMode === ColorMode.DARK ? '#E5E7EB' : '#374151';
7.4 横竖屏适配

鸿蒙 PC 和平板可能横屏使用,需要响应式布局:

// 监听窗口尺寸变化
@StorageProp('windowWidth') windowWidth: number = 0;

build() {
  if (this.windowWidth > 840) {
    // 宽屏:左右分栏布局
    Row() {
      // 左侧:单词列表
      // 右侧:手写画布
    }
  } else {
    // 窄屏:上下布局
    Column() {
      // 上方:提示信息
      // 下方:手写画布
    }
  }
}

八、数据层升级

数据层的升级是应用从单机版走向完整产品的必经之路。当前我们使用硬编码数据,虽然开发效率高,但无法满足用户自定义词库、学习记录持久化、多设备同步等需求。以下是三个主要的数据层升级方向,可以根据产品规划分阶段实施。

8.1 RDB 关系型数据库

使用鸿蒙的关系型数据库存储学习记录:

import { relationalStore } from '@kit.ArkData';

// 创建数据库和表
const SQL_CREATE_TABLE = `
  CREATE TABLE IF NOT EXISTS learning_records (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    word_id TEXT NOT NULL,
    is_correct INTEGER NOT NULL,
    recognized_text TEXT,
    timestamp INTEGER NOT NULL
  )
`;

const store = await relationalStore.getRdbStore(getContext(this), {
  name: 'vocabulary.db',
  securityLevel: relationalStore.SecurityLevel.S1
});
await store.executeSql(SQL_CREATE_TABLE);
8.2 云同步

多设备数据同步,让用户在手机和平板上都能学习:

// 使用分布式数据管理
import { distributedKVStore } from '@kit.ArkData';

const kvManager = distributedKVStore.createKVManager({
  bundleName: 'com.example.vocabulary'
});

const kvStore = await kvManager.getKVStore('learning_data', {
  createIfMissing: true,
  encrypt: false,
  backup: true,
  autoSync: true  // 自动同步到其他设备
});
8.3 导入导出词库

支持用户导入自定义词库(CSV/JSON 格式):

// 从文件选择器导入
import { picker } from '@kit.CoreFileKit';

async importWordList(): Promise<void> {
  const documentPicker = new picker.DocumentViewPicker();
  const result = await documentPicker.select({
    fileSuffixFilters: ['.json', '.csv']
  });

  if (result.length > 0) {
    const fileUri = result[0];
    // 读取文件内容并解析...
  }
}

九、发布准备

当应用开发完成并经过充分测试后,就可以准备发布到华为应用市场了。发布流程涉及签名证书申请、隐私合规声明、应用图标设计和市场审核等多个环节。建议在开发后期就开始准备这些材料,避免最后阶段手忙脚乱。

9.1 签名证书

发布到华为应用市场需要正式签名证书:

  1. 登录 AppGallery Connect
  2. 创建应用 → 获取 App ID
  3. 申请发布证书(.p12 + .cer)
  4. 在 DevEco Studio 中配置签名
9.2 隐私声明

本应用使用了网络权限(调用百度 OCR),需要在隐私声明中说明:

// module.json5
{
  "requestPermissions": [
    {
      "name": "ohos.permission.INTERNET",
      "reason": "$string:internet_permission_reason",
      "usedScene": {
        "abilities": ["EntryAbility"],
        "when": "inuse"
      }
    }
  ]
}
9.3 应用图标

华为应用市场要求提供以下尺寸的图标:

用途 尺寸 格式
应用图标 216×216 px PNG(圆角自动裁切)
市场展示 1024×1024 px PNG
通知栏图标 72×72 px PNG(单色)
9.4 华为应用市场提交流程
  1. 在 AppGallery Connect 创建应用
  2. 填写应用信息(名称、描述、截图)
  3. 上传签名后的 HAP/APP 包
  4. 提交审核(通常 1-3 个工作日)
  5. 审核通过后上架

十、最终文件清单

项目所有关键文件一览:

✅ electron/src/main/ets/models/VocabularyWord.ets        — 数据模型
✅ electron/src/main/ets/data/SpeedWordRepository.ets     — 极速划词数据仓库
✅ electron/src/main/ets/data/HandwritingWordRepository.ets — 手写练习数据仓库
✅ electron/src/main/ets/services/BaiduOCRService.ets     — 百度 OCR 服务
✅ electron/src/main/ets/components/HandwritingCanvas.ets  — 手写画布组件
✅ electron/src/main/ets/pages/NativeListPage.ets         — 主入口页面
✅ electron/src/main/ets/pages/SpeedVocabPage.ets         — 极速划词 + 默写
✅ electron/src/main/ets/pages/DictationPage.ets          — 独立默写页面
✅ electron/src/main/ets/pages/Index.ets                  — 手写详情页
✅ electron/src/main/ets/utils/CanvasManager.ets          — Canvas 管理工具
✅ electron/src/main/module.json5                         — 模块配置(含权限)
✅ electron/src/main/resources/base/profile/main_pages.json — 路由注册
✅ build-profile.json5                                    — 构建配置
✅ oh-package.json5                                       — 依赖管理

十一、系列结语

经过这 10 篇教程,我们从零搭建了一个基于 HarmonyOS NEXT 的单词学习应用,完整覆盖了以下技术领域:

这个系列教程的目标不仅仅是教会你如何实现一个具体的应用,更重要的是让你掌握鸿蒙应用开发的思维方式和最佳实践。从数据模型设计到组件封装,从状态管理到性能优化,每一个环节都体现了现代应用开发的工程化思想。希望通过这个完整的项目实战,你能够建立起对鸿蒙开发的系统性认知,并将这些知识迁移到自己的项目中去。

篇章 核心技能
第 1 篇 开发环境搭建、项目结构理解
第 2 篇 数据模型设计、Repository 模式
第 3 篇 页面路由、导航结构
第 4 篇 列表渲染、Tab 导航、卡片组件
第 5 篇 Canvas 2D 绑图、触摸事件处理
第 6 篇 HTTP 网络请求、图片处理、OCR 接入
第 7 篇 文本比对、浮层 UI、颜色语义系统
第 8 篇 状态管理、导航交互、布局间距
第 9 篇 条件渲染、组件复用、Canvas 绘图
第 10 篇 架构总结、优化方向、发布准备

这个项目虽然功能不复杂,但涵盖了鸿蒙应用开发的核心知识点:声明式 UI、状态管理、网络请求、Canvas 绑图、组件封装、数据层设计。掌握这些基础后,你可以轻松扩展到更复杂的应用场景。无论是社交应用、电商平台还是工具类软件,底层的开发模式和架构思想都是相通的。

鸿蒙生态正在快速发展,越来越多的设备(手机、平板、PC、车机、穿戴)都将运行 HarmonyOS NEXT。作为开发者,现在入场学习鸿蒙开发正是最好的时机。华为正在大力推动鸿蒙原生应用的生态建设,各大互联网公司也在积极适配鸿蒙平台,市场对鸿蒙开发人才的需求持续增长。希望这个系列教程能够成为你鸿蒙开发之旅的起点,祝你在鸿蒙生态中找到属于自己的机会。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐