鸿蒙 OS 开发单词打卡 APP 项目实战 20240922 笔记和源码分享

news2024/9/24 15:21:56

配套有完整的录播课, 需要的私信.
在这里插入图片描述

零基础入门级别, 有点前端基础都能学会.

效果截图:
在这里插入图片描述

代码截图:
在这里插入图片描述

页面完整代码:

import { AnswerStatus } from '../enums/AnswerStatus'
import { PracticeStatus } from '../enums/PracticeStatus'
import { getRandomQuestions, Question } from '../model/Question'
import { promptAction } from '@kit.ArkUI'
import { OptionButton } from '../components/OptionButton'
import { StatItem } from '../components/StatItem'
import { ResultDialog } from '../components/ResultDialog'
import { trustedAppService } from '@kit.DeviceSecurityKit'

@Entry
@Component
struct PracticePage {
  // 练习状态
  @State status: PracticeStatus = PracticeStatus.STOPPED
  // 题目个数
  @State totalQuestion: number = 3
  // 题目数组
  @State questions: Question[] = getRandomQuestions(this.totalQuestion)
  // 当前题目的索引
  @State currentIndex: number = 0
  // 用户选中的选项
  @State selectedOption: string = ""
  // 作答状态
  @State answerStatus: AnswerStatus = AnswerStatus.Answering
  // 已作答个数
  @State answeredCount: number = 0
  // 答对的个数
  @State rightCount: number = 0
  // 控制定时器
  timerController = new TextTimerController()
  // 总用时时间
  @State totalTime: number = 0
  // 自定义的弹窗组件控制器
  dialogController: CustomDialogController = new CustomDialogController({
    builder: ResultDialog({
      answeredCount: this.answeredCount,
      rightCount: this.rightCount,
      totalTime: this.totalTime,
      onStartFunc: () => {
        this.status = PracticeStatus.RUNNING
        this.timerController.start()
      },
      onCloseFunc: () => {
        this.questions = getRandomQuestions(this.totalQuestion)
        this.currentIndex = 0
        this.answeredCount = 0
        this.rightCount = 0
        this.totalTime = 0
        this.timerController.reset()
        this.answerStatus = AnswerStatus.Answering
        this.status = PracticeStatus.STOPPED
      },
    }),
    customStyle: true, // 使用自定义样式, 否则那个 x 出不来
    autoCancel: false, // 点击空白区域不会被自动关闭
  })

  // 统计准确率
  getRightPercent() {
    if (this.rightCount === 0) {
      return "0%"
    }
    return `${((this.rightCount / this.answeredCount) * 100).toFixed()}%`
  }

  // 停止练习
  stopPractice() {
    this.status = PracticeStatus.STOPPED
    this.timerController.pause()
    this.dialogController.open()
  }

  build() {
    Column() {
      // 统计面板
      Column() {
        // 准确率
        StatItem({
          icon: $r("app.media.ic_accuracy"),
          name: "准确率",
          fontColor: Color.Black,
        }) {
          Text(this.getRightPercent())
            .width(100)
            .textAlign(TextAlign.Center)
        }

        // 进度
        StatItem({
          icon: $r("app.media.ic_progress"),
          name: "进度",
          fontColor: Color.Black,
        }) {
          Progress({ value: this.answeredCount, total: this.totalQuestion })
            .width(100)
        }

        // 题目个数
        StatItem({
          icon: $r("app.media.ic_count"),
          name: "个数",
          fontColor: Color.Black,
        }) {
          Button(this.totalQuestion.toString())
            .width(100)
            .height(25)
            .backgroundColor("#EBEBEB")
            .enabled(this.status === PracticeStatus.STOPPED)
            .onClick(() => {
              TextPickerDialog.show({
                range: ["5", "10", "20", "50", "100"],
                value: this.totalQuestion.toString(), // 默认值
                onAccept: (result) => {
                  this.totalQuestion = parseInt(result.value.toString())
                  this.questions = getRandomQuestions(this.totalQuestion)
                }
              })
            })
        }

        // 计时
        StatItem({
          icon: $r("app.media.ic_timer"),
          name: "用时",
          fontColor: Color.Black,
        }) {
          Row() {
            TextTimer({ controller: this.timerController })
              .onTimer((utc, elapsedTime) => {
                this.totalTime = elapsedTime
              })
          }.width(100)
          .justifyContent(FlexAlign.Center)
        }
      }.statBgStyle()

      // 题目
      Column() {
        Text(this.questions[this.currentIndex].word).wordStyle()
        Text(this.questions[this.currentIndex].sentence).sentenceStyle()
      }

      // 选项
      Column({ space: 15 }) {
        ForEach(
          this.questions[this.currentIndex].options,
          (item: string) => {
            OptionButton({
              option: item,
              answer: this.questions[this.currentIndex].answer,
              selectedOption: this.selectedOption,
              answerStatus: this.answerStatus,
            })
              .enabled(this.answerStatus === AnswerStatus.Answering)
              .onClick(() => {
                // 判断练习状态
                if (this.status !== PracticeStatus.RUNNING) {
                  promptAction.showToast({ message: "请先点击开始测试按钮" })
                  return
                }

                // 先将答题状态改为已作答
                this.answerStatus = AnswerStatus.Answered

                // 判断答案是否正确
                this.selectedOption = item
                this.answeredCount++
                if (this.questions[this.currentIndex].answer === this.selectedOption) {
                  this.rightCount++
                }

                // 判断题目状态
                if (this.currentIndex < this.questions.length - 1) {
                  setTimeout(() => {
                    this.currentIndex++
                    this.answerStatus = AnswerStatus.Answering
                  }, 500)
                } else {
                  // 停止测试
                  this.stopPractice()
                }
              })
          },
          (item: string) => this.questions[this.currentIndex].word + "_" + item,
        )
      }

      // 控制按钮
      Row({ space: 20 }) {
        Button("停止测试").controlButtonStyle(
          Color.Transparent,
          this.status === PracticeStatus.STOPPED ? Color.Gray : Color.Black,
          this.status === PracticeStatus.STOPPED ? Color.Gray : Color.Black,
        ).enabled(this.status !== PracticeStatus.STOPPED)
          .onClick(() => this.stopPractice())

        Button(this.status === PracticeStatus.RUNNING ? "暂停测试" : "开始测试")
          .controlButtonStyle(
            this.status === PracticeStatus.RUNNING ? "#666666" : Color.Black,
            this.status === PracticeStatus.RUNNING ? "#666666" : Color.Black,
            Color.White,
          )
          .stateEffect(false)
          .onClick(() => {
            if (this.status === PracticeStatus.RUNNING) {
              // 暂停测试
              this.status = PracticeStatus.PAUSED
              this.timerController.pause()
            } else {
              // 开始测试
              this.status = PracticeStatus.RUNNING
              this.timerController.start()
            }
          })
      }
    }.practiceBgStyle()
  }
}

// 页面背景
@Extend(Column)
function practiceBgStyle() {
  .width("100%")
  .height("100%")
  .backgroundImage($r("app.media.img_practice_bg"))
  .backgroundImageSize({ width: "100%", height: "100%" })
  .justifyContent(FlexAlign.SpaceEvenly)
}

// 统计面板背景
@Styles
function statBgStyle() {
  .backgroundColor(Color.White)
  .width("90%")
  .borderRadius(10)
  .padding(20)
}

// 单词样式
@Extend(Text)
function wordStyle() {
  .fontSize(50)
  .fontWeight(FontWeight.Bold)
}

// 例句样式
@Extend(Text)
function sentenceStyle() {
  .height(40)
  .fontSize(16)
  .fontColor("#9BA1A5")
  .fontWeight(FontWeight.Medium)
  .width("80%")
  .textAlign(TextAlign.Center)
}

// 控制按钮样式
@Extend(Button)
function controlButtonStyle(
  bgColor: ResourceColor,
  borderColor: ResourceColor,
  fontColor: ResourceColor,
) {
  .fontSize(16)
  .borderWidth(1)
  .backgroundColor(bgColor)
  .borderColor(borderColor)
  .fontColor(fontColor)
}

选项按钮组件完整代码:

import { AnswerStatus } from '../enums/AnswerStatus'
import { OptionStatus } from '../enums/OptionStatus'

@Component
export struct OptionButton {
  // 选项内容
  option: string = ""
  // 答案
  answer: string = ""
  // 选项状态
  @State optionStatus: OptionStatus = OptionStatus.DEFAULT
  // 用户选中的选项
  @Prop selectedOption: string = ""
  // 属性
  @Prop @Watch("onAnswerStatusChange") answerStatus: AnswerStatus = AnswerStatus.Answering

