在这里插入图片描述

一、应用概述与设计理念

心情日记小应用是一款记录每日心情的温馨工具,灵感来源于人们记录生活点滴的习惯。在快节奏的现代生活中,我们经常会有各种情绪体验,开心、难过、平静、激动等。记录这些心情不仅可以帮助我们回顾生活,还能帮助我们更好地理解自己的情绪变化规律。

这款应用基于HarmonyOS ArkUI框架开发,采用了声明式UI开发范式。用户可以选择表情符号表达心情,并写下当天的感受。应用会自动记录日期,并将日记条目按时间顺序展示。界面温馨简洁,操作简单直观,适合各年龄段用户使用。

从技术角度来看,这个应用涵盖了HarmonyOS开发中的多个核心知识点:自定义数据模型、表情选择器实现、日期格式化、List列表渲染、Flex布局应用、数据管理等。通过学习这个应用的开发过程,开发者可以深入理解ArkTS语言的日期处理能力和列表渲染机制。

二、功能特性详解

2.1 核心功能列表

心情日记小应用提供了以下核心功能:

功能模块 功能描述 技术实现
心情选择 8种表情符号可选 Button + ForEach
日记输入 TextInput记录心情文字 TextInput + onChange
日期记录 自动记录当前日期 Date对象 + 格式化
日记保存 创建并保存日记条目 Button + onClick
历史展示 List列表展示历史日记 List + ForEach
数据管理 日记数据的增删操作 数组操作方法

2.2 用户交互流程

不为空

为空

打开应用

选择心情表情

输入日记内容

点击保存按钮

内容是否为空

创建日记条目

添加到历史列表

清空输入框

列表展示日记

不保存日记

查看历史日记

2.3 界面设计说明

应用的界面设计遵循温馨简洁的原则,主要分为以下几个区域:

  1. 顶部标题栏:包含返回按钮和应用标题,采用灰色背景
  2. 心情选择区:8种表情符号,圆形按钮设计,选中时背景变色
  3. 输入区域:TextInput组件用于输入日记内容,高度设置为80
  4. 保存按钮:蓝色按钮,宽度60%,居中显示
  5. 历史记录区:List列表展示历史日记,卡片式设计

三、数据模型设计

3.1 日记数据模型

在ArkTS中,使用class定义日记数据模型:

class DiaryEntry_1 {
  date_1: string = '';
  mood_1: string = '';
  text_1: string = '';

  constructor(date_1: string, mood_1: string, text_1: string) {
    this.date_1 = date_1;
    this.mood_1 = mood_1;
    this.text_1 = text_1;
  }
}

数据模型分析:

属性 类型 说明 示例值
date_1 string 日记日期 ‘2024-06-15’
mood_1 string 心情表情 ‘😊’
text_1 string 日记内容 ‘今天心情很好’

构造函数说明:

  • 构造函数接收三个参数:日期、表情、内容
  • 使用构造函数可以方便地创建新的日记实例
  • 初始值设置为空字符串,确保属性初始化

3.2 数据模型的优势

使用class定义数据模型有以下优势:

优势 说明 应用场景
类型安全 明确定义属性类型 避免类型错误
结构清晰 数据结构一目了然 代码可读性强
易于扩展 可以方便添加新属性 功能扩展方便
语义明确 DiaryEntry名称直观 理解数据含义

3.3 数据模型扩展方向

如果需要扩展日记功能,可以在数据模型中添加更多属性:

class EnhancedDiaryEntry {
  id: string = '';              // 日记唯一标识
  date: string = '';            // 日记日期
  mood: string = '';            // 心情表情
  text: string = '';            // 日记内容
  weather: string = '';         // 天气信息
  location: string = '';        // 地理位置
  tags: string[] = [];          // 标签列表
  images: string[] = [];        // 图片列表
  createTime: Date = new Date(); // 创建时间
}

四、状态管理实现

4.1 状态变量声明

应用使用了多个状态变量来管理数据:

@State selectedMood_1: string = '😊';
@State diaryText_1: string = '';
@State entries_1: DiaryEntry_1[] = [
  new DiaryEntry_1('2024-06-15', '😊', '今天心情很好,天气也不错'),
  new DiaryEntry_1('2024-06-14', '😐', '平平淡淡的一天'),
  new DiaryEntry_1('2024-06-13', '😢', '有点难过,工作不太顺利')
];

状态变量说明表:

状态变量 类型 初始值 作用
selectedMood_1 string ‘😊’ 当前选中的心情表情
diaryText_1 string ‘’ 用户输入的日记内容
entries_1 DiaryEntry_1[] […] 存储所有日记条目

4.2 表情配置

应用提供了8种心情表情:

moods_1: string[] = ['😊', '😄', '😍', '😐', '😔', '😢', '😤', '😴'];

表情方案表:

表情 名称 心情含义 建议场景
😊 微笑 开心愉快 好心情、小确幸
😄 大笑 非常开心 特别开心的事
😍 爱慕 喜欢喜爱 恋爱、喜爱的事物
😐 平淡 平静普通 日常平淡
😔 失落 失望沮丧 不如意的事
😢 难过 悲伤难过 遇到困难
😤 生气 愤怒不满 不开心的事
😴 困倦 疲惫困乏 累了、想休息

4.3 状态变量响应机制

当状态变量变化时,UI会自动更新:

界面组件 状态变量 TextInput 表情按钮 用户 界面组件 状态变量 TextInput 表情按钮 用户 选择表情 更新selectedMood_1 触发表情背景变化 输入日记 更新diaryText_1 点击保存 创建日记条目 触发列表更新 显示新日记

五、表情选择器实现

5.1 表情选择器UI结构

表情选择器使用Flex布局,支持自动换行:

Flex({ wrap: FlexWrap.Wrap }) {
  ForEach(this.moods_1, (mood_1: string) => {
    Button(mood_1)
      .width(56)
      .height(56)
      .fontSize(28)
      .backgroundColor(this.selectedMood_1 === mood_1 ? '#E8F0FE' : '#F1F3F5')
      .borderRadius(28)
      .onClick(() => {
        this.selectedMood_1 = mood_1;
      })
      .margin({ right: 8 })
  })
}
.margin({ top: 12 })

5.2 Flex布局详解

Flex是弹性布局容器,支持灵活的子组件排列:

Flex容器

wrap: FlexWrap.Wrap

支持自动换行

ForEach循环

Button表情按钮

width: 56

height: 56

fontSize: 28

borderRadius: 28

圆形设计

Flex属性说明:

属性 说明
wrap FlexWrap.Wrap 支持自动换行
direction FlexDirection.Row 横向排列(默认)
justifyContent FlexAlign.Start 从左开始排列
alignItems ItemAlign.Center 垂直居中

5.3 Button表情按钮详解

表情按钮采用圆形设计:

Button(mood_1)
  .width(56)
  .height(56)
  .fontSize(28)
  .backgroundColor(this.selectedMood_1 === mood_1 ? '#E8F0FE' : '#F1F3F5')
  .borderRadius(28)

按钮属性说明:

属性 说明
text mood_1 表情符号作为按钮文本
width 56 宽度56px
height 56 高度56px
fontSize 28 字体大小28px
borderRadius 28 圆角半径28px(圆形)
backgroundColor 动态颜色 选中时蓝色背景

5.4 动态背景颜色

表情按钮的背景颜色根据选中状态动态变化:

.backgroundColor(this.selectedMood_1 === mood_1 ? '#E8F0FE' : '#F1F3F5')

颜色逻辑:

  • 选中时:#E8F0FE(淡蓝色)
  • 未选中:#F1F3F5(灰色)
  • 提供直观的选中反馈
  • 圆形设计更加美观

5.5 ForEach循环渲染

ForEach用于循环渲染表情按钮:

ForEach(this.moods_1, (mood_1: string) => {
  Button(mood_1)
    // ...
})

