『中工开发者』鸿蒙——日历APP

maomao茂
发布于 2024-12-6 21:15
浏览
0收藏

日历 app 是我们每个人都离不开的手机 app,它能帮我实现日程添加、日期计算、农历查看等功能。所以,本次期末软件设计我选择开发实现日历 app,复现现有 app 的基本功能。

介绍

  • 日期展示功能(公历、农历):主界面显示当月所有日期的数字以及对应的农历。
  • 日历切换:左滑右滑日历进行日历切换。
  • 日期跳转:点击“三角”图标弹出日期选择器,选择对应年月后
    进行日期跳转。
  • 当日标注:显示时,如果是今天,日期会用不同颜色显示。
  • 日期间隔计算:选中日期后界面自动显示当前日期与选中日期的间隔。
  • 日程新增:选中相应的日期后,点击“+”图标,弹出对话框新建日程。
  • 日程提醒:后台在设置日期提醒用户设置的日程。

开发和运行环境

  1. 开发平台:ArkTS 编程语言。
  2. 开发工具:DevEco Studio 64 位。
  3. 运行平台:原则上可在支持鸿蒙操作系统的所有终端上使用。
  4. 第三方组件:公历转农历算法来自 chinese-lunar 库。
  5. 支持软件运行的条件:部分功能需要用户给予网络权限。

项目结构

『中工开发者』鸿蒙——日历APP -鸿蒙开发者社区

项目展示

日程新增、显示、编辑
『中工开发者』鸿蒙——日历APP -鸿蒙开发者社区
日程模糊搜索、日程删除
『中工开发者』鸿蒙——日历APP -鸿蒙开发者社区
日期跳转、日历侧滑切换
『中工开发者』鸿蒙——日历APP -鸿蒙开发者社区
日期详情页、加载动画、网络请求失败提示并自动返回
『中工开发者』鸿蒙——日历APP -鸿蒙开发者社区
日程提醒、点击提醒打开页面
『中工开发者』鸿蒙——日历APP -鸿蒙开发者社区

项目解析

Lunar.ets
该类主要实现通过公历日期,如:2023-12-05转换成农历日期,即:十月廿三。除此之外,还有获得传统干支纪年,如癸卯年、生肖,如 2023年为兔年。下面展示主要源代码:

solarToLunar(solar:Date):any{
    let year = solar.getFullYear()
    let offset = year - 1900;  //获得偏移量
    if(offset < 0 || offset >= MAPPING.length){
      throw new Error('Specified date range is invalid.')
    }
    //查找范围内的农历数据
    let data = this.findLunar(solar, offset, 0, 13, false)

    //如果没有找到,则找前一年的,因为农历在公历之前,并且不会超过一年,查一年就可以了
    data = data || this.findLunar(solar, offset-1, 12, 99, true)
    //还是没有找到,表示超出范围
    if(!data) return false;
    let firstDay = new Date(data.year, data.month-1, data.day)
    let day = this.solarDiff(solar, firstDay, "d")+1 ;
    let month = data.leapMonth > 0 && data.lunarMonth > data.leapMonth ? data.lunarMonth - 1 : data.lunarMonth;
    let leap = data.leapMonth > 0 && data.leapMonth + 1 == data.lunarMonth;
    let result = {
      leap: leap,
      year: data.lunarYear,
      month: month,
      day: day,
      leapMonth: data.leapMonth
    };
    return result;
  }

  findLunar(solar: Date, index, minMonth, maxMonth, isPerYear): any{
    let mapping =  MAPPING[index] ;
    if(!mapping) return false;
    let year = solar.getFullYear();
    let month = solar.getMonth() + 1;
    let day = solar.getDate();
    let lunaryear = year;
    let lunarMonth, find, solarMonth;

    let segMonth, segDay;
    //查找农历
    for(let i=mapping.length-1; i>0;i--){
      lunarMonth = i;
      // @ts-ignore
      segMonth = Number(mapping[i].substring(0, 2))
      // @ts-ignore
      segDay = Number(mapping[i].substring(2, 4))
      solarMonth = isPerYear && segMonth > 12 ? segMonth-12: segMonth
      find = solarMonth < month || (solarMonth == month && segDay <= day) ||
      ((segMonth <= minMonth || segMonth >= maxMonth) && isPerYear);
      if ((solarMonth == 12 && solarMonth > month && i == 1)) {
        find = true;
        year--;
      };
      if (find) break;

    }
    //如果找到,则赋值
    if(!find) return false;
    //取前一年
    if (isPerYear && segMonth == 12) year = year - 1;
    lunaryear = isPerYear ? lunaryear - 1 : lunaryear;
    return {
      year: year,
      month: solarMonth,
      day: segDay,
      lunarYear: lunaryear,
      lunarMonth: lunarMonth,
      leapMonth: mapping[0]			//闰月
    };
  }

