exam11/src/views/user/ExamineeHomeView.vue
2025-09-13 09:08:55 +08:00

1125 lines
37 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>
<!-- 添加外层容器确保占满整个空间 -->
<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>