ForEach特点:

  • 遍历moods数组
  • 为每个表情创建按钮
  • 自动处理数组变化
  • 提高渲染效率

六、日记输入处理

6.1 TextInput组件实现

日记输入使用TextInput组件:

TextInput({ placeholder: '写下今天的心情...' })
  .width('90%')
  .height(80)
  .margin({ top: 20 })
  .onChange((value_1: string) => {
    this.diaryText_1 = value_1;
  })

6.2 TextInput属性说明

属性 说明
placeholder ‘写下今天的心情…’ 占位提示文本
width ‘90%’ 宽度为父容器90%
height 80 高度80px,舒适输入
onChange 回调函数 输入变化时更新状态

6.3 onChange回调详解

onChange回调实时更新输入内容:

.onChange((value_1: string) => {
  this.diaryText_1 = value_1;
})

回调机制说明:

  • 参数value_1:当前输入的文本
  • 每次输入变化都会触发
  • 实时更新状态变量
  • 为保存提供输入数据

6.4 输入高度设计

日记输入框高度设置为80px,提供舒适的输入体验:

高度 适用场景 优势
40px 简短输入 紧凑布局
60px 中等输入 平衡设计
80px 详细日记 舒适体验

七、日记保存功能

7.1 保存按钮实现

保存按钮用于创建日记条目:

Button('保存日记')
  .width('60%')
  .height(40)
  .margin({ top: 16 })
  .backgroundColor('#0A59F7')
  .onClick(() => {
    let now_1: Date = new Date();
    let date_1: string = now_1.getFullYear() + '-' + 
                        String(now_1.getMonth() + 1).padStart(2, '0') + '-' + 
                        String(now_1.getDate()).padStart(2, '0');
    let newEntry_1 = new DiaryEntry_1(date_1, this.selectedMood_1, this.diaryText_1);
    this.entries_1 = [newEntry_1].concat(this.entries_1);
    this.diaryText_1 = '';
  })

7.2 保存流程分析

点击保存按钮

获取当前日期

格式化日期字符串

创建日记条目

添加到列表开头

清空输入框

触发UI更新

显示新日记

7.3 日期获取与格式化

保存日记时需要获取并格式化当前日期:

let now_1: Date = new Date();
let date_1: string = now_1.getFullYear() + '-' + 
                    String(now_1.getMonth() + 1).padStart(2, '0') + '-' + 
                    String(now_1.getDate()).padStart(2, '0');

日期格式化说明:

步骤 方法 说明
获取年份 getFullYear() 四位数年份
获取月份 getMonth() + 1 月份从0开始,需加1
获取日期 getDate() 当前日期
格式化 padStart(2, ‘0’) 补零为两位数

7.4 padStart方法详解

padStart用于字符串补零:

String(now_1.getMonth() + 1).padStart(2, '0')

padStart说明:

参数 说明 示例
targetLength 目标长度 2
padString 补充字符 ‘0’
原字符串 ‘6’ 月份
结果 ‘06’ 补零后

7.5 日记添加到列表

新日记添加到列表开头,最新的在最前面:

this.entries_1 = [newEntry_1].concat(this.entries_1);

添加方式说明:

方法 效果 说明
concat到开头 新日记在最前 最新日记优先显示
concat到末尾 新日记在最后 按时间顺序排列

为什么添加到开头:

  • 用户习惯查看最新日记
  • 最新日记优先展示
  • 符合时间倒序排列习惯
  • 提升用户体验

7.6 输入框清空

保存成功后清空输入框:

this.diaryText_1 = '';

清空说明:

  • 更新状态变量为空字符串
  • TextInput自动清空
  • 方便用户继续写日记
  • 提升交互体验

八、日期处理技术

8.1 Date对象常用方法

JavaScript的Date对象提供了丰富的方法:

