1125 lines
37 KiB
Vue
1125 lines
37 KiB
Vue
<template>
|
||
<!-- 添加外层容器确保占满整个空间 -->
|
||
<div class="student-home-container">
|
||
<div class="content-wrapper">
|
||
<!-- 考试须知卡片 -->
|
||
<div class="exam-card rounded-lg shadow-lg p-4 border">
|
||
<div class="justify-between text-center">
|
||
<h3 class="font-bold text-primary">考生信息</h3>
|
||
</div>
|
||
<!-- 考生信息部分 -->
|
||
<el-descriptions class="margin-top" :column="3" :size="size" border>
|
||
<el-descriptions-item>
|
||
<template slot="label">
|
||
<div class="cell-item">
|
||
<font-awesome-icon :icon="['fas', 'user']" :style="iconStyle"/>
|
||
姓名
|
||
</div>
|
||
</template>
|
||
{{ examinee && examinee.examinee_name }}
|
||
</el-descriptions-item>
|
||
<el-descriptions-item>
|
||
<template slot="label">
|
||
<div class="cell-item">
|
||
<font-awesome-icon :icon="['fas', 'id-card']" :style="iconStyle" />
|
||
身份证号
|
||
</div>
|
||
</template>
|
||
{{ formatIdCard(examinee && examinee.examinee_id_card) }}
|
||
</el-descriptions-item>
|
||
<el-descriptions-item>
|
||
<template slot="label">
|
||
<div class="cell-item">
|
||
<font-awesome-icon :icon="['fas', 'ticket-alt']" :style="iconStyle" />
|
||
准考证号
|
||
</div>
|
||
</template>
|
||
{{ examinee && examinee.examinee_admission_ticket }}
|
||
</el-descriptions-item>
|
||
</el-descriptions>
|
||
<el-divider />
|
||
<div class="justify-between text-center mt-2">
|
||
<h3 class="font-bold text-primary">考试信息</h3>
|
||
</div>
|
||
<div class="space-y-4">
|
||
<el-descriptions class="margin-top" :column="2" :size="size" border>
|
||
<el-descriptions-item>
|
||
<template slot="label">
|
||
<div class="cell-item">
|
||
<font-awesome-icon :icon="['fas', 'clock']" :style="iconStyle"/>
|
||
考试时长
|
||
</div>
|
||
</template>
|
||
{{ lastExam && lastExam.exam_minutes }}分钟
|
||
</el-descriptions-item>
|
||
<el-descriptions-item>
|
||
<template slot="label">
|
||
<div class="cell-item">
|
||
<font-awesome-icon :icon="['fas', 'hourglass-half']" :style="iconStyle"/>
|
||
最短考试时长
|
||
</div>
|
||
</template>
|
||
{{ lastExam && lastExam.exam_minutes_min }}分钟
|
||
</el-descriptions-item>
|
||
<el-descriptions-item>
|
||
<template slot="label">
|
||
<div class="cell-item">
|
||
<font-awesome-icon :icon="['fas', 'list-ol']" :style="iconStyle"/>
|
||
考题数量
|
||
</div>
|
||
</template>
|
||
<span class="text-gray-500">{{ totalQuestions }}</span>
|
||
</el-descriptions-item>
|
||
<el-descriptions-item>
|
||
<template slot="label">
|
||
<div class="cell-item">
|
||
<font-awesome-icon :icon="['fas', 'scroll']" :style="iconStyle"/>
|
||
考试总分
|
||
</div>
|
||
</template>
|
||
<span class="text-gray-500">{{ totalScore }}</span>
|
||
</el-descriptions-item>
|
||
</el-descriptions>
|
||
</div>
|
||
<!-- 未完成试卷提示区域 -->
|
||
<div v-if="unfinishedPaper" class="warning-card mt-4 p-4 bg-yellow-50 border-l-4 border-yellow-500 rounded">
|
||
<div class="text-red-600 font-bold mb-2">你有未完成的考试</div>
|
||
<el-descriptions class="margin-top" :column="2" :size="size" border>
|
||
<el-descriptions-item>
|
||
<template slot="label">
|
||
<div class="cell-item">
|
||
<font-awesome-icon :icon="['fas', 'calendar']" :style="iconStyle"/>
|
||
开始时间
|
||
</div>
|
||
</template>
|
||
{{ unfinishedPaper.paper_start_time }}
|
||
</el-descriptions-item>
|
||
<el-descriptions-item>
|
||
<template slot="label">
|
||
<div class="cell-item">
|
||
<font-awesome-icon :icon="['fas', 'clock']" :style="iconStyle"/>
|
||
已用时长
|
||
</div>
|
||
</template>
|
||
{{ usedMinutes }}分钟
|
||
</el-descriptions-item>
|
||
<el-descriptions-item>
|
||
<template slot="label">
|
||
<div class="cell-item">
|
||
<font-awesome-icon :icon="['fas', 'hourglass-half']" :style="iconStyle"/>
|
||
剩余时长
|
||
</div>
|
||
</template>
|
||
{{ remainingMinutes }}分钟
|
||
</el-descriptions-item>
|
||
<el-descriptions-item>
|
||
<template slot="label">
|
||
<div class="cell-item">
|
||
<font-awesome-icon :icon="['fas', 'check-square']" :style="iconStyle"/>
|
||
已答题量
|
||
</div>
|
||
</template>
|
||
{{ answeredQuestionsCount }}题 (选择题:{{ answeredChoicesCount }},填空题:{{ answeredFillBlanksCount }})
|
||
</el-descriptions-item>
|
||
</el-descriptions>
|
||
<div class="mt-3 text-sm text-gray-700">
|
||
你可以继续进行未完成的考试,或者重新开始,重新开始后将清除所有历史考试记录。
|
||
</div>
|
||
<div class="mt-4 space-x-2 text-center">
|
||
<el-button type="primary" @click="continueExam">继续考试</el-button>
|
||
<el-button type="warning" @click="restartExam">重新开始</el-button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 已完成试卷提示区域 -->
|
||
<div v-else-if="completedPaper" class="completed-card mt-4 p-4 bg-green-50 border-l-4 border-green-500 rounded">
|
||
<div class="text-green-600 font-bold mb-2">你有已完成的考试</div>
|
||
<el-descriptions class="margin-top" :column="2" :size="size" border>
|
||
<el-descriptions-item>
|
||
<template slot="label">
|
||
<div class="cell-item">
|
||
<font-awesome-icon :icon="['fas', 'calendar']" :style="iconStyle"/>
|
||
开始时间
|
||
</div>
|
||
</template>
|
||
{{ completedPaper.paper_start_time }}
|
||
</el-descriptions-item>
|
||
<el-descriptions-item>
|
||
<template slot="label">
|
||
<div class="cell-item">
|
||
<font-awesome-icon :icon="['fas', 'calendar-check']" :style="iconStyle"/>
|
||
结束时间
|
||
</div>
|
||
</template>
|
||
{{ completedPaper.paper_end_time }}
|
||
</el-descriptions-item>
|
||
<el-descriptions-item>
|
||
<template slot="label">
|
||
<div class="cell-item">
|
||
<font-awesome-icon :icon="['fas', 'clock']" :style="iconStyle"/>
|
||
考试用时
|
||
</div>
|
||
</template>
|
||
{{ completedExamDuration }}分钟
|
||
</el-descriptions-item>
|
||
<el-descriptions-item>
|
||
<template slot="label">
|
||
<div class="cell-item">
|
||
<font-awesome-icon :icon="['fas', 'check-square']" :style="iconStyle"/>
|
||
已答题量
|
||
</div>
|
||
</template>
|
||
{{ completedAnsweredQuestionsCount }}题
|
||
</el-descriptions-item>
|
||
<el-descriptions-item>
|
||
<template slot="label">
|
||
<div class="cell-item">
|
||
<font-awesome-icon :icon="['fas', 'list-ol']" :style="iconStyle"/>
|
||
总题量
|
||
</div>
|
||
</template>
|
||
{{ completedTotalQuestions }}题
|
||
</el-descriptions-item>
|
||
<el-descriptions-item>
|
||
<template slot="label">
|
||
<div class="cell-item">
|
||
<font-awesome-icon :icon="['fas', 'scroll']" :style="iconStyle"/>
|
||
试卷满分
|
||
</div>
|
||
</template>
|
||
{{ completedPaper.paper_score }}分
|
||
</el-descriptions-item>
|
||
</el-descriptions>
|
||
<div class="mt-4 space-x-2 text-center">
|
||
<el-button type="success" @click="restartExam">重新考试</el-button>
|
||
</div>
|
||
</div>
|
||
<el-divider />
|
||
<div class="justify-between text-center mt-2">
|
||
<h3 class="font-bold text-primary">考试须知</h3>
|
||
</div>
|
||
<div class="space-y-4">
|
||
<div v-if="!examLoading && examNotices.length > 0" class="space-y-2 pl-4">
|
||
<ol class="list-decimal pl-4 space-y-2">
|
||
<li v-for="(notice, index) in examNotices" :key="index" class="text-gray-700">
|
||
{{ notice }}
|
||
</li>
|
||
</ol>
|
||
</div>
|
||
<div v-else-if="examLoading" class="text-center text-gray-500 py-4">
|
||
加载考试信息中...
|
||
</div>
|
||
<div v-else class="text-center text-gray-500 py-4">
|
||
暂无考试须知
|
||
</div>
|
||
</div>
|
||
<div class="text-center mt-6">
|
||
<el-button type="info" @click="exitExam">返回</el-button>
|
||
<el-button v-if="!unfinishedPaper && !completedPaper" type="primary" @click="startExam"
|
||
:loading="isLoading">开始考试</el-button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<script>
|
||
export default {
|
||
name: 'ExamineeHomeView',
|
||
components: {
|
||
ElDescriptions: require('element-ui').Descriptions,
|
||
ElDescriptionsItem: require('element-ui').DescriptionsItem,
|
||
ElDivider: require('element-ui').Divider,
|
||
ElButton: require('element-ui').Button
|
||
},
|
||
data () {
|
||
return {
|
||
isLoading: false,
|
||
size: 'default',
|
||
lastExam: null,
|
||
examLoading: true,
|
||
examNotices: [],
|
||
totalQuestions: 0,
|
||
totalScore: 0,
|
||
unfinishedPaper: null, // 未完成的试卷数据
|
||
completedPaper: null // 已完成的试卷数据
|
||
}
|
||
},
|
||
computed: {
|
||
examinee () {
|
||
return this.$store.state.examinee
|
||
},
|
||
isLoggedIn () {
|
||
return this.$store.state.isLoggedIn
|
||
},
|
||
iconStyle () {
|
||
const marginMap = {
|
||
large: '8px',
|
||
default: '6px',
|
||
small: '4px'
|
||
}
|
||
return {
|
||
marginRight: marginMap[this.size] || marginMap.default
|
||
}
|
||
},
|
||
// 已用时长(分钟)
|
||
usedMinutes() {
|
||
if (!this.unfinishedPaper) return 0
|
||
|
||
// 优先使用paper_duration_seconds
|
||
if (this.unfinishedPaper.paper_duration_seconds !== undefined) {
|
||
return Math.ceil(this.unfinishedPaper.paper_duration_seconds / 60)
|
||
}
|
||
|
||
// 保留原有逻辑作为后备
|
||
if (this.unfinishedPaper.paper_start_time && this.unfinishedPaper.paper_last_time) {
|
||
const startTime = new Date(this.unfinishedPaper.paper_start_time)
|
||
const lastTime = new Date(this.unfinishedPaper.paper_last_time)
|
||
return Math.floor((lastTime - startTime) / (1000 * 60))
|
||
}
|
||
|
||
return 0
|
||
},
|
||
// 剩余时长(分钟)
|
||
remainingMinutes() {
|
||
if (!this.unfinishedPaper || !this.unfinishedPaper.paper_minutes) return 0
|
||
|
||
// 使用paper_duration_seconds计算剩余时间
|
||
if (this.unfinishedPaper.paper_duration_seconds !== undefined) {
|
||
const totalMinutes = this.unfinishedPaper.paper_minutes
|
||
const usedMinutes = Math.ceil(this.unfinishedPaper.paper_duration_seconds / 60)
|
||
return Math.max(0, totalMinutes - usedMinutes)
|
||
}
|
||
|
||
// 保留原有逻辑作为后备
|
||
if (this.unfinishedPaper.paper_start_time && this.unfinishedPaper.paper_last_time) {
|
||
const totalMinutes = this.unfinishedPaper.paper_minutes
|
||
const usedMinutes = this.usedMinutes
|
||
return Math.max(0, totalMinutes - usedMinutes)
|
||
}
|
||
|
||
return this.unfinishedPaper.paper_minutes
|
||
},
|
||
// 格式化的已用时长
|
||
formattedUsedTime() {
|
||
const minutes = this.usedMinutes
|
||
const hours = Math.floor(minutes / 60)
|
||
const mins = minutes % 60
|
||
return `${hours.toString().padStart(2, '0')}:${mins.toString().padStart(2, '0')}`
|
||
},
|
||
// 格式化的剩余时长
|
||
formattedRemainingTime() {
|
||
const minutes = this.remainingMinutes
|
||
const hours = Math.floor(minutes / 60)
|
||
const mins = minutes % 60
|
||
return `${hours.toString().padStart(2, '0')}:${mins.toString().padStart(2, '0')}`
|
||
},
|
||
// 计算已答题数量
|
||
answeredQuestionsCount () {
|
||
if (!this.unfinishedPaper || !this.unfinishedPaper.questions) {
|
||
return 0
|
||
}
|
||
const count = this.answeredChoicesCount + this.answeredFillBlanksCount
|
||
console.log('计算已答题数量:', {
|
||
total: count,
|
||
choices: this.answeredChoicesCount,
|
||
fillBlanks: this.answeredFillBlanksCount
|
||
})
|
||
return count
|
||
},
|
||
// 已答选择题数量
|
||
answeredChoicesCount () {
|
||
if (!this.unfinishedPaper || !this.unfinishedPaper.questions) {
|
||
return 0
|
||
}
|
||
return this.unfinishedPaper.questions.filter(q => {
|
||
if (q.question_type !== 'choice') return false
|
||
|
||
// 检查选择题的答案是否有效
|
||
// 注意:这里的逻辑需要根据实际数据结构调整
|
||
// 如果questions中的数据结构与ExamingPreview.vue中的不同,可能需要调整
|
||
if (q.choices && q.choices.length > 0) {
|
||
// 处理choices数组结构
|
||
return q.choices.some(choice => {
|
||
// 对于每个选项,检查examinee_answers是否有效
|
||
return this.getValidAnswerCount(choice.examinee_answers) > 0
|
||
})
|
||
} else if (q.examinee_answers) {
|
||
// 处理直接在question对象中的examinee_answers
|
||
return this.getValidAnswerCount(q.examinee_answers) > 0
|
||
}
|
||
return false
|
||
}).length
|
||
},
|
||
// 已答填空题数量
|
||
answeredFillBlanksCount () {
|
||
if (!this.unfinishedPaper || !this.unfinishedPaper.questions) {
|
||
return 0
|
||
}
|
||
return this.unfinishedPaper.questions.filter(q => {
|
||
if (q.question_type !== 'fill_blank') return false
|
||
|
||
// 检查填空题的答案是否有效
|
||
// 注意:这里的逻辑需要根据实际数据结构调整
|
||
if (q.blanks && q.blanks.length > 0) {
|
||
// 处理blanks数组结构
|
||
return q.blanks.some(blank => {
|
||
return this.getValidAnswerCount(blank.examinee_answers) > 0
|
||
})
|
||
} else if (q.examinee_answers) {
|
||
// 处理直接在question对象中的examinee_answers
|
||
return this.getValidAnswerCount(q.examinee_answers) > 0
|
||
}
|
||
return false
|
||
}).length
|
||
},
|
||
// 已完成试卷的考试用时(分钟)
|
||
completedExamDuration () {
|
||
if (!this.completedPaper) return 0
|
||
|
||
// 优先使用paper_duration_seconds
|
||
if (this.completedPaper.paper_duration_seconds !== undefined) {
|
||
return Math.ceil(this.completedPaper.paper_duration_seconds / 60)
|
||
}
|
||
|
||
// 保留原有逻辑作为后备
|
||
if (this.completedPaper.paper_start_time && this.completedPaper.paper_end_time) {
|
||
const startTime = new Date(this.completedPaper.paper_start_time)
|
||
const endTime = new Date(this.completedPaper.paper_end_time)
|
||
// 计算分钟数差值
|
||
return Math.ceil((endTime - startTime) / (1000 * 60))
|
||
}
|
||
|
||
return 0
|
||
},
|
||
// 已完成试卷的已答题量
|
||
completedAnsweredQuestionsCount () {
|
||
if (!this.completedPaper || !this.completedPaper.questions) {
|
||
return 0
|
||
}
|
||
return this.completedPaper.questions.filter(q => {
|
||
// 判断问题是否已回答,逻辑与未完成试卷相同
|
||
if (q.choices && q.choices.length > 0) {
|
||
// 处理选择题
|
||
return q.choices.some(choice => {
|
||
return this.getValidAnswerCount(choice.examinee_answers) > 0
|
||
})
|
||
} else if (q.blanks && q.blanks.length > 0) {
|
||
// 处理填空题
|
||
return q.blanks.some(blank => {
|
||
return this.getValidAnswerCount(blank.examinee_answers) > 0
|
||
})
|
||
} else if (q.examinee_answers) {
|
||
// 直接处理examinee_answers
|
||
return this.getValidAnswerCount(q.examinee_answers) > 0
|
||
}
|
||
return false
|
||
}).length
|
||
},
|
||
// 已完成试卷的总题量
|
||
completedTotalQuestions () {
|
||
if (!this.completedPaper || !this.completedPaper.questions) {
|
||
return 0
|
||
}
|
||
|
||
// 计算总题量:选择题数量 + 填空题数量
|
||
let totalQuestionsCount = 0
|
||
|
||
this.completedPaper.questions.forEach(q => {
|
||
// 计算选择题数量
|
||
if ((q.question_type === 'choice' || q.question_type === 'multiple_choice') && q.choices && Array.isArray(q.choices)) {
|
||
totalQuestionsCount += q.choices.length
|
||
}
|
||
// 计算填空题数量
|
||
else if (q.question_type === 'fill_blank' && q.blanks && Array.isArray(q.blanks)) {
|
||
totalQuestionsCount += q.blanks.length
|
||
}
|
||
})
|
||
|
||
return totalQuestionsCount
|
||
},
|
||
// 已完成试卷的已答选择题数量
|
||
completedAnsweredChoicesCount () {
|
||
if (!this.completedPaper || !this.completedPaper.questions) {
|
||
return 0
|
||
}
|
||
return this.completedPaper.questions.filter(q => {
|
||
if (q.question_type !== 'choice' && q.question_type !== 'multiple_choice') return false
|
||
if (q.choices && q.choices.length > 0) {
|
||
return q.choices.some(choice => {
|
||
return this.getValidAnswerCount(choice.examinee_answers) > 0
|
||
})
|
||
}
|
||
return false
|
||
}).length
|
||
},
|
||
// 已完成试卷的已答填空题数量
|
||
completedAnsweredFillBlanksCount () {
|
||
if (!this.completedPaper || !this.completedPaper.questions) {
|
||
return 0
|
||
}
|
||
return this.completedPaper.questions.filter(q => {
|
||
if (q.question_type !== 'fill_blank') return false
|
||
if (q.blanks && q.blanks.length > 0) {
|
||
return q.blanks.some(blank => {
|
||
return this.getValidAnswerCount(blank.examinee_answers) > 0
|
||
})
|
||
}
|
||
return false
|
||
}).length
|
||
}
|
||
},
|
||
watch: {
|
||
lastExam (newValue) {
|
||
if (newValue) {
|
||
this.examLoading = false
|
||
}
|
||
},
|
||
isLoggedIn (newVal) {
|
||
if (!newVal) {
|
||
// 如果未登录,重定向到欢迎页
|
||
this.$router.push('/')
|
||
}
|
||
}
|
||
},
|
||
mounted () {
|
||
// 检查是否已登录
|
||
if (!this.isLoggedIn) {
|
||
this.$router.push('/')
|
||
return
|
||
}
|
||
|
||
this.fetchLastExam()
|
||
this.getQuestionsCountAndScore()
|
||
this.checkUnfinishedPaper() // 检查是否有未完成的试卷
|
||
},
|
||
methods: {
|
||
// 计算有效答案数量(与ExamingPreview.vue保持一致)
|
||
getValidAnswerCount (answer) {
|
||
if (!answer) return 0
|
||
|
||
// 处理字符串类型
|
||
if (typeof answer === 'string') {
|
||
const trimmed = answer.trim()
|
||
// 空字符串、只包含空格的字符串、空数组字符串都视为0个有效答案
|
||
if (trimmed === '' || trimmed === '[]') {
|
||
return 0
|
||
}
|
||
|
||
// 检查是否是JSON数组格式
|
||
if (trimmed.startsWith('[') && trimmed.endsWith(']')) {
|
||
try {
|
||
const parsed = JSON.parse(trimmed)
|
||
if (Array.isArray(parsed)) {
|
||
// 计算非空且清除空格后不为空的元素数量
|
||
return parsed.filter(item => {
|
||
const itemStr = String(item || '')
|
||
return itemStr.trim() !== ''
|
||
}).length
|
||
}
|
||
} catch (e) {
|
||
// 解析失败,视为普通字符串
|
||
return trimmed !== '' ? 1 : 0
|
||
}
|
||
}
|
||
|
||
// 普通字符串
|
||
return trimmed !== '' ? 1 : 0
|
||
}
|
||
|
||
// 处理数组类型
|
||
else if (Array.isArray(answer)) {
|
||
// 计算非空且清除空格后不为空的元素数量
|
||
return answer.filter(item => {
|
||
const itemStr = String(item || '')
|
||
return itemStr.trim() !== ''
|
||
}).length
|
||
}
|
||
|
||
// 其他类型
|
||
return 0
|
||
},
|
||
|
||
// 检查是否有未完成的试卷...
|
||
async checkUnfinishedPaper () {
|
||
if (!this.examinee || !this.examinee.id) {
|
||
console.warn('未获取到考生信息,无法检查未完成试卷')
|
||
return
|
||
}
|
||
|
||
console.log('开始检查未完成试卷:', {
|
||
examineeId: this.examinee.id,
|
||
examineeName: this.examinee.examinee_name
|
||
})
|
||
|
||
try {
|
||
// 调用接口获取考生试卷数据
|
||
const result = await window.electronAPI.examingGetExamineePaper({
|
||
examineeId: this.examinee.id
|
||
})
|
||
|
||
console.log('检查未完成试卷API响应结果:', result)
|
||
|
||
if (result && result.success && result.data) {
|
||
console.log('检查未完成试卷成功,开始应用自定义判断逻辑')
|
||
|
||
// 根据用户指定的逻辑判断是否有未完成的考试
|
||
let unfinishedPaper = null
|
||
let completedPaper = null
|
||
|
||
// 如果data是数组,遍历查找未完成和已完成的试卷
|
||
if (Array.isArray(result.data)) {
|
||
unfinishedPaper = result.data.find(paper => this.isPaperUnfinished(paper))
|
||
completedPaper = result.data.find(paper => this.isPaperCompleted(paper))
|
||
}
|
||
// 如果data是单个对象,直接检查是否未完成或已完成
|
||
else if (typeof result.data === 'object') {
|
||
if (this.isPaperUnfinished(result.data)) {
|
||
unfinishedPaper = result.data
|
||
} else if (this.isPaperCompleted(result.data)) {
|
||
completedPaper = result.data
|
||
}
|
||
}
|
||
|
||
if (unfinishedPaper) {
|
||
// 存在未完成的试卷
|
||
this.unfinishedPaper = unfinishedPaper
|
||
console.log('发现未完成的试卷,详细信息:', {
|
||
paperId: this.unfinishedPaper.id,
|
||
paperStatus: this.unfinishedPaper.paper_status,
|
||
startTime: this.unfinishedPaper.paper_start_time,
|
||
lastTime: this.unfinishedPaper.paper_last_time,
|
||
submitTime: this.unfinishedPaper.paper_submit_time,
|
||
endTime: this.unfinishedPaper.paper_end_time,
|
||
paperMinutes: this.unfinishedPaper.paper_minutes,
|
||
questionsCount: this.unfinishedPaper.questions ? this.unfinishedPaper.questions.length : 0,
|
||
answeredQuestionsCount: this.answeredQuestionsCount,
|
||
totalScore: this.unfinishedPaper.total_score
|
||
})
|
||
} else {
|
||
console.log('没有发现未完成的试卷')
|
||
}
|
||
|
||
if (completedPaper) {
|
||
// 存在已完成的试卷
|
||
this.completedPaper = completedPaper
|
||
console.log('发现已完成的试卷,详细信息:', {
|
||
paperId: this.completedPaper.id,
|
||
paperStatus: this.completedPaper.paper_status,
|
||
startTime: this.completedPaper.paper_start_time,
|
||
endTime: this.completedPaper.paper_end_time,
|
||
paperMinutes: this.completedPaper.paper_minutes
|
||
})
|
||
} else {
|
||
console.log('没有发现已完成的试卷')
|
||
}
|
||
} else {
|
||
console.warn('检查未完成试卷失败:', result ? result.message : '未知错误')
|
||
}
|
||
} catch (error) {
|
||
console.error('检查未完成试卷异常:', error)
|
||
}
|
||
},
|
||
|
||
// 判断试卷是否未完成的辅助方法
|
||
isPaperUnfinished (paper) {
|
||
if (!paper) return false
|
||
|
||
// 新的判定规则:
|
||
// 1. paper_status值不是2
|
||
const statusNotTwo = paper.paper_status !== 2
|
||
|
||
// 2. duration_seconds大于0且小于paper_minutes换成的秒数
|
||
let durationValid = false
|
||
if (paper.paper_duration_seconds !== undefined && paper.paper_minutes) {
|
||
const totalSeconds = paper.paper_minutes * 60
|
||
durationValid = paper.paper_duration_seconds >= 0 && paper.paper_duration_seconds < totalSeconds
|
||
}
|
||
|
||
// 3. paper_end_time为空
|
||
const noEndTime = !paper.paper_end_time
|
||
|
||
// 记录判断过程
|
||
console.log('判断试卷是否未完成:', {
|
||
paperId: paper.id,
|
||
statusNotTwo,
|
||
durationValid,
|
||
noEndTime,
|
||
isUnfinished: statusNotTwo && durationValid && noEndTime
|
||
})
|
||
|
||
// 同时满足以上所有条件才是未完成的考试
|
||
return statusNotTwo && durationValid && noEndTime
|
||
},
|
||
|
||
// 判断试卷是否已完成的辅助方法
|
||
isPaperCompleted (paper) {
|
||
if (!paper) return false
|
||
|
||
// 条件1: paper_status值为2
|
||
const statusIsTwo = paper.paper_status === 2
|
||
|
||
// 条件2: paper_end_time有值
|
||
const hasEndTime = !!paper.paper_end_time
|
||
|
||
// 记录判断过程
|
||
console.log('判断试卷是否已完成:', {
|
||
paperId: paper.id,
|
||
statusIsTwo,
|
||
hasEndTime,
|
||
isCompleted: statusIsTwo && hasEndTime
|
||
})
|
||
|
||
// 同时满足以上所有条件才是已完成的考试
|
||
return statusIsTwo && hasEndTime
|
||
},
|
||
|
||
// 继续考试
|
||
continueExam () {
|
||
if (this.unfinishedPaper && this.unfinishedPaper.id) {
|
||
this.$confirm(
|
||
'确定要继续上次的考试吗?',
|
||
'继续考试确认',
|
||
{
|
||
confirmButtonText: '确定',
|
||
cancelButtonText: '取消',
|
||
type: 'warning'
|
||
}
|
||
).then(async () => {
|
||
try {
|
||
// 直接跳转到Examing页面,传递paperId和action=continue
|
||
this.$router.push({
|
||
name: 'Examing',
|
||
params: {
|
||
paperId: this.unfinishedPaper.id,
|
||
action: 'continue'
|
||
}
|
||
})
|
||
} catch (error) {
|
||
console.error('继续考试失败:', error)
|
||
this.$message.error(`继续考试失败: ${error.message || '未知错误'}`)
|
||
}
|
||
}).catch(() => {
|
||
// 用户取消继续考试
|
||
console.log('用户取消继续考试')
|
||
})
|
||
}
|
||
},
|
||
|
||
// 重新开始考试
|
||
async restartExam () {
|
||
this.$confirm(
|
||
'确定要重新开始考试吗?这将清除所有历史考试记录。',
|
||
'重新开始确认',
|
||
{
|
||
confirmButtonText: '确定',
|
||
cancelButtonText: '取消',
|
||
type: 'warning'
|
||
}
|
||
).then(async () => {
|
||
try {
|
||
this.isLoading = true
|
||
|
||
// 首先获取要清除的试卷ID
|
||
let paperIdToClear = null
|
||
if (this.completedPaper && this.completedPaper.id) {
|
||
paperIdToClear = this.completedPaper.id
|
||
} else if (this.unfinishedPaper && this.unfinishedPaper.id) {
|
||
paperIdToClear = this.unfinishedPaper.id
|
||
}
|
||
|
||
// 如果有试卷ID,则调用clear接口清除试卷数据
|
||
if (paperIdToClear) {
|
||
console.log('开始清除试卷数据,试卷ID:', paperIdToClear)
|
||
const clearResult = await window.electronAPI.examingClearPaper({
|
||
paperId: paperIdToClear
|
||
})
|
||
|
||
if (clearResult && clearResult.success) {
|
||
console.log('清除试卷数据成功')
|
||
} else {
|
||
console.warn('清除试卷数据失败:', clearResult)
|
||
// 不中断流程,继续尝试开始考试
|
||
}
|
||
}
|
||
|
||
// 清除本地的试卷状态
|
||
this.unfinishedPaper = null
|
||
this.completedPaper = null
|
||
|
||
// 重新检查试卷状态,确保本地状态与实际数据同步
|
||
await this.checkUnfinishedPaper()
|
||
|
||
// 调用开始考试方法
|
||
await this.startExam()
|
||
} catch (error) {
|
||
console.error('重新开始考试失败:', error)
|
||
this.$message.error('重新开始考试失败')
|
||
} finally {
|
||
this.isLoading = false
|
||
}
|
||
}).catch(() => {
|
||
// 用户取消操作
|
||
console.log('用户取消重新开始')
|
||
})
|
||
},
|
||
|
||
// 所有方法保持不变
|
||
// 格式化身份证号(第11-15位替换为*)
|
||
formatIdCard (idCard) {
|
||
if (!idCard || idCard.length !== 18) return idCard
|
||
return idCard.substring(0, 9) + '*****' + idCard.substring(14)
|
||
},
|
||
|
||
// 格式化考试时长(分钟转小时)
|
||
formatExamDuration (minutes) {
|
||
if (!minutes) return '未知'
|
||
const hours = Math.floor(minutes / 60)
|
||
const mins = minutes % 60
|
||
return `${hours}小时${mins}分钟`
|
||
},
|
||
|
||
// 得到考题数量和总分
|
||
async getQuestionsCountAndScore () {
|
||
try {
|
||
// 注意:这里需要确认接口是否存在
|
||
// 如果不存在,可能需要创建或修改现有接口
|
||
const result = await window.electronAPI.questionGetStatistics()
|
||
this.totalQuestions = result.totalQuestions || 0
|
||
this.totalScore = result.totalScore || 0
|
||
} catch (error) {
|
||
console.error('获取考题数量和总分失败:', error)
|
||
this.$message.error(`获取考题数量和总分失败: ${error.message || '未知错误'}`)
|
||
}
|
||
},
|
||
|
||
// 获取最新考试信息
|
||
async fetchLastExam () {
|
||
this.examLoading = true
|
||
try {
|
||
// 调用Electron API获取最新考试信息
|
||
const examData = await window.electronAPI.examFetchLast()
|
||
this.lastExam = examData
|
||
|
||
// 解析考试须知数组
|
||
if (this.lastExam && this.lastExam.exam_notice) {
|
||
try {
|
||
// 尝试解析JSON字符串
|
||
if (typeof this.lastExam.exam_notice === 'string') {
|
||
this.examNotices = JSON.parse(this.lastExam.exam_notice)
|
||
} else if (Array.isArray(this.lastExam.exam_notice)) {
|
||
this.examNotices = this.lastExam.exam_notice
|
||
} else {
|
||
this.examNotices = []
|
||
}
|
||
} catch (error) {
|
||
console.warn('解析考试须知失败,使用原始数据:', error)
|
||
this.examNotices = [this.lastExam.exam_notice]
|
||
}
|
||
} else {
|
||
this.examNotices = []
|
||
}
|
||
} catch (error) {
|
||
console.error('获取考试信息失败:', error)
|
||
this.$message.error(`获取考试信息失败: ${error.message || '未知错误'}`)
|
||
} finally {
|
||
this.examLoading = false
|
||
}
|
||
},
|
||
|
||
// 退出考试
|
||
exitExam () {
|
||
this.$confirm(
|
||
'确定要退出考试吗?',
|
||
'退出确认',
|
||
{
|
||
confirmButtonText: '确定',
|
||
cancelButtonText: '取消',
|
||
type: 'warning'
|
||
}
|
||
).then(() => {
|
||
// 完全清除store中的数据
|
||
this.$store.commit('clearUser') // 修改为已定义的mutation
|
||
// 移除:不再清除试卷信息
|
||
// this.$store.commit('clearPaper')
|
||
// 重定向到欢迎页
|
||
this.$router.push('/')
|
||
}).catch(() => {
|
||
// 用户取消操作
|
||
console.log('用户取消退出')
|
||
})
|
||
},
|
||
|
||
// 开始考试方法
|
||
// 修改startExam方法,移除保存完整试卷信息到store的代码
|
||
async startExam () {
|
||
if (!this.lastExam) {
|
||
this.$message.warning('请先获取考试信息')
|
||
return
|
||
}
|
||
|
||
if (!this.examinee || !this.examinee.id || !this.examinee.examinee_name) {
|
||
this.$message.warning('未获取到完整的考生信息')
|
||
return
|
||
}
|
||
|
||
this.isLoading = true
|
||
|
||
try {
|
||
console.log('开始生成试卷...', { examinee: this.examinee, exam: this.lastExam })
|
||
|
||
// 创建可序列化的完整考生数据对象
|
||
const examineeData = {
|
||
id: this.examinee.id,
|
||
examinee_name: this.examinee.examinee_name || '',
|
||
examinee_id_card: this.examinee.examinee_id_card || '',
|
||
examinee_admission_ticket: this.examinee.examinee_admission_ticket || '',
|
||
examinee_gender: this.examinee.examinee_gender || '',
|
||
examinee_unit: this.examinee.examinee_unit || '',
|
||
written_exam_room: this.examinee.written_exam_room || '',
|
||
written_exam_seat: this.examinee.written_exam_seat || '',
|
||
computer_exam_room: this.examinee.computer_exam_room || '',
|
||
computer_exam_seat: this.examinee.computer_exam_seat || ''
|
||
}
|
||
|
||
// 使用JSON序列化/反序列化确保对象可克隆
|
||
const examData = JSON.parse(JSON.stringify({
|
||
id: this.lastExam.id,
|
||
exam_name: this.lastExam.exam_name || '',
|
||
exam_description: this.lastExam.exam_description || '',
|
||
exam_minutes: this.lastExam.exam_minutes || 0,
|
||
exam_minutes_min: this.lastExam.exam_minutes_min || 0,
|
||
exam_notice: this.lastExam.exam_notice || []
|
||
}))
|
||
|
||
// 直接调用ipcRenderer接口,跳过preload.js中定义的不匹配方法
|
||
const result = await window.electronAPI.ipcRenderer.invoke('examing-generate-paper', {
|
||
examineeData: examineeData,
|
||
examData: examData
|
||
})
|
||
|
||
if (result && result.success) {
|
||
console.log('生成试卷成功:', result)
|
||
|
||
// 移除:不再保存完整试卷信息到store
|
||
// this.$store.commit('setPaper', result.data)
|
||
|
||
// 仅保存试卷ID到localStorage(作为备用机制)
|
||
try {
|
||
localStorage.setItem('currentPaperId', result.data.id)
|
||
} catch (error) {
|
||
console.warn('保存试卷ID到localStorage失败:', error)
|
||
}
|
||
|
||
this.$alert(
|
||
'已完成组卷,点击"进入考试"开始答题',
|
||
'组卷完成',
|
||
{
|
||
confirmButtonText: '进入考试',
|
||
type: 'success'
|
||
}
|
||
).then(() => {
|
||
// 跳转到Examing页面,传递paperId和action=start
|
||
this.$router.push({
|
||
name: 'Examing',
|
||
params: {
|
||
paperId: result.data.id,
|
||
action: 'start'
|
||
}
|
||
})
|
||
})
|
||
} else {
|
||
console.error('生成试卷失败:', result)
|
||
this.$message.error(`生成试卷失败: ${result.message || '未知错误'}`)
|
||
}
|
||
} catch (error) {
|
||
console.error('生成试卷异常:', error)
|
||
this.$message.error(`无法生成试卷: ${error.message || '未知错误'}`)
|
||
} finally {
|
||
this.isLoading = false
|
||
}
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style scoped>
|
||
/* 自定义样式 */
|
||
.bg-primary {
|
||
background-color: #1E88E5 !important;
|
||
/* 蓝色主题,与WelcomeView保持一致 */
|
||
}
|
||
|
||
.text-primary {
|
||
color: #1E88E5 !important;
|
||
}
|
||
|
||
/* 确保容器占满可用空间 */
|
||
.student-home-container {
|
||
height: 100%;
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
|
||
/* 内容包装器,用于居中卡片并使其可扩展 */
|
||
.content-wrapper {
|
||
flex: 1;
|
||
width: 100%;
|
||
/* 移除固定的height: 100vh */
|
||
/* height: 100vh; */
|
||
display: flex;
|
||
align-items: flex-start;
|
||
/* 修改为顶部对齐而非居中 */
|
||
/* align-items: center; */
|
||
justify-content: center;
|
||
box-sizing: border-box;
|
||
/* 添加padding-top来避开Header */
|
||
padding-top: 20px;
|
||
padding-bottom: 20px;
|
||
}
|
||
|
||
/* 考试卡片样式 - 响应式设计 */
|
||
.exam-card {
|
||
width: 100%;
|
||
margin: 0px 0px 20px 0px;
|
||
background-color: white;
|
||
display: flex;
|
||
flex-direction: column;
|
||
box-sizing: border-box;
|
||
/* 确保卡片在小屏幕上不会太宽 */
|
||
max-width: 900px;
|
||
}
|
||
|
||
/* 未完成试卷提示卡片样式 */
|
||
.warning-card {
|
||
animation: pulse 2s infinite;
|
||
}
|
||
|
||
@keyframes pulse {
|
||
0% {
|
||
box-shadow: 0 0 0 0 rgba(245, 158, 11, 0.4);
|
||
}
|
||
|
||
70% {
|
||
box-shadow: 0 0 0 10px rgba(245, 158, 11, 0);
|
||
}
|
||
|
||
100% {
|
||
box-shadow: 0 0 0 0 rgba(245, 158, 11, 0);
|
||
}
|
||
}
|
||
|
||
/* 适配中小屏幕 */
|
||
@media (max-width: 768px) {
|
||
.exam-card {
|
||
max-width: 100%;
|
||
}
|
||
}
|
||
|
||
/* 适配大屏幕 - 适当增加最大宽度但保留边距 */
|
||
@media (min-width: 1200px) {
|
||
.exam-card {
|
||
max-width: 80vw;
|
||
}
|
||
}
|
||
|
||
/* 适配超大屏幕 - 进一步增加最大宽度 */
|
||
@media (min-width: 1600px) {
|
||
.exam-card {
|
||
max-width: 90vw;
|
||
}
|
||
}
|
||
|
||
.cell-item {
|
||
display: flex;
|
||
align-items: center;
|
||
}
|
||
|
||
.margin-top {
|
||
margin-top: 20px !important;
|
||
}
|
||
|
||
.bg-yellow-50 {
|
||
background-color: #fffbeb;
|
||
}
|
||
|
||
.border-l-4 {
|
||
border-left-width: 4px;
|
||
}
|
||
|
||
.border-yellow-500 {
|
||
border-color: #f59e0b;
|
||
}
|
||
|
||
.text-red-600 {
|
||
color: #dc2626;
|
||
}
|
||
|
||
.text-sm {
|
||
font-size: 14px;
|
||
}
|
||
|
||
.space-y-2> :not([hidden])~ :not([hidden]) {
|
||
margin-top: 8px;
|
||
}
|
||
|
||
.space-y-4> :not([hidden])~ :not([hidden]) {
|
||
margin-top: 16px;
|
||
}
|
||
|
||
.flex {
|
||
display: flex;
|
||
}
|
||
|
||
.space-x-2> :not([hidden])~ :not([hidden]) {
|
||
margin-left: 8px;
|
||
}
|
||
|
||
.mt-3 {
|
||
margin-top: 12px;
|
||
}
|
||
|
||
.mt-4 {
|
||
margin-top: 16px;
|
||
}
|
||
|
||
.text-gray-700 {
|
||
color: #374151;
|
||
}
|
||
|
||
.text-gray-500 {
|
||
color: #6b7280;
|
||
}
|
||
|
||
/* 已完成试卷提示卡片样式 */
|
||
.completed-card {
|
||
animation: successPulse 2s infinite;
|
||
}
|
||
|
||
@keyframes successPulse {
|
||
0% {
|
||
box-shadow: 0 0 0 0 rgba(34, 197, 94, 0.4);
|
||
}
|
||
|
||
70% {
|
||
box-shadow: 0 0 0 10px rgba(34, 197, 94, 0);
|
||
}
|
||
|
||
100% {
|
||
box-shadow: 0 0 0 0 rgba(34, 197, 94, 0);
|
||
}
|
||
}
|
||
|
||
.bg-green-50 {
|
||
background-color: #f0fdf4;
|
||
}
|
||
|
||
.border-green-500 {
|
||
border-color: #22c55e;
|
||
}
|
||
|
||
.text-green-600 {
|
||
color: #15803d;
|
||
}
|
||
</style>
|