#我的鸿蒙开发手记#鸿蒙日程功能实现 原创 精华

乂心IsOneHeart
发布于 2025-5-6 15:53
浏览
0收藏

一、介绍与准备
1.整体介绍
本项目是基于 HarmonyOS 5.0.0 及以上版本、DevEco Studio 5.0.0 及以上版本等环境进行鸿蒙日程功能实现的开发展开。
对应用整体构建布局设计进行了介绍,包括日程模块(ScheduleView.ets)的设置以及日程入口 UI 在 products 产品层下的 pages>MainEntry.ets 中的关键代码实现,展示了如何引入日历模块组件、构建导航容器以及实现底部导航栏的标签切换等。
详细阐述了日程功能模块与实现流程,日程模块的 UI 结构由日程主视图(支持月视图、周视图切换,高亮显示当日日程)、日程详情页(展示时间、地点、参与人及提醒设置)和新增 / 编辑页(提供表单输入、地图选点及附件上传功能)等核心组件构成。着重对 ScheduleView.ets 文件的创建与实现代码进行了说明,包括日程数据的加载、搜索功能的实现、新建日程的操作等。
还分别对日历组件的开发进行了介绍,包括日历头部 ScheduleTitle.ets(实现年月切换、农历开关等功能)和日历日期 CalenderView.ets(构建月视图或周视图的日期数据展示、日期选择等)的代码实现。
介绍了日历日程 ScheduleItem.ets 的代码实现,涵盖了日程信息的展示、时间格式化、判断日程是否已结束以及点击跳转到日程详情等功能。
2.基础准备
(1)设备类型:华为手机(直板机)
(2)HarmonyOS版本:HarmonyOS 5.0.0 Release及以上
(3)DevEco Studio版本:DevEco Studio 5.0.0 Release及以上
(4)HarmonyOS SDK版本:HarmonyOS 5.0.0 Release SDK及以上
二、应用整体构建布局设计
1.模块设计
日程模块:ScheduleView.ets
三、日程入口UI实现
products产品层下的pages>MainEntry.ets关键代码实现:

// 导入模块:底部导航配置、各功能模块视图、类型定义等
import { TAB_LIST } from '../constants/Constants';
import { AgencyTaskView } from '@ohos_agcit/office_attendance_agency';
import { ScheduleView } from '@ohos_agcit/office_attendance_schedule';
import { TabListItem } from '@ohos_agcit/office_attendance_agency';
import { CheckInView } from '@ohos_agcit/office_attendance_checkin';
import { CommonConstants, MainEntryVM } from '@ohos_agcit/office_attendance_common_lib';
import { MineView } from '@ohos_agcit/office_attendance_account';
import { router } from '@kit.ArkUI';

// --------------------------
// 主入口页面构建器(供外部调用)
// --------------------------
@Builder
export function mainEntryBuilder() {
  MainEntry();
}
@Entry // 标记为应用入口组件
@ComponentV2 // 声明为ArkUI V2版本组件
struct MainEntry {
  // 路由管理单例
  vm: MainEntryVM = MainEntryVM.instance;
  
  // 页面状态控制
  @Provider() isPageShow: boolean = false; // 页面是否显示
  @Provider() currentTabIndex: number = this.vm.curIndex; // 当前选中Tab索引
  onPageShow(): void {
    this.isPageShow = true; // 页面显示时更新状态
  }

