YNUtdPlatform/pages/YNEduApp/exam/examination.vue

685 lines
20 KiB
Vue
Raw Permalink 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 :style="{ fontSize: fontSize + 'px' }">
<u-navbar leftIcon="" title="考试" :placeholder="true">
<view class="u-nav-slot" slot="right">
<u-tag
text="小"
:type="fontSize == 19 ? 'primary' : 'info'"
@click="changeFont(19)"
style="margin-right: 8px"
></u-tag>
<u-tag
text="中"
:type="fontSize == 20.5 ? 'primary' : 'info'"
@click="changeFont(20.5)"
style="margin-right: 8px"
></u-tag>
<u-tag text="大" :type="fontSize == 22 ? 'primary' : 'info'" @click="changeFont(22)"></u-tag>
</view>
</u-navbar>
<div class="content" @touchstart="touchStart" @touchmove="touchMove" @touchend="touchEnd">
<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"
>
<view class="time" style="color: #3c9cff">
<view class="time__custom">
<text class="time__custom__item">
{{ timeData.hours > 10 ? timeData.hours : '0' + timeData.hours }}
</text>
</view>
<text class="time__doc">:</text>
<view class="time__custom">
<text class="time__custom__item">
{{ timeData.minutes > 10 ? timeData.minutes : '0' + timeData.minutes }}
</text>
</view>
<text class="time__doc">:</text>
<view class="time__custom">
<text class="time__custom__item">
{{ timeData.seconds > 10 ? timeData.seconds : '0' + timeData.seconds }}
</text>
</view>
</view>
</u-count-down>
</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">
<span style="font-size: 16px">{{ isRotating ? '收起' : '展开' }}</span>
<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" :style="{ height: fontSize - 8 + 'px' }" />
<div class="question-type" :style="{ fontSize: fontSize - 2 + 'px' }">
<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" :style="{ fontSize: fontSize + 2 + 'px' }">
{{ currentIndex + 1 }}. {{ item.examTopic }}
</div>
<u-image
v-if="item.examTopicUrl"
:showLoading="true"
:src="fileUrl + item.examTopicUrl || ''"
width="60px"
height="60px"
style="margin: 0 10px 15px 0"
@click="clickImg(fileUrl + item.examTopicUrl)"
/>
<u-line />
<div class="options">
<div class="option-wrapper" v-for="(option, optionIndex) in item.listOption" :key="optionIndex">
<div
class="option"
: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>
<u-image
v-if="option.optionUrl"
:showLoading="true"
:src="fileUrl + option.optionUrl || ''"
width="60px"
height="60px"
style="margin: 15px 10px 5px 0"
@click="clickImg(fileUrl + option.optionUrl)"
/>
</div>
</div>
</div>
<!-- 底部按钮 -->
<div style="height: 75px"></div>
<div class="bottom-btn">
<div class="btn" v-if="currentIndex != 0">
<u-button shape="circle" text="上一题" @click="currentIndex--" />
</div>
<div class="btn" v-if="currentIndex !== questionList.length - 1">
<u-button type="primary" shape="circle" text="下一题" @click="currentIndex++" />
</div>
<div class="btn">
<u-button type="primary" 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 {
getExamQuestionList,
insertQuestionAnswerById,
commitExamByRecordId,
getFaceRecognition,
updStudyDurationExamPractice
} from '@/api/eduApp'
import config from '@/config'
export default {
data() {
return {
isLoading: false,
// 考试id
examId: '',
// 考试记录id
recordId: '',
// 切屏次数
screenCount: 0,
// 允许切屏次数
switchCount: 0,
showConfirmModal: false,
// 考试时间
time: 0,
examTime: 0,
random1: 0,
random2: 0,
// 答题时间
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,
isHide: false,
failCount: 3,
startX: 0, // 起始触摸点X坐标
startY: 0, // 起始触摸点Y坐标
endX: 0, // 结束触摸点X坐标
endY: 0, // 结束触摸点Y坐标
fontSize: 19,
timeData: {}
}
},
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
this.getList()
},
onShow() {
this.isHide = false
},
onHide() {
setTimeout(() => {
if (!this.isHide) {
this.handleConfirmSubmit()
}
}, 500)
},
methods: {
changeFont(size) {
console.log('🚀 ~ changeFont ~ size:', size)
this.fontSize = size
},
// 获取列表
async getList() {
try {
const params = {
examId: this.examId
}
console.log('🚀 ~ 考试 ~ params:', params)
this.$verificationToken()
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) {
this.timeData = time
// console.log('🚀 ~ changeCountDown ~ time:', time)
// 等时间赋值后再开始计时
if (this.time == 0 && !this.isLoading) return
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()
}
},
// 点击题号
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
}
if (item.examType == 3) {
params.selectAnswer = selectAnswer == 'A' ? '对' : '错'
}
console.log('🚀 ~ handleSelectOption ~ params:', params)
// insertQuestionAnswerById(params)
this.$verificationToken()
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')
},
success: res => {
console.log('🚀 ~ handleSelectOption ~ res:', res)
}
})
},
openConfirmModal() {
this.unDoCount = this.questionList.filter(item => !item.isActive).length
this.showConfirmModal = true
},
// 确认提交
handleConfirmSubmit() {
console.log('🚀 ~ 提交-->', this.isLoading)
if (this.isLoading) {
uni.showToast({
title: '正在提交中, 请稍后',
icon: 'none'
})
return
}
this.isLoading = true
// 停止计时
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)
this.$verificationToken()
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
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 + 1,
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)}`
})
setTimeout(() => {
this.showConfirmModal = false
}, 1000)
}
})
if (this.studyId) {
// updStudyDurationExamPractice({ userId: params.userId, studyId: this.studyId })
this.updStudyDurationExamPractice({ userId: params.userId, studyId: this.studyId })
}
console.log('🚀 ~ handleConfirmSubmit ~ params:', params, res)
},
clickImg(url) {
this.isHide = true
uni.previewImage({
urls: [url]
})
// this.screenCount--
},
updStudyDurationExamPractice(params) {
this.$verificationToken()
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)
}
})
},
touchStart(event) {
this.startX = 0
this.endX = 0
this.startY = 0
this.endY = 0
console.log('🚀 ~ 开始 ~ event:', event)
// 记录触摸开始的位置
this.startX = event.touches[0].clientX
this.startY = event.touches[0].clientY
console.log('🚀 ~ touchStart ~ this.startX:', this.startX)
console.log('🚀 ~ touchStart ~ this.startY:', this.startY)
},
touchMove(event) {
console.log('🚀 ~ 移动 ~ event:', event)
// 在触摸过程中可以获取当前触摸点位置
this.endX = event.touches[0].clientX
this.endY = event.touches[0].clientY
console.log('🚀 ~ touchMove ~ this.endX:', this.endX)
console.log('🚀 ~ touchMove ~ this.endY:', this.endY)
},
touchEnd() {
// 判断滑动方向
const deltaX = this.startX - this.endX
const deltaY = this.startY - this.endY
if (Math.abs(deltaY) < Math.abs(deltaX)) {
if (this.startX != 0 && this.endX != 0 && deltaX > 10) {
// 左滑
console.log('向左滑动')
this.currentIndex++
} else if (this.startX != 0 && this.endX != 0 && deltaX < -10) {
// 右滑
console.log('向右滑动')
this.currentIndex--
}
if (this.currentIndex < 0) {
this.currentIndex = 0
} else if (this.currentIndex > this.questionList.length - 1) {
this.currentIndex = this.questionList.length - 1
}
}
}
},
onBackPress(options) {
console.log(options)
if (options.from == 'backbutton') {
// 来自手势返回
console.log('手势返回')
// 返回为 true 时,不会执行返回操作,可以自定义返回逻辑
// 返回为 false 或者不返回时,则执行默认返回操作
return true
}
// 返回为 false 或者不返回时,则执行默认返回操作
return false
}
}
</script>
<style lang="scss" scoped>
.u-nav-slot {
display: flex;
justify-content: flex-end;
align-items: center;
}
.content {
width: 100vw;
height: 90vh;
.top-content {
margin: 10px;
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 {
padding: 10px;
.question-type-wrapper {
margin: 20px 0 10px;
display: flex;
justify-content: flex-start;
align-items: center;
.line {
border: 2px solid #1989fa;
margin-right: 3px;
}
.question-type {
color: #8a8a8a;
}
}
.question {
margin-bottom: 8px;
font-weight: 800;
color: #333333;
}
}
.options {
margin: 5px 0 30px 20px;
overflow: auto;
.option-wrapper {
max-height: 200px;
overflow: auto;
.option {
display: flex;
justify-content: flex-start;
align-items: center;
margin-top: 10px;
margin-bottom: 5px;
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;
padding-bottom: 18px;
display: flex;
justify-content: space-between;
align-items: center;
.btn {
width: 100px;
&:first-child {
margin-left: 15px;
}
&:last-child {
margin-right: 15px;
}
}
}
}
::v-deep .u-count-down__text {
font-weight: 700;
color: #1989fa;
}
</style>