从零构建鸿蒙古诗文 App:HarmonyOS NEXT 应用开发实战

一、引言

2024年,HarmonyOS NEXT 的正式发布标志着中国自主操作系统生态进入全新阶段。作为一名移动端开发者,我怀着对传统文化的热爱和对新技术的探索欲,决定用 HarmonyOS NEXT 的 ArkTS 语言开发一款古诗文赏析应用。本文将从项目搭建、架构设计、数据模型、页面实现、状态管理到编译打包,完整记录开发全过程,分享在鸿蒙原生应用开发中的实践心得。

古诗文是中华文化的瑰宝,从《诗经》的淳朴到唐诗的恢弘,从宋词的婉约到元曲的率真,每一篇都蕴含着深厚的人文精神。通过这款 App,用户可以浏览分类、阅读全文、查看译文注释、收藏喜欢的作品,并能搜索相关诗文。全文共 19 篇精选作品,涵盖唐诗、宋词、元曲、古文、诗经五大类别。


二、项目概述与技术栈

2.1 项目概况

项目 说明
应用名称 古诗文
开发工具 DevEco Studio 5.0+
目标系统 HarmonyOS NEXT (API 12+, SDK 6.1.1)
开发语言 ArkTS
编程模型 Stage 模型
包名 com.example.myapplication

2.2 技术栈选型

ArkTS — 鸿蒙原生声明式 UI 框架,基于 TypeScript 语法扩展,提供 @Component@Entry@State 等装饰器驱动的响应式编程范式。与 SwiftUI 和 Jetpack Compose 类似,但更贴近 Web 开发者的 TypeScript 使用习惯。

Stage 模型 — HarmonyOS NEXT 推荐的应用开发模型,以 Ability 为基本单元,每个 Ability 有独立的上下文(Context),支持多实例和模块化部署。

AppStorage — 鸿蒙提供的全局应用状态管理方案,数据在应用进程内全局共享,支持跨页面状态同步。类似于 Android 的 SharedPreferences,但类型安全且支持响应式更新。

Router — 页面路由 API,支持 pushUrl(带参数跳转)和 back(返回上一页),是页面级导航的核心工具。

2.3 为什么选择原生开发而非跨平台

对于古诗文 App 这种以文本展示为主的轻量级应用,跨平台方案(如 React Native、Flutter)当然也能实现。但选择 HarmonyOS 原生开发有以下优势:

  1. 原生性能 — 直接使用系统 API,无桥接开销,长列表滚动更流畅
  2. 系统集成 — 可直接使用 HarmonyOS 的系统资源(颜色、字体、图标等)
  3. 生态适配 — 更好地适配鸿蒙设备的折叠屏、平板等形态
  4. 开发效率 — DevEco Studio 提供可视化预览、热重载等高效开发工具

三、项目架构设计

3.1 整体架构

MyApplication/
├── AppScope/                          # 应用级配置
│   ├── app.json5                      # 应用元信息(包名、版本号等)
│   └── resources/base/element/string.json  # 应用名字符串
├── entry/                             # 主模块
│   └── src/main/
│       ├── ets/
│       │   ├── entryability/          # Ability 入口
│       │   ├── entrybackupability/    # 备份扩展能力
│       │   ├── model/                 # 数据层
│       │   │   ├── PoemModel.ets      # 数据模型定义
│       │   │   └── PoemData.ets       # 数据源 + 收藏管理
│       │   └── pages/                 # 视图层
│       │       ├── Index.ets          # 首页
│       │       ├── DetailPage.ets     # 详情页
│       │       ├── FavoritesPage.ets  # 收藏页
│       │       └── SearchPage.ets     # 搜索页
│       ├── module.json5               # 模块配置
│       └── resources/                 # 资源文件
└── build-profile.json5                # 构建配置

整个应用遵循分层架构设计:

  • 数据层(model):负责数据定义、数据源提供、状态管理
  • 视图层(pages):负责 UI 呈现和用户交互
  • 路由层(Router):负责页面间导航和数据传递

3.2 数据流设计

用户点击 → 页面组件(@Component) → 状态更新(@State) → UI 自动刷新
                                 ↓
               调用 model 层方法 → AppStorage 持久化
                                 ↓
                      Router 跳转传递参数
                                 ↓
                     目标页面读取参数加载数据

ArkTS 的响应式机制使数据流非常清晰:@State 装饰的变量发生变化时,框架自动重新渲染关联的 UI 组件。开发者只需关注数据变化逻辑,无需手动操作 DOM。