  onPageHide(): void {
    this.isPageShow = false; // 页面隐藏时更新状态
  }
  build() {
    Navigation(this.vm.navStack) { // 导航容器(管理页面堆栈)
      // 底部导航栏配置
      Tabs({ 
        barPosition: BarPosition.End, // 导航栏位置(底部)
        index: this.vm.curIndex // 当前选中索引
      }) {
        // Tab1: 考勤打卡模块
        TabContent() {
          CheckInView(); // 加载打卡组件
        }
        .tabBar(this.tabBarBuilder(TAB_LIST[0], 0)); // 绑定导航栏样式

        // Tab2: 待办任务模块
        TabContent() {
          AgencyTaskView(); // 加载待办组件
        }
        .tabBar(this.tabBarBuilder(TAB_LIST[1], 1));

        // Tab3: 日程管理模块
        TabContent() {
          ScheduleView(); // 加载日程组件
        }
        .tabBar(this.tabBarBuilder(TAB_LIST[2], 2));

        // Tab4: 个人中心模块
        TabContent() {
          //MineView({ // 加载个人中心组件
          //  callback: () => { // 退出登录回调
          //    router.replaceUrl({ url: 'pages/Login' }) // 跳转登录页
          //  }
          });
        }
        .tabBar(this.tabBarBuilder(TAB_LIST[3], 3));
      }
      // 导航栏样式配置
      .scrollable(false) // 禁止滑动切换
      .barHeight(80) // 导航栏高度
      .height(CommonConstants.FULL_HEIGHT) // 全屏高度
      .animationDuration(0) // 禁用切换动画
      .barMode(BarMode.Fixed) // 固定模式(不随内容滚动)
      .onChange((index: number) => { // Tab切换事件
        this.vm.curIndex = index; // 更新路由管理状态
        this.currentTabIndex = index; // 更新当前索引
      });
    }
    // 导航栏标题配置
    .title({ 
      builder: this.titleBuilder(TAB_LIST[this.vm.curIndex]), // 动态标题
      height: 92 // 标题栏高度
    })
    .mode(NavigationMode.Stack) // 堆栈导航模式
  }
  @Builder
  titleBuilder(title: TabListItem) {
    Row() {
      Text(title.label) // 显示当前模块名称
        .fontWeight(FontWeight.Bold) // 加粗
        .fontColor('rgba(0,0,0,0.90)') // 字体颜色
        .fontSize($r('app.float.navigation_title_font_size')) // 字体大小(资源引用)
        .margin({ left: 16, top: 36 }) // 边距
        .height(56) // 高度
    }
    .justifyContent(FlexAlign.Start) // 左对齐
    .backgroundColor(title.titleBackgroundColor) // 背景色(动态配置)
    .width('100%') // 全宽
    .height('100%') // 全高
    .alignItems(VerticalAlign.Bottom) // 垂直底部对齐
  }

  // --------------------------
  // 底部导航栏Item构建器
  // --------------------------
  @Builder
  tabBarBuilder(item: TabListItem, index: number) {
    Column() { // 垂直布局
      // 动态显示选中/未选中图标
      Image(this.vm.curIndex === index ? item.iconChecked : item.icon)
        .width($r('app.float.navigation_image_size')) // 图标宽度(资源引用)
        .height($r('app.float.navigation_image_size')) // 图标高度
        .margin({ top: $r('app.string.margin_xs') }); // 上边距
      
      // Tab文字标签
      Text(item.label)
        .fontColor(this.vm.curIndex === index ? 
          $r('app.color.icon_color_highlight') : // 选中高亮色
          $r('app.color.icon_color_level2')) // 未选中默认色
        .fontSize($r('app.float.navigation_navi_size')) // 字体大小
        .height(14) // 固定高度
        .margin({ 
          top: $r('app.string.margin_xs'), 
          bottom: $r('app.string.margin_xs') 
        });
    }
    .height(80) // 固定高度
    .width('100%') // 全宽
    .justifyContent(FlexAlign.Start); // 顶部对齐
  }
}

四、日程功能模块与实现流程
2.日程模块UI结构设计
日程由以下核心组件构成:
·日程主视图:支持月视图、周视图切换,高亮显示当日日程;
·日程详情页:展示时间、地点、参与人及提醒设置;
·新增/编辑页:提供表单输入、地图选点及附件上传功能。
3.功能实现步骤
(1)ScheduleView.ets文件创建与实现代码:
①在根目录OfficeAttendance下创建scenes场景文件目录
②在scenes目录下创建schedule模块(HSP动态共享)
#我的鸿蒙开发手记#鸿蒙日程功能实现-鸿蒙开发者社区
③ScheduleView.ets代码实现:

// 导入日程数据类型定义和基础工具模块
import { ScheduleInfo } from '../types/Types';
import {
  AppStorageData,
  CommonConstants,
  DataPreferencesUtils,
  DateUtils,
  DialogMap,
  FormatUtil,
  MainEntryVM,
  RouterModule
} from '@ohos_agcit/office_attendance_common_lib';
import { ScheduleTitle } from './components/ScheduleTitle';
import { DateElement } from '@ohos_agcit/office_attendance_component_lib/src/main/ets/types/DateElement';
import { CalenderView } from '@ohos_agcit/office_attendance_component_lib';
import { RequestProxy } from '../api/RequestProxy';
import { AppStorageV2 } from '@kit.ArkUI';
import { ScheduleItem } from './components/ScheduleItem';

// --------------------------
// 日程管理主页面组件(支持预览)
// --------------------------
@Preview
@ComponentV2
export struct ScheduleView {
  // 状态管理
  @Local myStorage: AppStorageData = // 应用存储数据连接
    AppStorageV2.connect(AppStorageData, CommonConstants.APP_STORAGE_DATA_KEY, () => new AppStorageData())!;
  scroller: Scroller = new Scroller() // 列表滚动控制器
  @Local selectedDay: DateElement = new DateElement(0, 0, 0); // 当前选中日期
  @Local selectedMonth: DateElement = // 当前显示月份
    new DateElement(DateUtils.getCurrentYear(), DateUtils.getCurrentMonth(), 0);
  @Local showYear: number = DateUtils.getCurrentYear(); // 当前显示年份
  @Local showMonth: number = DateUtils.getCurrentMonth(); // 当前显示月份
  @Local searchTxt: string | undefined; // 搜索关键词
  vm: MainEntryVM = MainEntryVM.instance; // 路由管理单例
  @Provider() schedules: ScheduleInfo[] = []; // 日程数据列表
  private viewType: number = CommonConstants.MONTH_VIEW; // 视图类型(月/周)
  @Local hasScheduleDates: Array<string> = []; // 有日程的日期标记
  @Local isShowLunar: boolean = true; // 是否显示农历

  // --------------------------
  // 加载日程数据
  // --------------------------
  load() {
    let date = FormatUtil.formatDate(this.selectedDay.getDate(), FormatUtil.DATE_YYYY_MM_DD);
    // 查询选定日期的日程
    RequestProxy.queryByDate(date).then((data) => {
      this.schedules = data;
    });
    // 查询有日程的日期
    RequestProxy.queryScheduleDates('', '').then((data) => {
      this.hasScheduleDates = data;
    });
  }

