HarmonyOS ArkTS 实战:从零搭建桌面卡片管理工具的页面架构

做 HarmonyOS 应用时,最容易低估的不是某个页面怎么写,而是页面、数据、资源、扩展能力之间的边界怎么放。这个项目是一个“桌面卡片工具”:用户可以浏览模板、创建卡片、收藏、归档、备份,并把指定卡片同步到 HarmonyOS 桌面卡片。
这篇先讲整体架构,后面的文章再拆本地持久化、分类模板、Form Kit、备份恢复和统计页面。
项目结构先按能力分层
工程的主模块是 entry,页面、组件、服务和公共模型都放在 entry/src/main/ets 下:
entry/src/main/ets
├── common
│ ├── AppModels.ets
│ ├── AppSizes.ets
│ ├── AppTheme.ets
│ ├── CardImages.ets
│ ├── DesignData.ets
│ └── Routes.ets
├── components
│ ├── BottomNavBar.ets
│ ├── ShowcaseCard.ets
│ ├── MenuListItem.ets
│ └── PageHeader.ets
├── pages
│ ├── Index.ets
│ ├── CategoryPage.ets
│ ├── CardDetailPage.ets
│ ├── CardEditPage.ets
│ ├── CardManagePage.ets
│ ├── StatisticsPage.ets
│ └── BackupPage.ets
├── services
│ ├── AppDataService.ets
│ └── DesktopFormService.ets
├── entryability
├── entryformability
└── entrybackupability
这个拆法的重点是:页面只负责展示和轻量交互,真正的数据读写放在 AppDataService.ets。这样首页、分类页、详情页、管理页和桌面 Form 都能拿到同一份状态,避免每个页面自己维护一套临时数据。
页面注册:不要让路由散落在页面里
页面列表在 main_pages.json 里集中声明:
{
"src": [
"pages/Index",
"pages/MarketPage",
"pages/CategoryPage",
"pages/CardDetailPage",
"pages/CardEditPage",
"pages/CardStylePage",
"pages/StatisticsPage",
"pages/ReminderPage",
"pages/ThemeStorePage",
"pages/CardManagePage",
"pages/BackupPage",
"pages/ProfilePage",
"pages/AboutPage"
]
}
应用内跳转再通过 RoutePaths 统一管理。这样有两个好处:
- 页面路径变化时,只需要更新常量层。
- 共享组件和服务模型只保存稳定 route,不硬编码字符串。
在 ArkTS 严格模式下,裸对象和动态 key 访问都容易引发类型问题。把路由集中成静态类,也能减少编译器误判。
Ability 入口:先初始化数据,再加载主页面
应用入口是 EntryAbility.ets。这里做了两件事:初始化数据服务,加载 pages/Index。
onWindowStageCreate(windowStage: window.WindowStage): void {
this.windowStage = windowStage;
appDataService.initialize(this.context);
windowStage.loadContent('pages/Index', (err) => {
if (err.code) {
return;
}
this.registerIconFontIfNeeded();
});
}
这段代码看起来简单,但顺序很关键:
- 先
initialize(),页面渲染时才能读到持久化状态。 loadContent()成功后再注册 iconfont,规避窗口状态未就绪导致的图标注册异常。onForeground()中也补一次registerIconFontIfNeeded(),保证恢复前台后图标仍然稳定。
项目以前遇到过 window state abnormal 一类问题,所以没有把 iconfont 注册写在更早的生命周期里。
主入口不是单页堆叠,而是“首页 + 分类 + 我的”的本地 Tab
Index.ets 里没有通过 router 切三个一级页,而是用 currentTab 控制首页、分类、个人中心三个主视图:
@State currentTab: string = 'home';
@State categoryQuery: string = '';
@State selectedCategoryId: CardCategoryId = 'countdown';
private scroller: Scroller = new Scroller();
底部导航切换时,重置搜索和分类状态,再滚动回顶部:
private handleBottomNavChange(id: string): void {
if (id === 'create') {
router.pushUrl({ url: RoutePaths.cardEdit });
return;
}
this.currentTab = id;
this.categoryQuery = '';
this.hasSelectedCategory = false;
this.scrollToTop();
}
这种设计适合轻量工具类应用:一级入口切换速度快,状态刷新集中,底部导航也不用反复创建页面栈。
数据服务是项目的核心
AppDataService.ets 是整个项目最重要的文件。它负责:
- 从
preferences读取和写入本地状态。 - 维护模板目录、主题目录、样式目录。
- 生成首页摘要、分类卡片、详情视图、统计数据。
- 支持卡片创建、编辑、收藏、归档、恢复、删除。
- 处理本地备份、系统备份和桌面 Form 数据。
页面拿到的不是原始状态,而是视图模型:
const detailView = appDataService.getCardDetailView(params.cardId, params.templateId);
this.card = detailView.card;
this.isTemplate = detailView.isTemplate;
这让页面不需要知道“模板卡片”和“用户卡片”的底层差异,只根据 isTemplate 决定按钮文案和动作。
扩展能力:Form 和 Backup 都复用同一服务层
项目里除了主应用,还有两个扩展能力:
"extensionAbilities": [
{
"name": "EntryBackupAbility",
"type": "backup"
},
{
"name": "EntryFormAbility",
"type": "form"
}
]
EntryFormAbility 通过 DesktopFormService 读取当前桌面卡片数据,EntryBackupAbility 通过 AppDataService 导出和恢复快照。这样主应用、桌面卡片、系统备份三条链路不会各自维护状态。
这套架构适合什么项目
如果你的 HarmonyOS 应用符合下面几个特征,可以参考这个拆法:
- 页面多,但核心状态只有一份。
- 有本地编辑、收藏、归档、恢复一类操作。
- 需要桌面卡片或备份扩展能力读取同一份业务数据。
- UI 需要大量复用卡片、列表、导航、搜索栏等组件。
- ArkTS 严格模式下希望减少动态对象和松散字段。
验证建议
架构完成后,不要只看首页。建议按这个顺序跑一遍:
D:\dev\command-line-tools\bin\hvigorw.bat assembleHap --no-daemon --stacktrace
然后手工检查:
- 启动首页,确认
AppDataService.initialize()后首屏能显示。 - 切换首页、分类、我的,确认底部导航状态不串。
- 从分类进入详情,再进入编辑,确认路由参数不丢。
- 新建卡片后回到管理页,确认列表刷新。
- 设置桌面卡片后,确认 Form 数据能同步。
小结
这个项目的架构核心不是“页面很多”,而是“状态只有一份,能力围着状态走”。页面负责展示,服务层负责业务状态,资源层负责图片和主题,Ability/ExtensionAbility 只做入口和桥接。
对 HarmonyOS ArkTS 工程来说,这种边界比一开始写出漂亮页面更重要。边界清楚之后,后续加模板、加桌面卡片、加统计和备份,都不会把页面逻辑越写越散。
更多推荐


所有评论(0)