『中工开发者』鸿蒙——日历APP
maomao茂
发布于 2024-12-6 21:15
浏览
0收藏
日历 app 是我们每个人都离不开的手机 app,它能帮我实现日程添加、日期计算、农历查看等功能。所以,本次期末软件设计我选择开发实现日历 app,复现现有 app 的基本功能。
介绍
- 日期展示功能(公历、农历):主界面显示当月所有日期的数字以及对应的农历。
- 日历切换:左滑右滑日历进行日历切换。
- 日期跳转:点击“三角”图标弹出日期选择器,选择对应年月后
进行日期跳转。 - 当日标注:显示时,如果是今天,日期会用不同颜色显示。
- 日期间隔计算:选中日期后界面自动显示当前日期与选中日期的间隔。
- 日程新增:选中相应的日期后,点击“+”图标,弹出对话框新建日程。
- 日程提醒:后台在设置日期提醒用户设置的日程。
开发和运行环境
- 开发平台:ArkTS 编程语言。
- 开发工具:DevEco Studio 64 位。
- 运行平台:原则上可在支持鸿蒙操作系统的所有终端上使用。
- 第三方组件:公历转农历算法来自 chinese-lunar 库。
- 支持软件运行的条件:部分功能需要用户给予网络权限。
项目结构
项目展示
日程新增、显示、编辑
日程模糊搜索、日程删除
日期跳转、日历侧滑切换
日期详情页、加载动画、网络请求失败提示并自动返回
日程提醒、点击提醒打开页面
项目解析
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);
}
});
}
赞
收藏
回复
相关推荐
菜鸟疑问:
鸿蒙里面没有日历组件吗?
为什么不能直接调用,要自己写一个?