实现打卡功能
首页会展示当前用户已经开启的任务列表,每条任务会显示对应的任务名称以及任务目标、当前任务完成情况。用户只可对当天任务进行打卡操作,用户可以根据需要对任务列表中相应的任务进行点击打卡。如果任务列表中的每个任务都在当天完成则为连续打卡一天,连续打卡多天会获得成就徽章。打卡效果如下图所示:
开发前请熟悉鸿蒙开发指导文档:gitee.com/li-shizhen-skin/harmony-os/blob/master/README.md
点击或者复制转到。
任务列表
使用List组件展示用户当前已经开启的任务,每条任务对应一个TaskCard组件,clickAction包装了点击和长按事件,用户点击任务卡时会触发弹起打卡弹窗,从而进行打卡操作;长按任务卡时会跳转至任务编辑界面,对相应的任务进行编辑处理。代码如下:
// HomeComponent.ets
// 任务列表
ForEach(this.homeStore.getTaskListOfDay(), (item: TaskInfo) => {
TaskCard({
taskInfoStr: JSON.stringify(item),
clickAction: (isClick: boolean) => this.taskItemAction(item, isClick)
})
.margin({ bottom: Const.DEFAULT_12 })
.height($r('app.float.default_64'))
}, (item: TaskInfo) => JSON.stringify(item))
...
CustomDialogView() // 自定义弹窗中间件
自定义弹窗中间件CustomDialogView
在组件CustomDialogView的aboutToAppear生命周期中注册SHOW_TASK_DETAIL_DIALOG的事件回调方法 ,当通过emit触发此事件时即触发回调方法执行。代码如下:
// CustomDialogView.ets
export class CustomDialogCallback {
confirmCallback: Function = () => {};
cancelCallback: Function = () => {};
}
@Component
export struct CustomDialogView {
@State isShow: boolean = false;
@Provide achievementLevel: number = 0;
@Consume broadCast: BroadCast;
@Provide currentTask: TaskInfo = TaskItem;
@Provide dialogCallBack: CustomDialogCallback = new CustomDialogCallback();
// 成就对话框
achievementDialog: CustomDialogController = new CustomDialogController({
builder: AchievementDialog(),
autoCancel: true,
customStyle: true
});
// 任务时钟对话框
taskDialog: CustomDialogController = new CustomDialogController({
builder: TaskDetailDialog(),
autoCancel: true,
customStyle: true
});
aboutToAppear() {
Logger.debug('CustomDialogView', 'aboutToAppear');
// 成就对话框
this.broadCast.on(BroadCastType.SHOW_ACHIEVEMENT_DIALOG, (achievementLevel: number) => {
Logger.debug('CustomDialogView', 'SHOW_ACHIEVEMENT_DIALOG');
this.achievementLevel = achievementLevel;
this.achievementDialog.open();
});
// 任务时钟对话框
this.broadCast.on(BroadCastType.SHOW_TASK_DETAIL_DIALOG,
(currentTask: TaskInfo, dialogCallBack: CustomDialogCallback) => {
Logger.debug('CustomDialogView', 'SHOW_TASK_DETAIL_DIALOG');
this.currentTask = currentTask || TaskItem;
this.dialogCallBack = dialogCallBack;
this.taskDialog.open();
});
}
aboutToDisappear() {
Logger.debug('CustomDialogView', 'aboutToDisappear');
}
build() {
}
}
点击任务卡片
点击任务卡片会emit触发 “SHOW_TASK_DETAIL_DIALOG” 事件,同时把当前任务,以及确认打卡回调方法传递下去。代码如下:
// HomeComponent.ets
// 任务卡片事件
taskItemAction(item: TaskInfo, isClick: boolean): void {
...
if (isClick) {
// 点击任务打卡
let callback: CustomDialogCallback = { confirmCallback: (taskTemp: TaskInfo) => {
this.onConfirm(taskTemp)
}, cancelCallback: () => {
} };
// 触发弹出打卡弹窗事件 并透传当前任务参数(item) 以及确认打卡回调
this.broadCast.emit(BroadCastType.SHOW_TASK_DETAIL_DIALOG, [item, callback]);
} else {
// 长按编辑任务
...
}
}
// 确认打卡
onConfirm(task) {
this.homeStore.taskClock(task).then((res: AchievementInfo) => {
// 打卡成功后 根据连续打卡情况判断是否 弹出成就勋章 以及成就勋章级别
if (res.showAchievement) {
// 触发弹出成就勋章SHOW_ACHIEVEMENT_DIALOG 事件, 并透传勋章类型级别
let achievementLevel = res.achievementLevel;
if (achievementLevel) {
this.broadCast.emit(BroadCastType.SHOW_ACHIEVEMENT_DIALOG, achievementLevel);
} else {
this.broadCast.emit(BroadCastType.SHOW_ACHIEVEMENT_DIALOG);
}
}
})
}
打卡弹窗组件TaskDetailDialog
打卡弹窗组件根据当前任务的ID获取任务名称以及弹窗背景图片资源。
打卡弹窗组件由两个小组件构成,代码如下:
// TaskDetailDialog.ets
Column() {
// 展示任务的基本信息
TaskBaseInfo({
taskName: TaskMapById[this.currentTask?.taskID - 1].taskName // 根据当前任务ID获取任务名称
});
// 打卡功能组件 (任务打卡、关闭弹窗)
TaskClock({
confirm: () => {
this.dialogCallBack.confirmCallback(this.currentTask);
this.controller.close();
},
cancel: () => {
this.controller.close();
},
showButton: this.showButton
})
}
...
TaskBaseInfo组件代码如下:
// TaskDetailDialog.ets
@Component
struct TaskBaseInfo {
taskName: string | Resource = '';
build() {
Column({ space: Const.DEFAULT_8 }) {
Text(this.taskName)
.fontSize($r('app.float.default_22'))
.fontWeight(FontWeight.Bold)
.fontFamily($r('app.string.HarmonyHeiTi_Bold'))
.taskTextStyle()
.margin({left: $r('app.float.default_12')})
}
.position({ y: $r('app.float.default_267') })
}
}
TaskClock组件代码如下:
// TaskDetailDialog.ets
@Component
struct TaskClock {
confirm: Function = () => {};
cancel: Function = () => {};
showButton: boolean = false;
build() {
Column({ space: Const.DEFAULT_12 }) {
Button() {
Text($r('app.string.clock_in'))
.height($r('app.float.default_42'))
.fontSize($r('app.float.default_20'))
.fontWeight(FontWeight.Normal)
.textStyle()
}
.width($r('app.float.default_220'))
.borderRadius($r('app.float.default_24'))
.backgroundColor('rgba(255,255,255,0.40)')
.onClick(() => {
GlobalContext.getContext().setObject('taskListChange', true);
this.confirm();
})
.visibility(!this.showButton ? Visibility.None : Visibility.Visible)
Text($r('app.string.got_it'))
.fontSize($r('app.float.default_14'))
.fontWeight(FontWeight.Regular)
.textStyle()
.onClick(() => {
this.cancel();
})
}
}
}
打卡接口调用
// HomeViewModel.ets
public async taskClock(taskInfo: TaskInfo) {
let taskItem = await this.updateTask(taskInfo);
let dateStr = this.selectedDayInfo?.dateStr;
// 更新任务失败
if (!taskItem) {
return {
achievementLevel: 0,
showAchievement: false
} as AchievementInfo;
}
// 更新当前时间的任务列表
this.selectedDayInfo.taskList = this.selectedDayInfo.taskList.map((item) => {
return item.taskID === taskItem?.taskID ? taskItem : item;
});
let achievementLevel: number = 0;
if(taskItem.isDone) {
// 更新每日任务完成情况数据
let dayInfo = await this.updateDayInfo();
...
// 当日任务完成数量等于总任务数量时 累计连续打卡一天
// 更新成就勋章数据 判断是否弹出获得勋章弹出及勋章类型
if (dayInfo && dayInfo?.finTaskNum === dayInfo?.targetTaskNum) {
achievementLevel = await this.updateAchievement(this.selectedDayInfo.dayInfo);
}
}
...
return {
achievementLevel: achievementLevel,
showAchievement: ACHIEVEMENT_LEVEL_LIST.includes(achievementLevel)
} as AchievementInfo;
}
`HarmonyOS与OpenHarmony鸿蒙文档籽料:mau123789是v直接拿`
// HomeViewModel.ets
// 更新当天任务列表
updateTask(task: TaskInfo): Promise<TaskInfo> {
return new Promise((resolve, reject) => {
let taskID = task.taskID;
let targetValue = task.targetValue;
let finValue = task.finValue;
let updateTask = new TaskInfo(task.id, task.date, taskID, targetValue, task.isAlarm, task.startTime,
task.endTime, task.frequency, task.isDone, finValue, task.isOpen);
let step = TaskMapById[taskID - 1].step; // 任务步长
let hasExceed = updateTask.isDone;
if (step === 0) { // 任务步长为0 打卡一次即完成该任务
updateTask.isDone = true; // 打卡一次即完成该任务
updateTask.finValue = targetValue;
} else {
let value = Number(finValue) + step; // 任务步长非0 打卡一次 步长与上次打卡进度累加
updateTask.isDone = updateTask.isDone || value >= Number(targetValue); // 判断任务是否完成
updateTask.finValue = updateTask.isDone ? targetValue : `${value}`;
}
TaskInfoTableApi.updateDataByDate(updateTask, (res: number) => { // 更新数据库
if (!res || hasExceed) {
Logger.error('taskClock-updateTask', JSON.stringify(res));
reject(res);
}
resolve(updateTask);
})
})
}