
前言
“暂无数据”是项目里最常见、也最容易被低估的文案。它的问题不是短,而是像一堵墙:用户不知道为什么没有内容,也不知道下一步该做什么。
HarmonyOS7 做收藏夹、地址簿、订单列表时,空页面应该给出原因、动作和一点上下文。空状态不是缺省文案,而是一个轻量引导页。
为什么这个问题经常被写乱
空页面不是写个暂无数据就完事 这类内容很容易被写成“代码能跑就算讲完了”,但对初学者来说,这恰恰是最不够的地方。真正让人卡住的,往往不是某个组件名记不住,而是不知道这段代码为什么要这样拆、状态为什么要这样放、以后需求变化时应该从哪里改。

所以这篇文章不只想给你一个能跑的例子,更想把背后的判断过程讲清楚。你只要把这个判断过程吃透,后面自己改页面、补需求、查问题时,心里会稳很多。
空页面要回答三个问题
| 问题 | 示例 | 对应 UI |
|---|---|---|
| 为什么空 | 还没有收藏内容 | 主标题 |
| 能做什么 | 去首页浏览并收藏 | 主按钮 |
| 有什么价值 | 收藏后会同步到多设备 | 辅助说明 |
主动作不要太多。空页面不是菜单页,一个明确按钮通常比三个按钮更好。
先把页面目标想清楚
在真正写代码之前,先别急着盯着 API。更有用的做法是先想清楚:这个页面到底想解决什么问题,用户最在意的反馈是什么,哪些状态必须一直保持一致。
当你先把这条主线想明白,再回头看组件和状态设计,很多选择都会顺理成章。对小白来说,这一步尤其重要,因为它能帮你从“照着抄”慢慢过渡到“看得懂、改得动”。
完整 ArkTS 示例
interface FavoriteItem24 {
id: number
title: string
folder: string
}
@Entry
@Component
struct EmptyFavoritePage {
@State favorites: FavoriteItem24[] = []
@State tip: string = '收藏文章、商品或课程后,会在这里统一查看。'
private addFavorite(): void {
this.favorites = [
{ id: 1, title: 'HarmonyOS7 ArkUI 状态管理笔记', folder: '技术文章' }
]
}
private clearFavorites(): void {
this.favorites = []
}
@Builder
EmptyState() {
Column({ space: 14 }) {
Stack() {
Text('☆').fontSize(42).fontColor('#2F6FED')
}
.width(88)
.height(88)
.backgroundColor('#EAF1FF')
.borderRadius(44)
Text('还没有收藏内容')
.fontSize(21)
.fontWeight(FontWeight.Bold)
Text(this.tip)
.fontSize(14)
.fontColor('#666666')
.textAlign(TextAlign.Center)
.lineHeight(22)
Button('去添加一个收藏')
.onClick(() => this.addFavorite())
Text('收藏后可以在手机和平板上继续查看。')
.fontSize(12)
.fontColor('#999999')
}
.width('100%')
.padding({ top: 44, left: 24, right: 24, bottom: 32 })
.backgroundColor(Color.White)
.borderRadius(14)
}
build() {
Column({ space: 16 }) {
Row() {
Text('我的收藏').fontSize(24).fontWeight(FontWeight.Bold)
Blank()
if (this.favorites.length > 0) {
Button('清空').onClick(() => this.clearFavorites())
}
}
.width('100%')
if (this.favorites.length === 0) {
this.EmptyState()
} else {
List({ space: 10 }) {
ForEach(this.favorites, (item: FavoriteItem24) => {
ListItem() {
Column({ space: 6 }) {
Text(item.title).fontSize(17).fontWeight(FontWeight.Medium)
Text(item.folder).fontSize(12).fontColor('#777777')
}
.alignItems(HorizontalAlign.Start)
.padding(14)
.backgroundColor(Color.White)
.borderRadius(10)
}
}, (item: FavoriteItem24) => item.id.toString())
}
.layoutWeight(1)
}
}
.padding(16)
.width('100%')
.height('100%')
.backgroundColor('#F6F7F9')
}
}

把关键代码一段段拆开
EmptyState() 用 @Builder 单独收起来,主 build() 只负责判断展示空状态还是列表。空页面细节很多,直接堆在主结构里会让页面很难读。
favorites.length === 0 是唯一判断来源。不要再额外维护一个 isEmpty,否则数据变了但 isEmpty 没同步,就会出现有数据还显示空态的尴尬问题。
addFavorite() 示例里模拟主动作。真实项目可以跳转首页、打开搜索页、进入创建页,重点是给用户一条明确路径。
真的空和筛选空不是一回事
同样是没有内容,业务含义可能完全不同。收藏夹第一次打开是“真的空”,搜索关键词没有命中是“筛选空”,接口失败后没有数据是“错误态”。
| 类型 | 典型文案 | 主动作 |
|---|---|---|
| 初始空 | 还没有收藏内容 | 去发现内容 |
| 筛选空 | 当前筛选没有结果 | 清空筛选 |
| 搜索空 | 没找到相关内容 | 换个关键词 |
| 错误空 | 内容加载失败 | 重新加载 |
如果筛选空也写成“暂无数据”,用户可能以为自己的账号没有内容。文案里带上“当前筛选”,能省掉很多误解。
新手最容易踩的坑
这一类示例最容易让人产生错觉:界面出来了,就以为已经掌握了。其实真正容易出问题的地方,通常都在效果之外,比如状态有没有收拢、失败后怎么兜底、以后要扩展时会不会牵一发动全身。
所以你练这篇内容时,别只看“现在能不能跑”,还要继续看“以后好不好改”。能把这个习惯养起来,你写出来的页面会比单纯照着示例拼出来的页面稳很多。
放进真实项目还要补什么
示例代码的重点是把核心思路讲明白,所以很多工程化细节会故意省掉。真正落到项目里时,你通常还要继续补接口联动、异常处理、边界保护、资源抽离,以及和其他页面状态之间的配合。
比较稳的做法是分三步走:先把结构和职责立住,再把真实业务接进去,最后再优化视觉和交互体验。这样改出来的页面不只是“能演示”,而是真的更接近可以长期维护的业务代码。
写在最后
空页面做得好,用户不会觉得自己撞墙。把“为什么空”和“下一步做什么”写出来,比一句“暂无数据”有价值得多。

3万+

被折叠的 条评论
为什么被折叠?



