基于OpenHarmony的自定义日历组件实现----详细的自定义日历组件-Bond! 原创

bond_heloworld
发布于 2025-2-11 09:08
4888浏览
2收藏

引言

在移动应用开发中,日历组件是一个常见的需求,用户可以通过查看日历来安排自己的行程、任务等。今天,我们将详细介绍一个基于HarmonyOS框架的自定义日历组件的实现细节。该日历组件不仅展示了当前月份的日期,还能根据用户安排的计划在特定日期上进行标记。下面,我们逐步分析这个组件的实现过程。

项目概述

项目主要功能是展示一个自定义的日历,并且可以根据用户输入的计划数据,在日历中标记出计划开始和进行中的日期。日历支持滑动手势来切换到前一个月或后一个月。

项目运行结果

基于OpenHarmony的自定义日历组件实现----详细的自定义日历组件-Bond!-鸿蒙开发者社区

代码结构解析

1. 接口定义

interface Riqi{
  startYear:number;
  startMonth:number;
  startDay:number;
  endYear:number;
  endMonth:number;
  endDay:number;
  jihuaName:string;
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.

首先,我们定义了一个名为Riqi的接口,用来描述每个计划的开始年、开始月、开始日、结束年、结束月、结束日以及计划名称。

2. 组件定义

@Entry
@Component
struct DataPage {
  @State jihua:Array<Riqi>=[
    {startYear:2025,startMonth:2,startDay:2,endYear:2025,endMonth:2,endDay:5,jihuaName:"洛阳之旅"},
    {startYear:2025,startMonth:2,startDay:11,endYear:2025,endMonth:2,endDay:25,jihuaName:"南京之旅"}
  ]
  @State selectedDate: Date = now;
  @State week:Array<string>=["日","一","二","三","四","五","六"]
  @State year:number=now.getFullYear();
  @State month:number=now.getMonth()+1;
  @State day:number=now.getDay();
  @State riqi:Array<string>=tian(this.year,this.month, findday(this.year,this.month,this.day))
  @State riqi_qian:Array<string>=tian(this.year,this.month-1, findday(this.year,this.month-1,this.day))
  @State riqi_hou:Array<string>=tian(this.year,this.month+1, findday(this.year,this.month+1,this.day))

  // ... methods ...
}

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.

我们使用@Entry和@Component装饰器定义了一个名为DataPage的组件。组件内部使用@State装饰器定义了一系列状态变量,包括计划列表jihua、选中的日期selectedDate、一周的天数week、当前年份year、当前月份month、当前星期几day以及当前月份的日历数据riqi。

3. 判断日期是否在计划中的方法

  private isDateInPlan(date: number): boolean {
    return this.jihua.some(plan => 
      this.year === plan.startYear && 
      this.month === plan.startMonth && 
      (plan.startDay === date || plan.endDay === date)
    );
  }

  private isPlanStartDate(date: number): boolean {
    return this.jihua.some(plan => 
      this.year === plan.startYear && 
      this.month === plan.startMonth && 
      plan.startDay === date
    );
  }

  private isDateInPlanRange(date: number): boolean {
    return this.jihua.some(plan => 
      this.year === plan.startYear && 
      this.month === plan.startMonth && 
      plan.startDay < date && 
      plan.endDay > date
    );
  }

  private getPlanName(date: number): string {
    const plan = this.jihua.find(p => 
      this.year === p.startYear && 
      this.month === p.startMonth && 
      p.startDay === date
    );
    return plan ? plan.jihuaName : '';
  }

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.

这些私有方法用于判断某个日期是否在用户定义的计划中,以及该日期是计划的开始日期还是在计划的进行期间。这些方法为后续的日历渲染提供了必要的逻辑条件。

4. 日历标题与日期选择器

