nxdt-uniapp/pages/myExam/examination.vue

775 lines
24 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>
<div v-if="!isEnd">
<Navbar title="考试" :showBack="false" />
<div class="content">
<div class="top-wrapper">
<div class="time">
<div>计时器</div>
<div>
<u-count-down
class="count-down"
ref="countDown"
:autoStart="false"
:time="time"
@change="changeCountDown"
/>
</div>
</div>
<div>
<span style="color: #1989fa">{{ currentIndex + 1 }}/{{ questionList.length }}</span>
</div>
</div>
<div class="center-wrapper">
<div class="answer-wrapper">
<div
class="item-wrapper"
v-for="(item, index) in questionList"
:key="index"
v-show="item.isShow"
@click="handleQuestionNumber(item, index)"
>
<div class="answer-item" :class="{ isActive: currentIndex == index, isAnswer: item.selectAnswer != '' }">
{{ index + 1 }}
</div>
</div>
</div>
<div class="unfold" @click="handleUnfold">
{{ this.isRotating ? '收起' : '展开' }}
<u-icon v-if="!this.isRotating" name="arrow-down-fill" size="10" />
<u-icon v-else name="arrow-up-fill" size="10" />
</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">单选题({{ opt.singleScore || 0 }}分)</div>
<div v-else-if="item.questionType == 2">多选题({{ opt.multipleScore || 0 }}分)</div>
<div v-else-if="item.questionType == 3">判断题({{ opt.judgeScore || 0 }}分)</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, item)"
>
<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, item)"
></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>
</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 || currentIndex == questionList.length"
>
下一题
</u-button>
<u-button class="btn" type="primary" size="small" shape="circle" @click="handleSubmit">交 卷</u-button>
</div>
</div>
<u-toast ref="uToast"></u-toast>
<u-modal
:show="showModal"
title="提示"
:content="content"
showCancelButton
@confirm="confirm"
@cancel="showModal = false"
/>
</div>
<div v-else>
<endOfExamination :states="endOfExamination" :isStudyTask="opt.isStudyTask" :isTrain="opt.isTrain" />
</div>
</view>
</template>
<script>
import config from '@/config'
import endOfExamination from './endOfExamination.vue'
import { getExamQuestion, submitAnswer, getQuestion, updateAnswer } from '@/api/educationTraining'
export default {
components: { endOfExamination },
data() {
return {
isAddLoading: false,
userId: uni.getStorageSync('userInfo').userId,
opt: {},
currentIndex: 0, // 当前题目索引
questionList: [], // 试题列表
endOfExamination: false, // 是否结束考试
correctNum: 0, // 正确题数
errorNum: 0, // 错误题数
totalScore: 0, // 总得分
timeCount: '00:00:00',
timer: null, // 计时器id
showModal: false,
showModal2: false,
showModal: false,
content: '是否确认提交?',
// 时间
time: 0,
// 答题时间
answerTime: 0,
// 试题列表
// 答题卡
answerSheet: [],
// 是否展开
isRotating: false,
// 答题结束
isEnd: false,
// 考后结算
endOfExamination: {
totalScore: 0, // 总分
score: 0, // 分数
isPass: '', // 是否及格
examType: '', // 考试类型
submitTime: '', // 交卷时间
answerSheet: [], // 答题卡
correctNum: 0, // 正确题数
errorNum: 0, // 错误题数
examTime: '', // 考试用时
totalNum: 0 // 总题数
}
}
},
onLoad(opt) {
this.opt = JSON.parse(opt.params)
this.time = Number(this.opt.examTime) * 60 * 1000
console.log('🚀 ~ onLoad ~ opt:', this.opt)
this.getQuestionList()
},
mounted() {
// this.getList()
},
// 未正常提交, 离开页面自动提交
onUnload() {
if (!this.isEnd) {
this.handleConfirmSubmit()
}
},
methods: {
// 获取题目列表
async getQuestionList() {
this.questionList = []
let res = {}
if (this.opt.isStudyTask) {
res = await getQuestion({ examId: this.opt.id })
} else {
const params = {
examPaperId: this.opt.examPaperId,
type: 1,
practiceType: 3
}
res = await getExamQuestion(params)
}
console.log('🚀 ~ 题目列表 ~ res:', res)
this.questionList = res.data
this.questionList.sort((a, b) => a.questionType - b.questionType)
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.selectAnswer = ''
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.getList()
},
// 获取列表
getList() {
this.questionList.forEach((item, index) => {
this.$set(item, 'isShow', index < 7)
this.$set(item, 'isActive', false)
this.$set(item, 'isShortAnswerValue', '')
if (item.options) {
item.options.forEach(option => {
this.$set(option, 'isActive', false)
})
}
})
console.log('🚀 ~ this.questionList.forEach ~ this.questionList:', this.questionList)
this.$refs.countDown.start()
},
radioChange(question, item) {
// console.log('🚀 ~ radioChange ~ item:', item)
console.log('🚀 ~ radioChange ~ question:', question)
setTimeout(() => {
this.handleOption(item)
}, 200)
},
// 上一题
handlePrev() {
if (this.currentIndex > 0) {
this.currentIndex--
if (this.currentIndex <= 6) {
this.isRotating = false
this.questionList.forEach((item, index) => {
if (index > 6) {
this.$set(item, 'isShow', false)
}
})
}
}
},
// 下一题
handleNext() {
if (this.currentIndex < this.questionList.length - 1) {
this.currentIndex++
if (this.currentIndex > 6) {
this.handleUnfold2()
}
}
},
// 点击预览
handleImg(img) {
uni.previewImage({
urls: [img]
})
},
// 选择选项
handleOption(item) {
console.log('🚀 ~ handleOption ~ item:', item)
if (item.questionType == 1 || item.questionType == 3) {
console.log('🚀 ~ handleOption ~ 确认选择-单选:', item)
item.selectAnswer = String(item.radioValue.split('')[0].charCodeAt() - 65)
item.correctAnswer1 = String.fromCharCode(65 + Number(item.correctAnswer))
item.chooseAnswer1 = String.fromCharCode(65 + Number(item.selectAnswer))
} else if (item.questionType == 2) {
console.log('🚀 ~ handleOption ~ 确认选择-多选:', item)
let selectAnswer = item.checkboxValue.map(item => item.split('')[0].charCodeAt() - 65)
console.log('🚀 ~ handleOption ~ selectAnswer:', selectAnswer)
selectAnswer.sort((a, b) => a - b)
item.selectAnswer = selectAnswer.join(',')
console.log('🚀 ~ handleOption ~ item.selectAnswer:', item.selectAnswer)
item.correctAnswer1 = item.correctAnswer
.split(',')
.map(item => String.fromCharCode(65 + Number(item)))
.join(',')
item.chooseAnswer1 = selectAnswer.map(item => String.fromCharCode(65 + Number(item))).join(',')
}
},
changeCountDown(time) {
// console.log('🚀 ~ changeCountDown ~ time:', time)
this.answerTime =
this.time - (time.days * 24 * 60 * 60 + time.hours * 60 * 60 + time.minutes * 60 + time.seconds) * 1000
if (this.answerTime == this.time) {
// 提示: 时间结束, 自动提交
this.$refs.uToast.show({
message: '考试时间结束, 系统将自动提交',
duration: 1000
})
this.handleConfirmSubmit()
setTimeout(() => {
this.isEnd = true
}, 1000)
}
},
// 展开
handleUnfold() {
this.questionList.forEach((item, index) => {
if (index > 6) {
this.$set(item, 'isShow', !item.isShow)
}
})
this.isRotating = !this.isRotating
},
handleUnfold2() {
this.questionList.forEach((item, index) => {
if (index > 6) {
this.$set(item, 'isShow', true)
}
})
this.isRotating = true
},
// 点击题号
handleQuestionNumber(item, index) {
console.log('🚀 ~ handleQuestionNumber ~ item:', item)
this.currentIndex = index
},
confirm() {
if (this.isAddLoading) {
// 提示
this.$refs.uToast.show({
message: '正在提交中...',
duration: 1000
})
return
}
this.isAddLoading = true
this.handleConfirmSubmit()
},
// 提交试卷
handleSubmit() {
if (this.questionList.some(item => item.selectAnswer == '')) {
this.showModal = true
this.content = '您还有题目未答, 是否确认提交?'
} else {
this.content = '是否确认提交?'
this.showModal = true
}
},
// 格式化时间
formatTime(seconds) {
const minutes = Math.floor(seconds / 60)
const remainingSeconds = seconds % 60
const formattedMinutes = String(minutes).padStart(2, '0')
const formattedSeconds = String(remainingSeconds).padStart(2, '0')
return `${formattedMinutes}:${formattedSeconds}`
},
async handleConfirmSubmit() {
try {
uni.showLoading({
title: '提交中...',
mask: true
})
console.log('🚀 ~ handleSubmit ~ 提交:')
// 提交试卷
this.$refs.countDown.pause()
// 计算使用时间
this.endOfExamination.examTime = this.formatTime(this.answerTime / 1000)
// 获取当前时间
const date = new Date()
this.endOfExamination.submitTime = this.formatDate(date)
// 计算正确题数, 错误题数, 每题得分, 总分
this.questionList.forEach(item => {
if (item.selectAnswer == item.correctAnswer) {
this.correctNum++
if (item.questionType == 1) {
item.scorePerQuestion = this.opt.singleScore
} else if (item.questionType == 2) {
item.scorePerQuestion = this.opt.multipleScore
} else if (item.questionType == 3) {
item.scorePerQuestion = this.opt.judgeScore
}
} else {
this.errorNum++
item.scorePerQuestion = '0'
}
if (item.scorePerQuestion) {
this.totalScore += Number(item.scorePerQuestion)
}
})
this.endOfExamination.score = this.totalScore // 总得分
this.endOfExamination.totalScore = this.opt.totalScore // 总分
// 考试类型
this.endOfExamination.examType = this.opt.name
// 计算答对题数
this.endOfExamination.correctNum = this.correctNum
// 计算答错题数
this.endOfExamination.errorNum = this.errorNum
// 生成答题卡
this.endOfExamination.answerSheet = this.generateAnswerSheet()
// 是否及格
// this.endOfExamination.isPass = this.endOfExamination.score >= this.passScore ? '及格' : '不及格'
this.endOfExamination.isPass = this.endOfExamination.score >= this.opt.passScore ? '及格' : '不及格'
console.log('🚀 ~ handleConfirmSubmit ~ this.endOfExamination:', this.endOfExamination)
// 交卷
let scoringRete = 0
scoringRete = ((this.correctNum / (this.correctNum + this.errorNum)) * 100).toFixed(2)
if (scoringRete == 'NaN' || scoringRete == 'Infinity' || scoringRete == '0.00') {
scoringRete = 0
}
if (this.opt.isStudyTask) {
const params = {
taskId: this.opt.taskId,
type: this.opt.type,
userId: this.userId,
examPaperId: this.opt.id,
id: this.opt.id,
scoringRete,
answerTime: this.answerTime / 1000,
score: this.totalScore,
selectAnswerList: this.questionList
}
console.log('🚀 ~ handleEnd ~ params:', params)
const res = await updateAnswer(params)
console.log('🚀 ~ handleEnd ~ res:', res)
if (res.code == 200) {
this.isEnd = true
uni.hideLoading()
}
this.isAddLoading = false
} else {
const params = {
taskId: this.opt.taskId,
type: this.opt.type,
userId: this.userId,
examPaperId: this.opt.examPaperId,
id: this.opt.examPaperId,
score: this.totalScore,
scoreRate: scoringRete,
answerTime: this.answerTime / 1000,
answerList: this.questionList
}
console.log('🚀 ~ handleConfirmSubmit ~ params:', params)
const res = await submitAnswer(params)
console.log('🚀 ~ handleConfirmSubmit ~ res:', res)
if (res.code == 200) {
this.isEnd = true
uni.hideLoading()
}
this.isAddLoading = false
}
} catch (error) {
console.log('🚀 ~ handleConfirmSubmit ~ error:', error)
// 提示
this.$refs.uToast.show({
message: '提交失败, 请重新提交',
duration: 1000
})
uni.hideLoading()
this.isAddLoading = false
}
},
// 格式化时间
formatDate(date) {
const year = date.getFullYear()
const month = date.getMonth() + 1
const day = date.getDate()
const hours = date.getHours()
const minutes = date.getMinutes().toString().padStart(2, '0')
const seconds = date.getSeconds().toString().padStart(2, '0')
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
},
// 生成答题卡
generateAnswerSheet() {
return this.questionList.map(item => {
return {
id: item.questionId,
isAnswer: item.selectAnswer == item.correctAnswer ? true : false,
isUnAnswer: item.selectAnswer == ''
}
})
},
onBackPress(options) {
console.log(options)
if (options.from == 'backbutton') {
// 来自手势返回
console.log('手势返回')
// 返回为 true 时,不会执行返回操作,可以自定义返回逻辑
// 返回为 false 或者不返回时,则执行默认返回操作
return true
}
// 返回为 false 或者不返回时,则执行默认返回操作
return false
}
}
}
</script>
<style lang="scss" scoped>
.content {
padding: 0 20px;
font-size: 13px;
word-wrap: break-word; // 自动换行
word-break: break-all; // 强制换行
.top-wrapper {
display: flex;
justify-content: space-between;
align-items: center;
.time {
display: flex;
justify-content: flex-start;
}
}
.center-wrapper {
display: flex;
justify-content: space-between;
.unfold {
height: 50px;
line-height: 50px;
text-align: center;
display: flex;
justify-content: center;
}
.answer-wrapper {
width: 86%;
max-height: 150px;
overflow: auto;
display: flex;
justify-content: flex-start;
align-items: flex-start;
flex-wrap: wrap;
.item-wrapper {
padding: 5px 5px 0 0;
width: 12%;
height: 45px;
display: flex;
justify-content: center;
align-items: center;
.answer-item {
width: 33px;
height: 33px;
line-height: 33px;
text-align: center;
border-radius: 5px;
background: #f4f9fe;
color: #333;
&.isAnswer {
background: #a9e08f;
}
&.isActive {
background: #1989fa;
}
}
}
}
}
.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;
}
}
/* .question-wrapper {
margin-top: 20px;
.question-title {
font-weight: 500;
font-size: 18px;
color: #08428d;
}
.option-item {
margin-top: 10px;
padding: 10px;
border-radius: 5px;
background: #f4f9fe;
color: #333;
&.isActive {
background: #e4f5de;
}
}
} */
.box {
height: 50px;
}
.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;
}
}
}
::v-deep .u-count-down__text {
color: #1989fa;
font-size: 13px;
}
::v-deep .u-modal__content {
flex-direction: column;
align-items: center;
}
</style>