dateModel.ets
该类主要是定义了 date数据结构,包含 isSelected(是否被选中)、isToday(是否是今天)、date(Date类型,日期)、weekDay(一周的第几天)、lunarmonth(农历月)、lunarday(农历日)成员属性等,为了加快页面显示速度,这里只设置了少量的成员属性,其他信息等到需要时在进行计算。源代码展示:

  isSelected: boolean = false;
  isToday: boolean = false;
  date: Date | null ;
  weekDay: number = 0; //星期几?
  // yearTips: string = ""; // 干支描述
  // chineseZodiac: string = ""; //生肖
  // lunaryear: string = "";
  lunarmonth:string = "";
  lunarday:string ="";
  result: any ;
  // solarTerms: ""; //节气描述
  // avoid: ""; // 不宜事项
  // lunarCalendar: string = ""; //农历日期
  // suit: ""; //宜事项
  // dayOfYear: number = 0; //一年的第几天
  // weekOfYear: number = 0; //一年的第几周
  // constellation: string = ""; //星座
  // indexWorkDayOfMonth: number = 0; //当月的第几个工作日
  lunar = new Lunar()
  constructor(isToday: boolean, date: Date | null) {
    this.isToday = isToday;
    this.date = date;
    if(this.date !== null){
      this.weekDay = this.date.getDay()
      this.result = this.lunar.solarToLunar(date)
      // this.chineseZodiac = result.shengxiao;
      // this.yearTips = result.Ganzhi;
      // this.lunaryear = result.lunaryear;
      this.lunarmonth = this.lunar.lunarFormat(this.result, "M");
      this.lunarday = this.lunar.lunarFormat(this.result, "D");
    }
  }
}

ScheduleData.ets
该类主要是对日程 schedule数据结构进行定义,包括:id、title、date、year、day、note(备注)成员属性。
LoadingDialog.ets & dialog.ets

  • LoadingDialog:由于日期详情页是使用 request进行数据请求,页面显示不及时,添加加载动画使页面衔接更自然。
@CustomDialog
export struct LoadingDialog{
  controller: CustomDialogController
  build(){
    Row(){
      LoadingProgress().width(40).height(40).color(0x317aff)
      Text("Loading...").fontSize(20).fontColor(Color.Gray)
    }.width(100).height(100)
  }
}

  • dialog:该类是用于用户新建日程时弹出的表单对话框,对话框显示表单比页面直接显示更自然。这里不作代码展示。

index.ets
日历首页。(由于篇幅限制,这里介绍部分功能。)

  • 使用手势实现 tab切换:
    由于 tab自带的切换模式不支持循环滑动,使用手势结合changeIndex接口实现循环 tab切换。
.gesture(
                PanGesture()
                  .onActionStart(() => {
                    console.log("拖动手势开始!!")
                  })
                  .onActionUpdate((event: GestureEvent) => {
                    this.offsetx = event.offsetX;
                    console.log("offset: " + event.offsetX)
                  })
                  .onActionEnd(() => {
                    if (this.offsetx > 150) {
                      //月份减小
                      if (this.month === 1) {
                        this.year = this.year - 1;
                      }
                      this.month = this.months[(this.month + 10)%12];
                      this.tabController.changeIndex(this.month - 1);
                      this.selectedindex = Math.floor(Math.random()*20) +7;
                      //this.getDaysOfMonth(this.year, this.month)
                      this.aboutToAppear()
                      this.offsetx = 0;
                    } else if (this.offsetx < -150) {
                      //月份增大
                      if (this.month === 12) {
                        this.year = this.year + 1;
                      }
                      this.month = this.months[(this.month)%12];
                      this.tabController.changeIndex(this.month - 1);
                      this.selectedindex = Math.floor(Math.random()*20) +7;
                      this.aboutToAppear()
                      this.offsetx = 0;
                    }
                  })
              )
  • 日期间隔计算:通过 Date的 getTime接口实现计算,最后通过双目运算符实现几天前、几天后、今天、明天、昨天显示。
