nxdt-web/src/views/educTrainLearn/libertyLearn/exercise.vue

773 lines
25 KiB
Vue

<template>
<div class="app-container">
<el-row :gutter="20">
<!-- 左侧答题卡 -->
<el-col :span="6" :offset="0">
<el-card class="box-card">
<div class="top-content">
<div class="title">答题卡</div>
</div>
<el-divider></el-divider>
<!-- 正确,错误,未答 三种颜色提示 -->
<el-row :gutter="10" class="mb8">
<el-col :span="8">
<div class="color-box" style="background-color: #67c23a"></div>
<span>正确</span>
</el-col>
<el-col :span="8">
<div class="color-box" style="background-color: #f56c6c"></div>
<span>错误</span>
</el-col>
<el-col :span="8">
<div class="color-box" style="background-color: #909399"></div>
<span>未答</span>
</el-col>
</el-row>
<!-- 单选题 -->
<el-row :gutter="10" class="mb8">
<div style="margin: 5px">单选题</div>
<el-col
:span="4.8"
v-for="(item, index) in questionList.filter(item => item.questionType == 1)"
:key="index"
>
<div style="margin-bottom: 5px">
<el-tag v-if="item.isPass" type="success">{{ item.questionIndex }}</el-tag>
<el-tag v-if="item.isError" type="danger">{{ item.questionIndex }}</el-tag>
<el-tag v-if="item.isNotAnswer" type="info">{{ item.questionIndex }}</el-tag>
</div>
</el-col>
</el-row>
<!-- 多选题 -->
<el-row :gutter="10" class="mb8">
<div style="margin: 5px">多选题</div>
<el-col
:span="4.8"
v-for="(item, index) in questionList.filter(item => item.questionType == 2)"
:key="index"
>
<div style="margin-bottom: 5px">
<el-tag v-if="item.isPass" type="success">{{ item.questionIndex }}</el-tag>
<el-tag v-if="item.isError" type="danger">{{ item.questionIndex }}</el-tag>
<el-tag v-if="item.isNotAnswer" type="info">{{ item.questionIndex }}</el-tag>
</div>
</el-col>
</el-row>
<!-- 判断题 -->
<el-row :gutter="10" class="mb8">
<div style="margin: 5px">判断题</div>
<el-col
:span="4.8"
v-for="(item, index) in questionList.filter(item => item.questionType == 3)"
:key="index"
>
<div style="margin-bottom: 5px">
<el-tag v-if="item.isPass" type="success">{{ item.questionIndex }}</el-tag>
<el-tag v-if="item.isError" type="danger">{{ item.questionIndex }}</el-tag>
<el-tag v-if="item.isNotAnswer" type="info">{{ item.questionIndex }}</el-tag>
</div>
</el-col>
</el-row>
<!-- 简答题 -->
<!-- <el-row :gutter="10" class="mb8">
<div style="margin: 5px">简答题</div>
<el-col :span="4.8" v-for="(item, index) in shortAnswerTopic" :key="index">
<div style="margin-bottom: 5px">
<el-tag v-if="item.isPass" type="success">{{ item.item.questionIndex }}</el-tag>
<el-tag v-if="item.isError" type="danger">{{ item.item.questionIndex }}</el-tag>
<el-tag v-if="item.isNotAnswer" type="info">{{ item.item.questionIndex }}</el-tag>
</div>
</el-col>
</el-row> -->
</el-card>
</el-col>
<!-- 右侧试卷详情 -->
<el-col :span="18" :offset="0">
<el-card class="box-card">
<div class="top-content">
<div class="title">{{ paperData.paperName }}</div>
</div>
<div style="margin-left: 10px">{{ questionList.length }}道练习题</div>
</el-card>
<!-- 题目 -->
<el-card class="box-card">
<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">
<el-image
:key="imgIndex"
:src="img.url"
:showLoading="true"
width="60px"
height="60px"
:preview-src-list="[img.url]"
/>
</div>
</div>
<div class="question-item">
<div v-if="item.questionType == 1 || item.questionType == 3">
<!-- 单选题 -->
<el-radio-group v-model="item.radioValue">
<el-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">
<el-image
:key="imgIndex"
:src="img.url"
:showLoading="true"
width="60px"
height="60px"
:preview-src-list="[img.url]"
/>
</div>
</div>
</div>
</el-radio>
</el-radio-group>
</div>
<div v-if="item.questionType == 2">
<!-- 多选题 -->
<el-checkbox-group v-model="item.checkboxValue">
<div v-for="(question, index) in item.questionAnswerVoList">
<el-checkbox
:customStyle="{ marginBottom: '8px' }"
:key="index"
:label="question.options + '. ' + question.answerOptions"
:name="question.options + '. ' + question.answerOptions"
:disabled="question.disabled"
@change="radioChange(question)"
></el-checkbox>
<div class="radio-item">
<div class="question-img-list" style="margin: 10px">
<div class="img" v-for="(img, imgIndex) in question.questionPictureVoList">
<el-image
:key="imgIndex"
:src="img.url"
:showLoading="true"
width="60px"
height="60px"
:preview-src-list="[img.url]"
/>
</div>
</div>
</div>
</div>
</el-checkbox-group>
</div>
<!-- 标签 -->
<div class="tag-list">
<el-tag :key="tag.id" v-for="(tag, tagIndex) in item.tagList" style="margin-right: 10px">
{{ tag.name }}
</el-tag>
</div>
<div style="padding: 10px">
<el-button
type="primary"
size="small"
shape="circle"
@click="handleOption(item)"
v-show="!item.chooseAnswer"
>
确定选择
</el-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="bottom-wrapper">
<el-button
class="btn"
type="primary"
size="small"
shape="circle"
@click="currentIndex--"
:disabled="currentIndex == 0"
>
上一题
</el-button>
<el-button
class="btn"
type="primary"
size="small"
shape="circle"
@click="currentIndex++"
:disabled="
currentIndex == questionList.length - 1 ||
(questionList[currentIndex] && questionList[currentIndex].chooseAnswer == '')
"
>
下一题
</el-button>
<el-button class="btn" type="primary" size="small" shape="circle" @click="dialogVisible = true">
结束练习
</el-button>
</div>
</el-card>
<div class="box"></div>
</el-col>
</el-row>
<el-dialog title="提示" :visible.sync="dialogVisible" width="30%">
<span style="margin-left: 15px; font-weight: 800; font-size: 16px" v-preventReClick="3000">
确定退出本次练习吗?
</span>
<span slot="footer" class="dialog-footer">
<el-button @click="dialogVisible = false">取 消</el-button>
<el-button type="primary" @click="openDialog2" v-preventReClick="5000">确 定</el-button>
</span>
</el-dialog>
<el-dialog
title="练习结果"
:visible.sync="dialogVisible2"
width="30%"
:before-close="handleClose"
:close-on-click-modal="false"
:close-on-press-escape="false"
@close="handleClose"
>
<div class="exercise-result">
<div class="top-content">本次练习结果</div>
<el-divider></el-divider>
<div class="content">
<div>{{ correctNum + errorNum > 0 ? ((correctNum / (correctNum + errorNum)) * 100).toFixed(0) : 0 }}%</div>
<div>正确率</div>
</div>
<div class="bt-content">
<div class="content-item">
<div>{{ correctNum + errorNum }}</div>
<div>答题数</div>
</div>
<div class="content-item">
<div>{{ correctNum }}</div>
<div>正确题数</div>
</div>
<div class="content-item">
<div>{{ timeCount }}</div>
<div>答题时长</div>
</div>
</div>
</div>
<span slot="footer" class="dialog-footer">
<el-button type="primary" @click="handleClose" v-preventReClick="5000"> </el-button>
</span>
</el-dialog>
</div>
</template>
<script>
import { lookFile } from '@/utils/bonus'
import { getWebQuestion, updateQuestionAnswer, getWebTestQuestion, wenEndPractice } from '@/api/educTrainLearn'
export default {
data() {
return {
dialogVisible: false,
dialogVisible2: false,
currentIndex: 0,
paperData: {
paperName: '2021年度考试题', // 名称
allQuestionNum: 20, // 总题数
},
correctNum: 0, // 正确题数
errorNum: 0, // 错误题数
time: 0, // 时间
timeCount: '', // 时间 00:00:00
item: {},
opt: {
isStudyTask: false, // 是否学习任务
practiceId: '', // 练习id
practiceType: 1, // 练习类型
lastPracticeQuestionId: '', // 上次练习的题目id
id: 1, // 试卷id/题库id
},
// 题目列表
questionList: [],
}
},
created() {
this.opt = this.$route.query
console.log('🚀 ~ created ~ this.opt:', this.opt)
// this.item = this.$route.query.row
// this.isStudyTask = this.$route.query.isStudyTask
this.getQuestionList()
},
methods: {
// 获取题目列表
async getQuestionList() {
let res = {}
if (!this.opt.isStudyTask) {
const params = {
practiceId: this.opt.id,
practiceType: this.opt.practiceType,
lastPracticeQuestionId: this.opt.lastPracticeQuestionId || '',
}
console.log('🚀 ~ getQuestionList ~ params:', params)
res = await getWebTestQuestion(params)
this.questionList = res.data.data
if (this.opt.practiceType == 1) {
// 过滤出包含 lastPracticeQuestionId 的题目, lastPracticeQuestionId 是未答题目所有id拼接的字符串
this.opt.lastPracticeQuestionId = this.opt.lastPracticeQuestionId.split(',')
this.questionList = this.questionList.filter(item =>
this.opt.lastPracticeQuestionId.includes(item.questionId)
)
}
} else {
res = await getWebQuestion({ examId: this.opt.id })
this.questionList = res.data
}
console.log('🚀 ~ 题目列表 ~ res:', res)
this.questionList.forEach((item, index) => {
item.questionIndex = this.$set(item, 'questionIndex', index + 1)
item.isPass = false
item.isError = false
item.isNotAnswer = true
if (item.questionPictureVoList && item.questionPictureVoList.length > 0) {
item.questionPictureVoList.forEach(img => {
img.url = lookFile() + img.pictureUrl
// img.url = lookFile + img.pictureUrl.replace(/\\/g, '/')
})
}
//
if (item.questionAnswerVoList && item.questionAnswerVoList.length > 0) {
// 添加一个 radiovalue 字段,用于记录用户选择的答案
if (item.questionType == 1 || item.questionType == 3) {
item.radioValue = ''
} else if (item.questionType == 2) {
item.checkboxValue = this.$set(item, 'checkboxValue', [])
}
item.chooseAnswer = ''
item.questionAnswerVoList.forEach(answer => {
if (answer.questionPictureVoList && answer.questionPictureVoList.length > 0) {
// 图片路径拼接
answer.questionPictureVoList.forEach(opt => {
opt.url = lookFile() + opt.pictureUrl
})
}
})
}
})
console.log('🚀 ~ getQuestionList ~ this.题目列表:', this.questionList)
this.startTimer()
},
// 开启计时器
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)
},
radioChange(item) {
console.log('🚀 ~ radioChange ~ item:', item)
},
// 选择选项
handleOption(item) {
console.log('🚀 ~ handleOption ~ item:', item)
if (item.questionType == 1 || item.questionType == 3) {
if (!item.radioValue) {
this.$message({
message: '请选择答案',
type: 'warning',
})
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++
item.isPass = true
item.isError = false
item.isNotAnswer = false
if (item.questionType == 1) {
item.scorePerQuestion = this.opt.singleScore
} else if (item.questionType == 3) {
item.scorePerQuestion = this.opt.judgeScore
}
} else {
this.errorNum++
item.isPass = false
item.isError = true
item.isNotAnswer = false
item.scorePerQuestion = '0'
}
} else if (item.questionType == 2) {
if (item.checkboxValue.length == 0) {
this.$message({
message: '请选择答案',
type: 'warning',
})
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.isPass = true
item.isError = false
item.isNotAnswer = false
item.scorePerQuestion = this.opt.multipleScore
} else {
this.errorNum++
item.isPass = false
item.isError = true
item.isNotAnswer = false
item.scorePerQuestion = '0'
}
}
},
async openDialog2() {
try {
this.dialogVisible = false
this.dialogVisible2 = true
clearInterval(this.timer)
console.log('🚀 ~ handleClose ~ this.timeCount:', this.timeCount, this.time)
if (this.opt.isStudyTask) {
let scoringRete = 0
if (this.correctNum + this.errorNum != 0) {
scoringRete = ((this.correctNum / (this.correctNum + this.errorNum)) * 100).toFixed(2)
}
// 保存答案
const params = {
userId: this.opt.userId,
examPaperId: this.opt.id,
scoringRete,
answerTime: this.time,
score: 0,
selectAnswerList: this.questionList,
}
console.log('🚀 ~ openDialog2 ~ params:', params)
const res = await updateQuestionAnswer(params)
console.log('🚀 ~ openDialog2 ~ 提交:', res)
} else {
const questionId = this.questionList
.filter(item => !item.chooseAnswer)
.map(item => item.questionId)
.join(',') // 未答题目id
// 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
)
// 保存答案
const params = {
practiceId: this.opt.id, // 题库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 wenEndPractice(params)
console.log('🚀 ~ handleEnd ~ 自主练习提交:', res)
}
} catch (error) {
console.log('🚀 ~ openDialog2 ~ error:', error)
}
},
handleClose() {
this.dialogVisible2 = false
// 关闭页面
this.$tab.closePage()
// this.$tab.closePage().then(() => {
// this.$router.push({ path: '/educTrainLearn/libertyLearn/index' })
// })
},
},
}
</script>
<style lang="scss" scoped>
.box-card {
margin-bottom: 20px;
}
.top-content {
display: flex;
justify-content: flex-start;
align-items: center;
padding: 10px;
.title {
font-size: 16px;
font-weight: bold;
}
.lf-line {
width: 2px;
height: 20px;
background-color: #ccc;
margin: 0 20px;
}
}
.tab-wrapper {
display: flex;
justify-content: flex-start;
padding: 10px;
margin-bottom: 20px;
.item {
cursor: pointer;
padding: 5px 10px;
border-radius: 5px;
border: 1px solid #409eff;
margin-right: 10px;
&.active {
background-color: #409eff;
color: #fff;
}
}
}
.color-box {
width: 10px;
height: 10px;
display: inline-block;
margin-right: 5px;
}
.score-wrapper {
width: 60px;
height: 60px;
display: flex;
flex-direction: column;
justify-content: center; // 垂直居中
align-items: center;
text-align: right;
background: #67c23a;
border-radius: 50%;
color: #fff;
font-size: 13px;
// 不及格整体背景色
&.is-not-pass {
background: #f56c6c;
}
}
.top-item {
color: #409eff;
}
.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;
overflow: hidden;
}
.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: 100px;
}
}
.el-radio-group {
display: flex;
flex-direction: column;
align-items: flex-start;
}
.el-radio {
display: flex;
margin-bottom: 10px;
}
.box {
height: 5vh;
}
.bottom-wrapper {
width: 100vw;
position: fixed;
bottom: 0;
right: 0;
padding: 10px;
background-color: #fff;
display: flex;
justify-content: center;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
.btn {
margin: 0 15px;
}
}
.exercise-result {
display: flex;
flex-direction: column;
justify-self: center;
align-items: center;
.top-content {
font-size: 18px;
font-weight: 800;
}
.content {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
width: 100%;
margin-top: 20px;
}
.bt-content {
display: flex;
align-items: center;
margin-top: 20px;
.content-item {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
margin: 10px;
}
& > :nth-child(2) {
margin: 0 39px;
}
}
}
</style>