YNUtdPlatform/pages/YNEduApp/exam/examination.vue

623 lines
19 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>
<u-navbar leftIcon="" title="考试" :placeholder="true" />
<div class="content">
<div class="top-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 }}</span>
/{{ questionList.length }}
</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: item.isActive, currentActive: currentIndex == index }">
{{ index + 1 }}
</div>
</div>
</div>
<div class="unfold" @click="handleUnfold">
{{ 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>
<!-- 题目 -->
<div class="question-wrapper" v-for="(item, index) in questionList" :key="index" v-show="index == currentIndex">
<div class="question-type-wrapper">
<div class="line" />
<div class="question-type">
<div v-if="item.examType == 1">单选题({{ item.questionScore }}分)</div>
<div v-if="item.examType == 2">多选题({{ item.questionScore }}分)</div>
<div v-if="item.examType == 3">判断题({{ item.questionScore }}分)</div>
</div>
</div>
<div class="question">{{ currentIndex + 1 }}. {{ item.examTopic }}</div>
<u--image
v-if="item.examTopicUrl"
:showLoading="true"
:src="fileUrl + item.examTopicUrl || ''"
width="60px"
height="60px"
style="margin-bottom: 10px"
@click="clickImg(fileUrl + item.examTopicUrl)"
/>
<div class="options">
<div
class="option"
v-for="(option, optionIndex) in item.listOption"
:key="optionIndex"
:class="{ isActive: option.isActive }"
@click="handleSelectOption(item, index, option, optionIndex)"
>
<div class="option-item">{{ option.optionIdent }}.</div>
<div class="option-content">{{ option.optionContent }}</div>
</div>
</div>
</div>
<!-- 底部按钮 -->
<div class="bottom-btn">
<div class="btn" v-show="currentIndex != 0">
<u-button size="small" shape="circle" text="上一题" @click="currentIndex--" />
</div>
<div class="btn" v-if="currentIndex !== questionList.length - 1">
<u-button type="primary" size="small" shape="circle" text="下一题" @click="currentIndex++" />
</div>
<div class="btn" v-else>
<u-button type="primary" size="small" shape="circle" text="交 卷" @click="openConfirmModal" />
</div>
</div>
</div>
<!-- 提交弹框 -->
<u-modal
:show="showConfirmModal"
title="提示"
showCancelButton
@cancel="showConfirmModal = false"
@confirm="handleConfirmSubmit"
>
<view class="slot-content">
<view v-if="unDoCount > 0">
本场考试还有
<span style="color: #1989fa; margin: 0 5px">{{ unDoCount }}</span>
题尚未完成
</view>
<view style="text-align: center">确定交卷?</view>
</view>
</u-modal>
<u-toast ref="uToast"></u-toast>
</view>
</template>
<script>
import face from '@/uni_modules/mcc-face/index.js'
import {
getExamQuestionList,
insertQuestionAnswerById,
commitExamByRecordId,
getFaceRecognition,
updStudyDurationExamPractice
} from '@/api/eduApp'
import config from '@/config'
export default {
data() {
return {
// 考试id
examId: '',
// 考试记录id
recordId: '',
// 切屏次数
screenCount: 0,
// 允许切屏次数
switchCount: 0,
showConfirmModal: false,
// 考试时间
time: 0,
examTime: 0,
random1: 0,
random2: 0,
hasScanned: false,
// 答题时间
answerTime: 0,
currentIndex: 0,
// 是否展开
isRotating: false,
// 未做题目数
unDoCount: 0,
examNum: 0, // 考试次数
examCount: 1, // 1: 不限次 2: 及格终止 3: 自定义
examCustom: 0, // 自定义次数
results: 0, // 考试结果
studyId: '', // 学习id
// 题目列表
questionList: [],
fileUrl: config.fileUrl,
score: 0,
passScore: 0
}
},
onLoad(opt) {
opt = JSON.parse(opt.params)
console.log('🚀 ~ onLoad ~ opt考试中--:', opt)
this.examId = opt.examId
this.switchCount = Number(opt.switchCount)
this.examNum = opt.examNum
this.examCount = opt.examCount
this.examCustom = opt.examCustom
this.studyId = opt.studyId || ''
this.score = opt.score
this.passScore = opt.passScore
},
// onShow() {
// setTimeout(() => {
// if (this.screenCount > this.switchCount) {
// this.$refs.uToast.show({
// message: '切屏次数已达上限, 系统将自动提交',
// duration: 1000
// })
// this.handleConfirmSubmit()
// }
// }, 1000)
// },
onHide() {
// this.screenCount++
// console.log('🚀 ~ onHide ~ this.screenCount:', this.screenCount)
this.handleConfirmSubmit()
},
mounted() {
this.getList()
this.random1 = Math.floor(Math.random() * 100000) + 420000 // random1 在 7-13分钟之间
// this.random1 = 10000
this.random2 = Math.floor(Math.random() * 100000) + 900000 // random2 在 15-19分钟之间
console.log('🚀 ~ mounted ~ this.random1:', this.random1, this.random2)
},
// onUnload() {
// console.log('🚀 ~ onUnload ~ 页面关闭')
// this.handleConfirmSubmit()
// },
methods: {
// 获取列表
async getList() {
try {
const params = {
examId: this.examId
}
// const res = await getExamQuestionList(params)
// const data = res.data
// this.questionList = data.examPaperData
// this.recordId = data.recordId
// this.examTime = data.answerTime
// this.time = Number(data.answerTime) * 60 * 1000
// console.log('🚀 ~ getList ~ this.time:', this.time)
// console.log('🚀 ~ getList ~ res考试题:', res)
uni.request({
url: config.baseUrl + '/exam-student/studentExam/getExamQuestionList',
method: 'post',
data: params,
header: {
'Content-Type': 'application/x-www-form-urlencoded',
Authorization: uni.getStorageSync('access_token')
},
success: res => {
console.log('🚀 ~ getList ~ res:', res)
res = res.data
const data = res.data
this.questionList = data.examPaperData
this.recordId = data.recordId
this.examTime = data.answerTime
this.time = Number(data.answerTime) * 60 * 1000
console.log('🚀 ~ getList ~ this.time:', this.time)
console.log('🚀 ~ getList ~ this.questionList:', this.questionList)
if (this.questionList.length > 0) {
this.questionList.forEach((item, index) => {
this.$set(item, 'isShow', index < 7)
this.$set(item, 'isActive', false)
if (item.listOption) {
item.listOption.forEach(option => {
this.$set(option, 'isActive', false)
})
}
})
console.log('🚀 ~ this.questionList.forEach ~ this.questionList:', this.questionList)
setTimeout(() => {
this.$refs.countDown.start()
}, 100)
}
},
fail: err => {
console.log(err)
}
})
} catch (error) {
console.log('🚀 ~ getList ~ error:', error)
}
},
changeCountDown(time) {
// console.log('🚀 ~ changeCountDown ~ time:', time)
// 等时间赋值后再开始计时
if (this.time == 0) return
this.answerTime =
this.time - (time.days * 24 * 60 * 60 + time.hours * 60 * 60 + time.minutes * 60 + time.seconds) * 1000
// console.log('🚀 ~ changeCountDown ~ this.answerTime:', this.answerTime)
if (this.answerTime > this.random1 && this.answerTime < this.random2 && !this.hasScanned) {
this.openFaceScan()
this.hasScanned = true
} else if (this.answerTime > this.random2 && this.hasScanned) {
this.openFaceScan()
this.hasScanned = false
}
if (this.answerTime == this.time) {
// 提示: 时间结束, 自动提交
this.$refs.uToast.show({
message: '考试时间结束, 系统将自动提交',
duration: 1000
})
this.handleConfirmSubmit()
}
},
// 点击题号
handleQuestionNumber(item, index) {
console.log('🚀 ~ handleQuestionNumber ~ item:', item)
this.currentIndex = index
},
// 展开
handleUnfold() {
this.questionList.forEach((item, index) => {
if (index > 6) {
this.$set(item, 'isShow', !item.isShow)
}
})
this.isRotating = !this.isRotating
console.log('🚀 ~ this.questionList.forEach ~ this.isRotating:', this.isRotating)
},
handleSelectOption(item, index, option, optionIndex) {
let selectAnswer = ''
console.log('🚀 ~ handleSelectOption ~ option:', item, option, optionIndex)
item.isActive = true
// 如果是单选题与判断题 则只能选中一个 多选题可以选中多个
if (item.examType == 1 || item.examType == 3) {
this.$set(option, 'isActive', true)
item.listOption.forEach((option, optIndex) => {
// 除了当前选中的其他都设置为未选中
if (optIndex != optionIndex) {
this.$set(option, 'isActive', false)
}
})
selectAnswer = option.optionIdent
} else {
this.$set(option, 'isActive', !option.isActive)
// 如果所有选项都是未选中状态, 则题目也是未选中状态
let isActive = item.listOption.some(option => option.isActive)
item.isActive = isActive
// 将点击的选项的 optionIdent 'ABC' 拼接成字符串
item.listOption.forEach(option => {
if (option.isActive) {
selectAnswer += option.optionIdent
}
})
}
const params = {
recordId: this.recordId,
questionId: item.id,
selectAnswer
}
console.log('🚀 ~ handleSelectOption ~ params:', params)
// insertQuestionAnswerById(params)
uni.request({
url: config.baseUrl + '/exam-student/studentExam/insertQuestionAnswerById',
method: 'post',
data: params,
header: {
'Content-Type': 'application/x-www-form-urlencoded',
Authorization: uni.getStorageSync('access_token')
}
})
},
openConfirmModal() {
this.unDoCount = this.questionList.filter(item => !item.isActive).length
this.showConfirmModal = true
},
// 确认提交
handleConfirmSubmit() {
// 停止计时
this.$refs.countDown.pause()
const params = {
userId: uni.getStorageSync('userId'),
recordId: this.recordId,
examId: this.examId,
// 时间 转换为分钟, 不足一分钟算一分钟
answerTime: Math.ceil(this.answerTime / 60000)
}
// const res = await commitExamByRecordId(params)
uni.request({
url: config.baseUrl + '/exam-student/studentExam/commitExamByRecordId',
method: 'post',
data: params,
header: {
'Content-Type': 'application/x-www-form-urlencoded',
Authorization: uni.getStorageSync('access_token')
},
success: res => {
console.log('🚀 ~ handleConfirmSubmit ~ res:', res)
res = res.data
this.showConfirmModal = false
const params2 = {
examId: this.examId,
examGrade: res.examGrade,
examResult: res.examResult,
gradeAverage: res.gradeAverage,
answerTime: Math.ceil(this.answerTime / 60000),
examTime: this.examTime,
questionCount: this.questionList.length,
switchCount: this.switchCount,
examNum: this.examNum,
examCount: this.examCount,
examCustom: this.examCustom,
results: res.examResult,
studyId: this.studyId || '',
score: this.score,
passScore: this.passScore
}
uni.navigateTo({
url: `/pages/YNEduApp/exam/examinationDetails?params=${JSON.stringify(params2)}`
})
}
})
if (this.studyId) {
// updStudyDurationExamPractice({ userId: params.userId, studyId: this.studyId })
this.updStudyDurationExamPractice({ userId: params.userId, studyId: this.studyId })
}
console.log('🚀 ~ handleConfirmSubmit ~ params:', params, res)
// this.showConfirmModal = false
// const params2 = {
// examId: this.examId,
// examGrade: res.examGrade,
// examResult: res.examResult,
// gradeAverage: res.gradeAverage,
// answerTime: Math.ceil(this.answerTime / 60000),
// examTime: this.examTime,
// questionCount: this.questionList.length,
// switchCount: this.switchCount,
// examNum: this.examNum,
// examCount: this.examCount,
// examCustom: this.examCustom,
// results: res.examResult,
// studyId: this.studyId || ''
// }
// uni.navigateTo({
// url: `/pages/YNEduApp/exam/examinationDetails?params=${JSON.stringify(params2)}`
// })
},
// 人脸识别
async openFaceScan() {
face.open(['a'], e => {
console.log('🚀 ~ e-人脸识别:', e)
face.close()
let params = {
userId: uni.getStorageSync('userId'),
img: e
}
params = JSON.stringify(params)
uni.request({
url: config.baseUrl + '/exam-student/personalCenter/getFaceRecognition',
method: 'POST',
header: {
'content-type': 'application/json',
Authorization: uni.getStorageSync('access_token')
},
data: params,
success: res => {
res = res.data
console.log('🚀 ~ openFaceScan ~ res-人脸识别:', res, res.code)
if (res.code == 200) {
this.$refs.uToast.show({
message: '人脸识别成功',
duration: 1000
})
} else {
this.$refs.uToast.show({
message: '人脸识别失败, 即将结束考试',
duration: 1000
})
setTimeout(() => {
this.handleConfirmSubmit()
}, 1000)
}
},
fail(err) {
console.log('🚀 ~ openFaceScan ~ 人脸识别失败', err)
}
})
})
},
clickImg(url) {
uni.previewImage({
urls: [url]
})
this.screenCount--
},
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)
}
})
}
}
}
</script>
<style lang="scss" scoped>
.content {
padding: 10px;
.top-content {
background: #fff;
padding: 10px;
border-radius: 5px;
}
.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%;
display: flex;
justify-content: flex-start;
align-items: center;
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;
&.isActive {
background: #1989fa;
}
&.currentActive {
background: #f4c14a;
}
}
}
}
}
.question-wrapper {
.question-type-wrapper {
margin: 20px 0;
display: flex;
justify-content: flex-start;
align-items: center;
.line {
width: 2px;
height: 11px;
background: #1989fa;
margin-right: 3px;
}
.question-type {
color: #8a8a8a;
}
}
.question {
font-weight: 800;
font-size: 15px;
color: #333333;
}
}
.options {
margin-top: 10px;
.option {
display: flex;
justify-content: flex-start;
align-items: center;
margin-top: 10px;
background: #f4f9fe;
border-radius: 5px;
&.isActive {
background: #8cbff1;
color: #fff;
}
.option-item {
width: 33px;
height: 33px;
line-height: 33px;
text-align: center;
color: #333;
}
.option-content {
margin-left: 10px;
color: #333;
}
}
}
.bottom-btn {
position: fixed;
bottom: 0;
left: 0;
width: 100%;
background-color: #fff;
padding: 15px 0;
display: flex;
justify-content: flex-end;
align-items: center;
.btn {
width: 100px;
margin-right: 10px;
}
}
}
::v-deep .u-count-down__text {
font-weight: 700;
color: #1989fa;
}
</style>