欢迎加入开源鸿蒙跨平台社区:
https://openharmonycrossplatform.csdn.net


项目效果

本文实现的是一个基于 dayjs 的校园日程助手页面。应用启动后,会自动生成几条校园日程数据,并使用 dayjs 对时间进行格式化处理,最终以卡片列表的形式展示课程、地点、教师、时间范围和当前状态。

最终运行效果如下:

在这里插入图片描述

页面主要包含以下内容:

  • 顶部标题:校园日程助手;
  • 当前时间展示;
  • 日程卡片列表;
  • 每个卡片展示课程名称、地点、教师、时间范围和状态;
  • 支持点击刷新按钮重新更新时间;
  • 页面支持上下滚动查看更多日程。

这个页面可以作为课程表、考试提醒、社团活动安排、校园待办事项等功能的基础版本。


前言

在校园类应用中,时间相关功能非常常见,例如课程表、考试倒计时、活动提醒、会议安排等。这些功能看起来只是展示几行文字,但实际开发时会涉及时间格式转换、时间差计算、状态判断等逻辑。

如果直接使用原生 Date 对象处理这些内容,代码会比较繁琐,可读性也不够好。因此本篇文章选择使用三方库 dayjs 来完成时间格式化和日程状态判断。

本篇文章以“校园日程助手”为场景,使用 OpenHarmony 项目中的 ArkTS 结合 dayjs 实现一个简单的日程卡片页面。通过这个项目,可以理解三方库在 OpenHarmony 项目中的基本接入方式,也可以掌握如何在页面中处理时间数据。

相比手动拼接日期字符串,dayjs 的写法更简洁,也更适合后续扩展倒计时、日历、课程提醒等功能。


一、项目目标

本次实践主要实现以下目标:

  • 在 OpenHarmony 项目中安装 dayjs 三方库;
  • 在 ArkTS 页面中引入并使用 dayjs
  • 使用 dayjs 格式化当前时间;
  • 使用 dayjs 生成模拟日程数据;
  • 根据当前时间判断日程状态;
  • 使用 ArkUI 的 ListForEach 渲染日程卡片;
  • 实现刷新按钮,重新更新时间显示;
  • 在 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. 使用列表渲染日程卡片

页面使用 ListForEach 渲染日程数据:

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. 页面卡片内容太挤

如果页面内容太贴边,可以通过 paddingmargin 调整布局。

例如本项目中卡片使用:

.padding(16)
.backgroundColor('#F7F8FA')
.borderRadius(16)

这样可以让每个日程卡片看起来更清楚,也更像真实应用页面。


十二、总结

本篇完成了一个基于 dayjs 的校园日程助手页面。项目通过 dayjs 处理时间格式化、时间差计算和日程状态判断,并使用 ArkUI 将数据渲染成卡片式列表。

通过本次实践,我主要完成了以下内容:

  • 安装并使用 dayjs 三方库;
  • 在 ArkTS 页面中引入三方库;
  • 使用 dayjs 获取和格式化当前时间;
  • 使用 dayjs 生成模拟日程数据;
  • 根据当前时间判断日程状态;
  • 使用 ListForEach 渲染日程卡片;
  • 在模拟器中完成运行验证。

虽然这个项目只是校园日程助手的基础版本,但它已经具备了课程表、考试提醒、活动安排等应用场景的核心能力。

后续可以继续扩展为一个更完整的校园日程应用,例如:

  • 添加日程详情页;
  • 添加日程分类筛选;
  • 添加考试倒计时;
  • 添加本地日程存储;
  • 添加提醒开关;
  • 接入真实课程表接口。

整体来看,dayjs 让时间处理代码更加简洁,也让页面逻辑更容易维护。通过这个项目,可以更清楚地理解 OpenHarmony 中三方库接入、时间数据处理和页面渲染之间的关系。

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