四、数据模型设计

4.1 分类枚举与接口定义

数据模型是整个应用的基石。我首先定义了一个 PoemCategory 枚举来表示古诗文的分类:

// PoemModel.ets
export enum PoemCategory {
  TANG_SHI = '唐诗',
  SONG_CI = '宋词',
  YUAN_QU = '元曲',
  GU_WEN = '古文',
  SHI_JING = '诗经',
  CHU_CI = '楚辞'
}

选择枚举而非字符串常量,好处是类型安全——在编译阶段就能发现拼写错误,IDE 也能提供智能补全。

PoemItem 接口定义了每篇诗文的完整数据结构:

export interface PoemItem {
  id: number;           // 唯一标识
  title: string;        // 标题
  author: string;       // 作者
  dynasty: string;      // 朝代
  category: PoemCategory;  // 分类
  content: string;      // 正文
  translation?: string;    // 译文(可选)
  notes?: string;          // 注释(可选)
  appreciation?: string;   // 赏析(可选)
  background?: string;     // 创作背景(可选)
}

4.2 设计思考

在接口设计中,我将 translationnotesappreciationbackground 等字段设为可选(?),因为并非每篇作品都有完整的赏析或背景资料。这样可以灵活处理数据缺失的情况,前端代码中只需做简单的 if 判断即可。

id 字段使用 number 类型,既可作为数据查找的键,又可作为路由参数传递。相比字符串类型的 ID,数字在序列化和传输中更高效。

4.3 数据源设计

数据源 PoemData.ets 包含了 19 篇精选古诗文,覆盖 5 个主要类别。每篇作品包含完整的正文、译文、注释和赏析。这里以《静夜思》为例展示数据格式:

{
  id: 1,
  title: '静夜思',
  author: '李白',
  dynasty: '唐',
  category: PoemCategory.TANG_SHI,
  content: '床前明月光,\n疑是地上霜。\n举头望明月,\n低头思故乡。',
  translation: '明亮的月光洒在床前的窗户纸上...',
  notes: '①静夜思:静静的夜里产生的思绪。\n②疑:好像。',
  appreciation: '这首诗写的是在寂静的月夜思念家乡的感受...',
  background: '李白的《静夜思》创作于唐玄宗开元十四年...'
}

选择硬编码内置数据而非网络请求的原因:

  1. 开箱即用 — 用户安装后无需联网即可使用
  2. 启动速度快 — 无网络等待时间
  3. 内容可控 — 确保数据质量和版权合规
  4. 架构简单 — 无需搭建后端服务

如果需要扩展到更大规模的内容,可以方便地切换到本地数据库(HarmonyOS 提供了 RelationalStore)或网络 API 方案。


五、收藏系统实现

收藏功能是 App 的核心交互之一。我选择 AppStorage 来实现持久化存储。

5.1 AppStorage 原理

AppStorage 是 HarmonyOS 提供的全局键值存储系统:

  • 数据在应用进程内全局共享,所有页面都可访问
  • 支持基本类型和 ArrayMap 等复杂类型
  • 数据保存在内存中,应用退出后丢失(配合 PersistentStorage 可实现磁盘持久化)
  • 可以通过 @StorageLink 装饰器实现双向绑定

5.2 收藏管理实现

const FAVORITES_KEY = 'poem_favorites';

// 获取收藏集合
export function getFavorites(): Set<number> {
  const stored = AppStorage.get<number[]>(FAVORITES_KEY);
  return stored ? new Set(stored) : new Set();
}

// 切换收藏状态,返回新的状态
export function toggleFavorite(id: number): boolean {
  const favs = getFavorites();
  let isNowFav: boolean;
  if (favs.has(id)) {
    favs.delete(id);
    isNowFav = false;
  } else {
    favs.add(id);
    isNowFav = true;
  }
  AppStorage.set<number[]>(FAVORITES_KEY, Array.from(favs));
  return isNowFav;
}

// 检查是否已收藏
export function isFavorite(id: number): boolean {
  return getFavorites().has(id);
}

5.3 设计要点

1. Set 与 Array 的转换

AppStorage 不直接支持 Set 类型,所以我将收藏的 ID 集合以 number[](数组)形式存储,读取时再转换为 Set 以利用其 O(1) 的查找性能。写回时使用 Array.from() 将 Set 转回数组。