      Text(`${this.year}${this.month}`)
        .fontSize(24)
        .fontWeight(500)
        .onClick(()=>{
          DatePickerDialog.show({
            start: new Date("2000-1-1"),
            end: new Date("2100-12-31"),
            selected: this.selectedDate,
            showTime:false,
            useMilitaryTime:false,
            disappearTextStyle: {color: Color.Pink, font: {size: '22fp', weight: FontWeight.Bold}},
            textStyle: {color: '#ff00ff00', font: {size: '18fp', weight: FontWeight.Normal}},
            selectedTextStyle: {color: '#ff182431', font: {size: '14fp', weight: FontWeight.Regular}},
            onDateAccept: (value: Date) => {
              this.selectedDate = value
              this.year=value.getFullYear();
              this.month=value.getMonth()+1;
              this.day=value.getDay();
              this.riqi=tian(this.selectedDate.getFullYear(),this.selectedDate.getMonth()+1, findday(this.selectedDate.getFullYear(),this.selectedDate.getMonth()+1,this.selectedDate.getDay()))
            },
            onCancel: () => {},
            onDateChange: () => {},
            onDidAppear: () => {},
            onDidDisappear: () => {},
            onWillAppear: () => {},
            onWillDisappear: () => {}
          })
        })

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.

这段代码定义了一个文本按钮,用于显示当前日期的年份和月份,并且当用户点击该按钮时,会弹出一个日期选择器对话框。用户可以在对话框中选择日期,选择完成后,该组件会更新当前选中的日期并刷新日历显示。

5. 星期导航栏

      ForEach(this.week,(item:string)=>{
        GridItem() {
          Button({ type: ButtonType.Normal }) {
            Text(`${item}`)
              .fontSize(16)
              .fontColor("#000000")
          }.width('90%')
          .height('90%')
          .borderRadius(0)
          .backgroundColor("rgba(245, 245, 245, 1)")
        }
      })//循环渲染星期导航栏

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.

这段代码用于渲染星期导航栏,通过ForEach循环遍历week数组,为每个星期几创建一个按钮,并设置按钮的样式。

6. 日期渲染

      ForEach(this.riqi, (item:string, index:number) => {
        GridItem() {
          Column(){
            // 判断是否为当前月份的日期
            if (index >= findday(this.year, this.month, this.day) && 
                index < findday(this.year, this.month, this.day) + getDaysInMonth(this.year, this.month)) {
              
              if(this.isDateInPlan(Number(item))) {
                // 标记计划开始和进行中的日期
              } else {
                // 渲染普通日期
              }
            } else {
              // 渲染非当前月份的日期
            }
          }
        }
      })

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.

这段代码通过ForEach循环遍历riqi数组,渲染出当前月份的日历。每个日期都会被包裹在一个按钮中,并根据不同的条件设置不同的样式。如果日期在用户的计划中,会用特定的颜色标记;如果不是当前月份的日期,会以浅灰色显示。

7. 滑动手势支持

    .gesture(
      SwipeGesture({
        direction: SwipeDirection.Horizontal,
        fingers: 1,
        speed: 50
      })
        .onAction(event => {
          if(event.angle > 0){
            // 从右往左滑
            this.month++;
            if(this.month > 12){
              this.month=1;
              this.year++;
            }
            this.riqi=tian(this.year,this.month, findday(this.year,this.month,this.day))
          }else{
            // 从左往右滑
            this.month--;
            if(this.month < 1){
              this.month=12;
              this.year--;
            }
            this.riqi=tian(this.year,this.month, findday(this.year,this.month,this.day))
          }
        })
    )

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.

为了支持用户滑动切换月份的功能,我们给整个日历组件添加了一个滑动手势。当检测到左滑(即event.angle < 0)时,月份减一,如果月份小于1,则将月份设置为12并年份减一;当检测到右滑(即event.angle > 0)时,月份加一,如果月份大于12,则将月份设置为1并年份加一。每次月份切换后,都会重新计算并渲染该月份的日历。

辅助函数解析

findday函数

function findday(y:number,m:number,d:number) {
  // 计算指定日期是该月第几天
}

  • 1.
  • 2.
  • 3.
  • 4.

findday函数用于计算指定的日期是该月的第几天。它首先计算出从2000年1月1日到指定日期之前的总天数,然后通过计算得出该日期在当月的星期几,从而确定日历显示时前面需要空出几个格子。

tian函数

function tian(y: number, m: number, kong: number): Array<string> {
  // 根据年份、月份和前面空出的格子数,生成一个包含42个格子的日历数组
}

  • 1.
  • 2.
  • 3.
  • 4.

tian函数根据年份、月份以及前面需要空出的格子数来生成一个包含42个元素的数组,这个数组代表了当前月份的日历布局。数组中的元素可能是上个月的日期、本月的日期或者下个月的日期。

getDaysInMonth函数

function getDaysInMonth(year: number, month: number): number {
  // 返回指定月份的天数
}

  • 1.
  • 2.
  • 3.
  • 4.

这个简单的函数用来返回指定年份和月份的天数。它根据年份是否为闰年来判断二月的天数是28天还是29天。

总结

通过以上详细的代码解析,我们可以看到这个自定义日历组件的实现采用了多种HarmonyOS提供的布局和交互组件,包括Grid、GridItem、ForEach、DatePickerDialog等。日历不仅能够展示当前月份的日期,还能根据用户定义的计划来标记特定日期,提供了一个不错的用户交互体验。此外,组件还支持通过滑动手势来切换月份,使得用户操作更加便捷。

详细代码展示

const now = new Date();

interface Riqi{
  startYear:number;
  startMonth:number;
  startDay:number;
  endYear:number;
  endMonth:number;
  endDay:number;
  jihuaName:string;
}

@Entry
@Component
struct DataPage {
  @State jihua:Array<Riqi>=[
    {startYear:2025,startMonth:2,startDay:2,endYear:2025,endMonth:2,endDay:5,jihuaName:"洛阳之旅"},
    {startYear:2025,startMonth:2,startDay:11,endYear:2025,endMonth:2,endDay:25,jihuaName:"南京之旅"}
  ]
  @State selectedDate: Date = now;
  @State week:Array<string>=["日","一","二","三","四","五","六"]
  @State year:number=now.getFullYear();
  @State month:number=now.getMonth()+1;
  @State day:number=now.getDay();
  @State riqi:Array<string>=tian(this.year,this.month, findday(this.year,this.month,this.day))
  @State riqi_qian:Array<string>=tian(this.year,this.month-1, findday(this.year,this.month-1,this.day))
  @State riqi_hou:Array<string>=tian(this.year,this.month+1, findday(this.year,this.month+1,this.day))

  private isDateInPlan(date: number): boolean {
    return this.jihua.some(plan => 
      this.year === plan.startYear && 
      this.month === plan.startMonth && 
      (plan.startDay === date || plan.endDay === date)
    );
  }

  private isPlanStartDate(date: number): boolean {
    return this.jihua.some(plan => 
      this.year === plan.startYear && 
      this.month === plan.startMonth && 
      plan.startDay === date
    );
  }

  private isDateInPlanRange(date: number): boolean {
    return this.jihua.some(plan => 
      this.year === plan.startYear && 
      this.month === plan.startMonth && 
      plan.startDay < date && 
      plan.endDay > date
    );
  }

  private getPlanName(date: number): string {
    const plan = this.jihua.find(p => 
      this.year === p.startYear && 
      this.month === p.startMonth && 
      p.startDay === date
    );
    return plan ? plan.jihuaName : '';
  }