  // 监听器方法
  onAnswerStatusChange() {
    if (this.option === this.answer) {
      // 答案正确
      this.optionStatus = OptionStatus.RIGHT
    } else {
      if (this.option === this.selectedOption) {
        // 如果当前选项按钮是被选中但错误的按钮
        this.optionStatus = OptionStatus.ERROR
      } else {
        this.optionStatus = OptionStatus.DEFAULT
      }
    }
  }

  // 获取背景颜色
  getBgColor() {
    switch (this.optionStatus) {
      case OptionStatus.RIGHT:
        return "#1DBF7B"
      case OptionStatus.ERROR:
        return "#FA635F"
      default:
        return Color.White
    }
  }

  build() {
    Stack() {
      Button(this.option)
        .optionButtonStyle(
          this.getBgColor(), // 动态获取背景颜色
          this.optionStatus === OptionStatus.DEFAULT ? Color.Black : Color.White,
        )
      // 根据状态设置不同的图标
      if (this.optionStatus === OptionStatus.RIGHT) {
        Image($r("app.media.ic_right"))
          .width(22)
          .height(22)
          .offset({ x: 10 })
      } else if (this.optionStatus === OptionStatus.ERROR) {
        Image($r("app.media.ic_wrong"))
          .width(22)
          .height(22)
          .offset({ x: 10 })
      }
    }.alignContent(Alignment.Start)
  }
}

// 选项按钮样式
@Extend(Button)
function optionButtonStyle(bgColor: ResourceColor, fontColor: ResourceColor) {
  .width(240)
  .height(48)
  .fontSize(16)
  .type(ButtonType.Normal)
  .fontWeight(FontWeight.Medium)
  .borderRadius(8)
  .backgroundColor(bgColor)
  .fontColor(fontColor)
}

弹窗组件完整代码:

import { millisecondsToTimeStr } from '../utils/DateUtil'
import { StatItem } from './StatItem'

@CustomDialog
export struct ResultDialog {
  answeredCount: number = 0 // 已答题个数
  rightCount: number = 0 // 正确个数
  totalTime: number = 0 // 总计耗时
  // 再来一局开始执行的函数
  onStartFunc: () => void = () => {
  }
  // 在关闭弹窗时触发方法
  onCloseFunc: () => void = () => {
  }
  // 弹窗控制器
  controller: CustomDialogController = new CustomDialogController({
    builder: ResultDialog()
  })

  // 统计准确率
  getRightPercent() {
    if (this.rightCount === 0) {
      return "0%"
    }
    return `${((this.rightCount / this.answeredCount) * 100).toFixed()}%`
  }

  build() {
    Column({ space: 10 }) {
      // 右上角有个 X 的按钮
      Image($r("app.media.ic_close"))
        .width(25)
        .height(25)
        .alignSelf(ItemAlign.End)
        .onClick(() => {
          this.controller.close() // 关闭弹窗
          this.onCloseFunc() // 触发关闭的函数
        })

      // 主体内容
      Column({ space: 10 }) {
        // 图片
        Image($r("app.media.img_post"))
          .width("100%")
          .borderRadius(10)

        // 用时
        StatItem({
          icon: $r("app.media.ic_timer"),
          name: "用时",
          fontColor: Color.Black
        }) {
          Text(millisecondsToTimeStr(this.totalTime))
        }

        // 准确率
        StatItem({
          icon: $r("app.media.ic_accuracy"),
          name: "准确率",
          fontColor: Color.Black
        }) {
          Text(this.getRightPercent())
        }

        // 个数
        StatItem({
          icon: $r("app.media.ic_count"),
          name: "个数",
          fontColor: Color.Black
        }) {
          Text(this.answeredCount.toString())
        }

        // 分割线
        Divider()

        // 控制按钮
        Row({ space: 30 }) {
          Button("再来一局")
            .controlButtonStyle(
              Color.Transparent,
              Color.Black,
              Color.Black,
            )
            .onClick(() => {
              this.controller.close()
              this.onCloseFunc() // 先关闭
              this.onStartFunc() // 再启动
            })


          Button("登录打卡")
            .controlButtonStyle(
              Color.Black,
              Color.Black,
              Color.White,
            )
            .onClick(() => {
              this.controller.close()
              this.onCloseFunc() // 先关闭
              // TODO: 登录并打卡
            })
        }
      }
      .backgroundColor(Color.White)
      .width("100%")
      .padding(20)
      .borderRadius(10)
    }
    .backgroundColor(Color.Transparent)
    .width("80%")
  }
}