2. toggle 模式的设计

toggleFavorite 函数实现了"切换"语义——如果已收藏则取消收藏,未收藏则添加收藏。返回布尔值让调用方可以立即知道当前状态,无需再次查询。这种设计在函数式编程中称为"命令查询分离"的变体,一个函数同时完成命令(修改状态)和查询(返回结果)两个职责。

3. 函数式工具方法

getFavoritestoggleFavoriteisFavorite 提取为独立的工具函数,而非封装成类或单例,目的是与 ArkTS 的响应式系统更好的配合。在页面组件中可以直接调用这些函数,并将结果赋值给 @State 变量,触发 UI 更新。


六、首页实现详解

首页是用户打开 App 后看到的第一个界面,承载着分类导航和诗文列表两大核心功能。

6.1 页面结构

┌────────────────────────────┐
│  古诗文               🔍   │  ← 标题栏 + 搜索入口
├────────────────────────────┤
│ [推荐] [唐诗] [宋词] ...   │  ← 分类标签(水平滚动)
├────────────────────────────┤
│ ┌──────────────────────┐  │
│ │ 静夜思              →│  │
│ │ 唐·李白         [唐诗]│  │
│ │ 床前明月光...        │  │
│ └──────────────────────┘  │
│ ┌──────────────────────┐  │
│ │ 水调歌头            →│  │
│ │ 宋·苏轼         [宋词]│  │
│ │ 明月几时有...        │  │
│ └──────────────────────┘  │
│         ...               │
├────────────────────────────┤
│    🏠 首页     ☆ 收藏     │  ← 底部导航
└────────────────────────────┘

6.2 分类标签实现

分类标签使用 List 组件横向排列,实现可水平滚动的标签栏:

List({ space: 10 }) {
  ForEach(this.categories, (cat: string) => {
    ListItem() {
      Text(cat)
        .fontSize(15)
        .fontColor(this.currentCategory === cat ? '#FFFFFF' : '#6B5B4F')
        .padding({ left: 18, right: 18, top: 7, bottom: 7 })
        .backgroundColor(this.currentCategory === cat ? '#8B4513' : '#F5F0EB')
        .borderRadius(18)
        .onClick(() => {
          this.filterByCategory(cat);
        })
    }
  })
}
.listDirection(Axis.Horizontal)

关键点:

  • listDirection(Axis.Horizontal) 将列表方向设为水平
  • 当前选中标签使用棕色背景+白色文字,未选中使用浅米色背景+深灰文字,形成清晰的视觉层级
  • 圆角 borderRadius(18) 使标签呈现药丸形状,符合当前设计趋势

6.3 分类筛选逻辑

filterByCategory(category: string): void {
  this.currentCategory = category;
  if (category === '推荐') {
    // 从各类别中精选 9 篇代表作
    this.poemList = POEM_LIST.filter(
      p => [1, 2, 3, 9, 12, 13, 14, 16, 18].includes(p.id)
    );
  } else if (category === '唐诗') {
    this.poemList = POEM_LIST.filter(
      p => p.category === PoemCategory.TANG_SHI
    );
  }
  // ... 其他分类类似
}

"推荐"分类通过硬编码 ID 列表选择 9 篇代表作,涵盖各个类别的大师名篇,给首次使用的用户一个绝佳的入门体验。

6.4 诗文卡片设计

每个诗文条目使用白色卡片展示,包含标题、朝代作者、分类标签和首句预览:

Column() {
  // 第一行:标题 + 箭头
  Row() {
    Text(item.title).fontSize(18).fontWeight(FontWeight.Medium)
    Blank()
    Text('→').fontSize(16).fontColor('#B0A090')
  }
  // 第二行:朝代·作者 + 分类标签
  Row() {
    Text(`${item.dynasty} · ${item.author}`).fontSize(13)
    Blank()
    Text(item.category)  // 带背景色的分类标签
  }
  // 首句预览
  Text(item.content.split('\n')[0])
    .maxLines(1)
    .textOverflow({ overflow: TextOverflow.Ellipsis })
}
.padding(16)
.backgroundColor('#FFFFFF')
.borderRadius(12)
.shadow({ radius: 4, color: '#1A000000', offsetX: 0, offsetY: 2 })

设计要点:

  • 卡片阴影:使用 shadow API 添加微妙的投影,创造层次感
  • 圆角处理borderRadius(12) 使卡片边缘柔和
  • 文本溢出:首句预览使用 maxLines(1) + textOverflow(Ellipsis) 确保单行显示,溢出时自动省略号
  • 点击交互:整个卡片可点击,通过 router.pushUrl 跳转到详情页