方法 返回值 说明
getFullYear() 年份 四位数年份
getMonth() 0-11 月份(需要+1)
getDate() 1-31 日期
getHours() 0-23 小时
getMinutes() 0-59 分钟
getSeconds() 0-59
getTime() 时间戳 毫秒数

8.2 日期格式化技巧

将Date对象格式化为字符串是常见需求:

// 格式化为 YYYY-MM-DD
let date_1: string = year + '-' + 
                    String(month).padStart(2, '0') + '-' + 
                    String(day).padStart(2, '0');

// 使用模板字符串
let formattedDate_1: string = `${year}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}`;

8.3 日期计算注意事项

在进行日期计算时,需要注意以下几点:

注意事项 说明 影响
时区问题 Date对象使用本地时区 时间显示可能不同
月份偏移 getMonth()返回0-11 需要加1
闰年处理 系统自动处理闰年 2月天数正确
边界情况 月末、年末的日期 需要特殊处理

8.4 日期格式扩展

如果需要更详细的日期格式,可以扩展格式化:

// 格式化为 YYYY-MM-DD HH:mm:ss
let fullDate_1: string = now_1.getFullYear() + '-' + 
                        String(now_1.getMonth() + 1).padStart(2, '0') + '-' + 
                        String(now_1.getDate()).padStart(2, '0') + ' ' +
                        String(now_1.getHours()).padStart(2, '0') + ':' +
                        String(now_1.getMinutes()).padStart(2, '0') + ':' +
                        String(now_1.getSeconds()).padStart(2, '0');

九、历史记录展示

9.1 List列表实现

历史记录使用List列表展示:

List() {
  ForEach(this.entries_1, (entry_1: DiaryEntry_1) => {
    ListItem() {
      Row() {
        Column() {
          Text(entry_1.mood_1)
            .fontSize(32)
          Text(entry_1.date_1)
            .fontSize(12)
            .fontColor('#999999')
            .margin({ top: 4 })
        }
        .width(80)
        Text(entry_1.text_1)
          .fontSize(14)
          .layoutWeight(1)
          .padding({ left: 12 })
      }
      .padding(12)
      .width('100%')
      .backgroundColor('#F8F8F8')
      .borderRadius(8)
      .margin({ bottom: 8 })
    }
  })
}
.width('90%')
.margin({ top: 8 })

9.2 List组件详解

List是列表容器组件,用于展示列表数据:

List容器

ForEach循环

ListItem项

Row横向布局

Column左侧

Text右侧

表情显示

日期显示

日记内容

List属性说明:

属性 说明
width ‘90%’ 宽度为父容器90%
margin { top: 8 } 上边距8px

9.3 ListItem组件详解

ListItem是List的子组件,代表列表中的一个项:

ListItem() {
  Row() {
    // 日记内容
  }
  .padding(12)
  .width('100%')
  .backgroundColor('#F8F8F8')
  .borderRadius(8)
  .margin({ bottom: 8 })
}

ListItem特点:

  • 代表列表中的一个项
  • 可以包含任意组件
  • 支持滚动和懒加载
  • 性能优化友好

9.4 日记卡片设计

日记卡片采用Row横向布局:

Row() {
  Column() {
    Text(entry_1.mood_1)
      .fontSize(32)
    Text(entry_1.date_1)
      .fontSize(12)
      .fontColor('#999999')
      .margin({ top: 4 })
  }
  .width(80)
  Text(entry_1.text_1)
    .fontSize(14)
    .layoutWeight(1)
    .padding({ left: 12 })
}

卡片布局说明:

区域 内容 样式
左侧 表情 + 日期 固定宽度80px
右侧 日记内容 layoutWeight自适应
背景 灰色背景 #F8F8F8
圆角 8px圆角 borderRadius(8)

9.5 layoutWeight详解

layoutWeight用于自适应布局:

Text(entry_1.text_1)
  .layoutWeight(1)

layoutWeight说明:

属性 说明
layoutWeight 1 占据剩余空间
固定宽度 80 左侧固定宽度
自适应 剩余宽度 右侧自适应