  //以2024.12的日历作为初始页面
  //对各个变量进行初始化
  build() {
    Grid() {
      GridItem(){
        Row(){
          Text(`${this.year}${this.month}`)
            .fontSize(24)
            .fontWeight(500)
            .onClick(()=>{
              DatePickerDialog.show({ // 建议使用 this.getUIContext().showDatePickerDialog()接口
                start: new Date("2000-1-1"),
                end: new Date("2100-12-31"),
                selected: this.selectedDate,
                showTime:false,
                useMilitaryTime:false,
                disappearTextStyle: {color: Color.Pink, font: {size: '22fp', weight: FontWeight.Bold}},
                textStyle: {color: '#ff00ff00', font: {size: '18fp', weight: FontWeight.Normal}},
                selectedTextStyle: {color: '#ff182431', font: {size: '14fp', weight: FontWeight.Regular}},
                onDateAccept: (value: Date) => {
                  // 通过Date的setFullYear方法设置按下确定按钮时的日期,这样当弹窗再次弹出时显示选中的是上一次确定的日期
                  this.selectedDate = value
                  console.info("DatePickerDialog:onDateAccept()" + value.toString())
                  this.year=value.getFullYear();
                  this.month=value.getMonth()+1;
                  this.day=value.getDay();
                  this.riqi=tian(this.selectedDate.getFullYear(),this.selectedDate.getMonth()+1, findday(this.selectedDate.getFullYear(),this.selectedDate.getMonth()+1,this.selectedDate.getDay()))
                },
                onCancel: () => {
                  console.info("DatePickerDialog:onCancel()")
                },
                onDateChange: (value: Date) => {
                  console.info("DatePickerDialog:onDateChange()" + value.toString())
                },
                onDidAppear: () => {
                  console.info("DatePickerDialog:onDidAppear()")
                },
                onDidDisappear: () => {
                  console.info("DatePickerDialog:onDidDisappear()")
                },
                onWillAppear: () => {
                  console.info("DatePickerDialog:onWillAppear()")
                },
                onWillDisappear: () => {
                  console.info("DatePickerDialog:onWillDisappear()")
                }
              })
            })
          Image($r('app.media.sanjiao')).width(13.56).height(9.81).margin({left:5})
        }
        .width('90%')
        .height('100%')

        //根据用户输入的年月更改页面标题
      }.columnStart(1)
      .columnEnd(3)

      GridItem() {
        Column() {
          Blank()
        }
      }.columnStart(4)
      .columnEnd(7)

      //跳转前一个或下一个月
      ForEach(this.week,(item:string)=>{
        GridItem() {
          Button({ type: ButtonType.Normal }) {
            Text(`${item}`)
              .fontSize(16)
              .fontColor("#000000")
          }.width('90%')
          .height('90%')
          .borderRadius(0)
          .backgroundColor("rgba(245, 245, 245, 1)")
        }
      })//循环渲染星期导航栏
      // ForEach(this.riqi_qian, (item:string) => {})
      ForEach(this.riqi, (item:string, index:number) => {
        GridItem() {
          Column(){
            // 判断是否为当前月份的日期
            if (index >= findday(this.year, this.month, this.day) && 
                index < findday(this.year, this.month, this.day) + getDaysInMonth(this.year, this.month)) {
              
              if(this.isDateInPlan(Number(item))) {
                // if(this.isPlanStartDate(Number(item)))
                Row() {
                  Button({type: ButtonType.Normal}) {
                    Text(`${item}`)
                      .fontSize(20)
                  }.width(32)
                  .height(32)
                  .borderRadius(5)
                  .backgroundColor("rgba(137, 186, 32, 1)")
                  // .margin({left:this.isPlanStartDate(Number(item))?0:'26%',right:this.isPlanStartDate(Number(item))?'26%':0})
                }.width('84%')
                .height(32)
                .justifyContent(this.isPlanStartDate(Number(item))?FlexAlign.Start:FlexAlign.End)
                .backgroundColor("rgba(137, 186, 32, 0.2)")
                .margin({left:this.isPlanStartDate(Number(item))?'16%':0,right:this.isPlanStartDate(Number(item))?0:'16%'})
                .borderRadius({topLeft:this.isPlanStartDate(Number(item))?5:0,topRight:this.isPlanStartDate(Number(item))?0:5,bottomLeft:this.isPlanStartDate(Number(item))?5:0,bottomRight:this.isPlanStartDate(Number(item))?0:5})

                if(this.isPlanStartDate(Number(item))) {
                  Column() {
                    Button({type: ButtonType.Normal}) {
                      // Image($r('app.media.startIcon'))
                      // Text(this.getPlanName(Number(item)))
                      //   .fontSize(12)
                      //   .fontColor("#ff034980")
                      Row(){
                        Image($r('app.media.qizi'))
                          .height(13)
                          .width(14.06)
                        Text(this.getPlanName(Number(item)))
                          .fontSize(16)
                          .fontColor("rgba(133, 133, 133, 1)")
                      }.justifyContent(FlexAlign.SpaceAround).width('80%')
                    }.width(104).height(29).margin({left:70})
                    .backgroundColor("rgba(237, 237, 237, 1)")
                    .borderRadius(5)
                  }
                  .height('60%')
                  .backgroundColor("rgba(245, 245, 245, 1)")
                  .margin({top:'20%'})
                  // .justifyContent(FlexAlign.Center)
                }
              } else if(this.isDateInPlanRange(Number(item))) {
                Button({type: ButtonType.Normal}) {
                  Text(`${item}`)
                    .fontSize(20)
                }.width(index%7==0||(index+1)%7==0?'84%':'100%')
                .height(32)
                .borderRadius({
                   topLeft:index%7==0?5:0,
                   topRight:(index+1)%7==0?5:0,
                   bottomLeft:index%7==0?5:0,
                   bottomRight:(index+1)%7==0?5:0
                })
                .backgroundColor("rgba(137, 186, 32, 0.2)")
                .margin({left:(index+1)%7==0?0:'16%',right:index%7==0?0:'16%'})
              } else {
                Button({type: ButtonType.Normal}) {
                  Text(`${item}`)
                    .fontSize(20)
                    .fontColor('#000000')
                }.width('100%')
                .height(32)
                .borderRadius(0)
                .backgroundColor("rgba(245, 245, 245, 1)")
                .margin({left:(index+1)%7==0?0:'16%',right:index%7==0?0:'16%'})

              }
            } else {
              Button({type: ButtonType.Normal}) {
                Text(`${item}`)
                  .fontSize(20)
                  .fontColor('#999999')
              }.width('100%')
              .height(32)
              .borderRadius(0)
              .backgroundColor("rgba(245, 245, 245, 1)")
              .margin({left:(index+1)%7==0?0:'16%',right:index%7==0?0:'16%'})
            }
          }.width('100%')
          .height('100%')
          .justifyContent(FlexAlign.SpaceBetween)
        }
      })//渲染日期


    }
    .backgroundColor("rgba(245, 245, 245, 1)")
    .columnsTemplate('1fr 1fr 1fr 1fr 1fr 1fr 1fr')
    .rowsTemplate(this.riqi.length>35?'1fr 1fr 2fr 2fr 2fr 2fr 2fr 2fr 2fr 1fr':'1fr 1fr 2fr 2fr 2fr 2fr 2fr 2fr 1fr' )
    .gesture(
      SwipeGesture({
        //触发滑动手势的滑动方向为水平方向。
        direction: SwipeDirection.Horizontal,
        fingers: 1,
        speed: 50
      })
        //滑动手势识别成功回调。
        .onAction(event => {
          //angle是负的就是从左往右滑
          if(event.angle > 0){
            //从右往左滑
            if(this.month==12){
              this.month=1;
              this.year++;
            }else{
              this.month++;
            }
            this.riqi=tian(this.year,this.month, findday(this.year,this.month,this.day))

          }else{
            //从左往右滑
            if(this.month==1){
              this.month=12;
              this.year--;
            }else{
              this.month--;
            }
            this.riqi=tian(this.year,this.month, findday(this.year,this.month,this.day))
          }
        })
    )
    //根据月份天数判断需要几行
  }
}

function findday(y:number,m:number,d:number) {
  let run: Array<number> = [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
  let feirun: Array<number> = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
  let sum: number = 0;
  let nian: number = 2000;
  let yue: number = 1;
  let ri: number = 1;
  let jujin: number = 0;
  for (let j = nian; j < y; j++) {
    //计算前几年有多少天,需区分是否为闰年
    sum = 0;
    if ((j % 400 == 0) || (j % 4 == 0 && j % 100 != 0)) {
      sum += 366;
    } else {
      sum += 365;
    }
    jujin += sum;
  }
  sum = 0; //计算当年有多少天
  if ((y % 400 == 0) || (y % 4 == 0 && y % 100 != 0)) {
    for (let i = 0; i < m-1; i++) {
      sum += run[i];
    }
  } else {
    for (let i = 0; i < m-1; i++) {
      sum += feirun[i];
    }
  }
  jujin += sum;
  jujin = (jujin+6) % 7;
  //计算出要显示的日历前面需要空几格
  return jujin;
}

function tian(y: number, m: number, kong: number): Array<string> {
  let day: Array<string> = new Array(42); // 总共42个格子
  let d: number;
  let run: Array<number> = [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
  let feirun: Array<number> = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];

  // 确定本月天数
  if ((y % 400 == 0) || (y % 4 == 0 && y % 100 != 0)) {
    d = run[m - 1];
  } else {
    d = feirun[m - 1];
  }

  // 计算上个月的天数
  let lastMonth: number = (m - 2 + 12) % 12;
  let lastMonthDays: number = (y % 400 == 0 || (y % 4 == 0 && y % 100 != 0)) ? run[lastMonth] : feirun[lastMonth];

  // 填充前面的空格为上个月的最后几天
  for (let i = 0; i < kong; i++) {
    day[i] = (lastMonthDays - kong + i + 1).toString(); // 依次填充
  }

  // 填充本月的日期
  for (let i = kong; i < d + kong; i++) {
    day[i] = (i - kong + 1).toString();
  }

  // 填充后面的空格为下个月的前几天
  for (let i = d + kong; i < 42; i++) {
    day[i] = (i - d - kong + 1).toString();
  }

  return day;
}

function padZero(arg0: number): number {
  throw new Error('Function not implemented.');
}

// 添加一个新的辅助函数来获取指定月份的天数
function getDaysInMonth(year: number, month: number): number {
  let run: Array<number> = [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
  let feirun: Array<number> = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
  
  if ((year % 400 == 0) || (year % 4 == 0 && year % 100 != 0)) {
    return run[month - 1];
  } else {
    return feirun[month - 1];
  }
}

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
  • 81.
  • 82.
  • 83.
  • 84.
  • 85.
  • 86.
  • 87.
  • 88.
  • 89.
  • 90.
  • 91.
  • 92.
  • 93.
  • 94.
  • 95.
  • 96.
  • 97.
  • 98.
  • 99.
  • 100.
  • 101.
  • 102.
  • 103.
  • 104.
  • 105.
  • 106.
  • 107.
  • 108.
  • 109.
  • 110.
  • 111.
  • 112.
  • 113.
  • 114.
  • 115.
  • 116.
  • 117.
  • 118.
  • 119.
  • 120.
  • 121.
  • 122.
  • 123.
  • 124.
  • 125.
  • 126.
  • 127.
  • 128.
  • 129.
  • 130.
  • 131.
  • 132.
  • 133.
  • 134.
  • 135.
  • 136.
  • 137.
  • 138.
  • 139.
  • 140.
  • 141.
  • 142.
  • 143.
  • 144.
  • 145.
  • 146.
  • 147.
  • 148.
  • 149.
  • 150.
  • 151.
  • 152.
  • 153.
  • 154.
  • 155.
  • 156.
  • 157.
  • 158.
  • 159.
  • 160.
  • 161.
  • 162.
  • 163.
  • 164.
  • 165.
  • 166.
  • 167.
  • 168.
  • 169.
  • 170.
  • 171.
  • 172.
  • 173.
  • 174.
  • 175.
  • 176.
  • 177.
  • 178.
  • 179.
  • 180.
  • 181.
  • 182.
  • 183.
  • 184.
  • 185.
  • 186.
  • 187.
  • 188.
  • 189.
  • 190.
  • 191.
  • 192.
  • 193.
  • 194.
  • 195.
  • 196.
  • 197.
  • 198.
  • 199.
  • 200.
  • 201.
  • 202.
  • 203.
  • 204.
  • 205.
  • 206.
  • 207.
  • 208.
  • 209.
  • 210.
  • 211.
  • 212.
  • 213.
  • 214.
  • 215.
  • 216.
  • 217.
  • 218.
  • 219.
  • 220.
  • 221.
  • 222.
  • 223.
  • 224.
  • 225.
  • 226.
  • 227.
  • 228.
  • 229.
  • 230.
  • 231.
  • 232.
  • 233.
  • 234.
  • 235.
  • 236.
  • 237.
  • 238.
  • 239.
  • 240.
  • 241.
  • 242.
  • 243.
  • 244.
  • 245.
  • 246.
  • 247.
  • 248.
  • 249.
  • 250.
  • 251.
  • 252.
  • 253.
  • 254.
  • 255.
  • 256.
  • 257.
  • 258.
  • 259.
  • 260.
  • 261.
  • 262.
  • 263.
  • 264.
  • 265.
  • 266.
  • 267.
  • 268.
  • 269.
  • 270.
  • 271.
  • 272.
  • 273.
  • 274.
  • 275.
  • 276.
  • 277.
  • 278.
  • 279.
  • 280.
  • 281.
  • 282.
  • 283.
  • 284.
  • 285.
  • 286.
  • 287.
  • 288.
  • 289.
  • 290.
  • 291.
  • 292.
  • 293.
  • 294.
  • 295.
  • 296.
  • 297.
  • 298.
  • 299.
  • 300.
  • 301.
  • 302.
  • 303.
  • 304.
  • 305.
  • 306.
  • 307.
  • 308.
  • 309.
  • 310.
  • 311.
  • 312.
  • 313.
  • 314.
  • 315.
  • 316.
  • 317.
  • 318.
  • 319.
  • 320.
  • 321.
  • 322.
  • 323.
  • 324.
  • 325.
  • 326.
  • 327.
  • 328.
  • 329.
  • 330.
  • 331.
  • 332.
  • 333.
  • 334.
  • 335.
  • 336.
  • 337.
  • 338.
  • 339.
  • 340.
  • 341.
  • 342.
  • 343.
  • 344.
  • 345.
  • 346.
  • 347.
  • 348.
  • 349.
  • 350.
  • 351.
  • 352.
  • 353.
  • 354.
  • 355.
  • 356.
  • 357.
  • 358.

©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
已于2025-2-11 10:19:48修改
6
收藏 2
回复
举报
6
3
2
3条回复
按时间正序
/
按时间倒序
Crips
Crips

写的太好了,日历开发再也不难了

1
回复
2025-2-11 11:41:33
在敲键盘的小鱼干很饥饿
在敲键盘的小鱼干很饥饿

有点意思,可以的


回复
2025-2-12 20:05:22
言程序plus
言程序plus

代码封装简洁清晰

1
回复
2025-2-20 16:06:37


回复
    相关推荐