// 控制按钮样式
@Extend(Button)
function controlButtonStyle(
  bgColor: ResourceColor,
  borderColor: ResourceColor,
  fontColor: ResourceColor,
) {
  .fontSize(16)
  .borderWidth(1)
  .backgroundColor(bgColor)
  .borderColor(borderColor)
  .fontColor(fontColor)
}

代码比较多, 需要整套完整代码的可以私信我获取.

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

江协科技STM32学习- P17 TIM输入捕获

&#x1f680;write in front&#x1f680; &#x1f50e;大家好&#xff0c;我是黄桃罐头&#xff0c;希望你看完之后&#xff0c;能对你有所帮助&#xff0c;不足请指正&#xff01;共同学习交流 &#x1f381;欢迎各位→点赞&#x1f44d; 收藏⭐️ 留言&#x1f4dd;​…

【C++篇】探寻C++ STL之美:从string类的基础到高级操作的全面解析

文章目录 C string 类详解&#xff1a;从入门到精通前言第一章&#xff1a;C 语言中的字符串 vs C string 类1.1 C 语言中的字符串1.2 C string 类的优势 第二章&#xff1a;string 类的构造与基础操作2.1 string 类的构造方法2.1.1 示例代码&#xff1a;构造字符串 2.2 string…

Kotlin 多种形式的 when 表达式(七)

导读大纲 1.0.1 在变量中捕捉 when 表达式1.0.2 对任意对象使用 when 表达式1.0.3 使用不带参数的 when 表达式 when 表达式专题系列 从枚举类引出 when 表达式 1.0.1 在变量中捕捉 when 表达式 在前面的示例中,when 表达式的评估值是color变量 它是通过调用 measureColor() …

pip的安装和使用

pip的安装和使用 1、 pip 是一个现代的&#xff0c;通用的 Python 包管理工具。提供了对 Python 包的查找、下载、安装、卸载的功能。便于我们对Python的资源包进行管理。 2、注&#xff1a;pip 已内置于 Python 3.4 和 2.7 及以上版本&#xff0c;其他版本需另行安装。 3、在安…

java并发工具包JUC(Java Util Concurrent)

1. 什么是JUC 1.1 JUC简介 JUC&#xff08;Java Util Concurrent&#xff09;是Java中的一个并发工具包&#xff0c;提供了一系列用于多线程编程的类和接口&#xff0c;旨在简化并发编程并提高其效率和可维护性。JUC库包含了许多强大的工具和机制&#xff0c;用于线程管理、同…

多比特AI事业部VP程伟光受邀为第四届中国项目经理大会演讲嘉宾

全国项目经理专业人士年度盛会 武汉市多比特信息科技有限公司AI事业部VP程伟光先生受邀为PMO评论主办的全国项目经理专业人士年度盛会——2024第四届中国项目经理大会演讲嘉宾&#xff0c;演讲议题为“AI对于项目经理工作的影响和变化解析”。大会将于10月26-27日在北京举办&am…

如何将Vue项目部署至 nginx

一、准备工作 1.确保安装了开发软件 VS Code&#xff08;此处可查阅安装 VS Code教程&#xff09;&#xff0c;确保相关插件安装成功 2.安装Node.js 和创建Vue项目&#xff08;此处可查阅安装创建教程&#xff09; 3.成功在VS Code运行一个Vue项目&#xff08;此处可查阅运行…

【LeetCode】动态规划—打家劫舍(附完整Python/C++代码)

动态规划—#198. 打家劫舍 前言题目描述基本思路1. 问题定义:2. 理解问题和递推关系:3. 解决方法:4. 进一步优化:5. 小总结: 代码实现Python3代码实现Python 代码解释C代码实现C 代码解释 总结: 前言 在这个问题中&#xff0c;你是一个专业的小偷&#xff0c;计划偷窃沿街的房…

JinDouYun性能测试工具使用方法

1.功能介绍 2. 安卓端支持安卓6及以上的版本&#xff0c;ios支持大部分版本 3. 可以测试游戏&#xff0c;视频&#xff0c;普通应用的性能数据&#xff0c;数据精准&#xff0c;低延迟&#xff0c;无侵入 4.工具下载链接 筋斗云 5.后续功能添加&#xff0c;高版本支持&…

网页爬虫法律与道德:探索法律边界与道德规范

