HarmonyOS Next 之日历组件开发实战

139 阅读1分钟

一、组件功能分析

核心功能需求:

  1. 支持多行周视图布局(6行×7列)
  2. 日期与状态混合展示
  3. 不同状态差异化样式:
    • 未填写(红色背景)
    • 已锁定(绿色背景)
  4. 跨月日期显示处理
  5. 表头星期展示

二、核心实现代码

1. 数据结构定义

// CalendarData.ets
export interface CalendarData {
  date: number
  isCurrentMonth: boolean
  status: 'none' | 'pending' | 'completed'
  hours: number
} 

2. 组件UI实现

@Builder
  CalendarHeader() {
    Row() {
      Text(`${this.currentDate.getFullYear()}${this.currentDate.getMonth() + 1}月`)
        .fontSize(24)
        .fontWeight(FontWeight.Bold)
      Blank()
      Row() {
        Text('今天')
          .border({
            width: 1,
            color: '#ccc',
          })
          .margin({ right: 8 })
          .padding(10)
        Image($r('app.media.ic_prev'))
          .width(24)
          .height(24)
          .margin({ right: 8 })
          .onClick(() => {
            this.currentDate = new Date(this.currentDate.getFullYear(), this.currentDate.getMonth() - 1)
            this.generateCalendarData()
          })
        Image($r('app.media.ic_next'))
          .width(24)
          .height(24)
          .onClick(() => {
            this.currentDate = new Date(this.currentDate.getFullYear(), this.currentDate.getMonth() + 1)
            this.generateCalendarData()
          })
      }
    }
    .width('100%')
    .padding(16)
  }

  @Builder
  WeekHeader() {
    Row() {
      ForEach(['日', '一', '二', '三', '四', '五', '六'], (day: string) => {
        Text(day)
          .fontSize(14)
          .width('14.28%')
          .textAlign(TextAlign.Center)
      })
    }
    .width('100%')
    .padding({ left: 16, right: 16 })
  }

  build() {
    Column() {
      this.CalendarHeader()
      this.WeekHeader()
      Grid() {
        ForEach(this.calendarData, (item: CalendarData) => {
          GridItem() {
            Column() {
              Text(`${item.date}`)
                .fontSize(16)
                .fontColor(item.isCurrentMonth ? '#000000' : '#CCCCCC')
              if (item.status !== 'none') {
                Column() {
                  Row() {
                    Image(item.status === 'completed' ?
                    $r('app.media.ic_completed') :
                    $r('app.media.ic_pending'))
                      .width(16)
                      .height(16)
                    // Text(item.status === 'completed' ? '已锁定' : '未填写')
                    //   .fontSize(12)
                    //   .margin({ left: 4 })
                    //   .fontColor(item.status === 'completed' ? '#36B365' : '#E84026')
                  }

                  if (item.hours > 0) {
                    Text(`${item.hours}h`)
                      .fontSize(12)
                      .margin({ top: 4 })
                  }
                }
                .backgroundColor(item.status === 'completed' ? '#E8F5ED' : '#FFEFED')
                .padding(8)
                .borderRadius(4)
                .width('100%')
              }
            }
            .width('100%')
            .height('100%')
            .padding(8)
            .justifyContent(FlexAlign.SpaceBetween)
          }
        })
      }
      .columnsTemplate('1fr 1fr 1fr 1fr 1fr 1fr 1fr')
      .rowsTemplate('1fr 1fr 1fr 1fr 1fr 1fr')
      .width('100%')
      .height('100%')
      .padding({ left: 16, right: 16 })
    }
  }

3. js交互数据初始化

  @State currentDate: Date = new Date()
  @State calendarData: CalendarData[] = []

  aboutToAppear() {
    this.generateCalendarData()
  }
  generateCalendarData() {
    const year = this.currentDate.getFullYear()
    const month = this.currentDate.getMonth()

    // 获取当月第一天是星期几
    const firstDay = new Date(year, month, 1).getDay()
    // 获取当月天数
    const daysInMonth = new Date(year, month + 1, 0).getDate()
    // 获取上月天数
    const daysInLastMonth = new Date(year, month, 0).getDate()

    let days: CalendarData[] = []

    // 添加上月末尾几天
    for (let i = firstDay - 1; i >= 0; i--) {
      days.push({
        date: daysInLastMonth - i,
        isCurrentMonth: false,
        status: 'none',
        hours: 0
      })
    }

    // 添加当月天数
    for (let i = 1; i <= daysInMonth; i++) {
      days.push({
        date: i,
        isCurrentMonth: true,
        status: i <= 12 ? (i === 3 || i === 4 ? 'completed' : 'pending') : 'none',
        hours: i < 0 ? 8 : 0
      })
    }

    // 补充下月开始几天
    const remainingDays = 42 - days.length
    for (let i = 1; i <= remainingDays; i++) {
      days.push({
        date: i,
        isCurrentMonth: false,
        status: 'none',
        hours: 0
      })
    }

    this.calendarData = days
  }

运行截图: