Flutter for OpenHarmony 三方库实战:使用 dayjs 构建校园日程助手页面
在校园类应用中,时间相关功能非常常见,例如课程表、考试倒计时、活动提醒、会议安排等。这些功能看起来只是展示几行文字,但实际开发时会涉及时间格式转换、时间差计算、状态判断等逻辑。如果直接使用原生Date对象处理这些内容,代码会比较繁琐,可读性也不够好。因此本篇文章选择使用三方库dayjs来完成时间格式化和日程状态判断。本篇文章以“校园日程助手”为场景,使用 OpenHarmony 项目中的 ArkT
欢迎加入开源鸿蒙跨平台社区:
https://openharmonycrossplatform.csdn.net
项目效果
本文实现的是一个基于 dayjs 的校园日程助手页面。应用启动后,会自动生成几条校园日程数据,并使用 dayjs 对时间进行格式化处理,最终以卡片列表的形式展示课程、地点、教师、时间范围和当前状态。
最终运行效果如下:

页面主要包含以下内容:
- 顶部标题:校园日程助手;
- 当前时间展示;
- 日程卡片列表;
- 每个卡片展示课程名称、地点、教师、时间范围和状态;
- 支持点击刷新按钮重新更新时间;
- 页面支持上下滚动查看更多日程。
这个页面可以作为课程表、考试提醒、社团活动安排、校园待办事项等功能的基础版本。
前言
在校园类应用中,时间相关功能非常常见,例如课程表、考试倒计时、活动提醒、会议安排等。这些功能看起来只是展示几行文字,但实际开发时会涉及时间格式转换、时间差计算、状态判断等逻辑。
如果直接使用原生 Date 对象处理这些内容,代码会比较繁琐,可读性也不够好。因此本篇文章选择使用三方库 dayjs 来完成时间格式化和日程状态判断。
本篇文章以“校园日程助手”为场景,使用 OpenHarmony 项目中的 ArkTS 结合 dayjs 实现一个简单的日程卡片页面。通过这个项目,可以理解三方库在 OpenHarmony 项目中的基本接入方式,也可以掌握如何在页面中处理时间数据。
相比手动拼接日期字符串,dayjs 的写法更简洁,也更适合后续扩展倒计时、日历、课程提醒等功能。
一、项目目标
本次实践主要实现以下目标:
- 在 OpenHarmony 项目中安装
dayjs三方库; - 在 ArkTS 页面中引入并使用
dayjs; - 使用
dayjs格式化当前时间; - 使用
dayjs生成模拟日程数据; - 根据当前时间判断日程状态;
- 使用 ArkUI 的
List和ForEach渲染日程卡片; - 实现刷新按钮,重新更新时间显示;
- 在 OpenHarmony 模拟器中完成运行验证。
二、技术栈
| 类型 | 内容 |
|---|---|
| 实战方向 | Flutter for OpenHarmony 三方库实战 |
| 实现平台 | OpenHarmony |
| 开发语言 | ArkTS |
| 三方库 | dayjs |
| UI 框架 | ArkUI |
| 功能场景 | 校园日程 / 课程提醒 |
| 开发工具 | DevEco Studio |
| 运行环境 | OpenHarmony 模拟器 |
三、为什么选择 dayjs
在应用开发中,时间处理是很常见但也很容易写乱的部分。比如:
- 当前时间格式化;
- 课程开始时间展示;
- 活动结束时间展示;
- 判断日程是否已经开始;
- 计算距离开始还有多久;
- 将时间转换成更适合用户阅读的格式。
如果全部使用原生 Date 来处理,代码会比较长,而且格式化时不够直观。dayjs 是一个轻量级时间处理库,常用于日期格式化、时间计算和时间展示场景。
在本项目中,dayjs 主要负责:
- 获取当前时间;
- 格式化当前时间;
- 生成模拟日程时间;
- 格式化日程开始和结束时间;
- 判断日程是未开始、进行中还是已结束。
这样可以让页面逻辑更加清晰,代码也更容易维护。
四、安装 dayjs 三方库
在项目根目录打开终端,执行以下命令:
ohpm install dayjs
安装完成后,可以检查项目中是否出现以下内容:
oh_modules
oh-package-lock.json5
oh-package.json5
如果依赖安装成功,就可以在 ArkTS 页面中引入:
import dayjs from 'dayjs';
五、项目结构
本次主要修改页面文件即可:
entry
└── src
└── main
└── ets
└── pages
└── Index.ets
如果你的项目中原本已经有 Index.ets,可以直接替换页面内容。
文件说明:
| 文件 | 作用 |
|---|---|
| Index.ets | 页面展示、时间处理、日程数据渲染 |
因为本项目不需要请求网络接口,所以不需要额外配置网络权限。页面中使用的是本地模拟日程数据,重点是展示 dayjs 在时间格式化和状态判断中的使用方式。
六、定义日程数据结构
在页面中先定义日程数据类型:
interface ScheduleItem {
id: number;
title: string;
location: string;
teacher: string;
startTime: string;
endTime: string;
tag: string;
}
字段说明如下:
| 字段 | 含义 |
|---|---|
| id | 日程编号 |
| title | 课程或活动名称 |
| location | 地点 |
| teacher | 教师或负责人 |
| startTime | 开始时间 |
| endTime | 结束时间 |
| tag | 日程类型标签 |
通过定义数据结构,后续页面渲染时可以更清楚地知道每个字段的作用。
七、页面状态设计
页面中主要使用两个状态变量:
@State nowText: string = '';
@State schedules: ScheduleItem[] = [];
含义如下:
nowText:保存当前时间展示文本;schedules:保存日程列表数据。
页面启动后,会通过 aboutToAppear() 初始化日程数据,并更新时间文本:
aboutToAppear(): void {
this.initSchedules();
this.updateNowText();
}
这样应用一进入页面,就能显示当前时间和日程卡片。
八、Index.ets 完整代码
打开文件:
entry/src/main/ets/pages/Index.ets
完整代码如下:
import dayjs from 'dayjs';
interface ScheduleItem {
id: number;
title: string;
location: string;
teacher: string;
startTime: string;
endTime: string;
tag: string;
}
@Entry
@Component
struct Index {
@State nowText: string = '';
@State schedules: ScheduleItem[] = [];
aboutToAppear(): void {
this.initSchedules();
this.updateNowText();
}
initSchedules(): void {
const now = dayjs();
this.schedules = [
{
id: 1,
title: '计算机网络',
location: '教学楼 A302',
teacher: '王老师',
startTime: now.add(30, 'minute').format('YYYY-MM-DD HH:mm:ss'),
endTime: now.add(120, 'minute').format('YYYY-MM-DD HH:mm:ss'),
tag: '专业课'
},
{
id: 2,
title: '数据结构实验',
location: '实验楼 B405',
teacher: '李老师',
startTime: now.add(3, 'hour').format('YYYY-MM-DD HH:mm:ss'),
endTime: now.add(5, 'hour').format('YYYY-MM-DD HH:mm:ss'),
tag: '实验课'
},
{
id: 3,
title: '英语口语练习',
location: '图书馆自习区',
teacher: '自学任务',
startTime: now.add(1, 'day').format('YYYY-MM-DD HH:mm:ss'),
endTime: now.add(1, 'day').add(1, 'hour').format('YYYY-MM-DD HH:mm:ss'),
tag: '学习计划'
},
{
id: 4,
title: '社团项目讨论',
location: '大学生活动中心',
teacher: '项目组',
startTime: now.add(2, 'day').format('YYYY-MM-DD HH:mm:ss'),
endTime: now.add(2, 'day').add(2, 'hour').format('YYYY-MM-DD HH:mm:ss'),
tag: '社团活动'
},
{
id: 5,
title: '高等数学复习',
location: '宿舍自习',
teacher: '复习任务',
startTime: now.subtract(1, 'hour').format('YYYY-MM-DD HH:mm:ss'),
endTime: now.add(20, 'minute').format('YYYY-MM-DD HH:mm:ss'),
tag: '复习计划'
}
];
}
updateNowText(): void {
this.nowText = dayjs().format('YYYY年MM月DD日 HH:mm');
}
getTimeRange(item: ScheduleItem): string {
const start = dayjs(item.startTime).format('MM月DD日 HH:mm');
const end = dayjs(item.endTime).format('HH:mm');
return `${start} - ${end}`;
}
getStatus(item: ScheduleItem): string {
const now = dayjs();
const start = dayjs(item.startTime);
const end = dayjs(item.endTime);
const nowValue = now.valueOf();
const startValue = start.valueOf();
const endValue = end.valueOf();
if (nowValue < startValue) {
const minutes = start.diff(now, 'minute');
if (minutes < 60) {
return `还有 ${minutes} 分钟开始`;
}
const hours = start.diff(now, 'hour');
if (hours < 24) {
return `还有 ${hours} 小时开始`;
}
const days = start.diff(now, 'day');
return `还有 ${days} 天开始`;
}
if (nowValue >= startValue && nowValue <= endValue) {
return '进行中';
}
return '已结束';
}
getStatusColor(item: ScheduleItem): string {
const status = this.getStatus(item);
if (status === '进行中') {
return '#00A870';
}
if (status === '已结束') {
return '#999999';
}
return '#0A59F7';
}
build() {
Column() {
Row() {
Column() {
Text('校园日程助手')
.fontSize(28)
.fontWeight(FontWeight.Bold)
.fontColor('#182431')
Text(`当前时间:${this.nowText}`)
.fontSize(14)
.fontColor('#666666')
.margin({ top: 6 })
}
.alignItems(HorizontalAlign.Start)
.layoutWeight(1)
Button('刷新')
.fontSize(14)
.height(36)
.onClick(() => {
this.updateNowText();
})
}
.width('100%')
.padding({
left: 16,
right: 16,
top: 24,
bottom: 14
})
List({ space: 12 }) {
ForEach(this.schedules, (item: ScheduleItem) => {
ListItem() {
Column() {
Row() {
Text(item.tag)
.fontSize(13)
.fontColor('#0A59F7')
.padding({
left: 10,
right: 10,
top: 4,
bottom: 4
})
.backgroundColor('#EAF2FF')
.borderRadius(12)
Blank()
Text(this.getStatus(item))
.fontSize(13)
.fontColor(this.getStatusColor(item))
}
.width('100%')
Text(item.title)
.fontSize(20)
.fontWeight(FontWeight.Bold)
.fontColor('#182431')
.margin({ top: 12 })
Text(`地点:${item.location}`)
.fontSize(14)
.fontColor('#666666')
.margin({ top: 8 })
Text(`教师/负责人:${item.teacher}`)
.fontSize(14)
.fontColor('#666666')
.margin({ top: 4 })
Text(`时间:${this.getTimeRange(item)}`)
.fontSize(14)
.fontColor('#666666')
.margin({ top: 4 })
}
.width('100%')
.alignItems(HorizontalAlign.Start)
.padding(16)
.backgroundColor('#F7F8FA')
.borderRadius(16)
}
}, (item: ScheduleItem) => item.id.toString())
}
.width('100%')
.layoutWeight(1)
.padding({
left: 16,
right: 16,
bottom: 16
})
}
.width('100%')
.height('100%')
.backgroundColor('#FFFFFF')
}
}
九、代码实现说明
1. 引入 dayjs
页面顶部引入三方库:
import dayjs from 'dayjs';
后续页面中所有时间格式化、时间差计算都通过 dayjs 完成。
2. 初始化模拟日程数据
在 initSchedules() 方法中,通过 dayjs() 获取当前时间,再使用 add() 和 subtract() 生成不同时间段的日程数据:
const now = dayjs();
startTime: now.add(30, 'minute').format('YYYY-MM-DD HH:mm:ss')
这样做的好处是,页面每次运行时都能根据当前时间生成相对合理的日程状态,不会因为写死日期导致页面显示过期。
3. 格式化时间范围
通过 getTimeRange() 方法,将原始时间字符串转换成更适合页面展示的格式:
getTimeRange(item: ScheduleItem): string {
const start = dayjs(item.startTime).format('MM月DD日 HH:mm');
const end = dayjs(item.endTime).format('HH:mm');
return `${start} - ${end}`;
}
最终页面中显示效果类似:
时间:05月10日 14:30 - 16:00
这种格式比完整时间字符串更简洁,适合移动端页面展示。
4. 判断日程状态
通过 getStatus() 方法判断当前日程状态:
if (nowValue < startValue) {
return '未开始';
}
if (nowValue >= startValue && nowValue <= endValue) {
return '进行中';
}
return '已结束';
在本项目中,我还对未开始状态做了进一步优化,会显示:
还有 30 分钟开始
还有 3 小时开始
还有 1 天开始
这样比单纯显示“未开始”更有信息量。
5. 使用列表渲染日程卡片
页面使用 List 和 ForEach 渲染日程数据:
List({ space: 12 }) {
ForEach(this.schedules, (item: ScheduleItem) => {
ListItem() {
// 日程卡片内容
}
}, (item: ScheduleItem) => item.id.toString())
}
每张卡片中展示:
- 日程标签;
- 当前状态;
- 课程或活动名称;
- 地点;
- 教师或负责人;
- 时间范围。
这种卡片式布局比普通文本列表更清楚,也更适合校园类应用的页面风格。
十、运行效果说明
完成代码后,点击 DevEco Studio 中的运行按钮,将应用运行到 OpenHarmony 模拟器中。
运行成功后,页面顶部显示:
校园日程助手
下方展示当前时间和多条日程卡片,例如:
计算机网络
地点:教学楼 A302
教师/负责人:王老师
时间:05月10日 14:30 - 16:00
还有 30 分钟开始
点击右上角“刷新”按钮后,当前时间文本会更新。页面中不同日程会根据当前时间显示“还有多久开始”“进行中”或“已结束”等状态。
十一、开发中遇到的问题
1. 找不到 dayjs 模块
如果代码中出现找不到 dayjs 的问题,可以先检查是否已经执行安装命令:
ohpm install dayjs
同时检查项目中是否存在:
oh_modules
oh-package-lock.json5
如果依赖没有正确安装,可以重新执行安装命令。
2. import dayjs 报错
如果引入时报错:
import dayjs from 'dayjs';
可以先确认依赖名称是否写对,以及是否在项目根目录执行了安装命令。
如果 DevEco Studio 仍然没有识别,可以尝试重新同步项目或重启 DevEco Studio。
3. 时间状态不更新
本项目中点击“刷新”按钮只会更新当前时间显示,不会重新生成所有日程数据。
如果希望每次刷新都重新生成日程,可以将按钮中的代码改成:
.onClick(() => {
this.initSchedules();
this.updateNowText();
})
这样点击刷新时,日程数据也会重新计算。
4. 页面卡片内容太挤
如果页面内容太贴边,可以通过 padding 和 margin 调整布局。
例如本项目中卡片使用:
.padding(16)
.backgroundColor('#F7F8FA')
.borderRadius(16)
这样可以让每个日程卡片看起来更清楚,也更像真实应用页面。
十二、总结
本篇完成了一个基于 dayjs 的校园日程助手页面。项目通过 dayjs 处理时间格式化、时间差计算和日程状态判断,并使用 ArkUI 将数据渲染成卡片式列表。
通过本次实践,我主要完成了以下内容:
- 安装并使用
dayjs三方库; - 在 ArkTS 页面中引入三方库;
- 使用
dayjs获取和格式化当前时间; - 使用
dayjs生成模拟日程数据; - 根据当前时间判断日程状态;
- 使用
List和ForEach渲染日程卡片; - 在模拟器中完成运行验证。
虽然这个项目只是校园日程助手的基础版本,但它已经具备了课程表、考试提醒、活动安排等应用场景的核心能力。
后续可以继续扩展为一个更完整的校园日程应用,例如:
- 添加日程详情页;
- 添加日程分类筛选;
- 添加考试倒计时;
- 添加本地日程存储;
- 添加提醒开关;
- 接入真实课程表接口。
整体来看,dayjs 让时间处理代码更加简洁,也让页面逻辑更容易维护。通过这个项目,可以更清楚地理解 OpenHarmony 中三方库接入、时间数据处理和页面渲染之间的关系。
更多推荐
所有评论(0)