nxdt-uniapp/pages/myExercise/exercises.vue

605 lines
20 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<view>
<Navbar title="练习" :showBack="false" />
<div class="content">
<div class="top-wrapper">
<div class="left">
<div style="color: #1989fa">正确{{ correctNum }}</div>
<div style="color: #ff4d4f">错误{{ errorNum }}</div>
<div>用时{{ timeCount }}</div>
</div>
<div class="right">
<span style="color: #1989fa">{{ currentIndex + 1 }}</span>
/{{ questionList.length }}
</div>
</div>
<!-- 题目内容 -->
<div class="question-wrapper">
<div class="question-list" v-for="(item, index) in questionList" :key="item.id" v-show="index == currentIndex">
<div class="question-type">
<div class="line"></div>
<div class="type">
<div v-if="item.questionType == 1">单选题</div>
<div v-else-if="item.questionType == 2">多选题</div>
<div v-else-if="item.questionType == 3">判断题</div>
</div>
</div>
<div class="question-title">{{ index + 1 }}. {{ item.content }}</div>
<div class="question-img-list">
<div class="img" v-for="(img, imgIndex) in item.questionPictureVoList">
<u-image
:key="imgIndex"
:src="img.url"
:showLoading="true"
width="60px"
height="60px"
@click="handleImg(img.url)"
/>
</div>
</div>
<div class="question-item">
<div v-if="item.questionType == 1 || item.questionType == 3">
<!-- 单选题 -->
<u-radio-group v-model="item.radioValue" placement="column">
<u-radio
:customStyle="{ marginBottom: '8px' }"
v-for="(question, index) in item.questionAnswerVoList"
:key="index"
:label="question.options + '. ' + question.answerOptions"
:name="question.options + '. ' + question.answerOptions"
:disabled="question.disabled"
@change="radioChange(question)"
>
<div class="radio-item">
<div class="radio-name">{{ question.options + '. ' + question.answerOptions }}</div>
<div class="question-img-list">
<div class="img" v-for="(img, imgIndex) in question.questionPictureVoList">
<u-image
:key="imgIndex"
:src="img.url"
:showLoading="true"
width="60px"
height="60px"
@click="handleImg(img.url)"
/>
</div>
</div>
</div>
</u-radio>
</u-radio-group>
</div>
<div v-if="item.questionType == 2">
<!-- 多选题 -->
<u-checkbox-group v-model="item.checkboxValue" placement="column">
<div v-for="(question, index) in item.questionAnswerVoList">
<u-checkbox
:customStyle="{ marginBottom: '8px' }"
:key="index"
:label="question.options + '. ' + question.answerOptions"
:name="question.options + '. ' + question.answerOptions"
:disabled="question.disabled"
@change="radioChange(question)"
></u-checkbox>
<div class="radio-item">
<div class="question-img-list" style="margin: 10px">
<div class="img" v-for="(img, imgIndex) in question.questionPictureVoList">
<u-image
:key="imgIndex"
:src="img.url"
:showLoading="true"
width="60px"
height="60px"
@click="handleImg(img.url)"
/>
</div>
</div>
</div>
</div>
</u-checkbox-group>
</div>
<div style="padding: 10px">
<u-button
type="primary"
size="small"
shape="circle"
@click="handleOption(item)"
v-show="!item.chooseAnswer"
>
确定选择
</u-button>
</div>
<div v-if="item.chooseAnswer">
<div style="color: #5ac725" v-if="item.chooseAnswer == item.correctAnswer">回答正确</div>
<div style="color: #ff4d4f" v-else>回答错误</div>
</div>
<div class="answer-wrap" v-if="item.chooseAnswer">
<div class="answer-item">
<div>正确答案</div>
<div style="color: #5ac725">{{ item.correctAnswer1 }}</div>
</div>
<div class="answer-line"></div>
<div class="answer-item">
<div>已选答案</div>
<div :style="{ color: item.chooseAnswer == item.correctAnswer ? '#5ac725' : '#ff4d4f' }">
{{ item.chooseAnswer1 }}
</div>
</div>
</div>
<!-- 解析 -->
<div v-if="item.chooseAnswer">
<div style="margin: 20px 0 10px; font-weight: 800">解析:</div>
<div class="analysis">{{ item.analysis }}</div>
</div>
</div>
</div>
</div>
<!-- 底部固定按钮, 上一题, 下一题, 结束练习 -->
<div class="box"></div>
<div class="bottom-wrapper">
<u-button
class="btn"
type="primary"
size="small"
shape="circle"
@click="handlePrev"
:disabled="currentIndex == 0"
>
上一题
</u-button>
<u-button
class="btn"
type="primary"
size="small"
shape="circle"
@click="handleNext"
:disabled="
currentIndex == questionList.length - 1 ||
(questionList[currentIndex] && questionList[currentIndex].chooseAnswer == '')
"
>
下一题
</u-button>
<u-button class="btn" type="primary" size="small" shape="circle" @click="showModal = true">结束练习</u-button>
</div>
</div>
<!-- 弹框 -->
<u-modal
:show="showModal"
title="温馨提示"
content="确定退出本次练习吗?"
:showCancelButton="true"
@cancel="showModal = false"
@confirm="handleEnd"
>
<view class="slot-content">确定退出本次练习吗</view>
</u-modal>
<u-modal :show="showModal2" title="答题结果" @confirm="goBack">
<view class="slot-content">
<div class="container">
<div>答题数:{{ correctNum + errorNum }}</div>
<div style="margin: 0 20px">
正确数:
<span style="color: #5ac725">{{ correctNum }}</span>
</div>
<div>
正确率:{{ correctNum + errorNum == 0 ? 0 : ((correctNum / (correctNum + errorNum)) * 100).toFixed(2) }}%
</div>
</div>
<u-line dashed :hairline="false" margin="20px 0"></u-line>
<div class="container">
<div>用时:</div>
<div>{{ timeCount }}</div>
</div>
</view>
</u-modal>
</view>
</template>
<script>
import { getQuestion, updateAnswer, getTestQuestion, endPractice } from '@/api/educationTraining'
import config from '@/config'
export default {
data() {
return {
isAddLoading: false,
userId: uni.getStorageSync('userInfo').userId,
opt: {},
currentIndex: 0, // 当前题目索引
questionList: [], // 试题列表
endOfExamination: false, // 是否结束考试
correctNum: 0, // 正确题数
errorNum: 0, // 错误题数
time: 0, // 用时
timeCount: '00:00:00',
timer: null, // 计时器id
showModal: false,
showModal2: false
}
},
onLoad(opt) {
this.opt = JSON.parse(opt.params)
console.log('🚀 ~ onLoad ~ this.opt:', this.opt)
this.getQuestionList()
},
methods: {
// 获取题目列表
async getQuestionList() {
let res = {}
if (this.opt.isAutoExercises) {
const params = {
practiceId: this.opt.practiceId,
practiceType: this.opt.practiceType,
lastPracticeQuestionId: this.opt.lastPracticeQuestionId || ''
}
console.log('🚀 ~ getQuestionList ~ params:', params)
res = await getTestQuestion(params)
} else {
res = await getQuestion({ examId: this.opt.id })
}
console.log('🚀 ~ 题目列表 ~ res:', res)
if (this.opt.practiceType == 1) {
// 过滤出包含 lastPracticeQuestionId 的题目, lastPracticeQuestionId 是未答题目所有id拼接的字符串
this.opt.lastPracticeQuestionId = this.opt.lastPracticeQuestionId.split(',')
res.data = res.data.filter(item => this.opt.lastPracticeQuestionId.includes(item.questionId))
}
this.questionList = res.data
this.questionList.forEach(item => {
if (item.questionPictureVoList && item.questionPictureVoList.length > 0) {
item.questionPictureVoList.forEach(img => {
img.url = config.fileUrl2 + img.pictureUrl.replace(/\\/g, '/')
// img.url = 'https://cdn.uviewui.com/uview/swiper/1.jpg'
})
}
//
if (item.questionAnswerVoList && item.questionAnswerVoList.length > 0) {
// 添加一个 radiovalue 字段,用于记录用户选择的答案
if (item.questionType == 1 || item.questionType == 3) {
item.radioValue = ''
} else if (item.questionType == 2) {
item.checkboxValue = []
}
item.chooseAnswer = ''
item.questionAnswerVoList.forEach(answer => {
if (answer.questionPictureVoList && answer.questionPictureVoList.length > 0) {
// 图片路径拼接
answer.questionPictureVoList.forEach(opt => {
opt.url = config.fileUrl2 + opt.pictureUrl.replace(/\\/g, '/')
// opt.url = 'https://cdn.uviewui.com/uview/swiper/1.jpg'
})
}
})
}
})
console.log('🚀 ~ getQuestionList ~ this.题目列表:', this.questionList)
this.startTimer()
},
// 上一题
handlePrev() {
this.currentIndex--
},
// 下一题
handleNext() {
this.currentIndex++
},
// 结束练习
async handleEnd() {
console.log('🚀 结束练习')
try {
if (this.isAddLoading) {
// 提示
uni.showToast({
title: '正在提交中...',
icon: 'none'
})
return
}
this.isAddLoading = true
// 计算总分
let totalScore = 0
this.questionList.forEach(item => {
if (item.scorePerQuestion) {
totalScore += Number(item.scorePerQuestion)
}
})
// 找到 this.questionList 中 chooseAnswer 最后一个有值的 questionId
// const questionId = this.questionList.filter(item => item.chooseAnswer).pop().questionId
// 过滤出所有未答题的questionId 拼接为一个字符串
const questionId = this.questionList
.filter(item => !item.chooseAnswer)
.map(item => item.questionId)
.join(',')
// errorQuestionList 错题列表
const errorQuestionList = this.questionList.filter(
item => item.chooseAnswer && item.chooseAnswer != item.correctAnswer
)
// correctQuestionList 正确题列表
const correctQuestionList = this.questionList.filter(
item => item.chooseAnswer && item.chooseAnswer == item.correctAnswer
)
clearInterval(this.timer)
this.showModal = false
// loading
uni.showLoading({
title: '提交中...',
mask: true
})
if (this.opt.isAutoExercises) {
const params = {
practiceId: this.opt.practiceId, // 题库id
practiceType: this.opt.practiceType, // 题库类型
personId: this.userId, // 用户id
practiceNum: this.correctNum + this.errorNum, // 练习题数
correctNum: this.correctNum, // 正确题数
errorNum: this.errorNum, // 错误题数
practiceTime: this.time, // 练习时间
lastPracticeQuestionId: questionId, // 最后一题id
errorQuestionList, // 错题列表
correctQuestionList // 正确题列表
}
console.log('🚀 ~ handleEnd ~ params:', params)
const res = await endPractice(params)
console.log('🚀 ~ handleEnd ~ res:', res)
} else {
let scoringRete = 0
if (this.correctNum + this.errorNum != 0) {
scoringRete = ((this.correctNum / (this.correctNum + this.errorNum)) * 100).toFixed(2)
}
const params = {
userId: this.userId,
examPaperId: this.opt.id,
scoringRete,
answerTime: this.time,
score: totalScore,
selectAnswerList: this.questionList
}
console.log('🚀 ~ handleEnd ~ params:', params)
const res = await updateAnswer(params)
console.log('🚀 ~ handleEnd ~ res:', res)
}
this.showModal2 = true
uni.hideLoading()
this.isAddLoading = false
} catch (error) {
console.log('🚀 ~ handleEnd ~ error:', error)
this.isAddLoading = false
}
},
// 返回
goBack() {
uni.navigateBack()
},
// 开启计时器
startTimer() {
this.timer = setInterval(() => {
this.time++
// 时间格式 00:00:00
const hour = Math.floor(this.time / 3600)
const minute = Math.floor((this.time % 3600) / 60)
const second = this.time % 60
this.timeCount = `${hour < 10 ? '0' + hour : hour}:${minute < 10 ? '0' + minute : minute}:${
second < 10 ? '0' + second : second
}`
}, 1000)
},
// 点击预览
handleImg(img) {
uni.previewImage({
urls: [img]
})
},
// 选择选项
handleOption(item) {
console.log('🚀 ~ handleOption ~ item:', item)
if (item.questionType == 1 || item.questionType == 3) {
if (!item.radioValue) {
uni.showToast({
title: '请选择答案',
icon: 'none'
})
return
}
console.log('🚀 ~ handleOption ~ 确认选择-单选:', item)
item.chooseAnswer = String(item.radioValue.split('')[0].charCodeAt() - 65)
item.correctAnswer1 = String.fromCharCode(65 + Number(item.correctAnswer))
item.chooseAnswer1 = String.fromCharCode(65 + Number(item.chooseAnswer))
item.questionAnswerVoList.forEach(answer => {
answer.disabled = true
})
// 判断答案是否正确
if (item.chooseAnswer == item.correctAnswer) {
this.correctNum++
if (item.questionType == 1) {
item.scorePerQuestion = this.opt.singleScore
} else if (item.questionType == 3) {
item.scorePerQuestion = this.opt.judgeScore
}
} else {
this.errorNum++
item.scorePerQuestion = '0'
}
} else if (item.questionType == 2) {
if (item.checkboxValue.length == 0) {
uni.showToast({
title: '请选择答案',
icon: 'none'
})
return
}
console.log('🚀 ~ handleOption ~ 确认选择-多选:', item)
let chooseAnswer = item.checkboxValue.map(item => item.split('')[0].charCodeAt() - 65)
chooseAnswer.sort((a, b) => a - b)
console.log('🚀 ~ handleOption ~ chooseAnswer:', chooseAnswer)
item.chooseAnswer = chooseAnswer.join(',')
console.log('🚀 ~ handleOption ~ item.chooseAnswer:', item.chooseAnswer)
item.correctAnswer1 = item.correctAnswer
.split(',')
.map(item => String.fromCharCode(65 + Number(item)))
.join(',')
item.chooseAnswer1 = chooseAnswer.map(item => String.fromCharCode(65 + Number(item))).join(',')
item.questionAnswerVoList.forEach(answer => {
answer.disabled = true
})
// 判断答案是否正确
if (item.chooseAnswer == item.correctAnswer) {
this.correctNum++
item.scorePerQuestion = this.opt.multipleScore
} else {
this.errorNum++
item.scorePerQuestion = '0'
}
}
},
// 单选题选择
radioChange(e) {
console.log('radioChange', e)
},
// 禁用手势返回
onBackPress(options) {
console.log(options)
if (options.from == 'backbutton') {
// 来自手势返回
console.log('手势返回')
// 返回为 true 时,不会执行返回操作,可以自定义返回逻辑
// 返回为 false 或者不返回时,则执行默认返回操作
return true
}
// 返回为 false 或者不返回时,则执行默认返回操作
return false
}
}
}
</script>
<style lang="scss">
.content {
padding: 20px;
word-wrap: break-word;
word-break: break-all;
.bottom-wrapper {
position: fixed;
bottom: 0;
left: 0;
right: 0;
padding: 10px;
background-color: #fff;
display: flex;
justify-content: space-around;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
.btn {
margin: 0 15px;
}
}
.box {
height: 50px;
}
.top-wrapper {
height: 50px;
padding: 0 10px;
display: flex;
justify-content: space-between;
align-items: center;
background-color: #fafafa;
border-radius: 5px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
.left {
display: flex;
}
}
.question-wrapper {
margin-top: 30px;
.question-type {
display: flex;
justify-content: flex-start;
align-items: center;
.line {
width: 3px;
height: 15px;
background-color: #1989fa;
}
.type {
padding: 0 10px;
}
}
.question-title {
margin: 10px 0;
font-size: 15px;
font-weight: 800;
color: #333;
}
.question-img-list {
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: center;
/* margin-top: 10px; */
}
.img {
margin-right: 10px;
width: 60px;
height: 60px;
}
.radio-item {
margin-left: 10px;
display: flex;
flex-direction: column;
align-items: flex-start;
.radio-name {
margin-bottom: 5px;
}
}
.question-item {
margin-top: 20px;
}
.answer-wrap {
margin-top: 20px;
display: flex;
justify-content: space-around;
background-color: #edf2f7;
border-radius: 8px;
padding: 10px;
.answer-item {
display: flex;
flex-direction: column;
align-items: center;
div {
margin-bottom: 5px;
}
}
.answer-line {
width: 1px;
height: 45px;
background-color: #ccc;
}
}
.analysis {
padding: 10px;
background-color: #f5f5f5;
border-radius: 8px;
min-height: 30px;
}
}
}
/deep/ .u-radio {
align-items: flex-start !important;
}
.container {
display: flex;
align-items: center;
justify-content: space-between;
}
</style>