鸿蒙 ArkUI 日冕手记:从 CRUD 到数据叙事的完整实践(HarmonyOS API 24)
《日冕手记:轻量级日记应用设计与实现》 摘要:本文介绍了基于HarmonyOS的轻量级日记应用"日冕手记"的设计与开发。该应用聚焦记录核心场景,提供信息录入、多维管理和统计分析全链路体验。系统采用ArkTS语言开发,通过@State实现数据响应式管理,包含三大功能模块:1)记录模块支持日期、分类、心情等多维度信息录入;2)管理模块提供搜索、过滤、排序等数据操作;3)分析模块实现分类分布、心情趋势等




目录
- 引言
- 产品定位与需求分析
- 数据模型设计与状态管理
- 首页场景:信息聚合与快速入口
- 列表场景:搜索、过滤与排序
- 统计场景:从数据到洞察
- 表单浮层:CRUD 交互设计
- 自定义组件体系与复用
- ArkTS 类型约束与常见陷阱
- 色彩、情感与品牌设计
- 六个应用的横向对比与能力图谱
- 扩展方向与产品化思考
- 总结
一、引言
1.1 「日冕手记」是什么
日冕手记是一款轻量级个人日记/记录管理应用。它不追求大而全,而是聚焦于「记录」这个核心场景,提供从数据录入、列表管理到统计分析的全链路体验。
「日冕」一词取「日光之冕」的意象——每天的生活如同太阳的光芒,而记录就是捕捉这些光芒的瞬间,让它们不至于被时间遗忘。
1.2 产品哲学
日冕手记的设计遵循三条核心理念:
记录即创造
每一次记录都是对当天生活的再创造。标题、内容、心情、分类、标签——五个维度的信息共同构成一个完整的「时间胶囊」。
数据服务于叙事
统计不是为了展示冰冷的数字,而是为了讲述「你的时间去了哪里」「你的心情如何变化」的故事。
简单但不简陋
6 个分类、5 级心情、全文搜索、分类统计——功能点到即止,但每一个都经过精心打磨。
1.3 技术栈
| 项目 | 内容 |
|---|---|
| 操作系统 | HarmonyOS 4.0+ |
| API 版本 | 24(6.1.1) |
| 语言 | ArkTS |
| 布局 | Column / Row / Scroll / GridRow/GridCol / Stack |
| 输入 | TextInput / TextArea / Button |
| 数据流 | @State 驱动 UI 重渲染 |
| 自定义组件 | @Component × 3 |
二、产品定位与需求分析
2.1 用户画像
| 用户类型 | 核心需求 | 使用频率 |
|---|---|---|
| 学生 | 记录学习心得、日常感悟 | 每日 |
| 上班族 | 记录工作日志、情绪管理 | 工作日 |
| 旅行者 | 记录旅行见闻 | 行程中 |
| 自我管理爱好者 | 追踪习惯、分析时间分配 | 每日 |
2.2 功能拆解
日冕手记
│
├── 记录(录入层)
│ ├── 日期(默认当天,支持修改)
│ ├── 分类(6类单选)
│ ├── 标题(必填,单行)
│ ├── 内容(必填,多行)
│ ├── 心情(5级Emoji)
│ └── 标签(逗号分隔,可选)
│
├── 管理(操作层)
│ ├── 列表查看(时间倒序)
│ ├── 分类过滤(6类+全部)
│ ├── 全文搜索(标题/内容/标签)
│ ├── 排序切换(正序/倒序)
│ ├── 编辑(修改已有记录)
│ └── 删除(移除记录)
│
└── 分析(洞察层)
├── 数据概览(总记录/活跃天数/平均心情)
├── 分类分布(每类占比)
├── 心情分布(每级频次)
└── 心情趋势(最近7天)
2.3 与同类应用的差异化
| 对比维度 | 传统日记App | 日冕手记 |
|---|---|---|
| 信息维度 | 时间+内容 | 时间+分类+心情+标签 |
| 数据管理 | 按时间浏览 | 搜索+过滤+排序多维操作 |
| 统计分析 | 无 | 分类分布+心情分布+趋势 |
| 交互模式 | 列表为主 | 首页聚合+列表管理+统计洞察 |
| 目标用户 | 日记爱好者 | 轻量记录+数据分析爱好者 |
2.4 六个应用的能力演进
| 序号 | 应用 | 核心能力 | 新增技能点 |
|---|---|---|---|
| 1 | 栅格布局 | 布局展示 | GridRow/GridCol |
| 2 | 儿童闹钟 | 状态+定时器 | @State / setInterval |
| 3 | 豆豆计数器 | 动画+里程碑 | scale动画 / 条件渲染 |
| 4 | 睡眠vs记忆力 | 数据可视化 | Canvas / 贝塞尔曲线 |
| 5 | 教师课时表 | 管理型CRUD | 表单 / 统计条 |
| 6 | 日冕手记 | 数据叙事 | 搜索/过滤/多维统计 |
三、数据模型设计与状态管理
3.1 数据模型
interface RecordItem {
id: string; // 唯一标识
date: string; // 日期,格式 "2026-06-01"
cat: string; // 分类:工作/学习/生活/健康/旅行/其他
title: string; // 标题
content: string; // 正文内容
mood: number; // 心情等级 1~5
tags: string; // 标签,逗号分隔
}
7 个字段的设计考量:
id:使用 Math.random().toString(36).substring(2, 9) 生成。8 位随机字母数字组合,冲突概率极低,足以满足单机应用需求。
date:使用 ISO 格式字符串 YYYY-MM-DD。这种格式可排序、可比较、可解析,是日期数据的最佳存储格式。
cat:字符串而非索引。虽然使用索引更节省空间,但字符串可读性更高,便于调试和序列化。
title / content:标题必填但较短,内容必填且可长。TextInput 和 TextArea 分别对应这两种输入需求。
mood:1~5 的整数。5 级量表是心理学中最常用的评分体系,既能区分情绪强度,又不会因选项过多而让用户选择困难。
tags:逗号分隔的字符串。为了简单性,没有使用数组,而是用一个字符串存储多个标签,搜索时用 includes() 匹配。
3.2 辅助数据类型
interface MoodDist {
mood: number; // 心情等级 1~5
count: number; // 该等级的记录数
emoji: string; // 对应 Emoji
}
MoodDist 用于统计 Tab 中展示心情分布。将其定义为独立接口而非内联对象类型,是为了满足 ArkTS 的「禁止内联对象类型作为类型声明」的编译约束。
3.3 状态变量设计
| 分组 | 变量 | 类型 | 用途 |
|---|---|---|---|
| 核心数据 | records |
RecordItem[] | 全部记录数据 |
| 列表状态 | filterCat |
string | 当前分类过滤 |
| 列表状态 | searchText |
string | 搜索关键词 |
| 列表状态 | sortAsc |
boolean | 排序方向 |
| 表单状态 | showForm |
boolean | 浮层显隐 |
| 表单状态 | formMode |
string | 添加/编辑 |
| 表单状态 | editId |
string | 编辑中的记录ID |
| 表单状态 | formDate/Cat/Title/Content/Mood/Tags |
多类型 | 表单各字段 |
3.4 数据流
用户操作(点击/输入/滑动)
↓
方法调用(saveRecord / deleteRecord / getFiltered)
↓
@State 变量更新(records / filterCat / searchText)
↓
ArkUI 响应式引擎检测变化
↓
UI 自动重渲染(课表/列表/统计同步更新)
ArkUI 的响应式机制保证了数据的一致性:不管用户在哪一个 Tab 中修改数据,所有 Tab 都能立即感知到变化。
四、首页场景:信息聚合与快速入口
4.1 首页信息架构
首页(HomeTab)
│
├── 欢迎卡片
│ ├── ☀️ 大标题
│ ├── "记录每一天的精彩"
│ └── "用文字留住时光,让回忆有迹可循"
│
├── 快捷统计(3张卡片)
│ ├── 📝 总记录数
│ ├── 📂 分类数
│ └── 💫 平均心情
│
├── 分类浏览(GridRow 2列网格)
│ ├── 💼 工作 → 点击进入列表并过滤
│ ├── 📖 学习 → ...
│ ├── 🏠 生活
│ ├── 💪 健康
│ ├── ✈️ 旅行
│ └── 📌 其他
│
└── 最近记录(最多3条)
├── 😊 标题 + 日期 + 分类
└── 点击可编辑
4.2 欢迎卡片
Column({ space: 8 }) {
Text('☀️').fontSize(48)
Text('记录每一天的精彩').fontSize(18).fontWeight(FontWeight.Bold).fontColor('#333')
Text('用文字留住时光,让回忆有迹可循').fontSize(13).fontColor('#999')
}
.width('100%').padding(24).backgroundColor('#FFFFFF').borderRadius(20)
.alignItems(HorizontalAlign.Center)
设计意图:欢迎卡片不仅是装饰,更是品牌传达的载体。大号 ☀️ Emoji + 两句slogan,在用户打开应用的第一秒就传递出「日冕手记」的产品调性。
4.3 分类网格
GridRow({ columns: 12, gutter: { x: 8, y: 8 } }) {
ForEach(this.categories.filter(c => c !== '全部'), (cat: string) => {
GridCol({ span: 6 }) {
Row({ space: 10 }) {
Text(this.getCatEmoji(cat)).fontSize(24)
Column({ space: 2 }) {
Text(cat).fontSize(14).fontWeight(FontWeight.Medium)
Text(`${this.getCatCount(cat)} 条`).fontSize(11).fontColor('#999')
}
.alignItems(HorizontalAlign.Start)
}
.width('100%').padding(14).backgroundColor('#FFFFFF').borderRadius(14)
.onClick(() => { this.filterCat = cat; this.tabIdx = 1; })
}
}, (cat: string) => cat)
}
使用 GridRow(columns: 12) + GridCol(span: 6) 实现两列等宽网格。每个分类卡片包含 Emoji 图标、分类名称和记录数,点击后跳转到列表页并自动应用该分类的过滤。
注意:categories.filter(c => c !== '全部') 过滤掉「全部」选项,因为首页只展示具体分类。
4.4 最近记录
ForEach(this.records.slice(0, 3), (r: RecordItem) => {
Row({ space: 12 }) {
Text(this.moods[r.mood - 1]).fontSize(24)
Column({ space: 2 }) {
Text(r.title).fontSize(15).fontWeight(FontWeight.Medium).fontColor('#333')
Text(`${this.formatDate(r.date)} · ${r.cat}`).fontSize(11).fontColor('#999')
}
.layoutWeight(1)
}
.onClick(() => { this.openEdit(r); })
})
slice(0, 3) 取前 3 条记录(默认倒序排列是最后 3 条),为用户提供快速进入编辑的入口。
4.5 首页的设计哲学
好的首页应该回答用户的三个问题:
- 「我现在有什么?」 → 快捷统计:总记录/分类数/平均心情
- 「我能做什么?」 → 分类网格:点击进入某个分类
- 「我最近做了什么?」 → 最近记录:快速回顾
三个模块从上到下,从抽象到具体,构成了完整的信息层级。
五、列表场景:搜索、过滤与排序
5.1 搜索栏
TextInput({ placeholder: '🔍 搜索标题、内容或标签...' })
.height(38).padding({ left: 12 }).backgroundColor('#FFFFFF').borderRadius(19)
.layoutWeight(1).fontSize(13)
.onChange((v: string) => { this.searchText = v; })
if (this.searchText !== '') {
Text('✕').fontSize(16).fontColor('#999').onClick(() => { this.searchText = ''; })
}
设计细节:
- 搜索框使用 19px 大圆角,视觉柔和
- 输入内容后显示 ✕ 清空按钮
onChange实时更新搜索文本,搜索结果即时变化
5.2 全文搜索实现
getFiltered(): RecordItem[] {
let list = [...this.records];
if (this.filterCat !== '全部') {
list = list.filter(r => r.cat === this.filterCat);
}
if (this.searchText !== '') {
const q = this.searchText.toLowerCase();
list = list.filter(r =>
r.title.toLowerCase().includes(q) ||
r.content.toLowerCase().includes(q) ||
r.tags.toLowerCase().includes(q)
);
}
list.sort((a, b) => {
const cmp = a.date.localeCompare(b.date);
return this.sortAsc ? cmp : -cmp;
});
return list;
}
搜索覆盖三个字段:标题、内容、标签。用户可以在一个搜索框中找到所有相关内容,无需选择搜索范围。
大小写不敏感:使用 toLowerCase() 转换后比较。
排序:按日期字符串比较,sortAsc 控制正序/倒序。
5.3 分类过滤
Scroll() {
Row({ space: 8 }) {
ForEach(this.categories, (cat: string) => {
Text(`${cat} (${this.getCatCount(cat)})`)
.fontColor(this.filterCat === cat ? '#FFFFFF' : '#666')
.backgroundColor(this.filterCat === cat ? '#FF6F00' : '#F0F0F0')
.borderRadius(16)
.onClick(() => { this.filterCat = cat; })
})
}
}
.height(42)
过滤栏使用 Scroll 包裹,当分类标签过多时支持横向滑动。每个标签显示分类名和记录数,选中态为橙底白字。
5.4 记录卡片
Column({ space: 6 }) {
Row({ space: 10 }) {
Text(this.moods[r.mood - 1]).fontSize(22)
Column({ space: 2 }) {
Text(r.title).fontSize(16).fontWeight(FontWeight.Bold).fontColor('#333')
Text(r.content).fontSize(13).fontColor('#666').lineHeight(20)
.maxLines(2).textOverflow({ overflow: TextOverflow.Ellipsis })
}
.layoutWeight(1)
}
Row({ space: 8 }) {
Text(`${this.formatDate(r.date)}`).fontSize(11).fontColor('#BBB')
Text(`#${r.cat}`).fontSize(11).fontColor('#FF6F00').fontWeight(FontWeight.Medium)
if (r.tags !== '') {
Text(r.tags.split(',')[0]).fontSize(10).fontColor('#4CAF50')
}
Blank()
Text('✏️').fontSize(16).onClick(() => { this.openEdit(r); })
}
}
双层布局:
- 上层(Row):心情 Emoji + 标题(粗体)+ 内容预览(最多 2 行)
- 下层(Row):日期、分类、第一个标签、编辑按钮
maxLines(2) + textOverflow(Ellipsis) 保证内容不会撑高卡片,保持列表统一高度。
5.5 空状态
if (this.getFiltered().length === 0) {
Column() {
Text('📭 暂无匹配记录').fontSize(16).fontColor('#CCC').margin({ top: 60 })
Text('试试其他关键词或分类').fontSize(13).fontColor('#DDD')
}
.width('100%').alignItems(HorizontalAlign.Center).layoutWeight(1)
}
当搜索结果为空时,显示友好的空状态提示而不是空白页面。
六、统计场景:从数据到洞察
6.1 概览卡片
Row({ space: 12 }) {
StatCard({ icon: '📝', value: `${this.records.length}`, label: '总记录', color: '#FF6F00' })
StatCard({ icon: '📅', value: `${this.getActiveDays()}`, label: '活跃天数', color: '#4CAF50' })
StatCard({ icon: '💫', value: `${this.getAvgMood()}`, label: '平均心情', color: '#2196F3' })
}
三张卡片分别回答:「写了多少」「坚持了多久」「心情如何」。
活跃天数通过 Set 去重计算:
getActiveDays(): number {
const set = new Set<string>();
this.records.forEach(r => set.add(r.date));
return set.size;
}
平均心情保留一位小数:
getAvgMood(): number {
if (this.records.length === 0) return 0;
const sum = this.records.reduce((s, r) => s + r.mood, 0);
return Math.round(sum / this.records.length * 10) / 10;
}
6.2 分类分布
ForEach(this.categories.filter(c => c !== '全部'), (cat: string) => {
Row({ space: 12 }) {
Text(this.getCatEmoji(cat)).fontSize(18)
Text(cat).fontSize(14).fontColor('#333').width(40)
Stack() {
Row().width('100%').height(22).backgroundColor('#F0F0F0').borderRadius(11)
Row().width(`${(this.getCatCount(cat) / Math.max(this.records.length, 1)) * 100}%`).height(22)
.backgroundColor(this.getCatColor(cat)).borderRadius(11)
Text(`${this.getCatCount(cat)} 条`).fontSize(11).fontColor('#FFFFFF').fontWeight(FontWeight.Bold)
}
.layoutWeight(1).height(22)
}
})
每个分类一行,包含 Emoji、名称和进度条。进度条宽度 = 该分类记录数 / 总记录数。
6.3 心情分布
ForEach(this.getMoodDist(), (m: MoodDist) => {
Row({ space: 12 }) {
Text(m.emoji).fontSize(20)
Text(`Level ${m.mood}`).fontSize(13).fontColor('#666').width(64)
Stack() {
Row().width('100%').height(24).backgroundColor('#F0F0F0').borderRadius(12)
Row().width(`${(m.count / Math.max(this.records.length, 1)) * 100}%`).height(24)
.backgroundColor(this.getMoodColor(m.mood)).borderRadius(12)
Text(`${m.count} 次`).fontSize(11).fontColor('#FFFFFF').fontWeight(FontWeight.Bold)
}
.layoutWeight(1).height(24)
}
})
getMoodColor 为 5 级心情分配从红到绿的渐变色:
getMoodColor(m: number): string {
const colors = ['#F44336', '#FF9800', '#FFC107', '#8BC34A', '#4CAF50'];
return colors[m - 1] || '#999';
}
6.4 心情趋势
Row({ space: 4 }) {
ForEach(this.records.slice(0, 7), (r: RecordItem) => {
Column({ space: 2 }) {
Text(this.moods[r.mood - 1]).fontSize(18)
Text(r.date.substring(5)).fontSize(9).fontColor('#999')
}
.layoutWeight(1).alignItems(HorizontalAlign.Center)
})
}
最近 7 条记录的心情 Emoji 排列成趋势图。layoutWeight(1) 让每个 Emoji 均匀分布。简洁的「微趋势」视图,让用户一眼看出最近心情的起伏。
6.5 统计模块的设计原则
从数据到洞察的三层递进:
- 数值概览(总记录/活跃天数/平均心情)→ 用户知道「多少」
- 分布构成(分类占比/心情占比)→ 用户知道「什么」
- 时间趋势(最近心情序列)→ 用户知道「变化」
好的数据可视化不是展示数据,而是帮助用户发现数据中的故事。
七、表单浮层:CRUD 交互设计
7.1 浮层架构
@Builder
FormOverlay() {
Column() {
// 遮罩层 — 点击关闭
Column().width('100%').height('100%').backgroundColor('#00000044')
.onClick(() => { this.showForm = false; })
// 表单卡片
Column({ space: 14 }) {
// ... 表单字段 ...
}
.width('90%').padding(20).backgroundColor('#FFFFFF').borderRadius(20)
.shadow({ radius: 12, color: '#30000000', offsetY: -4 })
.alignSelf(ItemAlign.Center)
}
.width('100%').height('100%').justifyContent(FlexAlign.Center)
}
设计模式:一个半透明遮罩 + 一个居中白色卡片。遮罩点击关闭,符合 Material Design 的 Dialog 设计规范。
7.2 心情选择器
Row({ space: 6 }) {
Text('💫').fontSize(18)
ForEach([1, 2, 3, 4, 5], (m: number) => {
Text(this.moods[m - 1]).fontSize(24)
.opacity(this.formMood === m ? 1 : 0.35)
.scale({ x: this.formMood === m ? 1.2 : 1 })
.onClick(() => { this.formMood = m; })
})
}
交互反馈:
- 未选中:35% 透明度
- 选中:100% 透明度 + 1.2 倍缩放
scale 链式调用在选中时放大 Emoji,给予清晰的视觉反馈。注意 .scale() 放在 .opacity() 之后。
7.3 保存按钮的禁用态
Button('💾 保存')
.backgroundColor(this.formTitle && this.formContent ? '#FF6F00' : '#CCC')
.enabled(this.formTitle !== '' && this.formContent !== '')
.onClick(() => { this.saveRecord(); })
标题和内容都填写后才可保存。按钮颜色从灰色 #CCC 变为橙色 #FF6F00。双重反馈(颜色 + enabled 属性)确保用户知道何时可以点击。
7.4 与教师课时表表单的对比
| 对比项 | 课时表表单 | 日冕手记表单 |
|---|---|---|
| 字段数 | 6 | 7 |
| 复杂字段 | 星期+节次选择器(按钮组) | 心情选择器(Emoji) |
| 输入类型 | TextInput | TextInput + TextArea |
| 校验规则 | 科目+班级必填 | 标题+内容必填 |
| 删除按钮 | 编辑模式显示 | 编辑模式显示 |
日冕手记的表单增加了多行文本输入(TextArea)和 Emoji 心情选择器,信息维度更丰富。
八、自定义组件体系与复用
8.1 组件一览
| 组件名 | 用途 | 参数 | 复用次数 |
|---|---|---|---|
| TabBtn | 底部导航项 | icon / label / active / act | 3 |
| QuickCard | 首页快捷统计 | icon / value / label / color | 3 |
| StatCard | 统计页概览卡 | icon / value / label / color | 3 |
8.2 TabBtn
@Component
struct TabBtn {
private icon: string = '';
private label: string = '';
private active: boolean = false;
private act: () => void = () => {};
build() {
Column({ space: 2 }) {
Text(this.icon).fontSize(20)
Text(this.label).fontSize(11)
.fontColor(this.active ? '#FF6F00' : '#999')
.fontWeight(this.active ? FontWeight.Bold : FontWeight.Normal)
}
.layoutWeight(1).height('100%').justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center)
.onClick(() => { this.act(); })
}
}
作为跨应用复用的典范,TabBtn 从儿童闹钟到教师课时表到日冕手记,结构和逻辑几乎不变,只有选中态颜色发生了变化。
8.3 QuickCard 与 StatCard
两个卡片组件的结构和逻辑高度相似:
// QuickCard - 首页使用
Column({ space: 4 }) {
Text(this.icon).fontSize(20)
Text(this.value).fontSize(22).fontWeight(FontWeight.Bold).fontColor(this.color)
Text(this.label).fontSize(11).fontColor('#999')
}
// StatCard - 统计页使用
Column({ space: 6 }) {
Text(this.icon).fontSize(24)
Text(this.value).fontSize(26).fontWeight(FontWeight.Bold).fontColor(this.color)
Text(this.label).fontSize(12).fontColor('#999')
}
区别仅在于字号和间距的微调。理论上可以合并为一个组件,但分开维护的好处是修改一个不影响另一个。
8.4 组件设计原则
- 单一职责:每个组件只做一件事
- 参数驱动:通过参数控制外观和行为
- 回调通信:通过
act: () => void回调与父组件通信 - 无内部状态:所有数据由父组件通过参数传入
九、ArkTS 类型约束与常见陷阱
9.1 内联对象类型禁止
错误信息:
Object literals cannot be used as type declarations (arkts-no-obj-literals-as-types)
错误示例:
// ❌ 方法返回内联对象类型
getMoodDist(): { mood: number; count: number; emoji: string }[] { ... }
// ❌ ForEach 参数使用内联对象类型
ForEach(list, (item: { mood: number; count: number; emoji: string }) => { ... })
解决方案:定义显式接口
interface MoodDist {
mood: number;
count: number;
emoji: string;
}
这是 ArkTS 与标准 TypeScript 的重要区别之一。ArkTS 要求所有对象类型必须有显式声明,不能使用内联类型标注。
9.2 数组修改不触发更新
// ❌ 不触发 UI 更新
this.records[idx].title = '新标题';
// ✅ 触发 UI 更新
this.records[idx].title = '新标题';
this.records = [...this.records]; // 创建新引用
// ✅ push / filter / unshift 也触发
this.records.unshift({ ... });
this.records = this.records.filter(r => r.id !== id);
ArkUI 通过引用比较检测数组变化。直接修改数组元素不会创建新引用,需要显式赋值或使用数组方法。
9.3 分类方法返回值
本应用中多个方法返回统计数据:
// ✅ 正确:方法中声明变量
getMoodDist(): MoodDist[] {
const result: MoodDist[] = [];
for (let i = 1; i <= 5; i++) {
result.push({ ... });
}
return result;
}
// ✅ 正确:显式的 forEach 循环
getDayStats(): DayStat[] {
const arr: DayStat[] = [];
this.weekDays.forEach((day, i) => {
arr.push({ day, count: ... });
});
return arr;
}
注意这里使用了 forEach + push 的 Imperative 风格,而不是 map 的 Functional 风格。这是因为 ArkTS 对某些函数式编程模式的类型推断有限。
9.4 @Builder 内禁止变量声明
@Builder
MyBuilder() {
// ❌ 不允许
const x = this.someMethod();
if (x > 0) { Text(x.toString()) }
// ✅ 允许
if (this.someMethod() > 0) {
Text(this.someMethod().toString())
}
}
这是 ArkTS 最严格的约束之一。@Builder 函数体只能包含 UI 组件语法(创建组件、设置属性、条件/循环渲染)。
9.5 Record 类型的遍历
// ❌ For...in 不支持
for (const key in this.subjectColors) { ... }
// ✅ Object.keys() 可行
ForEach(Object.keys(this.subjectColors), (key: string) => { ... })
Record<string, string> 类型使用 Object.keys() 获取键数组,然后通过 ForEach 遍历。
十、色彩、情感与品牌设计
10.1 主色调的选择
日冕手记采用 暖橙色 #FF6F00 作为主色调。
为什么是橙色?
| 色彩 | 情感联想 | 适用场景 |
|---|---|---|
| 蓝色 | 冷静、专业、理性 | 教师课时表 |
| 绿色 | 健康、自然、成长 | 豆豆计数器 |
| 橙色 | 温暖、活力、记录 | 日冕手记 |
橙色介于红色(激情)和黄色(愉悦)之间,既有活力又不过于热烈。配合「日冕」的品牌名,橙色让人联想到「阳光」「温暖」「记录生活」。这正是日记类应用想要传达的情感。
10.2 页面底色
.backgroundColor('#FFF8E1') // 浅橙黄
#FFF8E1 是一种极淡的橙黄色,类似米纸或旧书页的颜色,让人联想起纸笔记录的怀旧感。同时,浅色底色让白色的卡片和内容更醒目。
10.3 分类配色
| 分类 | 色值 | Emoji | 情感联想 |
|---|---|---|---|
| 工作 | #3F51B5 靛蓝 |
💼 | 专业、严谨 |
| 学习 | #4CAF50 绿 |
📖 | 成长、知识 |
| 生活 | #FF9800 橙 |
🏠 | 温馨、日常 |
| 健康 | #F44336 红 |
💪 | 活力、关注 |
| 旅行 | #9C27B0 紫 |
✈️ | 自由、探索 |
| 其他 | #607D8B 灰蓝 |
📌 | 中性、通用 |
每种颜色与分类语义对应,帮助用户快速识别。
10.4 心情色彩
getMoodColor(m: number): string {
const colors = ['#F44336', '#FF9800', '#FFC107', '#8BC34A', '#4CAF50'];
return colors[m - 1];
}
| 心情 | Emoji | 色值 | 含义 |
|---|---|---|---|
| 1 😞 | 红色 | #F44336 |
很差 |
| 2 😐 | 橙黄 | #FF9800 |
一般 |
| 3 🙂 | 金黄 | #FFC107 |
还行 |
| 4 😊 | 草绿 | #8BC34A |
不错 |
| 5 🤩 | 翠绿 | #4CAF50 |
很棒 |
从红到绿的自然过渡,符合「从差到好」的直觉认知。
10.5 品牌一致性
日冕手记的品牌元素贯穿整个应用:
- 名称:「☀️ 日冕手记」出现在标题栏
- Slogan:「记录每一天的精彩」出现在首页欢迎卡片
- 色调:橙色主色 + 浅橙底色 + 白色卡片
- Emoji:☀️ 太阳作为品牌符号
十一、六个应用的横向对比与能力图谱
11.1 复杂度雷达图
| 维度 | 栅格布局 | 儿童闹钟 | 豆豆计数器 | 睡眠曲线 | 课时表 | 日冕手记 |
|---|---|---|---|---|---|---|
| 布局复杂度 | ★★★★ | ★★ | ★★ | ★★★ | ★★★★ | ★★★ |
| 状态管理 | ★ | ★★★ | ★★★ | ★★ | ★★★ | ★★★★ |
| 交互复杂度 | ★ | ★★ | ★★ | ★★★ | ★★★ | ★★★★ |
| 数据复杂度 | ★ | ★ | ★ | ★★ | ★★★ | ★★★★ |
| 视觉设计 | ★★★ | ★★★ | ★★★ | ★★★ | ★★★ | ★★★★ |
| 代码行数 | ~420 | ~650 | ~380 | ~730 | ~450 | ~615 |
11.2 技能覆盖
| 技能点 | 栅格布局 | 儿童闹钟 | 豆豆计数器 | 睡眠曲线 | 课时表 | 日冕手记 |
|---|---|---|---|---|---|---|
| GridRow/GridCol | ✅ | ✅ | ✅ | |||
| Canvas 绘图 | ✅ | |||||
| @State 管理 | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| @Builder | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| @Component | ✅ | ✅ | ✅ | ✅ | ||
| setInterval | ✅ | ✅ | ||||
| 动画 (scale) | ✅ | ✅ | ✅ | |||
| 定时器 | ✅ | ✅ | ||||
| 条件渲染 | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| ForEach | ✅ | ✅ | ✅ | ✅ | ||
| TextInput | ✅ | ✅ | ||||
| TextArea | ✅ | |||||
| 搜索/过滤 | ✅ | |||||
| 数据统计 | ✅ | ✅ | ✅ | |||
| Scroll | ✅ | ✅ | ✅ | |||
| CRUD | ✅ | ✅ |
11.3 学习路径建议
入门 → 栅格布局(布局基础)
↓
基础 → 儿童闹钟(状态管理 + 生命周期)
↓
进阶 → 豆豆计数器(动画 + 里程碑系统)
↓
可视化 → 睡眠曲线(Canvas 绘图 + 数据可视化)
↓
管理 → 教师课时表(CRUD + 表单 + 统计条)
↓
综合 → 日冕手记(搜索 + 过滤 + 多维统计 + 情感设计)
每个应用都在前一个的基础上引入新的技能点,逐步构建完整的鸿蒙 ArkUI 开发知识体系。
十二、扩展方向与产品化思考
12.1 功能扩展
1. 数据持久化
当前数据存储在内存中,应用重启后丢失。使用 @ohos.data.preferences 或 @ohos.data.relationalStore 实现持久化:
import { preferences } from '@kit.ArkData';
async saveRecords(records: RecordItem[]): Promise<void> {
const prefs = await preferences.getPreferences(this.context, 'records');
await prefs.put('data', JSON.stringify(records));
await prefs.flush();
}
2. 图片附件
支持为每条记录添加图片,使用 @ohos.multimedia.media API 拍照或从相册选择。
3. 日历视图
在首页增加日历视图,有记录的日子显示小圆点,点击查看当天记录。
4. 导出备份
支持将记录导出为 JSON 或 Markdown 格式,通过分享功能发送到其他应用。
5. 密码锁
设置应用密码或生物识别锁,保护隐私。
6. 云同步
通过华为云或第三方云服务,在多设备间同步记录。
12.2 产品化思考
从「应用」到「产品」,需要考虑的远不止功能:
用户 onboarding
首次使用时,展示引导页或示例数据,帮助用户快速上手。本应用在 aboutToAppear 中初始化了 5 条示例记录,用户打开即有内容可看。
数据安全感
日记/记录类应用最核心的用户需求是「数据不丢失」。需要明确的「已保存」提示和自动保存机制。
情感设计
奖励机制、连续记录提醒、里程碑祝贺——这些「情感化设计」能提升用户的长期使用意愿。
隐私保护
记录内容属于高度隐私数据。应用需要明确说明数据存储位置(本地/云端),并提供数据清除功能。
12.3 从开发者到产品思维
回顾六个应用的开发过程,我们经历了一条从「技术实现」到「产品设计」的进化路径:
| 阶段 | 关注点 | 问题 |
|---|---|---|
| 1~2 | 「这个功能怎么做」 | 技术实现 |
| 3~4 | 「怎么做才好看」 | 用户体验 |
| 5~6 | 「做什么才有用」 | 产品价值 |
日冕手记作为这个系列的收官之作,它的价值不在于技术难度,而在于它思考了「用户为什么要用这个应用」这个问题。
十三、总结
13.1 核心知识点
| 技术 | 关键代码 |
|---|---|
| 多维数据过滤 | getFiltered() 同时处理搜索+分类+排序 |
| 全文搜索 | toLowerCase().includes() 三字段匹配 |
| 过滤栏 | Scroll + Row 横向滑动 |
| 心情选择器 | opacity + scale 选中态反馈 |
| 统计分布 | Stack + Row 构建进度条 |
| TextArea 多行输入 | TextArea({ height: 140 }) |
| @State 数组更新 | unshift() / filter() / 展开运算符 |
13.2 六个应用的共同模式
回顾六个应用,无论功能如何变化,都遵循着相同的架构模式:
@State 变量 → @Builder UI 组件 → 交互事件 → 方法调用 → @State 更新
这是 ArkUI 响应式编程的核心循环。掌握了这个循环,就掌握了鸿蒙应用开发的精髓。
13.3 写在最后
从栅格布局到日冕手记,六个应用覆盖了鸿蒙 ArkUI 开发的完整知识体系:
- 布局系统:GridRow/GridCol、Column/Row、Stack、Scroll
- 状态管理:@State、@Builder、@Component
- 生命周期:aboutToAppear、aboutToDisappear
- 交互事件:onClick、onChange
- 动画效果:scale、transition
- 数据可视化:Canvas、条形图、进度条
- CRUD 操作:增删改查、表单校验
- 搜索过滤:全文搜索、多维筛选
- 数据统计:聚合、分布、趋势
这不仅是技术的积累,更是产品思维的进阶。希望这六篇博客能为你提供从入门到实战的完整指引。
本文所有代码基于 HarmonyOS API 24(6.1.1),使用 ArkTS 语言编写。完整源码请参考项目中的 Index.ets 文件。
更多推荐




所有评论(0)