2025-12-03 18:58:36 +08:00
|
|
|
|
<template>
|
|
|
|
|
|
<view class="exam-detail-container">
|
|
|
|
|
|
<!-- 顶部信息栏 -->
|
|
|
|
|
|
<view class="exam-header">
|
|
|
|
|
|
<view class="header-info">
|
|
|
|
|
|
<text class="exam-name">{{ examInfo.examName || '加载中...' }}</text>
|
|
|
|
|
|
<text class="exam-subject" v-if="examInfo.subjectName">{{ examInfo.subjectName }}</text>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
<view class="timer-section">
|
|
|
|
|
|
<text class="timer-label">剩余时间</text>
|
|
|
|
|
|
<text class="timer-value" :class="{ 'timer-warning': remainingTime < 300 }">
|
|
|
|
|
|
{{ formatTime(remainingTime) }}
|
|
|
|
|
|
</text>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 答题卡 -->
|
|
|
|
|
|
<view class="answer-sheet" v-if="showAnswerSheet">
|
|
|
|
|
|
<view class="sheet-header">
|
|
|
|
|
|
<text class="sheet-title">答题卡</text>
|
|
|
|
|
|
<text class="close-btn" @click="showAnswerSheet = false">×</text>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
<view class="sheet-content">
|
|
|
|
|
|
<view
|
|
|
|
|
|
v-for="(question, index) in questions"
|
|
|
|
|
|
:key="question.id"
|
|
|
|
|
|
class="sheet-item"
|
|
|
|
|
|
:class="{
|
|
|
|
|
|
'answered': answers[question.id] !== undefined && answers[question.id] !== '',
|
|
|
|
|
|
'current': currentIndex === index
|
|
|
|
|
|
}"
|
|
|
|
|
|
@click="jumpToQuestion(index)"
|
|
|
|
|
|
>
|
|
|
|
|
|
{{ index + 1 }}
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 题目区域 -->
|
|
|
|
|
|
<scroll-view
|
|
|
|
|
|
class="questions-container"
|
|
|
|
|
|
scroll-y
|
|
|
|
|
|
:scroll-into-view="'question-' + currentIndex"
|
|
|
|
|
|
scroll-with-animation
|
|
|
|
|
|
>
|
|
|
|
|
|
<view
|
|
|
|
|
|
v-for="(question, index) in questions"
|
|
|
|
|
|
:key="question.id"
|
|
|
|
|
|
:id="'question-' + index"
|
|
|
|
|
|
class="question-item"
|
|
|
|
|
|
>
|
|
|
|
|
|
<view class="question-header">
|
|
|
|
|
|
<text class="question-number">第 {{ index + 1 }} 题</text>
|
|
|
|
|
|
<text class="question-score">{{ question.score }}分</text>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
|
|
<view class="question-content">
|
2025-12-06 14:53:35 +08:00
|
|
|
|
<text class="question-text">
|
|
|
|
|
|
{{ question.questionContent }}
|
|
|
|
|
|
<text v-if="question.questionType === 'multiple'" class="multiple-tip">(可多选)</text>
|
|
|
|
|
|
</text>
|
2025-12-03 18:58:36 +08:00
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 单选题 -->
|
|
|
|
|
|
<view v-if="question.questionType === 'single'" class="options-list">
|
|
|
|
|
|
<view
|
|
|
|
|
|
v-for="(option, optIndex) in parseOptions(question.options)"
|
|
|
|
|
|
:key="optIndex"
|
|
|
|
|
|
class="option-item"
|
|
|
|
|
|
:class="{ 'selected': answers[question.id] === option }"
|
|
|
|
|
|
@click="selectAnswer(question.id, option)"
|
|
|
|
|
|
>
|
|
|
|
|
|
<view class="option-radio">
|
|
|
|
|
|
<view class="radio-dot" v-if="answers[question.id] === option"></view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
<text class="option-label">{{ getOptionLabel(optIndex) }}</text>
|
|
|
|
|
|
<text class="option-text">{{ option }}</text>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 多选题 -->
|
2025-12-06 14:53:35 +08:00
|
|
|
|
<view v-if="question.questionType === 'multiple'">
|
|
|
|
|
|
<view class="multiple-hint">💡 提示:本题为多选题,可以选择多个答案</view>
|
|
|
|
|
|
<view class="options-list">
|
|
|
|
|
|
<view
|
|
|
|
|
|
v-for="(option, optIndex) in parseOptions(question.options)"
|
|
|
|
|
|
:key="optIndex"
|
|
|
|
|
|
class="option-item"
|
|
|
|
|
|
:class="{ 'selected': isOptionSelected(question.id, option) }"
|
|
|
|
|
|
@click="toggleMultipleAnswer(question.id, option)"
|
|
|
|
|
|
>
|
2025-12-03 18:58:36 +08:00
|
|
|
|
<view class="option-checkbox">
|
|
|
|
|
|
<text class="checkbox-icon" v-if="isOptionSelected(question.id, option)">✓</text>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
<text class="option-label">{{ getOptionLabel(optIndex) }}</text>
|
|
|
|
|
|
<text class="option-text">{{ option }}</text>
|
|
|
|
|
|
</view>
|
2025-12-06 14:53:35 +08:00
|
|
|
|
</view>
|
2025-12-03 18:58:36 +08:00
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 判断题 -->
|
|
|
|
|
|
<view v-if="question.questionType === 'judge'" class="options-list">
|
|
|
|
|
|
<view
|
|
|
|
|
|
class="option-item"
|
|
|
|
|
|
:class="{ 'selected': answers[question.id] === '正确' }"
|
|
|
|
|
|
@click="selectAnswer(question.id, '正确')"
|
|
|
|
|
|
>
|
|
|
|
|
|
<view class="option-radio">
|
|
|
|
|
|
<view class="radio-dot" v-if="answers[question.id] === '正确'"></view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
<text class="option-text">正确</text>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
<view
|
|
|
|
|
|
class="option-item"
|
|
|
|
|
|
:class="{ 'selected': answers[question.id] === '错误' }"
|
|
|
|
|
|
@click="selectAnswer(question.id, '错误')"
|
|
|
|
|
|
>
|
|
|
|
|
|
<view class="option-radio">
|
|
|
|
|
|
<view class="radio-dot" v-if="answers[question.id] === '错误'"></view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
<text class="option-text">错误</text>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 填空题 -->
|
|
|
|
|
|
<view v-if="question.questionType === 'fill'" class="fill-answer">
|
|
|
|
|
|
<textarea
|
|
|
|
|
|
v-model="answers[question.id]"
|
|
|
|
|
|
placeholder="请输入答案"
|
|
|
|
|
|
class="fill-input"
|
|
|
|
|
|
@blur="saveAnswers"
|
|
|
|
|
|
></textarea>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 简答题 -->
|
|
|
|
|
|
<view v-if="question.questionType === 'essay'" class="essay-answer">
|
|
|
|
|
|
<textarea
|
|
|
|
|
|
v-model="answers[question.id]"
|
|
|
|
|
|
placeholder="请输入答案"
|
|
|
|
|
|
class="essay-input"
|
|
|
|
|
|
:auto-height="true"
|
|
|
|
|
|
@blur="saveAnswers"
|
|
|
|
|
|
></textarea>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</scroll-view>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 底部操作栏 -->
|
|
|
|
|
|
<view class="bottom-actions">
|
|
|
|
|
|
<button class="btn-sheet" @click="showAnswerSheet = !showAnswerSheet">答题卡</button>
|
|
|
|
|
|
<button
|
|
|
|
|
|
class="btn-prev"
|
|
|
|
|
|
:disabled="currentIndex === 0"
|
|
|
|
|
|
@click="prevQuestion"
|
|
|
|
|
|
>上一题</button>
|
|
|
|
|
|
<button
|
|
|
|
|
|
class="btn-next"
|
|
|
|
|
|
:disabled="currentIndex === questions.length - 1"
|
|
|
|
|
|
@click="nextQuestion"
|
|
|
|
|
|
>下一题</button>
|
|
|
|
|
|
<button class="btn-submit" @click="showSubmitModal = true">提交</button>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 提交确认弹窗 -->
|
|
|
|
|
|
<view class="submit-modal" v-if="showSubmitModal" @click.stop>
|
|
|
|
|
|
<view class="modal-mask" @click="showSubmitModal = false"></view>
|
|
|
|
|
|
<view class="modal-content">
|
|
|
|
|
|
<view class="modal-header">
|
|
|
|
|
|
<text class="modal-title">确认提交</text>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
<view class="modal-body">
|
|
|
|
|
|
<text class="modal-text">确定要提交答案吗?提交后将无法修改。</text>
|
|
|
|
|
|
<view class="answer-stats">
|
|
|
|
|
|
<text class="stats-item">已答题:{{ answeredCount }}/{{ questions.length }}</text>
|
|
|
|
|
|
<text class="stats-item">未答题:{{ unansweredCount }}</text>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
<view class="modal-footer">
|
|
|
|
|
|
<button class="btn-cancel" @click="showSubmitModal = false">取消</button>
|
|
|
|
|
|
<button class="btn-confirm" @click="submitExam">确认提交</button>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 退出确认弹窗 -->
|
|
|
|
|
|
<view class="exit-modal" v-if="showExitModal" @click.stop>
|
|
|
|
|
|
<view class="modal-mask" @click="showExitModal = false"></view>
|
|
|
|
|
|
<view class="modal-content">
|
|
|
|
|
|
<view class="modal-header">
|
|
|
|
|
|
<text class="modal-title">退出考试</text>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
<view class="modal-body">
|
|
|
|
|
|
<view class="answer-stats">
|
|
|
|
|
|
<text class="stats-item">已答题:{{ answeredCount }}/{{ questions.length }}</text>
|
|
|
|
|
|
<text class="stats-item">未答题:{{ unansweredCount }}</text>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
<view class="modal-footer">
|
|
|
|
|
|
<button class="btn-cancel" @click="showExitModal = false">取消</button>
|
|
|
|
|
|
<button class="btn-confirm-exit" @click="confirmExit">确定退出</button>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<script>
|
|
|
|
|
|
import { getExamQuestions, submitExamAnswer } from '@/api/study/exam.js'
|
|
|
|
|
|
|
|
|
|
|
|
export default {
|
|
|
|
|
|
data() {
|
|
|
|
|
|
return {
|
|
|
|
|
|
examId: null,
|
|
|
|
|
|
examInfo: {},
|
|
|
|
|
|
questions: [],
|
|
|
|
|
|
answers: {},
|
|
|
|
|
|
currentIndex: 0,
|
|
|
|
|
|
loading: false,
|
|
|
|
|
|
showAnswerSheet: false,
|
|
|
|
|
|
showSubmitModal: false,
|
|
|
|
|
|
showExitModal: false,
|
|
|
|
|
|
isExiting: false, // 标记是否正在退出
|
|
|
|
|
|
// 倒计时相关
|
|
|
|
|
|
remainingTime: 0,
|
|
|
|
|
|
timer: null,
|
|
|
|
|
|
startTime: null,
|
|
|
|
|
|
duration: 0
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
computed: {
|
|
|
|
|
|
answeredCount() {
|
2025-12-06 14:53:35 +08:00
|
|
|
|
// 只计算当前考试题目的答案数量,避免包含旧题目的答案
|
|
|
|
|
|
return this.questions.filter(question => {
|
|
|
|
|
|
const answer = this.answers[question.id]
|
|
|
|
|
|
return answer !== undefined && answer !== '' && answer !== null
|
|
|
|
|
|
}).length
|
2025-12-03 18:58:36 +08:00
|
|
|
|
},
|
|
|
|
|
|
unansweredCount() {
|
|
|
|
|
|
return this.questions.length - this.answeredCount
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
onLoad(options) {
|
|
|
|
|
|
if (options.id) {
|
|
|
|
|
|
this.examId = options.id
|
|
|
|
|
|
this.loadExamData()
|
|
|
|
|
|
} else {
|
|
|
|
|
|
uni.showToast({
|
|
|
|
|
|
title: '缺少考试ID',
|
|
|
|
|
|
icon: 'none'
|
|
|
|
|
|
})
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
|
uni.navigateBack()
|
|
|
|
|
|
}, 1500)
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
onBackPress() {
|
|
|
|
|
|
// 如果用户已确认退出,不拦截
|
|
|
|
|
|
if (this.isExiting) {
|
|
|
|
|
|
return false
|
|
|
|
|
|
}
|
|
|
|
|
|
// 拦截返回按钮,显示退出确认弹窗
|
|
|
|
|
|
if (this.answeredCount > 0) {
|
|
|
|
|
|
this.showExitModal = true
|
|
|
|
|
|
return true // 阻止默认返回行为
|
|
|
|
|
|
}
|
|
|
|
|
|
return false
|
|
|
|
|
|
},
|
|
|
|
|
|
onUnload() {
|
|
|
|
|
|
this.clearTimer()
|
|
|
|
|
|
// 保存答案到本地(退出时也会保存)
|
|
|
|
|
|
this.saveAnswers()
|
|
|
|
|
|
},
|
|
|
|
|
|
onHide() {
|
|
|
|
|
|
// 页面隐藏时保存答案
|
|
|
|
|
|
this.saveAnswers()
|
|
|
|
|
|
},
|
|
|
|
|
|
methods: {
|
|
|
|
|
|
async loadExamData() {
|
|
|
|
|
|
this.loading = true
|
|
|
|
|
|
try {
|
|
|
|
|
|
const response = await getExamQuestions(this.examId)
|
|
|
|
|
|
if (response.code === 200 && response.data) {
|
|
|
|
|
|
this.examInfo = response.data.exam || {}
|
|
|
|
|
|
this.questions = response.data.questions || []
|
|
|
|
|
|
this.duration = (this.examInfo.duration || 120) * 60 // 转换为秒
|
|
|
|
|
|
|
|
|
|
|
|
// 初始化倒计时
|
|
|
|
|
|
this.remainingTime = this.duration
|
|
|
|
|
|
this.startTime = Date.now()
|
|
|
|
|
|
this.startTimer()
|
|
|
|
|
|
|
|
|
|
|
|
// 加载本地保存的答案
|
|
|
|
|
|
this.loadAnswers()
|
|
|
|
|
|
} else {
|
|
|
|
|
|
throw new Error(response.msg || '加载失败')
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('加载考试数据失败', error)
|
|
|
|
|
|
const errorMsg = error.message || '加载失败'
|
|
|
|
|
|
|
|
|
|
|
|
// 如果是已做过的提示,显示模态框而不是toast
|
|
|
|
|
|
if (errorMsg.includes('已经完成过') || errorMsg.includes('不能重复作答')) {
|
|
|
|
|
|
uni.showModal({
|
|
|
|
|
|
title: '提示',
|
|
|
|
|
|
content: errorMsg,
|
|
|
|
|
|
showCancel: false,
|
|
|
|
|
|
confirmText: '我知道了',
|
|
|
|
|
|
success: () => {
|
|
|
|
|
|
uni.navigateBack()
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
} else {
|
|
|
|
|
|
uni.showToast({
|
|
|
|
|
|
title: errorMsg,
|
|
|
|
|
|
icon: 'none',
|
|
|
|
|
|
duration: 2000
|
|
|
|
|
|
})
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
|
uni.navigateBack()
|
|
|
|
|
|
}, 2000)
|
|
|
|
|
|
}
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
this.loading = false
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
startTimer() {
|
|
|
|
|
|
this.clearTimer()
|
|
|
|
|
|
this.timer = setInterval(() => {
|
|
|
|
|
|
const elapsed = Math.floor((Date.now() - this.startTime) / 1000)
|
|
|
|
|
|
this.remainingTime = Math.max(0, this.duration - elapsed)
|
|
|
|
|
|
|
|
|
|
|
|
if (this.remainingTime <= 0) {
|
|
|
|
|
|
this.clearTimer()
|
|
|
|
|
|
uni.showModal({
|
|
|
|
|
|
title: '时间到',
|
|
|
|
|
|
content: '考试时间已到,系统将自动提交答案。',
|
|
|
|
|
|
showCancel: false,
|
|
|
|
|
|
success: () => {
|
|
|
|
|
|
this.submitExam()
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
}, 1000)
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
clearTimer() {
|
|
|
|
|
|
if (this.timer) {
|
|
|
|
|
|
clearInterval(this.timer)
|
|
|
|
|
|
this.timer = null
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
formatTime(seconds) {
|
|
|
|
|
|
const hours = Math.floor(seconds / 3600)
|
|
|
|
|
|
const minutes = Math.floor((seconds % 3600) / 60)
|
|
|
|
|
|
const secs = seconds % 60
|
|
|
|
|
|
if (hours > 0) {
|
|
|
|
|
|
return `${hours}:${minutes.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`
|
|
|
|
|
|
}
|
|
|
|
|
|
return `${minutes}:${secs.toString().padStart(2, '0')}`
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
parseOptions(optionsStr) {
|
|
|
|
|
|
if (!optionsStr) return []
|
|
|
|
|
|
try {
|
|
|
|
|
|
return JSON.parse(optionsStr)
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
// 如果不是JSON,尝试按逗号分割
|
|
|
|
|
|
return optionsStr.split(',').map(s => s.trim())
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
getOptionLabel(index) {
|
|
|
|
|
|
return String.fromCharCode(65 + index) + '.'
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
selectAnswer(questionId, answer) {
|
|
|
|
|
|
this.$set(this.answers, questionId, answer)
|
|
|
|
|
|
this.saveAnswers()
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
toggleMultipleAnswer(questionId, option) {
|
|
|
|
|
|
const currentAnswer = this.answers[questionId] || ''
|
|
|
|
|
|
const answerArray = currentAnswer ? currentAnswer.split(',') : []
|
|
|
|
|
|
const index = answerArray.indexOf(option)
|
|
|
|
|
|
|
|
|
|
|
|
if (index > -1) {
|
|
|
|
|
|
answerArray.splice(index, 1)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
answerArray.push(option)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-07 01:19:40 +08:00
|
|
|
|
// 修复:统一使用完整选项文本,并排序确保一致性
|
|
|
|
|
|
const sortedAnswer = answerArray.sort().join(',')
|
|
|
|
|
|
this.$set(this.answers, questionId, sortedAnswer)
|
2025-12-03 18:58:36 +08:00
|
|
|
|
this.saveAnswers()
|
2025-12-07 01:19:40 +08:00
|
|
|
|
|
|
|
|
|
|
console.log(`多选题答案更新 - 题目${questionId}:`, sortedAnswer)
|
2025-12-03 18:58:36 +08:00
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
isOptionSelected(questionId, option) {
|
|
|
|
|
|
const answer = this.answers[questionId] || ''
|
|
|
|
|
|
return answer.split(',').includes(option)
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
prevQuestion() {
|
|
|
|
|
|
if (this.currentIndex > 0) {
|
|
|
|
|
|
this.currentIndex--
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
nextQuestion() {
|
|
|
|
|
|
if (this.currentIndex < this.questions.length - 1) {
|
|
|
|
|
|
this.currentIndex++
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
jumpToQuestion(index) {
|
|
|
|
|
|
this.currentIndex = index
|
|
|
|
|
|
this.showAnswerSheet = false
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
saveAnswers() {
|
2025-12-06 14:53:35 +08:00
|
|
|
|
// 保存答案到本地存储,包含用户ID以区分不同用户
|
|
|
|
|
|
const userInfo = uni.getStorageSync('userInfo') || {}
|
|
|
|
|
|
const userId = userInfo.userId || userInfo.id
|
|
|
|
|
|
|
2025-12-03 18:58:36 +08:00
|
|
|
|
const key = `exam_answers_${this.examId}`
|
|
|
|
|
|
uni.setStorageSync(key, {
|
2025-12-06 14:53:35 +08:00
|
|
|
|
userId: userId, // 保存用户ID
|
2025-12-03 18:58:36 +08:00
|
|
|
|
answers: this.answers,
|
|
|
|
|
|
timestamp: Date.now()
|
|
|
|
|
|
})
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
loadAnswers() {
|
2025-12-06 14:53:35 +08:00
|
|
|
|
// 从本地存储加载答案,但只加载当前用户的答案
|
|
|
|
|
|
const userInfo = uni.getStorageSync('userInfo') || {}
|
|
|
|
|
|
const currentUserId = userInfo.userId || userInfo.id
|
|
|
|
|
|
|
2025-12-03 18:58:36 +08:00
|
|
|
|
const key = `exam_answers_${this.examId}`
|
|
|
|
|
|
const saved = uni.getStorageSync(key)
|
2025-12-06 14:53:35 +08:00
|
|
|
|
|
|
|
|
|
|
// 验证用户ID,只有当前用户的答案才会加载
|
2025-12-03 18:58:36 +08:00
|
|
|
|
if (saved && saved.answers) {
|
2025-12-06 14:53:35 +08:00
|
|
|
|
if (saved.userId && saved.userId === currentUserId) {
|
|
|
|
|
|
// 用户ID匹配,加载答案
|
|
|
|
|
|
this.answers = { ...this.answers, ...saved.answers }
|
|
|
|
|
|
console.log('加载了当前用户的答案缓存')
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 用户ID不匹配,清除旧答案
|
|
|
|
|
|
console.log('检测到用户切换,清除旧答案缓存')
|
|
|
|
|
|
uni.removeStorageSync(key)
|
|
|
|
|
|
this.answers = {}
|
|
|
|
|
|
}
|
2025-12-03 18:58:36 +08:00
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
confirmExit() {
|
|
|
|
|
|
// 保存答案到本地
|
|
|
|
|
|
this.saveAnswers()
|
|
|
|
|
|
// 标记正在退出,防止 onBackPress 拦截
|
|
|
|
|
|
this.isExiting = true
|
|
|
|
|
|
this.showExitModal = false
|
|
|
|
|
|
this.clearTimer()
|
|
|
|
|
|
// 返回上一页
|
|
|
|
|
|
this.$nextTick(() => {
|
|
|
|
|
|
const pages = getCurrentPages()
|
|
|
|
|
|
console.log('当前页面栈:', pages.length)
|
|
|
|
|
|
// 如果页面栈大于1,说明有上一页,可以返回
|
|
|
|
|
|
if (pages.length > 1) {
|
|
|
|
|
|
uni.navigateBack({
|
|
|
|
|
|
delta: 1
|
|
|
|
|
|
})
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 如果没有上一页,跳转到考试列表页
|
|
|
|
|
|
uni.redirectTo({
|
|
|
|
|
|
url: '/pages/exam/list'
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
async submitExam() {
|
|
|
|
|
|
this.showSubmitModal = false
|
|
|
|
|
|
this.clearTimer()
|
|
|
|
|
|
|
|
|
|
|
|
// 检查是否有题目
|
|
|
|
|
|
if (!this.questions || this.questions.length === 0) {
|
|
|
|
|
|
uni.showToast({
|
|
|
|
|
|
title: '没有可提交的题目',
|
|
|
|
|
|
icon: 'none'
|
|
|
|
|
|
})
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 构建答案数组
|
|
|
|
|
|
const answerList = this.questions.map(question => {
|
|
|
|
|
|
if (!question || !question.id) {
|
|
|
|
|
|
console.warn('题目数据不完整:', question)
|
|
|
|
|
|
return null
|
|
|
|
|
|
}
|
2025-12-07 01:19:40 +08:00
|
|
|
|
const answer = this.answers[question.id] || ''
|
|
|
|
|
|
console.log(`题目${question.id} [${question.questionType}]:`, {
|
|
|
|
|
|
questionContent: question.questionContent,
|
|
|
|
|
|
userAnswer: answer,
|
|
|
|
|
|
correctAnswer: question.correctAnswer
|
|
|
|
|
|
})
|
2025-12-03 18:58:36 +08:00
|
|
|
|
return {
|
|
|
|
|
|
questionId: question.id,
|
2025-12-07 01:19:40 +08:00
|
|
|
|
answer: answer
|
2025-12-03 18:58:36 +08:00
|
|
|
|
}
|
|
|
|
|
|
}).filter(item => item !== null) // 过滤掉无效项
|
|
|
|
|
|
|
|
|
|
|
|
// 检查答案列表是否为空
|
|
|
|
|
|
if (answerList.length === 0) {
|
|
|
|
|
|
uni.showToast({
|
|
|
|
|
|
title: '答案数据异常,请重试',
|
|
|
|
|
|
icon: 'none'
|
|
|
|
|
|
})
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 计算用时(秒)
|
|
|
|
|
|
const duration = Math.floor((Date.now() - this.startTime) / 1000)
|
|
|
|
|
|
|
|
|
|
|
|
// 调试信息
|
|
|
|
|
|
console.log('提交答案数据:', {
|
|
|
|
|
|
examId: this.examId,
|
|
|
|
|
|
answerCount: answerList.length,
|
|
|
|
|
|
questionCount: this.questions.length,
|
|
|
|
|
|
duration: duration,
|
|
|
|
|
|
answerList: answerList
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
uni.showLoading({
|
|
|
|
|
|
title: '提交中...',
|
|
|
|
|
|
mask: true
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
const response = await submitExamAnswer(this.examId, answerList, duration)
|
|
|
|
|
|
if (response.code === 200) {
|
|
|
|
|
|
// 清除本地保存的答案
|
|
|
|
|
|
const key = `exam_answers_${this.examId}`
|
|
|
|
|
|
uni.removeStorageSync(key)
|
|
|
|
|
|
|
|
|
|
|
|
uni.hideLoading()
|
|
|
|
|
|
uni.showToast({
|
|
|
|
|
|
title: '提交成功',
|
|
|
|
|
|
icon: 'success'
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
// 跳转到结果页面
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
|
// 检查响应数据中是否有 id
|
|
|
|
|
|
const scoreId = (response.data && response.data.id) ? response.data.id : ''
|
|
|
|
|
|
uni.redirectTo({
|
|
|
|
|
|
url: `/pages/exam/result?examId=${this.examId}${scoreId ? `&scoreId=${scoreId}` : ''}`
|
|
|
|
|
|
})
|
|
|
|
|
|
}, 1500)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
throw new Error(response.msg || '提交失败')
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
uni.hideLoading()
|
|
|
|
|
|
console.error('提交答案失败', error)
|
|
|
|
|
|
|
|
|
|
|
|
// 解析错误信息
|
|
|
|
|
|
let errorMessage = '网络错误,请重试'
|
|
|
|
|
|
if (error && error.message) {
|
|
|
|
|
|
errorMessage = error.message
|
|
|
|
|
|
// 如果是后端返回的数组越界错误,给出更友好的提示
|
|
|
|
|
|
if (errorMessage.includes('Index:') && errorMessage.includes('Size:')) {
|
|
|
|
|
|
errorMessage = '提交数据格式错误,请重试'
|
|
|
|
|
|
}
|
|
|
|
|
|
// 如果是已提交的错误,显示特殊提示
|
|
|
|
|
|
if (errorMessage.includes('已提交') || errorMessage.includes('重复提交') || errorMessage.includes('已经完成过')) {
|
|
|
|
|
|
uni.showModal({
|
|
|
|
|
|
title: '提示',
|
|
|
|
|
|
content: errorMessage,
|
|
|
|
|
|
showCancel: false,
|
|
|
|
|
|
confirmText: '返回',
|
|
|
|
|
|
success: () => {
|
|
|
|
|
|
uni.navigateBack()
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
} else if (error && error.errMsg) {
|
|
|
|
|
|
errorMessage = error.errMsg
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
uni.showModal({
|
|
|
|
|
|
title: '提交失败',
|
|
|
|
|
|
content: errorMessage,
|
|
|
|
|
|
showCancel: true,
|
|
|
|
|
|
confirmText: '重试',
|
|
|
|
|
|
cancelText: '取消',
|
|
|
|
|
|
success: (res) => {
|
|
|
|
|
|
if (res.confirm) {
|
|
|
|
|
|
this.submitExam()
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 重新启动计时器
|
|
|
|
|
|
this.startTimer()
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<style lang="scss" scoped>
|
|
|
|
|
|
.exam-detail-container {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
height: 100vh;
|
|
|
|
|
|
background-color: #f5f7fa;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.exam-header {
|
|
|
|
|
|
background: linear-gradient(135deg, rgb(55 140 224) 0%, rgb(45 120 200) 100%);
|
|
|
|
|
|
padding: 30rpx;
|
|
|
|
|
|
padding-top: calc(var(--status-bar-height) + 30rpx);
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
|
|
|
|
|
|
@media (min-width: 768px) {
|
|
|
|
|
|
padding: 30rpx 40rpx;
|
|
|
|
|
|
padding-top: calc(var(--status-bar-height) + 30rpx);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.header-info {
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
|
|
|
|
|
|
.exam-name {
|
|
|
|
|
|
display: block;
|
|
|
|
|
|
font-size: 32rpx;
|
|
|
|
|
|
font-weight: bold;
|
|
|
|
|
|
color: #fff;
|
|
|
|
|
|
margin-bottom: 10rpx;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.exam-subject {
|
|
|
|
|
|
display: block;
|
|
|
|
|
|
font-size: 24rpx;
|
|
|
|
|
|
color: rgba(255, 255, 255, 0.8);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.timer-section {
|
|
|
|
|
|
text-align: right;
|
|
|
|
|
|
|
|
|
|
|
|
.timer-label {
|
|
|
|
|
|
display: block;
|
|
|
|
|
|
font-size: 24rpx;
|
|
|
|
|
|
color: rgba(255, 255, 255, 0.8);
|
|
|
|
|
|
margin-bottom: 8rpx;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.timer-value {
|
|
|
|
|
|
display: block;
|
|
|
|
|
|
font-size: 36rpx;
|
|
|
|
|
|
font-weight: bold;
|
|
|
|
|
|
color: #fff;
|
|
|
|
|
|
|
|
|
|
|
|
&.timer-warning {
|
|
|
|
|
|
color: #ffeb3b;
|
|
|
|
|
|
animation: blink 1s infinite;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@keyframes blink {
|
|
|
|
|
|
0%, 100% { opacity: 1; }
|
|
|
|
|
|
50% { opacity: 0.5; }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.answer-sheet {
|
|
|
|
|
|
position: fixed;
|
|
|
|
|
|
top: 0;
|
|
|
|
|
|
left: 0;
|
|
|
|
|
|
right: 0;
|
|
|
|
|
|
bottom: 0;
|
|
|
|
|
|
background: rgba(0, 0, 0, 0.5);
|
|
|
|
|
|
z-index: 1000;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: flex-end;
|
|
|
|
|
|
|
|
|
|
|
|
.sheet-header {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
padding: 30rpx;
|
|
|
|
|
|
background: #fff;
|
|
|
|
|
|
border-bottom: 1rpx solid #f0f0f0;
|
|
|
|
|
|
|
|
|
|
|
|
.sheet-title {
|
|
|
|
|
|
font-size: 32rpx;
|
|
|
|
|
|
font-weight: bold;
|
|
|
|
|
|
color: #333;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.close-btn {
|
|
|
|
|
|
font-size: 48rpx;
|
|
|
|
|
|
color: #999;
|
|
|
|
|
|
line-height: 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.sheet-content {
|
|
|
|
|
|
background: #fff;
|
|
|
|
|
|
padding: 30rpx;
|
|
|
|
|
|
max-height: 60vh;
|
|
|
|
|
|
overflow-y: auto;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-wrap: wrap;
|
|
|
|
|
|
gap: 20rpx;
|
|
|
|
|
|
|
|
|
|
|
|
.sheet-item {
|
|
|
|
|
|
width: 80rpx;
|
|
|
|
|
|
height: 80rpx;
|
|
|
|
|
|
line-height: 80rpx;
|
|
|
|
|
|
text-align: center;
|
|
|
|
|
|
border: 2rpx solid #ddd;
|
|
|
|
|
|
border-radius: 8rpx;
|
|
|
|
|
|
font-size: 28rpx;
|
|
|
|
|
|
color: #333;
|
|
|
|
|
|
background: #fff;
|
|
|
|
|
|
|
|
|
|
|
|
&.answered {
|
|
|
|
|
|
background: rgb(55 140 224);
|
|
|
|
|
|
color: #fff;
|
|
|
|
|
|
border-color: rgb(55 140 224);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
&.current {
|
|
|
|
|
|
border-color: rgb(55 140 224);
|
|
|
|
|
|
border-width: 4rpx;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.questions-container {
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
padding: 20rpx;
|
|
|
|
|
|
overflow-y: auto;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.question-item {
|
|
|
|
|
|
background: #fff;
|
|
|
|
|
|
border-radius: 16rpx;
|
|
|
|
|
|
padding: 30rpx;
|
|
|
|
|
|
margin-bottom: 20rpx;
|
|
|
|
|
|
|
|
|
|
|
|
.question-header {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
margin-bottom: 20rpx;
|
|
|
|
|
|
padding-bottom: 20rpx;
|
|
|
|
|
|
border-bottom: 1rpx solid #f0f0f0;
|
|
|
|
|
|
|
|
|
|
|
|
.question-number {
|
|
|
|
|
|
font-size: 28rpx;
|
|
|
|
|
|
font-weight: bold;
|
|
|
|
|
|
color: #333;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.question-score {
|
|
|
|
|
|
font-size: 24rpx;
|
|
|
|
|
|
color: rgb(55 140 224);
|
|
|
|
|
|
background: #e6f7ff;
|
|
|
|
|
|
padding: 4rpx 12rpx;
|
|
|
|
|
|
border-radius: 8rpx;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.question-content {
|
|
|
|
|
|
margin-bottom: 30rpx;
|
|
|
|
|
|
|
|
|
|
|
|
.question-text {
|
|
|
|
|
|
font-size: 30rpx;
|
|
|
|
|
|
line-height: 1.6;
|
|
|
|
|
|
color: #333;
|
2025-12-06 14:53:35 +08:00
|
|
|
|
|
|
|
|
|
|
.multiple-tip {
|
|
|
|
|
|
color: rgb(55 140 224);
|
|
|
|
|
|
font-size: 26rpx;
|
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
|
margin-left: 8rpx;
|
|
|
|
|
|
}
|
2025-12-03 18:58:36 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-12-06 14:53:35 +08:00
|
|
|
|
|
|
|
|
|
|
.multiple-hint {
|
|
|
|
|
|
padding: 16rpx 20rpx;
|
|
|
|
|
|
margin-bottom: 20rpx;
|
|
|
|
|
|
background: linear-gradient(135deg, #e6f7ff 0%, #f0f9ff 100%);
|
|
|
|
|
|
border-left: 4rpx solid rgb(55 140 224);
|
|
|
|
|
|
border-radius: 8rpx;
|
|
|
|
|
|
font-size: 28rpx;
|
|
|
|
|
|
color: rgb(55 140 224);
|
|
|
|
|
|
line-height: 1.5;
|
|
|
|
|
|
}
|
2025-12-03 18:58:36 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.options-list {
|
|
|
|
|
|
.option-item {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
padding: 20rpx;
|
|
|
|
|
|
margin-bottom: 16rpx;
|
|
|
|
|
|
border: 2rpx solid #e0e0e0;
|
|
|
|
|
|
border-radius: 12rpx;
|
|
|
|
|
|
background: #fff;
|
|
|
|
|
|
|
|
|
|
|
|
&.selected {
|
|
|
|
|
|
border-color: rgb(55 140 224);
|
|
|
|
|
|
background: #e6f7ff;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.option-radio {
|
|
|
|
|
|
width: 40rpx;
|
|
|
|
|
|
height: 40rpx;
|
|
|
|
|
|
border: 2rpx solid #ddd;
|
|
|
|
|
|
border-radius: 50%;
|
|
|
|
|
|
margin-right: 20rpx;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
|
|
|
|
|
|
.radio-dot {
|
|
|
|
|
|
width: 24rpx;
|
|
|
|
|
|
height: 24rpx;
|
|
|
|
|
|
background: rgb(55 140 224);
|
|
|
|
|
|
border-radius: 50%;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.option-checkbox {
|
|
|
|
|
|
width: 40rpx;
|
|
|
|
|
|
height: 40rpx;
|
|
|
|
|
|
border: 2rpx solid #ddd;
|
|
|
|
|
|
border-radius: 8rpx;
|
|
|
|
|
|
margin-right: 20rpx;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
background: #fff;
|
|
|
|
|
|
|
|
|
|
|
|
.checkbox-icon {
|
|
|
|
|
|
color: rgb(55 140 224);
|
|
|
|
|
|
font-size: 28rpx;
|
|
|
|
|
|
font-weight: bold;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.option-label {
|
|
|
|
|
|
font-size: 28rpx;
|
|
|
|
|
|
color: rgb(55 140 224);
|
|
|
|
|
|
font-weight: bold;
|
|
|
|
|
|
margin-right: 12rpx;
|
|
|
|
|
|
min-width: 60rpx;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.option-text {
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
font-size: 28rpx;
|
|
|
|
|
|
color: #333;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.fill-answer, .essay-answer {
|
|
|
|
|
|
.fill-input, .essay-input {
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
min-height: 120rpx;
|
|
|
|
|
|
padding: 20rpx;
|
|
|
|
|
|
border: 2rpx solid #e0e0e0;
|
|
|
|
|
|
border-radius: 12rpx;
|
|
|
|
|
|
font-size: 28rpx;
|
|
|
|
|
|
background: #fff;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.essay-input {
|
|
|
|
|
|
min-height: 200rpx;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.bottom-actions {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
gap: 10rpx;
|
|
|
|
|
|
padding: 20rpx;
|
|
|
|
|
|
background: #fff;
|
|
|
|
|
|
border-top: 1rpx solid #f0f0f0;
|
|
|
|
|
|
|
|
|
|
|
|
button {
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
padding: 20rpx;
|
|
|
|
|
|
border-radius: 8rpx;
|
|
|
|
|
|
font-size: 28rpx;
|
|
|
|
|
|
border: none;
|
|
|
|
|
|
|
|
|
|
|
|
&.btn-sheet {
|
|
|
|
|
|
background: #f5f5f5;
|
|
|
|
|
|
color: #333;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
&.btn-prev, &.btn-next {
|
|
|
|
|
|
background: #e6f7ff;
|
|
|
|
|
|
color: rgb(55 140 224);
|
|
|
|
|
|
|
|
|
|
|
|
&:disabled {
|
|
|
|
|
|
background: #f5f5f5;
|
|
|
|
|
|
color: #ccc;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
&.btn-submit {
|
|
|
|
|
|
background: linear-gradient(135deg, rgb(55 140 224) 0%, rgb(45 120 200) 100%);
|
|
|
|
|
|
color: #fff;
|
|
|
|
|
|
font-weight: bold;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.submit-modal,
|
|
|
|
|
|
.exit-modal {
|
|
|
|
|
|
position: fixed;
|
|
|
|
|
|
top: 0;
|
|
|
|
|
|
left: 0;
|
|
|
|
|
|
right: 0;
|
|
|
|
|
|
bottom: 0;
|
|
|
|
|
|
z-index: 2000;
|
|
|
|
|
|
|
|
|
|
|
|
.modal-mask {
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
top: 0;
|
|
|
|
|
|
left: 0;
|
|
|
|
|
|
right: 0;
|
|
|
|
|
|
bottom: 0;
|
|
|
|
|
|
background: rgba(0, 0, 0, 0.5);
|
|
|
|
|
|
backdrop-filter: blur(4rpx);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.modal-content {
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
top: 50%;
|
|
|
|
|
|
left: 50%;
|
|
|
|
|
|
transform: translate(-50%, -50%);
|
|
|
|
|
|
width: 600rpx;
|
|
|
|
|
|
max-width: 90%;
|
|
|
|
|
|
background: #fff;
|
|
|
|
|
|
border-radius: 24rpx;
|
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.15);
|
|
|
|
|
|
|
|
|
|
|
|
@media (min-width: 768px) {
|
|
|
|
|
|
width: 500px;
|
|
|
|
|
|
border-radius: 20px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.modal-header {
|
|
|
|
|
|
padding: 40rpx 30rpx 30rpx;
|
|
|
|
|
|
text-align: center;
|
|
|
|
|
|
border-bottom: 1rpx solid #f0f0f0;
|
|
|
|
|
|
|
|
|
|
|
|
@media (min-width: 768px) {
|
|
|
|
|
|
padding: 36px 30px 24px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.modal-title {
|
|
|
|
|
|
font-size: 36rpx;
|
|
|
|
|
|
font-weight: bold;
|
|
|
|
|
|
color: #1a1a1a;
|
|
|
|
|
|
|
|
|
|
|
|
@media (min-width: 768px) {
|
|
|
|
|
|
font-size: 32px;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.modal-body {
|
|
|
|
|
|
padding: 40rpx 30rpx;
|
|
|
|
|
|
|
|
|
|
|
|
@media (min-width: 768px) {
|
|
|
|
|
|
padding: 36px 30px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.modal-text {
|
|
|
|
|
|
display: block;
|
|
|
|
|
|
font-size: 30rpx;
|
|
|
|
|
|
color: #333;
|
|
|
|
|
|
margin-bottom: 24rpx;
|
|
|
|
|
|
text-align: center;
|
|
|
|
|
|
line-height: 1.6;
|
|
|
|
|
|
|
|
|
|
|
|
@media (min-width: 768px) {
|
|
|
|
|
|
font-size: 28px;
|
|
|
|
|
|
margin-bottom: 20px;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.answer-stats {
|
|
|
|
|
|
background: #f8f9fa;
|
|
|
|
|
|
border-radius: 12rpx;
|
|
|
|
|
|
padding: 24rpx;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
justify-content: space-around;
|
|
|
|
|
|
|
|
|
|
|
|
@media (min-width: 768px) {
|
|
|
|
|
|
border-radius: 10px;
|
|
|
|
|
|
padding: 20px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.stats-item {
|
|
|
|
|
|
font-size: 28rpx;
|
|
|
|
|
|
color: #666;
|
|
|
|
|
|
line-height: 1.5;
|
|
|
|
|
|
|
|
|
|
|
|
@media (min-width: 768px) {
|
|
|
|
|
|
font-size: 26px;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.modal-footer {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
border-top: 1rpx solid #f0f0f0;
|
|
|
|
|
|
|
|
|
|
|
|
button {
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
padding: 28rpx;
|
|
|
|
|
|
border: none;
|
|
|
|
|
|
font-size: 30rpx;
|
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
|
background: transparent;
|
|
|
|
|
|
transition: background-color 0.2s;
|
|
|
|
|
|
|
|
|
|
|
|
@media (min-width: 768px) {
|
|
|
|
|
|
padding: 24px;
|
|
|
|
|
|
font-size: 28px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
&.btn-cancel {
|
|
|
|
|
|
color: #666;
|
|
|
|
|
|
border-right: 1rpx solid #f0f0f0;
|
|
|
|
|
|
|
|
|
|
|
|
&:active {
|
|
|
|
|
|
background: #f5f5f5;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
&.btn-confirm {
|
|
|
|
|
|
color: rgb(55 140 224);
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
|
|
|
|
|
|
&:active {
|
|
|
|
|
|
background: rgba(55, 140, 224, 0.1);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
&.btn-confirm-exit {
|
|
|
|
|
|
color: #ff4d4f;
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
|
|
|
|
|
|
&:active {
|
|
|
|
|
|
background: rgba(255, 77, 79, 0.1);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
</style>
|
|
|
|
|
|
|