6.5 关于图标选择的教训

在最初的实现中,我使用了系统资源 $r('sys.media.ohos_ic_public_*') 来获取系统图标。编译时报错 Unknown resource name,原因是 SDK 6.1.1 中这些资源名称已变更或不可用。解决方法是改用 Unicode 符号:

功能 原来 修复后
返回 $r('sys.media.ohos_ic_public_back') (U+2190)
搜索 $r('sys.media.ohos_ic_public_search') 🔍
收藏 $r('sys.media.ohos_ic_public_star') ☆/★
取消 $r('sys.media.ohos_ic_public_cancel')

使用 Unicode 符号的好处是零依赖、跨平台兼容、无需管理图标资源文件。缺点是不同操作系统和设备的渲染效果略有差异。对于正式项目,建议使用 SVG 矢量图标或 HarmonyOS 的 SymbolGlyph 组件。


七、详情页实现

详情页是用户阅读古诗文的核心页面,需要展示正文并支持多维度内容(译文、注释、赏析、背景)的展开和收起。

7.1 页面结构

┌────────────────────────────┐
│  ←    静夜思          ☆   │  ← 导航栏 + 收藏按钮
├────────────────────────────┤
│                            │
│        静夜思              │  ← 标题
│       唐·李白             │  ← 作者信息
│        [唐诗]             │  ← 分类标签
│  ───────────────────────  │
│   床前明月光,            │
│   疑是地上霜。            │  ← 正文
│   举头望明月,            │
│   低头思故乡。            │
│                            │
│  [译文] [注释] [赏析] [背景]│  ← 功能按钮
│                            │
│  📖 译文                   │
│  ┌────────────────────┐  │
│  │ 明亮的月光洒在...   │  │  ← 内容卡片
│  └────────────────────┘  │
│                            │
│  💡 赏析                   │
│  ┌────────────────────┐  │
│  │ 这首诗写的是...     │  │  ← 内容卡片(可展开多个)
│  └────────────────────┘  │
└────────────────────────────┘

7.2 状态管理

详情页使用 4 个 @State 变量控制各面板的展开状态:

@State showTranslation: boolean = true;   // 译文默认展开
@State showNotes: boolean = false;
@State showAppreciation: boolean = false;
@State showBackground: boolean = false;

其中 showTranslation 默认 true,因为译文是用户最常查看的内容,默认展示可以减少一次点击操作。

7.3 关于 Builder 的踩坑

在最初的实现中,我使用了 @Builder 来定义可复用的按钮组件:

// ❌ 错误写法:Builder 上链式调用 onClick
this.buildToggleBtn('译文', this.showTranslation)
  .onClick(() => { this.showTranslation = !this.showTranslation; })

编译时报错 Property 'onClick' does not exist on type 'void'。这是因为 ArkTS 的 @Builder 函数返回 void,并非 UI 组件实例,因此不能链式调用属性方法。

解决方案有两种:

方案一(我采用的):将交互逻辑内联到 Builder 内部

// ✅ 直接使用 Text 组件绑定点击事件
Text('译文')
  .fontSize(14)
  .fontColor(this.showTranslation ? '#FFFFFF' : '#8B4513')
  .backgroundColor(this.showTranslation ? '#8B4513' : '#F5EDE4')
  .padding({ left: 14, right: 14, top: 6, bottom: 6 })
  .borderRadius(16)
  .onClick(() => {
    this.showTranslation = !this.showTranslation;
  })

方案二:将点击事件作为 Builder 参数传入

@Builder
buildToggleBtn(label: string, isActive: boolean, onClick: () => void) {
  Text(label)
    .onClick(() => { onClick(); })
}

Builder 的正确使用方式是所有 UI 配置和交互逻辑都在 Builder 内部完成,外部调用时不应再链式修改。

7.4 条件渲染处理

详情页需要处理 this.poemnull 的情况。在 ArkTS 中,build() 方法内部不能使用 return 提前返回,必须使用 if-else 包裹:

build() {
  Column() {
    // ... 导航栏始终渲染 ...

    if (this.poem) {
      // 正文内容
    } else {
      Text('未找到诗文')
    }
  }
}

这是 ArkTS 与普通 TypeScript/JavaScript 的重要区别——build() 方法必须始终返回一个完整的组件树,不能提前中断。