目录 引言 一、网络爬虫技术概述 1.1 定义与功能 1.2 技术原理 1.3 案例分析 二、网络爬虫的法律边界 2.1 合法性要求 2.2 刑事风险 2.3 案例分析 三、网络爬虫的道德规范 3.1 尊重版权和隐私 3.2 合理使用爬虫技术 3.3 透明度和社会责任 四、技术挑战与应对策略…

[linux 驱动]块设备驱动详解与实战

目录 1 描述 2 结构体 2.1 block_device_operations 2.2 gendisk 2.3 block_device 2.4 request_queue 2.5 request 2.6 bio 3.7 blk_mq_tag_set 3.8 blk_mq_ops 3 相关函数 3.1 注册注销块设备 3.1.1 register_blkdev 3.1.2 unregister_blkdev 3.2 gendisk 结构…

SpringBoot开发——整合Hutool工具类轻松生成验证码

文章目录 1、Hutool简介2、验证码效果展示2.1 扭曲干扰验证码2.2 线条干扰验证码2.3 圆圈干扰验证码3、验证码应用场景3.1. 用户注册与身份验证3.2. 支付验证3.3. 订单与物流通知3.4. 信息安全与隐私保护3.5. 通知与提醒3.6. 其他应用场景4、Hutool工具类实现验证码生成4.1 引入…

如何使用ssm实现基于VUE的儿童教育网站的设计与实现+vue

TOC ssm676基于VUE的儿童教育网站的设计与实现vue 第一章 课题背景及研究内容 1.1 课题背景 信息数据从传统到当代&#xff0c;是一直在变革当中&#xff0c;突如其来的互联网让传统的信息管理看到了革命性的曙光&#xff0c;因为传统信息管理从时效性&#xff0c;还是安全…

API公共开放平台设计

背景 随着业务发展未来会有更多的三方应用接入公司平台,目前为服务商定制的机制无法满足三方应用快速接入,所以需要一个更加通用的解决方案,开放平台势在必行。 目标 设计一套通用协议,可以支持其他应用快速接入。 说明 本方案旨在设计整体架构,以及对为何这样设计做…

React 理解 re-render 的作用、概念,并提供详细的例子解释

一、什么是 re-render 在 React 中 re-render&#xff08;重新渲染&#xff09; 是经常发生的行为&#xff0c;主要确保视图要时刻保持最新的数据来呈现。 但每次发生 re-render 也是有代价的&#xff0c;比如数据状态、focus 焦点、表单数据、都得重置&#xff0c; 遇到代码…

获取商品销量详情API:深入解析返回值,助力电商决策

在电商行业&#xff0c;了解商品的销量详情对于商家制定营销策略、优化库存管理和提升用户体验至关重要。通过调用获取商品销量详情的API接口&#xff0c;商家可以实时获取关键的销售数据&#xff0c;从而做出更加明智的决策。本文将深入解析获取商品销量详情API的返回值&#…

linux信号| 学习信号三步走 | 学习信号需要打通哪些知识脉络?

前言: 本节内容主要讲解linux下信号的预备知识以及信号的概念&#xff0c; 信号部分我们将会分为几个阶段进行讲解&#xff1a;信号的概念&#xff0c; 信号的产生&#xff0c; 信号的保存。本节主要讲解信号 ps:本节内容适合学习了进程相关概念的友友们进行观看哦 目录 什么是…

轻松重置 MySQL 8.0 Root 密码的简便方法!

在Windows环境下安装MySQL数据后&#xff0c;如果忘记了 MySQL 8.0 的 root 密码&#xff0c;不必担心&#xff01;通过 --skip-grant-tables 和 named-pipe 模式登录后&#xff0c;只需几步简单的 SQL 命令即可重置密码&#xff1a;刷新权限表、修改密码、再刷新权限&#xff…

SpringBoot+Thymeleaf租房管理系统

> 这是一个基于SpringBootThymeleafBootstrap实现的租房管理系统。 > 功能比较完善&#xff0c;包括用户注册/登录、房源登记、账单费用配置、统计报告等功能。 > 模拟真实使用环境&#xff0c;包括了自然人与法人的身份证明录入、房产证信息录入、通过邮件推送月…

E2VPT: An Effective and Efficient Approach for Visual Prompt Tuning

论文汇总 存在的问题 1.以前的提示微调方法那样只关注修改输入&#xff0c;而应该明确地研究在微调过程中改进自注意机制的潜力&#xff0c;并探索参数效率的极限。 2.探索参数效率的极值来减少可调参数的数量? 解决办法 提示嵌入进行transformer中 提示剪枝 Token-wise …