本篇深入剖析鸿蒙日记应用的核心入口页面——Index 日记列表页的完整实现,涵盖页面结构、状态管理、数据加载与交互逻辑。

在这里插入图片描述

一、页面概述

Index 页面是整个日记应用的首页,用户打开应用后首先看到的就是日记列表。该页面负责:

  • 展示所有已创建的日记条目
  • 提供新建日记的入口(FAB 浮动按钮)
  • 支持点击进入日记详情
  • 支持长按删除日记
  • 空列表状态展示

二、页面结构设计

2.1 整体布局

┌─────────────────────────┐
│      标题栏 (日记)        │
├─────────────────────────┤
│                         │
│   日记列表 (List)        │
│   ┌─────────────────┐   │
│   │ 日期 | 天气心情  │   │
│   │ 内容预览...      │   │
│   └─────────────────┘   │
│   ┌─────────────────┐   │
│   │ 日期 | 天气心情  │   │
│   │ 内容预览...      │   │
│   └─────────────────┘   │
│                         │
│            ┌───┐        │
│            │ + │  FAB   │
│            └───┘        │
└─────────────────────────┘

2.2 状态变量定义

@Entry
@Component
struct Index {
  @State diaries: DiaryInfo[] = []
  @State isLoading: boolean = false
  private diaryStore: DiaryStore = DiaryStore.getInstance()

  aboutToAppear() {
    this.loadDiaries()
  }
}

关键点解析:

  • @State diaries:日记数组,是页面的核心状态数据,变化时自动触发 UI 刷新
  • @State isLoading:加载状态标志,控制加载动画的显示
  • diaryStore:通过单例模式获取数据存储实例,避免重复创建

三、数据加载逻辑

3.1 aboutToAppear 生命周期

aboutToAppear() {
  this.loadDiaries()
}

private async loadDiaries() {
  this.isLoading = true
  try {
    this.diaries = await this.diaryStore.getAllDiaries()
    // 按日期降序排序
    this.diaries.sort((a, b) => {
      return new Date(b.date).getTime() - new Date(a.date).getTime()
    })
  } catch (error) {
    console.error('加载日记失败: ' + error)
  } finally {
    this.isLoading = false
  }
}

执行流程:

  1. 页面即将显示时自动触发 aboutToAppear
  2. 调用 loadDiaries() 从 Preferences 异步读取数据
  3. 按日期降序排序,最新的日记显示在最前面
  4. 异常处理确保网络或存储错误不会导致页面崩溃

3.2 页面刷新机制

当用户从编辑页面返回时,需要刷新列表数据:

onPageShow() {
  this.loadDiaries()
}

这确保了新建或编辑日记后,列表能及时反映最新数据。

四、列表渲染

4.1 List 与 ForEach

List({ space: 12 }) {
  ForEach(this.diaries, (diary: DiaryInfo, index: number) => {
    ListItem() {
      this.DiaryCard(diary)
    }
    .onClick(() => {
      this.goToDetail(diary.id)
    })
  }, (diary: DiaryInfo) => diary.id)
}
.width('100%')
.layoutWeight(1)

ForEach 参数说明:

参数 说明
第一个参数 数据源数组 this.diaries
第二个参数 子项生成函数,接收 itemindex
第三个参数 键值生成函数,用于 Diff 算法的唯一标识

关键提示: 键值生成函数返回 diary.id 确保列表项的唯一性,在数据更新时能精确匹配并高效更新 DOM。

4.2 日记卡片组件

@Builder
DiaryCard(diary: DiaryInfo) {
  Row({ space: 12 }) {
    // 左侧日期
    Column() {
      Text(diary.day)
        .fontSize(28)
        .fontWeight(FontWeight.Bold)
      Text(diary.monthYear)
        .fontSize(12)
        .fontColor('#999')
    }
    .width(60)
    .alignItems(HorizontalAlign.Center)

    // 右侧内容
    Column({ space: 6 }) {
      Row({ space: 8 }) {
        Text(diary.weather)
        Text(diary.mood)
      }
      Text(diary.content)
        .fontSize(14)
        .maxLines(2)
        .textOverflow({ overflow: TextOverflow.Ellipsis })
    }
    .layoutWeight(1)
    .alignItems(HorizontalAlign.Start)
  }
  .width('100%')
  .padding(16)
  .backgroundColor('#FFFFFF')
  .borderRadius(12)
  .shadow({ radius: 4, color: 'rgba(0,0,0,0.08)' })
}