9.6 卡片样式设计

日记卡片采用灰色背景和圆角设计:

样式 设计意图
backgroundColor #F8F8F8 浅灰色背景,卡片感
borderRadius 8 圆角设计,柔和视觉
padding 12 内边距,内容不贴边
margin { bottom: 8 } 下边距,卡片间距

十、UI布局结构

10.1 整体布局结构

应用采用Column垂直布局作为根容器:

Column 根容器

Row 标题栏

Column 内容区

Button 返回

Text 标题

Text 心情提示

Flex 表情选择器

TextInput 输入框

Button 保存日记

Text 历史记录标题

List 日记列表

ForEach 表情循环

Button 表情按钮

ForEach 日记循环

ListItem 日记项

Row 日记卡片

10.2 标题栏实现

标题栏采用Row横向布局:

Row() {
  Button('返回')
    .onClick(() => router.back())
  Text('心情日记小应用')
    .fontSize(20)
    .fontWeight(FontWeight.Bold)
    .layoutWeight(1)
    .textAlign(TextAlign.Center)
}
.width('100%')
.padding(12)
.backgroundColor('#F1F3F5')

10.3 心情提示实现

心情提示使用Text组件:

Text('今天心情怎么样?')
  .fontSize(16)
  .fontWeight(FontWeight.Medium)
  .margin({ top: 24 })

10.4 响应式布局考虑

布局设计考虑了响应式特性:

特性 实现方式 说明
百分比宽度 width(‘90%’) 自适应屏幕宽度
布局权重 layoutWeight(1) 自适应剩余空间
弹性间距 margin 相对间距
自动换行 FlexWrap.Wrap 表情自动换行

十一、Flex布局详解

11.1 Flex基本概念

Flex是弹性布局容器,提供灵活的子组件排列方式:

Flex({ wrap: FlexWrap.Wrap }) {
  // 子组件
}

11.2 Flex属性说明

属性 可选值 说明
direction Row, Column, RowReverse, ColumnReverse 主轴方向
wrap NoWrap, Wrap, WrapReverse 是否换行
justifyContent Start, Center, End, SpaceBetween, SpaceAround, SpaceEvenly 主轴对齐
alignItems Start, Center, End, Stretch 交叉轴对齐

11.3 FlexWrap.Wrap详解

FlexWrap.Wrap支持自动换行:

Flex({ wrap: FlexWrap.Wrap }) {
  ForEach(this.moods_1, (mood_1: string) => {
    Button(mood_1)
      // ...
  })
}

换行效果:

  • 子组件超出容器宽度时自动换行
  • 适合不确定数量的子组件
  • 表情按钮可以自动排列
  • 适应不同屏幕宽度

11.4 Flex与其他布局对比

布局 特点 适用场景
Row 横向排列,不换行 固定数量的子组件
Column 纵向排列 垂直布局
Flex 弹性排列,可换行 不确定数量的子组件
Stack 层叠排列 重叠显示

十二、数组操作详解

12.1 数组常用方法

本应用使用了多种数组操作方法:

方法 用途 示例
concat 添加元素 [newEntry].concat(entries)
forEach 遍历元素 entries.forEach(entry => …)
filter 过滤元素 entries.filter(entry => …)

12.2 concat方法详解

concat用于合并数组,创建新数组:

this.entries_1 = [newEntry_1].concat(this.entries_1);

concat特点:

特点 说明 优势
不修改原数组 创建新数组 状态变量更新正确
可以添加到开头 [new].concat(old) 新元素在最前
可以添加到末尾 old.concat([new]) 新元素在最后

12.3 添加到开头 vs 添加到末尾

方式 代码 效果
添加到开头 [new].concat(old) 新日记在最前
添加到末尾 old.concat([new]) 新日记在最后

为什么添加到开头:

  • 用户习惯查看最新日记
  • 最新日记优先展示
  • 符合时间倒序排列
  • 提升用户体验

12.4 数组操作最佳实践