  aboutToAppear(): void {
    this.load(); // 组件初始化加载数据
  }

  // --------------------------
  // 监听年份变化
  // --------------------------
  @Monitor('showYear')
  onSelectedYearChange(monitor: IMonitor) {
    if (monitor.value()) {
      this.selectedMonth.year = monitor.value()?.now as number;
    }
  }

  // --------------------------
  // 监听月份变化
  // --------------------------
  @Monitor('showMonth')
  onSelectedMonthChange(monitor: IMonitor) {
    if (monitor.value()) {
      this.selectedMonth.month = monitor.value()?.now as number;
    }
  }

  // 切换月份
  changeMonth(selectedMonth: DateElement) {
    this.showYear = selectedMonth.year;
    this.showMonth = selectedMonth.month;
  }

  // --------------------------
  // 日程搜索
  // --------------------------
  search(data: string) {
    if (data && data.length > 0) {
      this.searchTxt = data;
      RequestProxy.queryByName(this.searchTxt).then((data) => {
        this.schedules = data; // 显示搜索结果
      });
    } else {
      this.searchTxt = undefined;
      this.load(); // 清空搜索恢复默认显示
    }
  }

  // --------------------------
  // 新建日程
  // --------------------------
  newSchedule() {
    RouterModule.openDialog(DialogMap.NEW_SCHEDULE, {
      param: this.selectedDay, // 传递当前选中日期
      onPop: () => { this.load(); } // 关闭后刷新
    });
  }

  // 检查农历显示设置
  isSettingLunar() {
    return DataPreferencesUtils.LUNAR_SETTING_ON ===
      DataPreferencesUtils.getSync(DataPreferencesUtils.LUNAR_SETTING, DataPreferencesUtils.LUNAR_SETTING_ON)
  }

  // 切换农历显示
  changeLunar() {
    this.isShowLunar = this.isSettingLunar()
  }

  // --------------------------
  // 主构建方法
  // --------------------------
  build() {
    Stack() {
      // 1. 主内容区
      Column() {
        Row() {
          Column().width('4%') // 左侧间距
          Column() {
            Blank().height('2%')

            // 1.1 搜索框
            Search({ placeholder: $r('app.string.search_tip') })
              .onChange((data: string) => { this.search(data); }) // 输入变化实时搜索
              .onSubmit((data: string) => { this.search(data); }) // 提交搜索

            Blank().height('2%')

            // 1.2 日历标题(年月切换+农历开关)
            Row() {
              ScheduleTitle({
                selectedMonth: this.selectedMonth,
                onChange: (selectedMonth: DateElement) => { // 月份切换回调
                  this.changeMonth(selectedMonth);
                },
                onLunarChange: () => { // 农历开关回调
                  this.changeLunar();
                }
              })
            }.visibility(this.searchTxt ? Visibility.None : Visibility.Visible) // 搜索时隐藏

            // 1.3 日历视图
            Row() {
              CalenderView({
                hasScheduleDates: this.hasScheduleDates, // 标记有日程的日期
                showMonth: new DateElement(this.showYear, this.showMonth, 0),
                isShowLunar: this.isShowLunar, // 是否显示农历
                onChangeDay: (day: DateElement) => { // 日期选择回调
                  this.selectedDay = day;
                  this.load();
                },
                onChangeMonth: (month: DateElement) => { // 月份切换回调
                  this.selectedMonth = month;
                  this.changeMonth(month);
                },
                onChangeViewType: (viewType: number) => { // 视图切换回调
                  this.viewType = viewType;
                }
              })
            }.visibility(this.searchTxt ? Visibility.None : Visibility.Visible)
          }.layoutWeight(1) // 中间内容区自适应

          Column().width('4%') // 右侧间距
        }

        // 1.4 日期详情(农历显示)
        Column() {
          Row().shadow({ radius: 10, color: $r('app.color.window_background') }) // 分割线阴影
          Column().height('2%')
          Row() {
            Column().width('4%')
            // 农历月份显示
            Text(DateUtils.getLunarMonthDesc(this.selectedDay.year, this.selectedDay.month, this.selectedDay.day))
              .fontSize(16)
              .opacity(0.9)
            Blank().width(8) // 间距
            // 农历日期显示
            Text(DateUtils.getCurrentLunarDayDesc(...))
              .fontSize(16)
              .opacity(0.9)
          }
        }.visibility(this.searchTxt ? Visibility.None : Visibility.Visible)

        // 1.5 日程列表
        Row() {
          Column().width('4%')
          Column() {
            // 正常模式下的日程列表
            List({ space: 8, scroller: this.scroller }) {
              ForEach(this.schedules, (schedule: ScheduleInfo) => {
                ListItem() {
                  ScheduleItem({
                    schedule: schedule, 
                    refresh: () => { this.load() } // 刷新回调
                  })
                }
              }, (schedule: ScheduleInfo) => JSON.stringify(schedule)) // 唯一键生成
            }.visibility(this.searchTxt ? Visibility.None : Visibility.Visible)

            // 搜索模式下的日程列表(样式差异)
            List({ space: 10, scroller: this.scroller }) {
              ForEach(this.schedules, (schedule: ScheduleInfo) => {
                ListItem() {
                  ScheduleItem({ schedule: schedule, refresh: () => { this.load() } })
                }
              }, (schedule: ScheduleInfo) => JSON.stringify(schedule))
            }.visibility(this.searchTxt ? Visibility.Visible : Visibility.None)
          }.layoutWeight(1)
          Column().width('4%')
        }.layoutWeight(1)
      }

      // 2. 悬浮添加按钮(非搜索状态显示)
      Image($r('app.media.ic_plus_filled'))
        .onClick(() => { this.newSchedule(); })
        .position({ 
          x: this.myStorage.windowWidthVp * 0.8, // 右侧20%位置
          y: this.myStorage.windowHeightVp * 0.7 // 底部30%位置
        })
        .visibility(this.searchTxt ? Visibility.None : Visibility.Visible)
    }.backgroundColor(Color.White)
  }
}