卡片设计要点:

  1. 左右布局:日期信息在左,内容预览在右,层次分明
  2. 阴影效果:轻微阴影提升卡片立体感
  3. 圆角设计:12px 圆角符合现代 UI 风格
  4. 文本截断maxLines(2) + Ellipsis 确保内容过长时优雅截断

五、空状态处理

当没有日记时,展示友好的空状态提示:

if (this.diaries.length === 0) {
  Column({ space: 16 }) {
    Image($r('app.media.empty'))
      .width(120)
      .height(120)
    Text('还没有日记,点击 + 开始记录吧')
      .fontSize(16)
      .fontColor('#999')
  }
  .width('100%')
  .height('100%')
  .justifyContent(FlexAlign.Center)
} else {
  // 正常列表展示
}

空状态设计原则:

  • 提供视觉引导(插图或图标)
  • 文案友好,引导用户操作
  • 居中布局,视觉平衡

六、导航跳转

6.1 跳转到详情页

private goToDetail(id: string) {
  router.pushUrl({
    url: 'pages/DiaryDetail',
    params: { id: id }
  })
}

6.2 跳转到编辑页

private goToEdit() {
  router.pushUrl({
    url: 'pages/DiaryEdit',
    params: { mode: 'create' }
  })
}

路由参数传递:

  • 通过 params 对象传递参数
  • 目标页面通过 router.getParams() 获取
  • 支持字符串、数字、对象等多种类型

七、浮动按钮 FAB

Button() {
  Image($r('app.media.add'))
    .width(28)
    .height(28)
}
.width(56)
.height(56)
.backgroundColor('#007DFF')
.borderRadius(28)
.position({ right: 20, bottom: 20 })
.shadow({ radius: 8, color: 'rgba(0,125,255,0.3)' })
.onClick(() => {
  this.goToEdit()
})

FAB 实现要点:

  • 使用 position 绝对定位,固定在右下角
  • 圆形按钮(borderRadius = 宽度的一半)
  • 蓝色阴影增强浮动感
  • 醒目的图标引导用户操作

八、下拉刷新

Refresh({ refreshing: $$this.isRefreshing }) {
  List({ space: 12 }) {
    ForEach(this.diaries, (diary: DiaryInfo) => {
      ListItem() {
        this.DiaryCard(diary)
      }
    }, (diary: DiaryInfo) => diary.id)
  }
}
.onRefreshing(() => {
  this.loadDiaries()
})

九、性能优化

9.1 懒加载

对于大量日记数据,可以使用 LazyForEach 替代 ForEach

LazyForEach(this.diaryDataSource, (diary: DiaryInfo) => {
  ListItem() {
    this.DiaryCard(diary)
  }
}, (diary: DiaryInfo) => diary.id)

9.2 数据分页

private async loadDiaries(page: number = 0, pageSize: number = 20) {
  const allDiaries = await this.diaryStore.getAllDiaries()
  const start = page * pageSize
  const end = start + pageSize
  const pageData = allDiaries.slice(start, end)
  
  if (page === 0) {
    this.diaries = pageData
  } else {
    this.diaries = this.diaries.concat(pageData)
  }
}

十、总结

Index 页面作为应用的首页,承担了数据展示和导航枢纽的双重职责。其核心设计要点包括:

  1. 声明式 UI:通过 @State 驱动界面自动更新
  2. Builder 复用:日记卡片通过 @Builder 实现代码复用
  3. 生命周期管理:在 aboutToAppearonPageShow 中处理数据加载
  4. 用户体验:空状态、加载状态、下拉刷新等细节完善
  5. 性能考量:ForEach 键值、懒加载等优化手段

这些模式在鸿蒙 ArkUI 开发中非常通用,掌握它们可以快速构建高质量的列表页面。

Logo

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

更多推荐