HarmonyOS开发案例:【生活健康app之实现打卡功能】(2)

实现打卡功能

首页会展示当前用户已经开启的任务列表,每条任务会显示对应的任务名称以及任务目标、当前任务完成情况。用户只可对当天任务进行打卡操作,用户可以根据需要对任务列表中相应的任务进行点击打卡。如果任务列表中的每个任务都在当天完成则为连续打卡一天,连续打卡多天会获得成就徽章。打卡效果如下图所示:

开发前请熟悉鸿蒙开发指导文档: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直接拿`

搜狗高速浏览器截图20240326151450.png

// 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);
    })
  })
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/610223.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

基于 AI 的 Python 爬虫

✦ 支持 OPENAI、Gemini、Groq、本地 Ollama、Azure 等 LLM ✦ 只需传递 Prompt 和链接 注意&#xff1a; 调用 Ollama 模型&#xff0c;需要运行下方指令&#xff0c;拉取 embedding 模型&#xff1a; ollama pull nomic-embed-text 问题&#xff1a; 似乎不能换成兼容 Ope…

进程间通信 管道

前言 ubuntu系统的默认用户名不为root的解决方案&#xff08;但是不建议&#xff09;&#xff1a;轻量应用服务器 常见问题-文档中心-腾讯云 (tencent.com) 进程间通信的基本概念 进程间通信目的&#xff1a;进程间也是需要协同的&#xff0c;比如数据传输、资源共享、通知事件…

人脸图像生成(DCGAN)

一、理论基础 1.什么是深度卷积对抗网络&#xff08;Deep Convolutional Generative Adversarial Network&#xff0c;&#xff09; 深度卷积对抗网络&#xff08;Deep Convolutional Generative Adversarial Network&#xff0c;DCGAN&#xff09;是一种生成对抗网络&#xf…

计算机通信SCI期刊推荐,JCR1区,IF=6+,审稿快,无版面费!

一、期刊名称 Computer Communications 二、期刊简介概况 期刊类型&#xff1a;SCI 学科领域&#xff1a;计算机科学 影响因子&#xff1a;6 中科院分区&#xff1a;3区 出版方式&#xff1a;订阅模式/开放出版 版面费&#xff1a;选择开放出版需支付$2300 三、期刊征稿…

STM32中的ICACHE是什么有什么用?如何使用?

什么是ICACHE&#xff1f; icache是一种用于缓存指令的存储器&#xff0c;其目的是提高CPU执行指令的效率。 在计算机系统中&#xff0c;icache&#xff08;指令缓存&#xff09;是处理器核心内部的一个关键组件&#xff0c;它专门用来存储最近使用过的指令。当CPU需要执行一个…

Bean的作用域

Bean的作用域 Bean的作用域是指在Spring整个框架中的某种行为模式&#xff0c;比如singleton单例作用域&#xff0c;就表示Bean在整个spring中只有一份&#xff0c;它是全局共享的&#xff0c;那么当其他人修改了这个值时&#xff0c;那么另一个人读取到的就是被修改的值 Bea…

每日OJ题_记忆化搜索②_力扣62. 不同路径(三种解法)

目录 力扣62. 不同路径 解析代码1_暴搜递归&#xff08;超时&#xff09; 解析代码2_记忆化搜索 解析代码3_动态规划 力扣62. 不同路径 62. 不同路径 难度 中等 一个机器人位于一个 m x n 网格的左上角 &#xff08;起始点在下图中标记为 “Start” &#xff09;。 机器…

element-ui skeleton 组件源码分享

今日简单分享 skeleton 骨架屏组件源码&#xff0c;主要从以下四个方面来讲解&#xff1a; 1、skeleton 组件的页面结构 2、skeleton 组件的属性 3、skeleton item 组件的属性 4、skeleton 组件的 slot 一、skeleton 组件的页面结构 二、skeleton 组件的属性 2.1 animate…

BS架构 数据权限--字段级权限 设计与实现

一、需求场景 1. 销售发货场景 销售出库单上 有 商品名称、发货数量、单价、总金额 等信息。 销售人员 关注 上述所有信息&#xff0c;但 仓管人员 不需要知道 单价、总金额 信息。 2. 配方、工艺保密 场景 配方研发人员 掌握核心配方&#xff0c; 但 交给车间打样、生产时…

一款免费的PDF转换工具分享

最近在吾爱上发现一款PDF免费转换工具&#xff0c;支持多种格式转换&#xff0c;试了一下&#xff0c;还不错 最重要的是免费&#xff0c;不用开会员转换&#xff0c;也没有限制&#xff08;文末有工具地址&#xff09; ps:转换完成后看一下是否符合&#xff0c;可能会有些许…

即插即用模块:Convolutional Triplet注意力模块(论文+代码)

目录 一、摘要 二、创新点总结 三、代码详解 论文&#xff1a;https://arxiv.org/pdf/2010.03045v2 代码&#xff1a;https://github. com/LandskapeAI/triplet-attention 一、摘要 由于注意机制具有在通道或空间位置之间建立相互依赖关系的能力&#xff0c;近年来在各种计…

Web功能测试之表单、搜索测试

初入职场接触功能测试老是碰到以下情况不知道怎么写测试用例&#xff1a; 一个界面很多搜索条件怎么写用例&#xff1f; 下拉框测试如何考虑测试点&#xff1f; 上传要考虑哪些验证点&#xff1f;...... 所以这篇主要是整理关于web测试之表单、搜索测试的相关要点。 一、表…

小程序(三)

十三、自定义组件 &#xff08;二&#xff09;数据方法声明位置 在js文件中 A、数据声明位置&#xff1a;data中 B、方法声明位置methods中&#xff0c;这点和普通页面不同&#xff01; Component({/*** 组件的属性列表*/properties: {},/*** 组件的初始数据*/data: {isCh…

离线使用evaluate

一、目录 步骤demorouge-n 含义 二、实现 步骤 离线使用evaluate: 1. 下载evaluate 文件&#xff1a;https://github.com/huggingface/evaluate/tree/main2. 离线使用 路径/evaluate-main/metrics/rougedemo import evaluate离线使用evaluate: 1. 下载evaluate 文件&…

# 从浅入深 学习 SpringCloud 微服务架构(十五)

从浅入深 学习 SpringCloud 微服务架构&#xff08;十五&#xff09; 一、SpringCloudStream 的概述 在实际的企业开发中&#xff0c;消息中间件是至关重要的组件之一。消息中间件主要解决应用解耦&#xff0c;异步消息&#xff0c;流量削锋等问题&#xff0c;实现高性能&…

Java中使用RediSearch进行高效数据检索

RediSearch是一款构建在Redis上的搜索引擎&#xff0c;它为Redis数据库提供了全文搜索、排序、过滤和聚合等高级查询功能。通过RediSearch&#xff0c;开发者能够在Redis中实现复杂的数据搜索需求&#xff0c;而无需依赖外部搜索引擎。本文将介绍如何在Java应用中集成并使用Red…

3. 多层感知机算法和异或门的 Python 实现

前面介绍过感知机算法和一些简单的 Python 实践&#xff0c;这些都是单层实现&#xff0c;感知机还可以通过叠加层来构建多层感知机。 2. 感知机算法和简单 Python 实现-CSDN博客 1. 多层感知机介绍 单层感知机只能表示线性空间&#xff0c;多层感知机就可以表示非线性空间。…

切割链表 问题的讲解和实现(带哨兵位)

一&#xff1a;题目 二&#xff1a;思路讲解 先 将小于x的放进一个新的链表&#xff0c;将≥x的也放进另一个新链表。 然后 第一个新链表的尾节点的next链接到第二个新链表的哨兵节点的next&#xff0c;因为本身不存在哨兵位。 最后 链接完成后的链表的最后一个节点的next一…

python数据分析——数据的选择和运算

数据的选择和运算 前言一、数据选择NumPy的数据选择一维数组元素提取示例 多维数组行列选择、区域选择示例 花式索引与布尔值索引布尔索引示例一示例二 花式索引示例一示例二 Pandas数据选择Series数据获取DataFrame数据获取列索引取值示例一示例二 取行方式示例loc() 方法示例…

天地图2024版正式启用!首次开放多时相影像专题,可直接查看历史影像

近日&#xff0c;国家基础地理信息中心正式发布了天地图2024版。 新版本更新2米分辨率遥感影像794万平方公里、优于1米分辨率遥感影像655万平方公里&#xff0c;在线服务2米分辨率遥感影像覆盖全部陆地国土、优于1米遥感影像覆盖陆地国土达到98%&#xff0c;同时新增多时相遥感…