Text(this.ComputeDays(this.selectedDate, new Date()) === 0? "今天" :
              (this.ComputeDays(this.selectedDate, new Date()) < 0?
                (this.ComputeDays(this.selectedDate, new Date()) === -1? "昨天":
                (-1*this.ComputeDays(this.selectedDate, new Date())).toString() + "天前") :
                (this.ComputeDays(this.selectedDate, new Date()) === 1? "明天":
                (this.ComputeDays(this.selectedDate, new Date())).toString() + "天后")) )
              .fontWeight(FontWeight.Bold)

  • 添加日程提醒:通过添加后台代理提醒,在特定时刻通知用户。
let datetime: reminderAgentManager.LocalDateTime = {
          year: newSchedule.year,
          month: newSchedule.month,
          day: (new Date(newSchedule.date)).getDate(),
          hour: 9,
          minute: 0,
        }
       let remindercalendar: reminderAgentManager.ReminderRequestCalendar = {
            dateTime : datetime,
            reminderType: reminderAgentManager.ReminderType.REMINDER_TYPE_CALENDAR,
            actionButton:[
           {
             title: '延迟通知',
             type: reminderAgentManager.ActionButtonType.ACTION_BUTTON_TYPE_SNOOZE
           }],
         wantAgent: {
           pkgName: "com.example.calendar",
           abilityName:"EntryAbility"
         },
         timeInterval: 3600,
         title: '日程提醒',
         content: newSchedule.title + '(点击查看详情)' ,
        }
        try{
          reminderAgentManager.publishReminder(remindercalendar, (err, reminderId) =>{
          })
          //console.log("添加提醒成功,日程:", newSchedule.title  + (newSchedule.note === ''? '' :(',详情:' + newSchedule.note)))
        }catch (error){
          //console.log("提醒添加失败, 错误信息为:", error.code + error.message)
        }
  • 日程表、日程数据插入、删除、更新不作展示,原理同实验三。
    SearchPage.ets&morePage.ets
  • SearchPage.ets:该界面实现搜索结果显示。
  • morePage.ets:该界面实现详情展示,通过查询到的获取黄历api,根据页面跳转时传过来的日期,使用 request进行数据求,并在请求失败后返回原页面。
  aboutToAppear() {
    let httpRequest = http.createHttp();
    let result = router.getParams()['date'].replace(/-/g, '');
    let url = 'https://www.mxnzp.com/api/holiday/single/' + result +
    '?ignoreHoliday=false&app_id=xljlk9pdslpnjqi9&app_secret=JDSTVBDRBwBNO9LXa5622S3dSxL11z2Z';
    httpRequest.request(url, {
      readTimeout: 3000,
    },(err, res) => {
      if (!err) {
        // @ts-ignore
        let tmp = JSON.parse(res.result).data;
        this.days = tmp;
        // @ts-ignore
        console.log("请求的数据为:" + JSON.stringify(JSON.parse(res.result).data))
        //console.log("请求的数据为:" + JSON.stringify(this.days))
        // @ts-ignore
        this.suit = tmp.suit.split('.')
        console.log("请求数据区成功, suit数组长度" + this.suit.length.toString())
        // @ts-ignore
        this.avoid = tmp.avoid.split('.')
        // @ts-ignore
        let tmp_str = "农历" + tmp.lunarCalendar
        this.words = []
        for (let i = 0;i < tmp_str.length; i++) {
          this.words.push(tmp_str[i])
        }
        this.weekDay = tmp.weekDay;
        this.typeDes = tmp.typeDes;
        this.yearTips = tmp.yearTips;
        this.chineseZodiac = tmp.chineseZodiac;
        this.solarTerms = tmp.solarTerms;
        this.dayOfYear = tmp.dayOfYear;
        this.weekOfYear = tmp.weekOfYear;
        this.constellation = tmp.constellation;
        this.LoadingdialogController.close()
        // @ts-ignore
        console.log("请求数据成功, 详细日期:" + tmp.date)
      } else {
        console.log("请求数据失败!")
        this.LoadingdialogController.close()
        prompt.showToast({ message: '加载失败,请检查网络后重试!', bottom: 70 });
        setTimeout(function (){
            router.back()
        }, 500);
      }
    });
  }

收藏
回复
举报
1条回复
按时间正序
/
按时间倒序
7788lx
7788lx

菜鸟疑问:

鸿蒙里面没有日历组件吗?

为什么不能直接调用,要自己写一个?

回复
2024-12-9 15:33:59
回复
    相关推荐