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

时间:2024-09-30 07:16:23
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) }