(2)日历组件开发
描述:日历-头部
ScheduleTitle.ets.ets代码实现:

import { DataPreferencesUtils } from '@ohos_agcit/office_attendance_common_lib';
import { DateElement } from '@ohos_agcit/office_attendance_component_lib/src/main/ets/types/DateElement';

@ComponentV2
export struct ScheduleTitle {
  @Param selectedMonth: DateElement = new DateElement(0, 0, 0); // 当前选中的月份
  @Param onChange: (selected: DateElement) => void = (selected: DateElement) => {
  } // 月份变化回调函数
  @Param onLunarChange: () => void = () => {
  } // 农历显示设置变化回调函数

  // 切换到上一个月
  private goBeforeMonth() {
    this.selectedMonth.year =
      this.selectedMonth.month - 1 >= 0 ? this.selectedMonth.year : this.selectedMonth.year - 1;
    this.selectedMonth.month = this.selectedMonth.month - 1 >= 0 ? this.selectedMonth.month - 1 : 11;
    this.onChange(this.selectedMonth);
  }

  // 切换到下一个月
  private goAfterMonth() {
    this.selectedMonth.year =
      this.selectedMonth.month + 1 < 12 ? this.selectedMonth.year : this.selectedMonth.year + 1;
    this.selectedMonth.month = this.selectedMonth.month + 1 < 12 ? this.selectedMonth.month + 1 : 0;
    this.onChange(this.selectedMonth);
  }

  // 检查是否设置了显示农历
  isSettingLunar() {
    return DataPreferencesUtils.LUNAR_SETTING_ON ===
    DataPreferencesUtils.getSync(DataPreferencesUtils.LUNAR_SETTING, DataPreferencesUtils.LUNAR_SETTING_ON)
  }

  @Builder
  settingBuilder() {
    Column() {
      Row() {
        Row() {
          Text($r('app.string.schedule_lc_desc')) // 显示农历设置描述文本
        }.alignItems(VerticalAlign.Center)
        .justifyContent(FlexAlign.Start)
        .width('60%')

        Row() {
          // 如果设置了显示农历,则显示勾选图标
          if (this.isSettingLunar()) {
            Image($r('app.media.ic_check_mask')).width(24).height(16)
          }
        }.alignItems(VerticalAlign.Center)
        .justifyContent(FlexAlign.End)
        .layoutWeight(1)
      }.alignItems(VerticalAlign.Center)
      .justifyContent(FlexAlign.Center)
      .onClick(() => {
        // 点击切换农历显示设置
        if (this.isSettingLunar()) {
          DataPreferencesUtils.putSync(DataPreferencesUtils.LUNAR_SETTING, DataPreferencesUtils.LUNAR_SETTING_OFF)
        } else {
          DataPreferencesUtils.putSync(DataPreferencesUtils.LUNAR_SETTING, DataPreferencesUtils.LUNAR_SETTING_ON)
        }
        this.onLunarChange();
      })
    }.height(45)
    .alignItems(HorizontalAlign.Center)
    .justifyContent(FlexAlign.Center)
    .width(240)
    .margin({ top: 10, bottom: 10 })
  }

  build() {
    Row() {
      // 左侧:月份导航区域
      Row() {
        // 上个月按钮
        Row() {
          Image($r('app.media.ic_public_small_left'))
            .id('pre_month')
            .height(20)
            .width(12)
        }
        .margin({ left: 5 })
        .height('100%')
        .aspectRatio(1)
        .justifyContent(FlexAlign.Center)
        .onClick(() => {
          this.goBeforeMonth();
        })
        Column().width('6%') // 间隔
        // 显示当前年月
        Text(`${this.selectedMonth.year}-${this.selectedMonth.month + 1 >= 10 ? '' : '0'}${this.selectedMonth.month +
          1}`)
          .fontSize(16)
          .fontWeight(FontWeight.Medium)
          .opacity(0.9)
        Column().width('6%') // 间隔
        // 下个月按钮
        Row() {
          Image($r('app.media.ic_public_small_right'))
            .id('next_month')
            .height(20)
            .width(12)
        }
        .margin({ left: 5 })
        .height('100%')
        .aspectRatio(1)
        .justifyContent(FlexAlign.Center)
        .onClick(() => {
          this.goAfterMonth();
        })
        .alignItems(VerticalAlign.Center)
        .justifyContent(FlexAlign.Start)
      }.width('50%')
      .alignItems(VerticalAlign.Center)
      .justifyContent(FlexAlign.Start)

      // 右侧:设置按钮
      Row() {
        Image($r('app.media.ic_ellipsis'))
          .width(20)
          .height(12)
          .bindMenu(this.settingBuilder) // 绑定设置菜单
      }
      .width('50%')
      .alignItems(VerticalAlign.Center)
      .justifyContent(FlexAlign.End)
    }.width('100%')
    .height(24)
  }
}