7.5 功能按钮区

四个切换按钮(译文、注释、赏析、背景)使用 Row 水平排列,通过 @State 控制颜色和文字变化模拟"选中/未选中"状态:

Text('译文')
  .fontColor(this.showTranslation ? '#FFFFFF' : '#8B4513')
  .backgroundColor(this.showTranslation ? '#8B4513' : '#F5EDE4')

这种实现方式本质上是一个"开关"按钮,比使用 Toggle 组件更轻量、更易于自定义样式。


八、收藏页实现

收藏页展示用户收藏的所有诗文,数据来源于 AppStorage 持久化的收藏 ID 集合。

8.1 数据加载时机

aboutToAppear(): void {
  this.loadFavorites();
}

loadFavorites(): void {
  const favIds = getFavorites();
  this.favList = POEM_LIST.filter(p => favIds.has(p.id));
}

aboutToAppear 是组件生命周期方法,在页面即将显示时调用,确保每次进入收藏页都能获取最新数据。

8.2 空状态设计

当用户还没有收藏时,显示一个友好的空状态提示:

         ☆
    还没有收藏
  在诗文详情页点击☆即可收藏

空状态包含一个视觉元素(大号星号图标)和两行提示文字,引导用户操作。相比直接显示空白页面,空状态设计能显著降低用户的困惑感。

8.3 收藏列表的交互

收藏列表的卡片样式与首页一致,点击跳转到详情页。这种一致性设计使用户在不同页面间切换时能保持熟悉的操作模式。


九、搜索页实现

搜索功能支持按标题、作者、内容、朝代、译文多维度匹配。

9.1 实时搜索

Search({ placeholder: '搜索诗文标题、作者、内容…', value: this.searchText })
  .onChange((val: string) => {
    this.searchText = val;
    if (val.trim() === '') {
      this.searchResults = [];
      this.hasSearched = false;
    } else {
      this.onSearch();
    }
  })

使用 onChange 事件实现实时搜索——用户每输入一个字符,结果列表都立即更新。对于 19 篇数据的轻量级搜索,客户端实时过滤的延迟在毫秒级,体验流畅。

9.2 搜索算法

onSearch(): void {
  const query = this.searchText.trim().toLowerCase();
  this.searchResults = POEM_LIST.filter(item => {
    return item.title.toLowerCase().includes(query)
      || item.author.toLowerCase().includes(query)
      || item.content.toLowerCase().includes(query)
      || item.dynasty.toLowerCase().includes(query)
      || (item.translation && item.translation.toLowerCase().includes(query));
  });
}

算法要点:

  1. 大小写归一化:将查询和内容都转为小写,确保不区分大小写
  2. 多字段匹配:在标题、作者、内容、朝代、译文五个字段中搜索,提高召回率
  3. 子串匹配:使用 includes 而非精确匹配,支持关键词模糊搜索

这种实现方式简单直接,对于小数据集完全够用。如果需要扩展到成百上千篇诗文,建议使用倒排索引或引入 @kit.ArkData 的全文搜索能力。

9.3 搜索结果呈现

搜索结果页面包含结果数量统计和与首页风格一致的卡片列表:

找到 3 篇相关诗文
┌──────────────────────┐
│ 静夜思           [唐诗]│
│ 唐·李白              │
│ 床前明月光...        │
└──────────────────────┘

十、路由与页面导航

10.1 路由配置

页面路由在 main_pages.json 中注册:

{
  "src": [
    "pages/Index",
    "pages/DetailPage",
    "pages/FavoritesPage",
    "pages/SearchPage"
  ]
}

所有页面必须在 main_pages.json 中注册,否则运行时会无法找到对应页面。

10.2 带参数跳转

// 跳转到详情页并传递诗文 ID
router.pushUrl({
  url: 'pages/DetailPage',
  params: { id: item.id }
});
// 详情页接收参数
aboutToAppear(): void {
  const params = router.getParams() as Record<string, Object>;
  const id = params!['id'] as number;
}

10.3 关于 router 废弃警告

编译时出现了 'pushUrl' has been deprecated 等警告。在 HarmonyOS NEXT 中,@kit.ArkUIrouter API 已被标记为废弃,推荐使用更新的导航 API。

虽然这些警告不影响编译运行,但在正式项目中应该迁移到新 API:

