一、引言

日记是最高频的个人记录行为之一。与习惯追踪(记录是否完成)和记账(记录金额和分类)不同,日记的核心是自由文本 + 情感标签——用户输入的是非结构化的文字,但需要日期组织、心情标记、编辑和检索。

从技术角度看,日记本涉及以下关键问题:日期导航(如何优雅地在前一天/后一天/回到今天之间切换)、条件渲染(某天有日记时显示内容,无日记时显示"写一篇"引导)、CRUD 操作(新建/编辑/保存/删除)、列表排序(按日期倒序展示最近日记)。

本文将用 ArkUI 从零构建一个心情日记本。功能包括:日期导航(← 前一天 / 今天 / 后一天 →)、6 种心情选择、日记的写入/编辑/删除、当天日记展示、最近日记列表(按日期倒序)。配色干净——深色顶部栏 + 白色内容区,所有文字与背景保持足够对比度。

阅读完本文,你将能够:

  • 实现自定义日期导航器(前后切换 + 回到今天)
  • 构建表情选择器(选中/未选中的不透明度区分)
  • 实现同一页面的写入模式和浏览模式条件切换
  • 掌握日记的完整 CRUD 操作
  • 设计最近列表的排序和点击跳转逻辑

二、数据模型与日期管理

2.1 日记实体

interface EntryInfo {
  id: number;
  date: string;    // YYYY-MM-DD
  mood: string;     // emoji:😊😢😡🤩😴😐
  title: string;
  content: string;
}

日期用 YYYY-MM-DD 格式存储——字符串比较(localeCompare)天然等于日期先后比较,方便排序。标题和内容是自由文本,心情用一个 emoji 字符表示。

6 种心情覆盖了人类情感的基本维度:开心(😊)、难过(😢)、愤怒(😡)、兴奋(🤩)、疲惫(😴)、平静(😐)。用 emoji 而非文字标签的好处:跨语言、节省空间、视觉直观。

2.2 日期导航

日期导航器由三个部分组成:← 按钮、日期显示(含"今天"标签)、→ 按钮:

goPrevDay(): void {
  const d = new Date(this.currentDate);
  d.setDate(d.getDate() - 1);
  this.currentDate = d;
  this.selectedDate = fmtCurr(d);
  this.isWriting = false;  // 切日期时退出编辑模式
}

goNextDay(): void {
  // 同理,setDate(getDate() + 1)
}

goToday(): void {
  this.currentDate = new Date();
  this.selectedDate = fmtCurr(this.currentDate);
  this.isWriting = false;
}

isToday() 判断 selectedDate 是否等于今天的字符串——如果是,日期下方显示金色"今天"标签,点击时隐藏"回今天"按钮。这种动态显隐减少了"今天按钮在今天时仍可点击"的冗余交互。

切换日期时自动退出编辑模式(this.isWriting = false)——这是一个重要的 UX 细节。如果用户正在编辑 A 天的日记,不小心滑到了 B 天,未保存的内容不会跨天污染数据。
在这里插入图片描述

三、写入模式

3.1 模式切换

页面有两个核心模式——浏览模式和写入模式,通过 isWriting 标志切换:

if (this.isWriting) {
  // 写入模式:心情选择 + 标题 + 内容 + 保存/取消
} else {
  // 浏览模式:当天日记 + 最近列表
}

浏览模式下,FAB("+ 写日记"按钮)浮动在右下角;写入模式下 FAB 隐藏——避免用户误触"写日记"进入另一个写日记的界面。

3.2 新建 vs 编辑

同一个写入模式服务于两个场景——新建和编辑。区别在于 editId

startNew(): void {
  this.isWriting = true;
  this.editId = -1;           // -1 表示新建
  this.editMood = '😊';
  this.editTitle = '';
  this.editContent = '';
}

editEntry(entry: EntryInfo): void {
  this.isWriting = true;
  this.editId = entry.id;     // 已有 ID,表示编辑
  this.editMood = entry.mood;
  this.editTitle = entry.title;
  this.editContent = entry.content;
}

新建时 editId = -1,编辑时 editId = 实际的 id。保存时根据 editId 判断——新建在数组头部插入新条目,编辑在原位置更新条目的各字段。

编辑模式下还显示"删除这篇日记"按钮(红色文字、透明背景)。删除后退出写入模式——日记消失,回到浏览模式。

3.3 心情选择器

6 个 emoji 排列成一行,点击切换选中状态:

Row() {
  ForEach(MOODS, (mood: string) => {
    Text(mood)
      .fontSize(28)
      .opacity(this.editMood === mood ? 1.0 : 0.4)
      .onClick(() => { this.editMood = mood; })
  })
}