描述:日历-日期
CalenderView.ets代码实现:
import { i18n } from '@kit.LocalizationKit';
import { CommonConstants, DateUtils } from '@ohos_agcit/office_attendance_common_lib';
import { DateElement } from '../types/DateElement';

@ObservedV2
class ShowDays {
  @Trace showDays: DateElement [][] = [];  // 月视图日期数据(二维数组)
  @Trace showWeekDays: DateElement [][] = [];  // 周视图日期数据
}

@ComponentV2
export struct CalenderView {
  // 组件参数
  @Param isShowLunar: boolean = true;  // 是否显示农历
  @Param fontSize: Resource = $r('app.float.calendar_title_font_size')  // 字体大小资源
  @Param titleFontOpacity: Resource = $r('app.float.calendar_title_font_opacity')  // 标题透明度
  @Param contentFontOpacity: Resource = $r('app.float.calendar_content_font_opacity')  // 内容透明度
  @Param weekendFontOpacity: Resource = $r('app.float.calendar_weekend_font_opacity')  // 周末透明度
  @Param beforeDateFontOpacity: Resource = $r('app.float.calendar_before_date_font_opacity')  // 非当月日期透明度
  @Param todayFontOpacity: Resource = $r('app.float.calendar_today_font_opacity')  // 今日透明度
  readonly TIME_DEF_VALUE = -1;  // 时间默认值

  // 本地化日历实例
  private calendar = i18n.getCalendar(CommonConstants.EN_LOCALE);
  @Local today: number = this.calendar.get(CommonConstants.FIELD_DAY);  // 当前日
  @Local currentMonth: number = this.calendar.get(CommonConstants.FIELD_MONTH);  // 当前月
  @Local currentYear: number = this.calendar.get(CommonConstants.FIELD_YEAR);  // 当前年

  // 组件状态
  @Param selectedDay: DateElement = new DateElement(0, 0, this.TIME_DEF_VALUE);  // 选中日期
  @Event onChangeDay: (val: DateElement) => void = (val: DateElement) => {};  // 日期变更事件
  @Param showMonth: DateElement = new DateElement(this.TIME_DEF_VALUE, this.TIME_DEF_VALUE, this.TIME_DEF_VALUE);  // 展示的月份
  @Event onChangeMonth: (showMonth: DateElement) => void = (showMonth: DateElement) => {};  // 月份变更事件
  @Param showDotDate: Array<string> = []  // 需要显示圆点的日期
  @Local viewType: number = CommonConstants.MONTH_VIEW;  // 视图类型(月/周)
  @Event onChangeViewType: (showType: number) => void = (showType: number) => {};  // 视图切换事件
  @Param hasScheduleDates: Array<string> = [];  // 有日程的日期列表
  showDays: ShowDays = new ShowDays();  // 日期数据实例

  // 监听月份和选中日变化
  @Monitor('showMonth','selectedDay')
  onSelectedMonthChange(monitor: IMonitor) {
    this.refresh();
  }

  // 判断是否是周末
  isWeekends(index: number) {
    return index === CommonConstants.COMMON_ZERO || index === CommonConstants.COMMON_SIX;
  }

  // 刷新日历数据
  refresh() {
    this.init();
    // 设置日历到展示月份的第一天
    this.calendar.set(this.showMonth.year, this.showMonth.month, 0);
    
    // 计算前后月份信息
    let preMonth = this.showMonth.month - 1 >= 0 ? this.showMonth.month - 1 : 11;
    let preMonthYear = this.showMonth.month - 1 >= 0 ? this.showMonth.year : this.showMonth.year - 1;
    let preMonthDays: number = -this.calendar.compareDays(new Date(preMonthYear, preMonth, 0)) - 1;
    
    let nextMonth = this.showMonth.month + 1 < 12 ? this.showMonth.month + 1 : 0;
    let nextMonthYear = this.showMonth.month + 1 < 12 ? this.showMonth.year : this.showMonth.year + 1;
    let selectMonthDays: number = this.calendar.compareDays(new Date(nextMonthYear, nextMonth, 0));
    
    // 构建日期数组
    let firstDayOfWeek = this.calendar.get(CommonConstants.FIELD_DAY_OF_WEEK);
    let weedDays: DateElement [] = [];
    let diffDays = firstDayOfWeek < 7 ? firstDayOfWeek : 0;

    // 填充上月日期
    for (let i = 1; i <= diffDays; i++) {
      weedDays.push(new DateElement(preMonthYear, preMonth, preMonthDays - diffDays + i));
    }
    
    // 填充本月日期
    for (let i = 1; i <= selectMonthDays; i++) {
      if (weedDays.length === 7) {
        this.showDays.showDays.push(weedDays);
        weedDays = [];
      }
      weedDays.push(new DateElement(this.showMonth.year, this.showMonth.month, i));
    }
    
    // 填充下月日期
    if (weedDays.length < 7) {
      let diffDays = 7 - weedDays.length;
      for (let i = 0; i < diffDays; i++) {
        weedDays.push(new DateElement(nextMonthYear, nextMonth, i + 1));
      }
    }
    this.showDays.showDays.push(weedDays);

    // 同步选中日期状态
    if (this.selectedDay.year !== this.showMonth.year || this.selectedDay.month !== this.showMonth.month) {
      this.selectedDay.year = this.showMonth.year;
      this.selectedDay.month = this.showMonth.month;
      this.onChangeDay(this.selectedDay);
    }
    if (this.selectedDay.day === this.TIME_DEF_VALUE) {
      this.selectedDay.day = this.today;
      this.onChangeDay(this.selectedDay);
    }
    this.setWeedViewData()
  }

