当前位置: 首页 > java >正文

uni-app 实现做练习题(每一题从后端接口请求切换动画记录错题)

1.每一道题都从后端去请求数据

2.选择完之后请求数据拿到结果 反馈用户

3.记录错题 做完计算正确率

<template><view class="exercise-content"><baseHead title="练习题" @toBack="goHome" /><!-- 题目内容区域 --><view class="question-container" @touchstart="touchStart" @touchend="touchEnd"><viewclass="question-card":class="[slideDirection]":style="{ transform: `translateX(${translateX}px)`, transition: isAnimating ? 'transform 0.3s ease' : 'none' }"><!-- 题目序号 --><view class="question-number">第{{ currentIndex }}题</view><!-- 题目内容 --><view class="question-content"><text>{{ currentQuestion?.content }}</text></view><!-- 选项列表 --><view class="options-list"><viewv-for="(option, index) in currentQuestion.options":key="index"class="option-item":class="{ selected: selectedOption === option.optionKey }"@click="selectOption(option.optionKey)"><image:src="getSpecImgUrl('problem/check.png')"v-if="selectedOption === option.optionKey && selectedOption === correctOption"mode="scaleToFill"/><image :src="getSpecImgUrl('problem/fork.png')" v-else-if="selectedOption === option.optionKey && correctOption" mode="scaleToFill" /><text v-else class="option-label">{{ option.optionKey }}</text><text class="option-text">{{ option.optionContent }}</text></view></view><!-- 下一题按钮 right_icon--><view class="next-btn"><view @click="nextQuestion"><text>下一题</text><image :src="getSpecImgUrl('problem/right_icon.png')" mode="scaleToFill" /></view></view></view></view><view class="question-result" v-if="selectedOption && !isAnimating"><view class="mr-40rpx"><text class="label">答案:</text><text class="c-#087BFF">{{ correctOption }}</text></view><view><text class="label">您选择:</text><text :class="correctOption == selectedOption ? 'c-#087BFF' : 'c-#E75D5C'">{{ selectedOption }}</text></view></view><BottomOperation :height="100"><view class="bottom-stats"><view class="correct-count"><image :src="getSpecImgUrl('problem/true_icon.png')" mode="scaleToFill" /><text>{{ correctCount }}</text></view><view class="line"></view><view class="wrong-count"><image :src="getSpecImgUrl('problem/false_icon.png')" mode="scaleToFill" /><text>{{ wrongCount }}</text></view></view></BottomOperation></view>
</template><script setup lang="ts">
import { ref, computed, onMounted } from 'vue'
import { debounce } from '@/utils/index'
import { onLoad, onShow } from '@dcloudio/uni-app'
import { getSpecImgUrl } from '@/config/app'
import baseHead from '@/components/base-head/base-head.vue'
import BottomOperation from '@/components/bottom-operation/bottom-operation.vue'
import { getQuestionApi, answerApi } from '@/api/modules/plate_management'// 动画相关状态
const translateX = ref(0)
const isAnimating = ref(false)
const slideDirection = ref('')
const loading = ref(false)const examId = ref()
const paramsVal = ref()
onLoad((options: any) => {examId.value = options.examIdparamsVal.value = JSON.parse(options.params)currentIndex.value = paramsVal.value.lastQuestionNum || 1getQuestion()
})// 获取选项
const getQuestion = () => {loading.value = trueconst params = {examId: examId.value,sortNum: currentIndex.value}getQuestionApi(params).then((res) => {if (res.code == 200) {currentQuestion.value = res.dataif (res.data.userAnswer && res.data.userAnswer.length > 0) {selectedOption.value = res.data.userAnswer[0]const current = res.data.options.find((val) => val.isCorrect)correctOption.value = current.optionKey}}loading.value = false})
}
let timer
//作答
const answer = (optionKey) => {const pramas = {id: currentQuestion.value.id,optionKey: optionKey}answerApi(pramas).then((res) => {if (res.code == 200) {correctOption.value = res.data.answerif (correctOption.value == selectedOption.value) {correctCount.value++timer = setTimeout(() => {nextQuestion()}, 500)} else {wrongCount.value++}}})
}
const nextResult = () => {uni.navigateTo({url: `/pages/exercise_result/index?correctCount=${correctCount.value}&wrongCount=${wrongCount.value}&bankId=${paramsVal.value.bankId}`})
}// 选择选项
const selectOption = (value) => {if (selectedOption.value !== '' || loading.value) returnselectedOption.value = valueanswer(value)// 记录用户答案userAnswers.value[currentQuestion.value.id] = value
}// 当前题目索引
const currentIndex = ref(1)
// 已选择的选项
const selectedOption = ref('')
const correctOption = ref('') // 正确选项
// 正确题目数量
const correctCount = ref(0)
// 错误题目数量
const wrongCount = ref(0)
// 用户答案记录
const userAnswers = ref({})// 计算当前题目const currentQuestion = ref()// 执行动画
const performAnimation = (direction, callback) => {slideDirection.value = direction === 'next' ? 'slide-out-left' : 'slide-out-right'isAnimating.value = true// 等待出场动画结束setTimeout(() => {if (callback) callback()// 切完题后再进场slideDirection.value = direction === 'next' ? 'slide-in-right' : 'slide-in-left'setTimeout(() => {isAnimating.value = falseslideDirection.value = ''}, 300) // 和CSS动画时长保持一致}, 300)
}// 下一题
const nextQuestion = () => {if (!selectedOption.value) {uni.showToast({title: '当前题目未作答',icon: 'none'})return}function foo() {// 如果不是最后一题,执行动画并切换if (!currentQuestion.value.isFinalQuestion) {clearTimeout(timer)if (loading.value) returncurrentIndex.value++performAnimation('next', () => {selectedOption.value = ''correctOption.value = ''getQuestion()})} else {nextResult()}}const resultFoo = debounce(foo, 300)resultFoo()
}// 上一题
const prevQuestion = async () => {if (currentIndex.value > 1) {currentIndex.value--selectedOption.value = ''performAnimation('prev', async () => {await getQuestion()selectedOption.value = userAnswers.value[currentQuestion.value.id]})} else {uni.showToast({title: '前面没有题目啦~',icon: 'none'})}
}// 触摸开始位置
let startX = 0// 触摸开始事件
const touchStart = (e) => {startX = e.touches[0].clientX
}// 触摸结束事件
const touchEnd = (e) => {const endX = e.changedTouches[0].clientXconst diff = endX - startX// 左滑:切换到下一题if (diff < -50) {if (!selectedOption.value) {uni.showToast({title: '当前题目未作答',icon: 'none'})return}nextQuestion()}// 右滑:切换到上一题if (diff > 50) {prevQuestion()}
}const goHome = () => {uni.switchTab({ url: `/pages/index/index` })
}
</script><style lang="scss" scoped>
.exercise-content {min-height: 100vh;background-color: #f5f5f6;
}
.question-container {padding: 24rpx;overflow: hidden; /* 确保动画不会溢出 */
}.question-card {background: #ffffff;border-radius: 24rpx;padding: 32rpx 34rpx;will-change: transform; /* 优化动画性能 */&.slide-left {animation: fadeIn 0.3s ease;}&.slide-right {animation: fadeIn 0.3s ease;}
}.question-card {background: #ffffff;border-radius: 24rpx;padding: 32rpx 34rpx;will-change: transform, opacity;
}/* 出场 */
.slide-out-left {animation: slideOutLeft 0.3s forwards ease;
}
.slide-out-right {animation: slideOutRight 0.3s forwards ease;
}/* 入场 */
.slide-in-left {animation: slideInLeft 0.3s forwards ease;
}
.slide-in-right {animation: slideInRight 0.3s forwards ease;
}@keyframes slideOutLeft {from { transform: translateX(0); opacity: 1; }to { transform: translateX(-100%); opacity: 0; }
}@keyframes slideOutRight {from { transform: translateX(0); opacity: 1; }to { transform: translateX(100%); opacity: 0; }
}@keyframes slideInLeft {from { transform: translateX(-100%); opacity: 0; }to { transform: translateX(0); opacity: 1; }
}@keyframes slideInRight {from { transform: translateX(100%); opacity: 0; }to { transform: translateX(0); opacity: 1; }
}</style>

动画通过@keyframes 设置入场和出场动画

http://www.xdnf.cn/news/19740.html

相关文章:

  • 国内免费低代码软件精选:四款工具助你快速开启数字化转型之路
  • 力扣72:编辑距离
  • windows docker(二) 启动存在的容器
  • 5招教你看透PHP开发框架的生态系统够不够“牛”?
  • 推荐一个论文阅读工具ivySCI
  • latex怎么写脚注:标共一声明,标通讯作者
  • 使用 Avidemux 去除视频的重复帧
  • 从实操到原理:一文搞懂 Docker、Tomcat 与 k8s 的关系(附踩坑指南 + 段子解疑)
  • 血缘元数据采集开放标准:OpenLineage Guides 在 Spark 中使用 OpenLineage
  • SpringBoot3中使用Caffeine缓存组件
  • 模版进阶及分离编译问题
  • ansible判断
  • 科学研究系统性思维的方法体系:数据分析模板
  • C语言:归并排序和计数排序
  • OCR识别在媒资管理系统的应用场景剖析与选择
  • 基于ZooKeeper实现分布式锁(Spring Boot接入)及与Kafka实现的对比分析
  • Pod自动重启问题排查:JDK 17 EA版本G1GC Bug导致的应用崩溃
  • Element Plus 表格表单校验功能详解
  • 【Web前端】JS+DOM来实现乌龟追兔子小游戏
  • 轻型载货汽车变速器设计cad+设计说明书
  • 【序列晋升】25 Spring Cloud Open Service Broker 如何为云原生「服务市集」架桥铺路?
  • 分布式光纤传感选型 3 问:你的场景该选 DTS、DAS 还是 BOTDA?
  • 2017考研数学(二)真题
  • vue2滑块验证
  • Coze源码分析-工作空间-资源查询-后端源码
  • 解读“2025年OWASP大模型十大安全风险”与相关攻击案例
  • 《驾驭云原生复杂性:隐性Bug的全链路防御体系构建》
  • Valkey vs Redis详解
  • thinkphp5配置hg/apidoc接口文档
  • 嵌入式硬件 - 51单片机1