在ArkUI中使用数组操作的最佳实践:

// 添加元素:使用concat
this.entries_1 = [newEntry_1].concat(this.entries_1);

// 删除元素:使用filter
this.entries_1 = this.entries_1.filter((_, i_1: number) => i_1 !== index_1);

// 修改元素:使用map
this.entries_1 = this.entries_1.map((entry_1, i_1: number) => {
  if (i_1 === index_1) {
    return new DiaryEntry_1(newDate_1, newMood_1, newText_1);
  }
  return entry_1;
});

十三、应用扩展方向

13.1 心情统计功能

添加心情统计,分析心情变化:

// 统计各心情出现次数
getMoodStatistics(): Record<string, number> {
  let stats: Record<string, number> = {};
  this.entries_1.forEach(entry_1 => {
    if (stats[entry_1.mood_1] === undefined) {
      stats[entry_1.mood_1] = 0;
    }
    stats[entry_1.mood_1]++;
  });
  return stats;
}

// 显示统计
ForEach(Object.keys(stats), (mood_1: string) => {
  Row() {
    Text(mood_1)
    Text(String(stats[mood_1]))
  }
})

13.2 心情日历功能

在日历上显示每天心情:

// 获取某天的心情
getMoodByDate(date: string): string {
  let entry_1 = this.entries_1.find(entry => entry.date_1 === date);
  return entry_1 ? entry_1.mood_1 : '';
}

// 日历展示
Calendar()
  .onDateSelect((date: Date) => {
    let mood_1 = this.getMoodByDate(formatDate(date));
    // 显示心情
  })

13.3 图片记录功能

支持添加图片到日记:

class EnhancedDiaryEntry {
  date: string = '';
  mood: string = '';
  text: string = '';
  images: string[] = [];  // 图片列表
}

// 添加图片按钮
Button('添加图片')
  .onClick(() => {
    // 选择图片
    // 添加到日记
  })

// 显示图片
ForEach(entry.images, (image: string) => {
  Image(image)
    .width(100)
    .height(100)
})

13.4 数据持久化

使用Preferences存储日记数据:

import preferences from '@ohos.data.preferences';

// 保存日记
async saveDiaries() {
  let prefs = await preferences.getPreferences(context, 'mood_diary');
  let diariesJson: string = JSON.stringify(this.entries_1);
  await prefs.put('diaries', diariesJson);
  await prefs.flush();
}

// 加载日记
async loadDiaries() {
  let prefs = await preferences.getPreferences(context, 'mood_diary');
  let diariesJson: string = await prefs.get('diaries', '[]') as string;
  this.entries_1 = JSON.parse(diariesJson);
}

13.5 日记编辑功能

添加编辑已有日记的功能:

@State editingIndex_1: number = -1;
@State editingText_1: string = '';

// 编辑按钮
Button('编辑')
  .onClick(() => {
    this.editingIndex_1 = index_1;
    this.editingText_1 = entry_1.text_1;
  })

// 保存编辑
Button('保存')
  .onClick(() => {
    this.entries_1 = this.entries_1.map((entry_1, i_1: number) => {
      if (i_1 === this.editingIndex_1) {
        return new DiaryEntry_1(entry_1.date_1, entry_1.mood_1, this.editingText_1);
      }
      return entry_1;
    });
    this.editingIndex_1 = -1;
  })

13.6 日记删除功能

添加删除日记的功能:

// 删除按钮
Button('删除')
  .onClick(() => {
    this.entries_1 = this.entries_1.filter((_, i_1: number) => i_1 !== index_1);
  })

13.7 日记搜索功能

添加搜索日记的功能:

@State searchText_1: string = '';

// 搜索输入框
TextInput({ placeholder: '搜索日记...' })
  .onChange((value_1: string) => {
    this.searchText_1 = value_1;
  })

// 搜索过滤
getFilteredEntries(): DiaryEntry_1[] {
  if (this.searchText_1 === '') {
    return this.entries_1;
  }
  return this.entries_1.filter(entry_1 => 
    entry_1.text_1.includes(this.searchText_1)
  );
}