  // 设置周视图数据
  setWeedViewData() {
    this.showDays.showDays.forEach(week => {
      week.forEach(day => {
        if (this.isSelectedDay(day)) {
          this.showDays.showWeekDays = [week];  // 找到选中日所在的周
        }
      })
    })
  }

  // 初始化月份
  init() {
    this.showDays.showDays = [];
    if (this.showMonth.year === this.TIME_DEF_VALUE) {
      this.showMonth.year = this.currentYear;
      this.showMonth.month = this.currentMonth;
      this.onChangeMonth(this.showMonth);
    }
  }

  // 生命周期:组件即将显示
  aboutToAppear() {
    this.refresh();
  }

  // 判断是否是今天
  private isToday(day: DateElement): boolean {
    return this.today === day.day && this.currentMonth === day.month &&
      this.currentYear === day.year;
  }

  // 判断是否是过去的日期
  isBeforeDay(day: DateElement) {
    return this.calendar.compareDays(new Date(day.year, day.month, day.day)) < 0;
  }

  // 补零格式化
  appendZero(num: number): string {
    return num < 10 ? '0' + num : '' + num;
  }

  // 判断是否选中日
  private isSelectedDay(day: DateElement): boolean {
    return this.selectedDay.isEquals(day);
  }

  // 判断是否当前月份
  isCurrentMonth(day: DateElement) {
    return this.showMonth.year === day.year && this.showMonth.month === day.month;
  }

  // 手势切换月份/周
  private changeMonthByGesture(e: GestureEvent) {
    if (e.offsetX > 10) {  // 向左滑动
      if (this.viewType === CommonConstants.MONTH_VIEW) {
        // 月视图切上个月
        this.showMonth.year = this.showMonth.month - 1 < 0 ? this.showMonth.year - 1 : this.showMonth.year;
        this.showMonth.month = this.showMonth.month - 1 < 0 ? 11 : this.showMonth.month - 1;
        this.onChangeMonth(this.showMonth);
      } else {
        // 周视图切上周
        let selectedDate = new Date(this.selectedDay.year, this.selectedDay.month, this.selectedDay.day);
        selectedDate = new Date(selectedDate.getTime() - CommonConstants.SEVEN_DAY);
        this.changeSelectDay(new DateElement(selectedDate.getFullYear(),
          selectedDate.getMonth(),
          selectedDate.getDate()))
      }
    } else if (e.offsetX < -10) {  // 向右滑动
      if (this.viewType === CommonConstants.MONTH_VIEW) {
        // 月视图切下个月
        this.showMonth.year = this.showMonth.month + 1 < 12 ? this.showMonth.year : this.showMonth.year + 1;
        this.showMonth.month = this.showMonth.month + 1 < 12 ? this.showMonth.month + 1 : 0;
        this.onChangeMonth(this.showMonth);
      } else {
        // 周视图切下周
        let selectedDate = new Date(this.selectedDay.year, this.selectedDay.month, this.selectedDay.day);
        selectedDate = new Date(selectedDate.getTime() + CommonConstants.SEVEN_DAY);
        this.changeSelectDay(new DateElement(selectedDate.getFullYear(),
          selectedDate.getMonth(),
          selectedDate.getDate()))
      }
    }
  }

  // 改变选中日
  changeSelectDay(day: DateElement) {
    this.selectedDay.year = day.year;
    this.selectedDay.month = day.month;
    this.selectedDay.day = day.day;
    this.onChangeDay(day);
    this.setWeedViewData();
    this.changeMonth(day);
  }

  // 切换月份
  changeMonth(month: DateElement) {
    this.showMonth.year = month.year;
    this.showMonth.month = month.month;
    this.onChangeMonth(month);
  }

  // 判断是否有日程
  isHasSchedule(day: DateElement) {
    let dayStr: string = day.getDateString();
    return this.hasScheduleDates.filter(t => {
      return t.indexOf(dayStr) > -1;
    }).length > 0;
  }