选中和未选中的区分用不透明度opacity)而非颜色或边框。不透明度方案的优势:它适用于任何 emoji 和任何背景色,不需要为每种心情配置选中态样式。高不透明度 = 选中(视觉更重),低不透明度 = 未选中(视觉更轻)——这种设计语言用户直觉上就能理解。

3.4 内容编辑

TextInput(标题)+ TextArea(正文)的组合。标题单行、加粗、大号字;正文多行、常规字重、160vp 高度。两者都配置了 placeholder——标题占位"日记标题",正文占位"今天发生了什么…"。

保存按钮在标题和内容都为空时显示灰色(禁用态),有任一输入后变为蓝色(可用态)。这允许用户只写标题不写内容,或只写内容不写标题——但不能提交完全空的日记。
在这里插入图片描述

四、浏览模式

4.1 当天日记

浏览模式下,首先显示当天(selectedDate)的日记:

dayEntries(): EntryInfo[] {
  return this.entries.filter(e => e.date === this.selectedDate);
}

如果当天有日记,以白色卡片展示:左侧大号心情 emoji,中间标题 + 正文前 3 行(maxLines(3) + 省略号),右侧编辑(✎)和删除(×)按钮。

如果当天没有日记,显示空状态:📝 emoji + "这天还没有日记"灰色提示。FAB 按钮始终可见——用户看到空状态后立刻知道点哪里写日记。

4.2 最近列表

"当天日记"下面是"最近日记"列表——按日期倒序展示所有日记条目:

recentEntries(): EntryInfo[] {
  return [...this.entries].sort((a, b) => b.date.localeCompare(a.date));
}

每条显示心情 emoji(24 号)、标题(加粗)、日期(MM月DD日 格式)。点击某条 → 跳转到该日期(更新 currentDateselectedDate),相当于自动导航到那天的日记。

这种设计让"最近列表"成了一个隐式的历史导航器——用户不需要逐天翻页找旧日记,在最近列表里点一下就到了。

4.3 编辑与删除入口

每条日记卡上两个小图标:

  • ✎(编辑):点击进入写入模式,预填该日记的所有字段。用户可以修改心情、标题、内容。
  • ×(删除):点击直接删除,无需确认弹窗。因为日记内容已经在卡片上可见,用户不会误删——如果多加一个"确认删除"弹窗反而增加操作步骤。

两个图标都是浅灰色(#BBBBCC),视觉上低调——它们不是用户的主要操作目标,但需要时容易找到。
在这里插入图片描述

五、完整代码结构

DiaryPage
├── Column(根容器)
│   ├── Row(标题栏:"📔 心情日记")
│   ├── Row(日期导航器:‹ 日期/今天 ›)
│   └── 条件切换:
│       ├── 写入模式
│       │   ├── Row(心情选择器:6 个 emoji)
│       │   ├── TextInput(标题)
│       │   ├── TextArea(正文)
│       │   ├── Row(取消 + 保存)
│       │   └── Button(删除 ×,仅编辑模式)
│       │
│       └── 浏览模式
│           ├── Column(当天日记区域)
│           │   ├── Text(区域标题)
│           │   ├── if 空态 → 空状态提示
│           │   └── ForEach → 日记卡片(心情 + 标题 + 正文 + 编辑/删除)
│           │
│           └── Column(最近日记区域)
│               ├── Text(区域标题)
│               └── ForEach → 列表行(心情 + 标题 + 日期,点击跳转)
│
└── Button(FAB:"+ 写日记",仅浏览模式显示)

六、总结

本文从零构建了一个心情日记本。与前面三篇的习惯追踪(状态标记)、记账本(结构化数据)、番茄钟(时间驱动)不同,日记本是自由文本 + 情感标签的 CRUD 应用。

核心要点回顾:

  1. 日期导航goPrevDay/goNextDay 通过 setDate(getDate() ± 1) 切换日期,goToday 回到今天。切换日期时自动退出编辑模式,防止跨天数据污染。

  2. 心情选择器:6 个 emoji,用 opacity 区分选中(1.0)和未选中(0.4)。不透明度方案适用于所有 emoji,无需为每种心情单独配置选中态样式。

  3. 模式切换isWriting 控制写入/浏览模式切换。新建时 editId = -1,编辑时 editId = 实际 ID,保存时根据此判断增还是改。

  4. 当天日记 + 最近列表:双层展示——当天日记用卡片式(标题 + 正文前 3 行),最近列表用紧凑行式(标题 + 日期)。点击最近条目自动跳转到对应日期。

  5. FAB 浮动按钮:浏览模式下固定在右下角,写入模式下隐藏。避免用户误触重复进入写入模式。

  6. 删除无需确认:日记内容已在卡片上可见,用户不会误删。省略确认弹窗减少操作步骤——这是"可见即确认"的设计原则。

Logo

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

更多推荐