// 未来推荐方式(伪代码)
import { UIAbilityContext } from '@kit.AbilityKit';
context.router.pushNamedRoute({ name: 'DetailPage', params: { id: 1 } });

不过截至 SDK 6.1.1,旧的 router API 仍然完全可用,且是社区中最常用的方案,短期内不会移除。


十一、样式与主题设计

11.1 色彩体系

App 的视觉风格定位为"暖色调书香风格",色彩设计围绕棕色系展开:

用途 色值 说明
页面背景 #F8F6F3 暖米色,纸书质感
标题文字 #3A2A1A 深棕,沉稳大气
正文文字 #3A2A1A 与标题一致
辅助文字 #9A8A7A 灰棕,用于作者朝代
强调色 #8B4513 棕色,用于标签活跃态
卡片背景 #FFFFFF 白色,保持内容清晰
标签背景 #F5F0EB / #F5EDE4 浅米色

11.2 字体与排版

  • 标题:26-24fp,粗体
  • 正文:18fp,2px 字间距,34px 行高,居中对齐
  • 辅助信息:13-15fp
  • 分类标签:11-12fp,小字标签

古诗文的正文采用居中对齐 + 较大行距的排版方式,模拟传统书法的竖排效果。字间距 letterSpacing(2) 增加了文字的呼吸感,阅读体验更舒适。

11.3 圆角与阴影

  • 卡片圆角:12px
  • 标签圆角:16-18px(药丸形)
  • 卡片阴影radius: 4, color: '#1A000000', offsetY: 2
  • 底部导航阴影radius: 8, color: '#1A000000', offsetY: -2