  build() {
    Column() {
      // 周标题区域
      Column() {
        Flex({ justifyContent: FlexAlign.SpaceBetween }) {
          ForEach(CommonConstants.WEEK_TITLE, (str: Resource, index: number) => {
            Column() {
              Text(str)  // 显示周几标题
                .fontSize(this.fontSize)
                .fontWeight(FontWeight.Regular)
                .textAlign(TextAlign.Center)
                .opacity(this.titleFontOpacity)
            }
            .justifyContent(FlexAlign.Center)
            .width($r('app.float.content_size'))
            .height($r('app.float.height_or_line_height'))
          })
        }
        .width(CommonConstants.FULL_PERCENT)
        .borderRadius($r('app.float.height_or_line_height'))
        .margin({
          bottom: $r('app.float.day_button_radius'),
          left: CommonConstants.COMMON_MARGIN,
          right: CommonConstants.COMMON_MARGIN
        })
      }

      // 日期主体区域
      Flex({ wrap: FlexWrap.Wrap }) {
        ForEach(this.viewType === CommonConstants.MONTH_VIEW ? this.showDays.showDays : this.showDays.showWeekDays,
          (weekDays: DateElement []) => {
            Flex({ justifyContent: FlexAlign.SpaceBetween }) {
              ForEach(weekDays, (day: DateElement, index: number) => {
                Column() {
                  // 阳历日期
                  Text(`${day.day}`)
                    .fontSize(this.fontSize)
                    .fontWeight(FontWeight.Medium)
                    .opacity(this.isToday(day) ? this.todayFontOpacity :  // 今日透明度
                      this.isWeekends(index) ? this.weekendFontOpacity :  // 周末透明度
                        this.isCurrentMonth(day) ? this.contentFontOpacity : this.beforeDateFontOpacity)  // 非本月透明度
                    .fontColor(this.isToday(day) && !this.isSelectedDay(day) ? $r('app.color.start_window_background') :
                    $r('app.color.black'))
                  
                  // 农历日期
                  Text(DateUtils.getCurrentLunarDayDesc(day.year, day.month, day.day))
                    .fontSize(this.fontSize)
                    .visibility(this.isShowLunar ? Visibility.Visible : Visibility.None)
                    .opacity(this.isToday(day) ? this.todayFontOpacity :  // 透明度设置同上
                      this.isWeekends(index) ? this.weekendFontOpacity :
                        this.isCurrentMonth(day) ? this.contentFontOpacity : this.beforeDateFontOpacity)
                    .fontColor(this.isToday(day) && !this.isSelectedDay(day) ? $r('app.color.start_window_background') :
                    $r('app.color.black'))

                  // 日程圆点标记
                  Column()
                    .width(4)
                    .height(4)
                    .borderRadius(2)
                    .backgroundColor(this.isSelectedDay(day) ? $r('app.color.sys_default_blue') :  // 选中状态颜色
                      this.isToday(day) ? Color.White : $r('app.color.sys_default_blue'))  // 今日颜色
                    .visibility(this.isHasSchedule(day) ? Visibility.Visible : Visibility.None)  // 有日程时显示
                }
                .onClick(() => {
                  this.changeSelectDay(day);  // 点击选择日期
                })
                // 背景样式
                .backgroundColor(this.isToday(day) && !this.isSelectedDay(day) ? $r('app.color.sys_default_blue') :
                Color.White)
                .borderRadius($r('app.float.schedule_border_radius'))
                .borderColor(this.isSelectedDay(day) ? $r('app.color.sys_default_blue') : Color.White)  // 选中边框
                .borderWidth(this.isSelectedDay(day) ? 1 : 0)
                .justifyContent(FlexAlign.Center)
                .width($r('app.float.content_size'))
                .height($r('app.float.content_size'))
              })
            }
            .width('100%')
            .margin({ top: '6vp' })
          })
      }
      .width('100%')
      // 添加水平滑动手势
      .gesture(PanGesture({ fingers: 1, direction: PanDirection.Horizontal, distance: 5 })
        .onActionEnd((e: GestureEvent) => {
          this.changeMonthByGesture(e);  // 手势切换月份/周
        })
      )

      Column().height('2%')  // 间隔区域

      // 视图切换按钮
      Column() {
        // 展开月视图按钮
        Image($r('app.media.ic_chevron_up'))
          .width(64)
          .height(16)
          .visibility(this.viewType === CommonConstants.MONTH_VIEW ? Visibility.Visible : Visibility.None)
          .onClick(() => {
            this.viewType = CommonConstants.WEEK_VIEW
            this.onChangeViewType(this.viewType)
          })
        // 收起周视图按钮  
        Image($r('app.media.ic_chevron_down'))
          .width(64)
          .height(16)
          .visibility(this.viewType === CommonConstants.WEEK_VIEW ? Visibility.Visible : Visibility.None)
          .onClick(() => {
            this.viewType = CommonConstants.MONTH_VIEW
            this.onChangeViewType(this.viewType)
          })
      }.height(16)
      .width('100%')
    }
  }
}

描述:日历-日程
ScheduleItem.ets代码实现:

import { ScheduleInfo } from '../../types/Types'
import {
  AppStorageData,
  CommonConstants,
  DialogMap,
  FormatUtil,
  RouterModule
} from '@ohos_agcit/office_attendance_common_lib'
import { AppStorageV2 } from '@kit.ArkUI';

@ComponentV2
export struct ScheduleItem {
  @Local myStorage: AppStorageData =  // 本地应用存储实例
    AppStorageV2.connect(AppStorageData, CommonConstants.APP_STORAGE_DATA_KEY, () => new AppStorageData())!;
  @Event refresh: () => void = () => {  // 刷新事件回调
  };
  @Param schedule: ScheduleInfo = { title: '', startTime: '', endTime: '' };  // 传入的日程数据

