YNUtdPlatform/pages/YNEduApp/prac/exercises.vue

915 lines
27 KiB
Vue

<template>
<view class="wrapper">
<div class="top-time">已用时间 {{ usedTime }}</div>
<div class="num-wrapper">
<div class="correct">
<u-icon name="/static/images/true.png" size="15" />
<div class="num">{{ trueNum }}</div>
</div>
<div class="error">
<u-icon name="/static/images/error.png" size="15" />
<div class="num">{{ falseNum }}</div>
</div>
<div class="total" @click="openSelect">
<u-icon name="/static/images/quanbufenlei.png" size="15" />
<div class="num">{{ nowNum }}/{{ allNum }}</div>
</div>
</div>
<div class="question-wrapper" v-for="(item, index) in questionList" :key="index">
<div v-if="item.examType !== 2">
<div class="title">{{ nowNum }}.{{ item.paperTopic }}{{ item.examType == 1 ? '(单选题)' : '(判断题)' }}</div>
<u--image
v-if="item.paperTopicUrl"
:showLoading="true"
:src="fileUrl + item.paperTopicUrl || ''"
width="60px"
height="60px"
style="margin-bottom: 10px"
@click="clickImg(fileUrl + item.paperTopicUrl || '')"
/>
<div class="options">
<div
class="option"
v-for="(option, index) in item.listOption"
:key="index"
@click="handleOption(option, index)"
:class="{ active: option.isCorrect || option.isError }"
>
<div class="label">{{ option.optionIdent }}.{{ option.optionContent }}</div>
<u-icon v-if="option.isCorrect" name="/static/images/right.png" size="25" />
<u-icon v-if="option.isError" name="/static/images/err.png" size="25" />
</div>
</div>
<div v-if="item.isSelect">
<div>正确答案:{{ item.correctGrade }}</div>
<div>你的答案:{{ item.select }}</div>
<div v-if="item.analysis">
<div class="analysis">答案解析:</div>
<div class="analysis-container">{{ item.answerAnaly }}</div>
</div>
</div>
</div>
<!-- 多选 -->
<div v-else-if="item.examType === 2">
<div class="title">{{ index + 1 }}. {{ item.paperTopic }}(多选题)</div>
<u--image
v-if="item.paperTopicUrl"
:showLoading="true"
:src="fileUrl + item.paperTopicUrl || ''"
width="60px"
height="60px"
style="margin-bottom: 10px"
@click="clickImg(fileUrl + item.paperTopicUrl || '')"
/>
<div class="options">
<div
class="option"
v-for="(option, index) in item.listOption"
:key="index"
@click="handleCheckbox(option, index, item)"
:class="{ active: option.isCheck || option.isCorrect || option.isError }"
>
<div>
<div class="label">
<div style="width: 18px">{{ option.optionIdent }}.</div>
<div class="label-cont">{{ option.optionContent }}</div>
</div>
<u--image
v-if="option.optionUrl"
:showLoading="true"
:src="fileUrl + option.optionUrl || ''"
width="60px"
height="60px"
style="margin-bottom: 10px"
@click="clickImg(fileUrl + option.optionUrl || '')"
/>
</div>
<u-icon v-if="option.isCorrect" name="/static/images/right.png" size="25" />
<u-icon v-if="option.isError" name="/static/images/err.png" size="25" />
</div>
</div>
<div class="btn">
<u-button
v-show="item.listOption.some(option => option.isCheck) && !item.isSelect"
text="选好了"
shape="circle"
size="small"
color="linear-gradient(to right, rgba(47, 195, 255, 1), rgba(5, 70, 173, 1))"
@click="handleOk"
/>
</div>
<div v-if="item.isSelect">
<div>正确答案:{{ item.correctGrade.split('').join('、') }}</div>
<div>你的答案:{{ item.select && item.select.join('、') }}</div>
<div class="analysis">知识点</div>
<div class="analysis-container">{{ item.answerAnaly }}</div>
</div>
</div>
</div>
<!-- 底部按钮 -->
<div class="bottom-btn">
<div class="btn">
<u-button
type="primary"
size="small"
shape="circle"
text="提交"
color="linear-gradient(to right, rgba(250, 223, 103, 1), rgba(207, 163, 14, 1)"
@click="handleSubmit"
/>
</div>
<div class="btn">
<u-button
v-show="nowNum > 1"
type="primary"
size="small"
shape="circle"
text="上一题"
color="linear-gradient(to right, rgba(47, 195, 255, 1), rgba(5, 70, 173, 1))"
@click="handleJump('prev')"
/>
</div>
<div class="btn">
<u-button
v-show="nowNum < allNum"
type="primary"
size="small"
shape="circle"
text="下一题"
color="linear-gradient(to right, rgba(47, 195, 255, 1), rgba(5, 70, 173, 1))"
@click="handleJump('next')"
/>
</div>
</div>
<!-- 提交弹框 -->
<u-modal
:show="showModal"
title="本次答题正确率"
showCancelButton
@cancel="handleConfirm"
@confirm="handleClose"
cancelText="继续答题"
confirmText="结束答题"
:buttonReverse="true"
confirmColor="#606266"
cancelColor="#2979ff"
>
<view class="slot-content">
<div class="correctRate">{{ correctRate }}%</div>
<div class="modal-container">
<div class="item">
<div class="num">{{ answerNum }}</div>
<div>答题数</div>
</div>
<div class="item">
<div class="num">{{ trueNum }}</div>
<div>正确数</div>
</div>
<div class="item">
<div class="num">{{ usedTime }}</div>
<div>答题时长</div>
</div>
</div>
</view>
</u-modal>
<!-- 题目选择弹框 -->
<u-modal :show="showModalSelect" title="题目选择" @confirm="showModalSelect = false">
<view class="slot-content">
<div class="topic-content">
<div
v-for="(item, index) in questionListSelect"
:key="index"
class="topic-wrapper"
:class="{ correct: item.isCorrect, error: item.isError }"
@click="handleJump('jump', item.index)"
>
<div v-if="currentIndex == item.index">*</div>
<div class="topic">{{ item.index }}</div>
</div>
</div>
</view>
</u-modal>
</view>
</template>
<script>
import {
getPracticeItData, // 获取练习上方题数
getPracticeQuestion, // 获取练习题目
insertPracticeAnswerById, // 提交答案
changeQuestion, // 切换题目
getPracticeQuestionList, // 获取题目列表-弹框
getPracticeQuestionRate, // 获取练习正确率
savePracticeDuration, // 保存练习时长
updateRemoveRecordData, // 错题消除记录
updStudyDurationExamPractice
} from '@/api/eduApp'
import config from '@/config'
export default {
data() {
return {
practiceId: '', // 练习id
recordId: '', // 记录id
isNew: 1, // 是否新练习
isOutOfOrder: 1, // 是否乱序
isError: false, // 是否错题消除
nowNum: 0, // 当前题目索引
showModal: false,
showModalSelect: false,
// 正确率
correctRate: '0',
// 已用时间 - 计时器 - 用于显示已用时间 格式 00:00:00
usedTime: '00:00:00',
serviceTime: 0,
// 正确题数
trueNum: 0,
// 错误题数
falseNum: 0,
allNum: 0, // 总题数
// 答题数
answerNum: 0,
// 当前题目索引
currentIndex: 0,
// 题目列表
questionList: [],
// 题目列表-弹框
questionListSelect: [],
studyId: '', // 学习id
fileUrl: config.fileUrl,
intervalId: '',
isEnd: false
}
},
onLoad(opt) {
opt = JSON.parse(opt.params)
this.practiceId = opt.practiceId
this.recordId = opt.recordId || ''
this.isNew = opt.isNew || ''
this.isOutOfOrder = opt.isOutOfOrder || ''
this.isError = opt.isError || ''
this.studyId = opt.studyId || ''
console.log('🚀 ~ onLoad ~ opt:', opt)
// 开始计时
this.startTimer()
this.getPracticeItData()
this.getPracticeQuestion()
},
onUnload() {
if (this.isEnd) return
// 清除计时器
clearInterval(this.intervalId)
this.handleClose()
},
methods: {
// 获取题目-上方数量
async getPracticeItData() {
const params = {
practiceId: this.practiceId,
recordId: this.recordId,
isNew: this.isNew || '',
isMiss: this.isError ? '1' : ''
}
// const res = await getPracticeItData(params)
// this.trueNum = res.data.trueNum
// this.falseNum = res.data.falseNum
// this.allNum = res.data.allNum
// this.nowNum = res.data.nowNum
// console.log('🚀 ~ getPracticeItData ~ res:', res)
uni.request({
url: config.baseUrl + '/exam-student/studentPractice/getPracticeItData',
method: 'post',
data: params,
header: {
'Content-Type': 'application/x-www-form-urlencoded',
Authorization: uni.getStorageSync('access_token')
},
success: res => {
console.log('🚀 ~ getPracticeItData ~ res:', res)
res = res.data
this.trueNum = res.data.trueNum
this.falseNum = res.data.falseNum
this.allNum = res.data.allNum
this.nowNum = res.data.nowNum
},
fail: err => {
console.log(err)
}
})
},
// 获取题目
async getPracticeQuestion() {
this.questionList = []
const params = {
practiceId: this.practiceId,
recordId: this.recordId,
isNew: this.isNew || '',
isOutOfOrder: this.isOutOfOrder || '',
isMiss: this.isError ? '1' : ''
}
// const res = await getPracticeQuestion(params)
// this.recordId = res.data.recordId
// this.questionList.push(res.data)
// console.log('🚀 ~ getPracticeQuestion ~ res:', res)
// console.log('🚀 ~ getPracticeQuestion ~ this.questionList:', this.questionList)
uni.request({
url: config.baseUrl + '/exam-student/studentPractice/getPracticeQuestion',
method: 'post',
data: params,
header: {
'Content-Type': 'application/x-www-form-urlencoded',
Authorization: uni.getStorageSync('access_token')
},
success: res => {
console.log('🚀 ~ getPracticeQuestion ~ res:', res)
res = res.data
this.recordId = res.data.recordId ? res.data.recordId : this.recordId
this.questionList.push(res.data)
},
fail: err => {
console.log(err)
}
})
},
// 跳题
async handleJump(item, index) {
console.log('🚀 ~ handleJump ~ item:', item, this.recordId)
if (item === 'prev') {
this.nowNum -= 1
} else if (item === 'next') {
this.nowNum += 1
} else if (item === 'jump') {
this.nowNum = index
}
this.currentIndex = index
this.questionList = []
const params = {
recordId: this.recordId || '',
practiceId: this.practiceId,
operate: item || '',
sort: this.nowNum,
isMiss: this.isError ? '1' : ''
}
console.log('🚀 ~ handleJump ~ params:', params)
// const res = await changeQuestion(params)
// this.questionList.push(res.data)
// console.log('🚀 ~ handleJump ~ res:', res)
uni.request({
url: config.baseUrl + '/exam-student/studentPractice/changeQuestion',
method: 'post',
data: params,
header: {
'Content-Type': 'application/x-www-form-urlencoded',
Authorization: uni.getStorageSync('access_token')
},
success: res => {
console.log('🚀 ~ handleJump ~ res:', res)
res = res.data
this.questionList.push(res.data)
},
fail: err => {
console.log(err)
}
})
},
// 获取题目列表-弹框
async getPracticeQuestionList() {
const params = {
recordId: this.recordId,
practiceId: this.practiceId,
isMiss: this.isError ? '1' : ''
}
// const res = await getPracticeQuestionList(params)
// console.log('🚀 ~ getPracticeQuestionList ~ res:', res)
// this.questionListSelect = res.data
uni.request({
url: config.baseUrl + '/exam-student/studentPractice/getPracticeQuestionList',
method: 'post',
data: params,
header: {
'Content-Type': 'application/x-www-form-urlencoded',
Authorization: uni.getStorageSync('access_token')
},
success: res => {
console.log('🚀 ~ getPracticeQuestionList ~ res:', res)
res = res.data
this.questionListSelect = res.data
},
fail: err => {
console.log(err)
}
})
},
// 开始计时 - 实时更新已用时间
startTimer() {
this.intervalId = setInterval(() => {
this.serviceTime++
this.handleTime()
// const time = this.usedTime.split(':')
// let hours = parseInt(time[0])
// let minutes = parseInt(time[1])
// let seconds = parseInt(time[2])
// seconds++
// if (seconds === 60) {
// seconds = 0
// minutes++
// if (minutes === 60) {
// minutes = 0
// hours++
// }
// }
// this.usedTime = `${hours < 10 ? '0' + hours : hours}:${minutes < 10 ? '0' + minutes : minutes}:${
// seconds < 10 ? '0' + seconds : seconds
// }`
}, 1000)
},
// 处理时间
handleTime() {
// this.serviceTime 是秒数
let hours = parseInt(this.serviceTime / 3600)
let minutes = parseInt((this.serviceTime % 3600) / 60)
let seconds = parseInt(this.serviceTime % 60)
this.usedTime = `${hours < 10 ? '0' + hours : hours}:${minutes < 10 ? '0' + minutes : minutes}:${
seconds < 10 ? '0' + seconds : seconds
}`
},
clickImg(url) {
console.log('🚀 ~ clickImg ~ url:', url)
uni.previewImage({
urls: [url]
})
},
// 处理选项点击事件
handleOption(option, index) {
console.log(option, index, this.questionList[0])
// 判断是否已经选择过
if (this.questionList[0].isSelect) {
return
} else {
// 标记已选择
this.questionList[0].isSelect = true
// 判断是否正确
if (option.optionIdent === this.questionList[0].correctGrade) {
// 当前选择
this.questionList[0].select = option.optionIdent
// 给当前题目添加正确标记
this.questionList[0].isCorrect = true
// 正确 - 给当前选项添加正确标记
this.questionList[0].listOption[index].isCorrect = true
// 正确题数+1
this.trueNum++
} else {
// 当前选择
this.questionList[0].select = option.optionIdent
// 给当前题目添加错误标记
this.questionList[0].isError = true
// 错误 - 给当前选项添加错误标记
this.questionList[0].listOption[index].isError = true
// 并将正确选项标记
this.questionList[0].listOption.forEach(item => {
if (item.optionIdent === this.questionList[0].correctGrade) {
item.isCorrect = true
}
})
// 错误题数+1
this.falseNum++
}
const params = {
practiceId: this.practiceId,
recordId: this.recordId,
questionId: this.questionList[0].questionId,
selectAnswer: option.optionIdent,
isTrue: option.optionIdent == this.questionList[0].correctGrade ? 1 : 0,
isMiss: this.isError ? '1' : ''
}
console.log('🚀 ~ handleOption ~ params:', params)
// 提交答案
// insertPracticeAnswerById(params)
uni.request({
url: config.baseUrl + '/exam-student/studentPractice/insertPracticeAnswerById',
method: 'post',
data: params,
header: {
'Content-Type': 'application/x-www-form-urlencoded',
Authorization: uni.getStorageSync('access_token')
},
success: res => {
console.log('🚀 ~ handleOption ~ res:', res)
}
})
}
},
// 处理多选题选项点击事件
handleCheckbox(option, index, item) {
console.log('🚀 ~ handleCheckbox ~ option, index:', option, index)
if (item.isSelect) {
return
}
// 给当前点击的增加边框 再次点击取消
if (!option.isCheck) {
this.questionList[0].listOption[index].isCheck = !option.isCheck
} else {
this.questionList[0].listOption[index].isCheck = false
}
// 将 isCheck: true 的选项的 value 放入 select 数组
this.questionList[0].select = this.questionList[0].listOption
.filter(item => item.isCheck)
.map(item => item.optionIdent)
console.log('🚀 ~ handleCheckbox ~ this.questionList[0].select:', this.questionList[0].select)
},
handleOk() {
// 判断是否已经选择过
if (this.questionList[0].isSelect) {
return
} else {
// 标记已选择
this.questionList[0].isSelect = true
const select = this.questionList[0].select
const correctGrade = this.questionList[0].correctGrade.split('')
// 判断是否正确
const isCorrect = select.sort().toString() === correctGrade.sort().toString()
if (isCorrect) {
// 正确题数+1
this.trueNum++
// 给当前题目添加正确标记
this.questionList[0].listOption.forEach(item => {
if (item.isCheck) {
item.isCorrect = true
}
})
// 给正确选项添加标记
this.questionList[0].listOption.forEach(item => {
if (correctGrade.includes(item.optionIdent)) {
item.isCorrect = true
}
})
} else {
// 错误题数+1
this.falseNum++
// 给当前题目添加错误标记
this.questionList[0].isError = true
// 给错误选项添加标记
this.questionList[0].listOption.forEach(item => {
if (correctGrade.includes(item.optionIdent)) {
item.isCorrect = true
}
if (!correctGrade.includes(item.optionIdent) && item.isCheck) {
item.isError = true
}
})
}
const params = {
practiceId: this.practiceId,
recordId: this.recordId,
questionId: this.questionList[0].questionId,
selectAnswer: select.join(''),
isTrue: isCorrect ? 1 : 0
}
console.log('🚀 ~ handleOk ~ params-多选:', params)
// insertPracticeAnswerById(params)
uni.request({
url: config.baseUrl + '/exam-student/studentPractice/insertPracticeAnswerById',
method: 'post',
data: params,
header: {
'Content-Type': 'application/x-www-form-urlencoded',
Authorization: uni.getStorageSync('access_token')
},
success: res => {
console.log('🚀 ~ handleOption ~ res:', res)
}
})
}
},
// 提交
async handleSubmit() {
// 暂停时间
clearInterval(this.intervalId)
console.log('提交')
this.answerNum = this.trueNum + this.falseNum
let rate = ((Number(this.trueNum) / Number(this.answerNum)) * 100).toFixed(2)
// 计算正确率 = 正确题数 / 已答题数
this.correctRate = rate == 'NaN' ? '0' : rate
this.showModal = true
// const params = {
// id: this.practiceId
// }
// uni.request({
// url: config.baseUrl + '/exam-student/studentPractice/getPracticeQuestionRate',
// method: 'post',
// data: params,
// header: {
// 'Content-Type': 'application/x-www-form-urlencoded',
// Authorization: uni.getStorageSync('access_token')
// },
// success: res => {
// console.log('🚀 ~ handleSubmit ~ res:', res)
// res = res.data
// },
// fail: err => {
// console.log(err)
// }
// })
},
handleConfirm() {
console.log('确认')
this.showModal = false
// 继续计时
this.startTimer()
},
// 结束答题
handleClose() {
this.isEnd = true
// 停止计时
clearInterval(this.intervalId)
console.log('结束', this.usedTime, this.serviceTime)
// 保存练习时长
let time = Math.ceil(this.serviceTime / 60)
console.log('🚀 ~ handleClose ~ time:', time)
const params = {
id: this.recordId,
practiceDuration: time
}
console.log('🚀 ~ handleClose ~ params:', params)
uni.request({
url: config.baseUrl + '/exam-student/studentPractice/savePracticeDuration',
method: 'post',
data: params,
header: {
'Content-Type': 'application/x-www-form-urlencoded',
Authorization: uni.getStorageSync('access_token')
},
fail: err => {
console.log(err)
}
})
if (this.isError) {
// 错题消除
// updateRemoveRecordData({
// practiceId: this.practiceId
// })
uni.request({
url: config.baseUrl + '/exam-student/studentPractice/updateRemoveRecordData',
method: 'post',
data: {
practiceId: this.practiceId
},
header: {
'Content-Type': 'application/x-www-form-urlencoded',
Authorization: uni.getStorageSync('access_token')
},
fail: err => {
console.log(err)
}
})
}
if (this.studyId) {
const params = {
userId: uni.getStorageSync('userId'),
studyId: this.studyId
}
// updStudyDurationExamPractice(params)
uni.request({
url: config.baseUrl + '/exam-student/student/updStudyDurationExamPractice',
method: 'post',
data: params,
header: {
'Content-Type': 'application/x-www-form-urlencoded',
Authorization: uni.getStorageSync('access_token')
},
fail: err => {
console.log(err)
}
})
setTimeout(() => {
uni.reLaunch({
url: '/pages/YNEduApp/learnProj/learnProjDetail?id=' + this.studyId
})
}, 500)
} else {
uni.reLaunch({
url: '/pages/YNEduApp/prac/prac'
})
}
this.showModal = false
},
// 打开题目选择弹框
async openSelect() {
this.showModalSelect = true
// const res = await this.getPracticeQuestionList()
// console.log('🚀 ~ openSelect ~ res:', res)
// this.questionListSelect = res.data
const params = {
recordId: this.recordId,
practiceId: this.practiceId,
isMiss: this.isError ? 1 : ''
}
uni.request({
url: config.baseUrl + '/exam-student/studentPractice/getPracticeQuestionList',
method: 'post',
data: params,
header: {
'Content-Type': 'application/x-www-form-urlencoded',
Authorization: uni.getStorageSync('access_token')
},
success: res => {
console.log('🚀 ~ getPracticeQuestionList ~ res:', res)
res = res.data
this.questionListSelect = res.data
},
fail: err => {
console.log(err)
}
})
}
}
}
</script>
<style lang="scss" scoped>
.wrapper {
height: 100vh;
background: url('/static/images/question-bg.png') no-repeat;
background-size: 100% 100%;
.slot-content {
width: 100%;
.correctRate {
font-size: 30px;
text-align: center;
}
.modal-container {
display: flex;
justify-content: space-around;
align-items: center;
margin-top: 20px;
background: #f5f8fb;
border-radius: 5px;
height: 73px;
.item {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
color: #9d9b9b;
.num {
font-size: 20px;
color: #3185f0;
}
}
}
.topic-content {
height: 300px;
display: flex;
justify-content: flex-start;
align-items: center;
flex-wrap: wrap;
overflow: auto;
.topic-wrapper {
width: 30px;
height: 30px;
border: 1px solid #409eff;
border-radius: 5px;
display: flex;
justify-content: center;
align-items: center;
margin: 10px;
.topic {
font-size: 11px;
color: #409eff;
}
// 正确背景
&.correct {
background: #67d279;
color: #fff;
.topic {
color: #fff;
}
}
// 错误背景
&.error {
background: #f0514c;
color: #fff;
.topic {
color: #fff;
}
}
}
}
}
.bottom-btn {
position: fixed;
bottom: 30px;
left: 0;
display: flex;
justify-content: space-around;
width: 100%;
.btn {
width: 100px;
display: flex;
justify-content: center;
margin: 0 auto;
}
}
.btn {
width: 100px;
display: flex;
justify-content: center;
margin: 0 auto;
margin-top: 40px;
}
.question-wrapper {
padding: 30px;
color: #08428d;
.title {
font-weight: 500;
font-size: 15px;
margin-bottom: 30px;
}
.option {
margin-bottom: 5px;
padding: 0 15px;
min-height: 45px;
max-height: 100px;
display: flex;
justify-content: space-between;
align-items: center;
background: #ffffff;
border-radius: 8px;
overflow: auto;
.label {
display: flex;
align-items: center;
justify-content: flex-start;
text-align: left;
}
}
.active {
border: 1px solid #1f92df;
}
.analysis {
margin-top: 20px;
font-weight: 700;
}
.analysis-container {
margin-top: 6px;
color: #475583;
text-indent: 1em;
}
}
.top-time {
text-align: center;
font-size: 13px;
color: #fff;
padding-top: 50px;
}
.num-wrapper {
display: flex;
justify-content: center;
margin-top: 20px;
.correct,
.error,
.total {
display: flex;
align-items: center;
font-size: 13px;
margin: 0 6px;
}
.correct {
color: #49ac5a;
}
.error {
color: #e34944;
}
.total {
color: #f08439;
}
.num {
margin-left: 3px;
}
}
}
</style>