十四、性能优化建议

14.1 渲染优化

  1. 使用唯一key:ForEach中提供唯一key,提高渲染效率
ForEach(this.entries_1, (entry_1: DiaryEntry_1, index_1: number) => {
  ListItem() {
    // ...
  }
}, (entry_1: DiaryEntry_1, index_1: number) => entry_1.date_1)
  1. 条件渲染:避免渲染不必要的组件
if (this.entries_1.length > 0) {
  List() {
    // 日记列表
  }
} else {
  Text('暂无日记记录')
}

14.2 数据优化

  1. 限制日记数量:避免数据过多影响性能
if (this.entries_1.length < 100) {
  this.entries_1 = [newEntry_1].concat(this.entries_1);
} else {
  // 提示用户删除部分日记
}
  1. 分页加载:大数据量时分页展示
@State currentPage: number = 1;
pageSize: number = 20;

getCurrentPageEntries(): DiaryEntry_1[] {
  let start: number = (this.currentPage - 1) * this.pageSize;
  let end: number = start + this.pageSize;
  return this.entries_1.slice(start, end);
}

14.3 内存管理

  1. 及时清理:删除日记时释放内存
  2. 避免重复创建:复用已有组件实例
  3. 合理使用状态变量:减少不必要的状态变量

14.4 用户体验优化

  1. 加载状态:显示加载动画
  2. 错误处理:友好的错误提示
  3. 操作反馈:保存成功后的视觉反馈
  4. 空状态提示:无日记时的友好提示

十五、完整代码清单

15.1 主组件完整代码

import { router } from '@kit.ArkUI';

class DiaryEntry_1 {
  date_1: string = '';
  mood_1: string = '';
  text_1: string = '';

  constructor(date_1: string, mood_1: string, text_1: string) {
    this.date_1 = date_1;
    this.mood_1 = mood_1;
    this.text_1 = text_1;
  }
}

@Entry
@Component
struct MoodDiaryApp {
  @State selectedMood_1: string = '😊';
  @State diaryText_1: string = '';
  @State entries_1: DiaryEntry_1[] = [
    new DiaryEntry_1('2024-06-15', '😊', '今天心情很好,天气也不错'),
    new DiaryEntry_1('2024-06-14', '😐', '平平淡淡的一天'),
    new DiaryEntry_1('2024-06-13', '😢', '有点难过,工作不太顺利')
  ];

  moods_1: string[] = ['😊', '😄', '😍', '😐', '😔', '😢', '😤', '😴'];