阴影参数中 color 使用带透明度的十六进制值(#1A000000 表示 α=10% 的黑色),相比 rgba 更简洁,是十六进制颜色的扩展用法。


十二、遇到的问题与解决方案

12.1 编译错误汇总

在开发过程中共遇到 18 个编译错误,按类型分类如下:

错误类型 数量 原因 解决方案
Argument of type 'string' is not assignable to parameter of type 'Resource' 2 字符串传递给期望 Resource 类型的参数 改用 $r() 或直接使用字符串字面量
Property 'onClick' does not exist on type 'void' 6 Builder 函数不能链式调用 将 onClick 移到 Builder 内部
Property 'SemiBold' does not exist on type 'typeof FontWeight' 1 FontWeight.SemiBold 在 API 12 中不存在 改用 FontWeight.Bold
Only UI component syntax can be written here 1 build() 中使用 return 改为 if-else 条件渲染
Unknown resource name 'ohos_ic_public_*' 8 系统资源名称在 SDK 6.1.1 中变更 改用 Unicode 符号

12.2 Builder 函数的正确用法

这是本次开发中最大的"坑"。在 ArkTS 中,@Builder 装饰的函数有以下特点:

  1. 返回类型为 void,不是组件实例
  2. 不能在 Builder 调用的结果上链式调用属性方法
  3. Builder 内部可以访问外层组件的 @State 变量
  4. Builder 主要用于提取可复用的 UI 片段

正确示例:

@Builder
buildSection(title: string, content: string) {
  Column() {
    Text(title).fontColor('#5A4A3A')
    Text(content).fontColor('#4A3A2A')
  }
  .backgroundColor('#FFFFFF')
  .borderRadius(12)
}

错误示例:

// ❌ 不能这样用
this.buildSection('标题', '内容')
  .backgroundColor('#FFFFFF')  // 编译错误

12.3 系统资源的兼容性

HarmonyOS 每个版本的系统资源名称可能不同。SDK 6.1.1(对应 API 12+)中,ohos_ic_public_backohos_ic_public_search 等常用的系统图标资源名称已不可用。推荐的做法是:

  1. 使用 SymbolGlyph 组件(如果 SDK 版本支持)
  2. 使用 Unicode 符号作为图标(适合轻量需求)
  3. 自行准备 SVG/PNG 图标资源放入 resources/base/media/
  4. 通过 $r('app.media.my_icon') 引用自定义资源

十三、编译与打包

13.1 构建流程

使用 hvigorw assembleApp 命令完成应用构建:

PreBuildApp → CreateModuleInfo → ProcessProfile → 
ProcessResource → CompileArkTS → GeneratePkgModuleJson → 
PackageHap → SignHap → PackageApp → assembleApp

编译流程中最重要的阶段是 CompileArkTS,负责将 ArkTS 源码编译为方舟字节码。

13.2 构建输出

成功构建后的产物包括:

  • entry/build/default/outputs/default/entry-default-unsigned.hap — 未签名的 HAP 包
  • build/default/outputs/default/MyApplication-default-unsigned.app — 未签名的 APP 包

13.3 签名说明

构建输出中出现了 Will skip sign 的警告,这是因为没有配置签名证书。在 DevEco Studio 中,可以通过以下步骤配置自动签名:

  1. 打开项目 → Build → Generate Key and CSR
  2. 配置密钥库和证书
  3. build-profile.json5 中配置 signingConfigs

对于开发和测试,也可以使用 DevEco Studio 自动生成的调试证书。


十四、项目扩展方向

目前的古诗文 App 虽然功能完整,但仍有很大的扩展空间:

14.1 内容扩展

  • 接入网络 API,获取海量古诗文数据
  • 支持用户投稿和社区评论
  • 添加 AI 语音朗读功能(HarmonyOS 的 AVPlayer)

14.2 功能增强

  • 朗读功能:使用 HarmonyOS 的文本转语音(TTS)能力
  • 日推功能:每天推送一篇精选古诗文
  • 笔记功能:允许用户在诗文上记录阅读心得
  • 分享功能:生成诗文卡片分享到社交媒体

14.3 技术升级

  • 迁移到新的导航 API,消除 router 废弃警告
  • 使用 PersistentStorage 实现收藏数据的磁盘持久化
  • 引入 RelationalStore(SQLite)存储大量内容数据
  • 添加折叠屏适配,利用 HarmonyOS 的窗口多任务能力

十五、总结与感悟

通过本次古诗文 App 的开发实践,我获得了以下收获:

关于 HarmonyOS 开发:

  1. ArkTS 的学习曲线:对于有 TypeScript 或 SwiftUI 经验的开发者,ArkTS 的上手非常快速。声明式 UI 的思维方式与 React/Vue 一脉相承,但语法更简洁、装饰器机制更强大。

  2. 生态成熟度:HarmonyOS NEXT 的 API 覆盖度已经相当完善,从 UI 组件到数据存储再到设备能力,基本满足常见应用开发需求。但部分 API 的文档和社区资源仍有提升空间。

  3. 开发工具:DevEco Studio 提供了完整的 IDE 体验,包括代码补全、实时预览、性能分析等。但在插件生态和稳定性方面,与 Android Studio 和 Xcode 相比还有一定差距。

关于工程实践:

  1. 分层架构的重要性:将数据模型、业务逻辑和 UI 分离,让项目结构清晰,修改一处不会牵动全局。对于小型项目也许显得"杀鸡用牛刀",但为后续扩展打下了良好基础。

  2. 响应式编程的优雅@State 驱动的 UI 更新让开发变得简单——不需要手动操作 DOM,不需要管理复杂的回调链条,只需要关注数据变化即可。

  3. 错误处理的心态:开发过程中遇到 18 个编译错误,每一个都是学习的机会。从"Builder 不能链式调用"到"系统资源名称变更",这些踩坑经验比官方文档更有记忆点。

关于产品设计:

  1. 细节决定体验:空状态引导、卡片圆角阴影、标签选中态反馈——这些"小"设计共同塑造了用户对 App 的整体印象。好的产品体验往往藏在细节里。

  2. 传统文化与技术的结合:古诗文是中华文化的瑰宝,通过移动 App 这种现代载体传播,既保留了传统文化的内涵,又赋予了其新的生命力。技术服务于文化,文化赋予技术温度——这是作为一名开发者最有价值感的事情。


附:完整源码结构

entry/src/main/ets/
├── model/
│   ├── PoemModel.ets          # 数据模型(37行)
│   └── PoemData.ets           # 19篇诗文数据 + 收藏管理(266行)
├── pages/
│   ├── Index.ets              # 首页(176行)
│   ├── DetailPage.ets         # 详情页(215行)
│   ├── FavoritesPage.ets      # 收藏页(118行)
│   └── SearchPage.ets         # 搜索页(162行)
└── entryability/
    └── EntryAbility.ets       # 应用入口

资源文件:
├── entry/resources/base/profile/main_pages.json  # 路由配置
├── entry/resources/base/element/string.json      # 字符串资源
├── entry/resources/base/element/color.json       # 颜色资源
├── entry/resources/base/element/float.json       # 字号资源
└── AppScope/resources/base/element/string.json   # 应用名资源

请添加图片描述


Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