  // 跳转到日程详情
  toDetail() {
    RouterModule.openDialog(DialogMap.SCHEDULE_DETAIL, {
      param: this.schedule.id,
      onPop: () => {
        this.refresh();  // 当详情页关闭时触发刷新
      }
    });
  }

  // 格式化时间显示
  getTime(time: string | undefined): string {
    if (time) {
      return FormatUtil.formatDate(new Date(time), FormatUtil.DATE_24H_mm);  // 使用24小时制格式
    }
    return '';
  }

  // 判断日程是否已结束
  isEnd() {
    if (this.schedule.isAllDay) {  // 全天日程不显示结束状态
      return false;
    }
    return new Date().getTime() > new Date(this.schedule.endTime).getTime();
  }

  // 获取整体透明度
  getOpacity() {
    return this.isEnd() ? 0.4 : 1;  // 已结束的日程降低透明度
  }

  // 获取地点信息的透明度
  getLocationOpacity() {
    return this.isEnd() ? 0.4 : 0.9;  // 地点信息使用不同透明度
  }

  build() {
    Row() {
      // 左侧时间显示区域
      Column() {
        if (this.schedule.isAllDay) {  // 全天日程样式
          Blank()
          // 全天标识图标
          Row()
            .borderColor($r('app.color.purple'))
            .borderRadius($r('app.float.trip_schedule_icon_radius'))
            .borderWidth($r('app.float.trip_schedule_icon_width'))
            .height(CommonConstants.COMMON_FIVE)
            .width(CommonConstants.COMMON_FIVE)
            .margin({ bottom: CommonConstants.COMMON_FIVE })
          // 全天文字
          Text($r('app.string.whole_day'))
            .fontSize($r('app.float.trip_content_font'))
            .opacity(this.getOpacity())

          Blank()
        } else {  // 普通日程样式
          // 开始时间
          Text(this.getTime(this.schedule.startTime))
            .fontSize(10)
            .fontWeight(FontWeight.Regular)
            .opacity(this.getOpacity())
          // 时间轴竖线
          Row()
            .border({
              style: { left: BorderStyle.Solid },
              color: { left: $r('sys.color.icon_fourth') },
              width: { left: CommonConstants.COMMON_ONE }
            })
            .margin({ left: $r('app.float.trip_icon_margin') })
            .height(CommonConstants.COMMON_SEVEN)
            .width(CommonConstants.COMMON_ONE)

          // 日程标识圆点
          Row()
            .borderColor($r('app.color.purple'))
            .borderRadius($r('app.float.trip_schedule_icon_radius'))
            .borderWidth($r('app.float.trip_schedule_icon_width'))
            .height(CommonConstants.COMMON_FIVE)
            .width(CommonConstants.COMMON_FIVE)

          // 时间轴竖线
          Row()
            .border({
              style: { left: BorderStyle.Solid },
              color: { left: $r('sys.color.icon_fourth') },
              width: { left: CommonConstants.COMMON_ONE }
            })
            .margin({ left: $r('app.float.trip_icon_margin') })
            .height(CommonConstants.COMMON_SEVEN)
            .width(CommonConstants.COMMON_ONE)

          // 结束时间
          Text(this.getTime(this.schedule.endTime))
            .fontSize(10)
            .fontWeight(FontWeight.Regular)
            .opacity(this.getOpacity())
        }
      }
      .width($r('app.float.trip_icon_width'))  // 固定宽度
      .justifyContent(FlexAlign.Center)
      .alignItems(HorizontalAlign.Center)
      .height('100%')

      // 右侧内容区域
      Column({ space: 8 }) {
        // 日程标题
        Text(this.schedule.title)
          .fontSize(12)
          .fontWeight(FontWeight.Regular)
          .opacity(this.getOpacity())
          .maxLines(2)  // 最多显示两行
          .textOverflow({ overflow: TextOverflow.Ellipsis })  // 超出显示省略号
          .width(CommonConstants.FULL_PERCENT)  // 100%宽度

        // 日程地点
        Text(this.schedule.location)
          .fontSize($r('app.float.font_size_common'))
          .fontWeight(FontWeight.Regular)
          .maxLines(1)
          .opacity(this.getLocationOpacity())
          .textOverflow({ overflow: TextOverflow.Ellipsis })
          .width(CommonConstants.FULL_PERCENT)
      }
      .layoutWeight(CommonConstants.COMMON_ONE)  // 剩余空间权重
      .height('100%')
      .justifyContent(FlexAlign.Center)
      .alignItems(HorizontalAlign.Center)
      .layoutWeight(1)
      .margin({ right: 12 })
    }
    .backgroundColor($r('app.color.window_background'))  // 背景颜色
    .height(72)  // 固定高度
    .width(CommonConstants.FULL_PERCENT)  // 100%宽度
    .borderRadius(8)  // 圆角
    .onClick(() => {  // 点击事件
      this.toDetail();
    })
  }
}

五、最终成果展示
#我的鸿蒙开发手记#鸿蒙日程功能实现-鸿蒙开发者社区

©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
收藏
回复
举报
回复
    相关推荐