  build() {
    Column() {
      Row() {
        Button('返回')
          .onClick(() => router.back())
        Text('心情日记小应用')
          .fontSize(20)
          .fontWeight(FontWeight.Bold)
          .layoutWeight(1)
          .textAlign(TextAlign.Center)
      }
      .width('100%')
      .padding(12)
      .backgroundColor('#F1F3F5')

      Column() {
        Text('今天心情怎么样?')
          .fontSize(16)
          .fontWeight(FontWeight.Medium)
          .margin({ top: 24 })

        Flex({ wrap: FlexWrap.Wrap }) {
          ForEach(this.moods_1, (mood_1: string) => {
            Button(mood_1)
              .width(56)
              .height(56)
              .fontSize(28)
              .backgroundColor(this.selectedMood_1 === mood_1 ? '#E8F0FE' : '#F1F3F5')
              .borderRadius(28)
              .onClick(() => {
                this.selectedMood_1 = mood_1;
              })
              .margin({ right: 8 })
          })
        }
        .margin({ top: 12 })

        TextInput({ placeholder: '写下今天的心情...' })
          .width('90%')
          .height(80)
          .margin({ top: 20 })
          .onChange((value_1: string) => {
            this.diaryText_1 = value_1;
          })

        Button('保存日记')
          .width('60%')
          .height(40)
          .margin({ top: 16 })
          .backgroundColor('#0A59F7')
          .onClick(() => {
            let now_1: Date = new Date();
            let date_1: string = now_1.getFullYear() + '-' + 
                                String(now_1.getMonth() + 1).padStart(2, '0') + '-' + 
                                String(now_1.getDate()).padStart(2, '0');
            let newEntry_1 = new DiaryEntry_1(date_1, this.selectedMood_1, this.diaryText_1);
            this.entries_1 = [newEntry_1].concat(this.entries_1);
            this.diaryText_1 = '';
          })

        Text('历史记录')
          .fontSize(16)
          .fontWeight(FontWeight.Medium)
          .margin({ top: 24, left: 20 })
          .width('90%')

        List() {
          ForEach(this.entries_1, (entry_1: DiaryEntry_1) => {
            ListItem() {
              Row() {
                Column() {
                  Text(entry_1.mood_1)
                    .fontSize(32)
                  Text(entry_1.date_1)
                    .fontSize(12)
                    .fontColor('#999999')
                    .margin({ top: 4 })
                }
                .width(80)
                Text(entry_1.text_1)
                  .fontSize(14)
                  .layoutWeight(1)
                  .padding({ left: 12 })
              }
              .padding(12)
              .width('100%')
              .backgroundColor('#F8F8F8')
              .borderRadius(8)
              .margin({ bottom: 8 })
            }
          })
        }
        .width('90%')
        .margin({ top: 8 })
      }
      .width('100%')
      .layoutWeight(1)
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#FFFFFF')
  }
}

15.2 代码结构说明

代码区域 行数 功能说明
导入声明 1 导入router模块
数据模型 3-13 定义DiaryEntry类
状态声明 18-27 声明状态变量和表情数组
build方法 29-128 UI构建方法

十六、开发经验总结

16.1 技术要点回顾

通过开发这个心情日记应用,我们掌握了以下技术要点:

  1. 数据模型设计:使用class定义日记数据结构
  2. 状态管理:使用@State装饰器管理响应式数据
  3. 表情选择器:Flex布局 + ForEach循环渲染
  4. 日期处理:Date对象 + padStart格式化
  5. List列表:List + ListItem展示历史记录
  6. 数组操作:concat添加元素到开头

16.2 常见问题解决

问题 原因 解决方案
日期格式错误 getMonth()返回0-11 使用时加1
月份显示单数 未补零 使用padStart(2, ‘0’)
UI不更新 直接修改数组 使用concat创建新数组
表情不换行 使用Row布局 使用Flex + FlexWrap.Wrap

16.3 最佳实践建议

  1. 数据管理:使用class定义数据模型,提高代码可维护性
  2. 状态更新:使用数组方法创建新数组,确保UI更新
  3. 日期处理:注意月份偏移和格式化补零
  4. 布局设计:合理使用Flex布局,支持自动换行
  5. 用户体验:提供温馨的界面设计,提升使用体验

十七、总结

心情日记小应用是一款温馨的记录工具,展示了HarmonyOS ArkUI框架在数据管理和UI布局方面的强大能力。应用采用了表情符号选择的设计理念,界面温馨简洁,操作便捷高效。

从技术实现角度来看,应用涵盖了自定义数据模型、状态管理、Flex布局、日期处理、List列表渲染、数组操作等核心技术点。通过学习这个应用的开发过程,开发者可以深入理解ArkTS语言的日期处理能力和列表渲染机制。

应用的核心价值在于帮助用户记录每日心情,回顾生活点滴。8种表情符号让心情表达更加直观,List列表让日记展示更加清晰。未来可以进一步扩展应用的功能,如添加心情统计、心情日历、图片记录、数据持久化、日记编辑、日记删除、日记搜索等特性,使其成为一个更加完善的心情记录工具。

这个应用也可以作为学习HarmonyOS开发的入门项目,帮助开发者快速掌握ArkTS和ArkUI的基础知识,理解日期处理和列表渲染机制。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