实现打卡功能
首页会展示当前用户已经开启的任务列表,每条任务会显示对应的任务名称以及任务目标、当前任务完成情况。用户只可对当天任务进行打卡操作,用户可以根据需要对任务列表中相应的任务进行点击打卡。如果任务列表中的每个任务都在当天完成则为连续打卡一天,连续打卡多天会获得成就徽章。打卡效果如下图所示:
开发前请熟悉鸿蒙开发指导文档: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);})})
}