|
|
@@ -0,0 +1,552 @@
|
|
|
+<template>
|
|
|
+ <el-container class="exam-container">
|
|
|
+ <el-aside width="280px" class="exam-aside">
|
|
|
+ <div class="aside-card answer-card">
|
|
|
+ <h4 class="card-title">阅卷进度卡</h4>
|
|
|
+ <div v-for="(section, sIndex) in sectionsList" :key="sIndex" class="card-group">
|
|
|
+ <div class="group-label">{{ section.sectionName }}</div>
|
|
|
+ <div class="number-grid">
|
|
|
+ <span
|
|
|
+ v-for="q in section.flatQuestions"
|
|
|
+ :key="q.questionId"
|
|
|
+ class="number-item"
|
|
|
+ :class="{
|
|
|
+ 'is-graded': checkIsGraded(q.questionId),
|
|
|
+ 'is-active': currentActiveId === q.questionId
|
|
|
+ }"
|
|
|
+ @click="scrollToQuestion(q.questionId)"
|
|
|
+ >
|
|
|
+ {{ q.number }}
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <el-button type="success" class="btn-submit" icon="el-icon-finished" @click="handleConfirmSubmit">
|
|
|
+ 提交阅卷结果
|
|
|
+ </el-button>
|
|
|
+ </div>
|
|
|
+ </el-aside>
|
|
|
+
|
|
|
+ <el-main class="exam-main">
|
|
|
+ <div class="paper-header">
|
|
|
+ <h2 class="paper-title">{{ paperInfo.title }}【阅卷流水线】</h2>
|
|
|
+ <div class="paper-meta">
|
|
|
+ <span>考试科目:{{ paperInfo.subjectName || '未指定' }}</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div v-for="(section, sIndex) in sectionsList" :key="sIndex" class="question-group-section">
|
|
|
+ <h3 class="group-header-title">
|
|
|
+ {{ section.sectionName }} <span class="group-desc">({{ section.sectionHint }})</span>
|
|
|
+ </h3>
|
|
|
+
|
|
|
+ <div
|
|
|
+ v-for="q in section.questions"
|
|
|
+ :id="'q-' + q.questionId"
|
|
|
+ :key="q.questionId"
|
|
|
+ class="question-card"
|
|
|
+ @mouseenter="currentActiveId = q.questionId"
|
|
|
+ >
|
|
|
+ <template v-if="q.typeCode === 1">
|
|
|
+ <div class="question-stem">
|
|
|
+ <span class="q-number">{{ q.number }}.</span>
|
|
|
+ <span class="q-score-highlight">({{ q.score }}分)</span>
|
|
|
+ <span class="q-text" v-html="formatStem(q.content)" />
|
|
|
+ <el-tag size="mini" type="info">单选</el-tag>
|
|
|
+ </div>
|
|
|
+ <div class="question-body">
|
|
|
+ <div class="options-display-list">
|
|
|
+ <div
|
|
|
+ v-for="opt in q.options"
|
|
|
+ :key="opt.pos"
|
|
|
+ class="mark-option-item"
|
|
|
+ :class="{
|
|
|
+ 'opt-correct': opt.correct,
|
|
|
+ 'opt-user-selected': answers[q.questionId] == opt.pos,
|
|
|
+ 'opt-user-wrong': answers[q.questionId] == opt.pos && !opt.correct
|
|
|
+ }"
|
|
|
+ >
|
|
|
+ <i v-if="opt.correct" class="el-icon-success text-success" />
|
|
|
+ <i v-else-if="answers[q.questionId] == opt.pos" class="el-icon-error text-danger" />
|
|
|
+ <i v-else class="el-icon-circle-check text-muted" />
|
|
|
+ <span class="opt-content">{{ opt.content }}</span>
|
|
|
+ <el-tag v-if="opt.correct" size="mini" type="success" effect="dark" class="mini-tag">正确答案</el-tag>
|
|
|
+ <el-tag v-if="answers[q.questionId] == opt.pos" size="mini" type="primary" effect="plain" class="mini-tag">考生选择</el-tag>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="auto-mark-result">
|
|
|
+ 系统判定:<span :class="markScores[q.questionId] > 0 ? 'score-win' : 'score-loss'">{{ markScores[q.questionId] }} 分</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+
|
|
|
+ <template v-else-if="q.typeCode === 2">
|
|
|
+ <div class="question-stem">
|
|
|
+ <span class="q-number">{{ q.number }}.</span>
|
|
|
+ <span class="q-score-highlight">({{ q.score }}分)</span>
|
|
|
+ <span class="q-text" v-html="formatStem(q.content)" />
|
|
|
+ <el-tag type="warning" size="mini" class="type-tag">多选</el-tag>
|
|
|
+ </div>
|
|
|
+ <div class="question-body">
|
|
|
+ <div class="options-display-list">
|
|
|
+ <div
|
|
|
+ v-for="opt in q.options"
|
|
|
+ :key="opt.pos"
|
|
|
+ class="mark-option-item"
|
|
|
+ :class="{
|
|
|
+ 'opt-correct': opt.correct,
|
|
|
+ 'opt-user-selected': (answers[q.questionId] || []).includes(opt.pos),
|
|
|
+ 'opt-user-wrong': (answers[q.questionId] || []).includes(opt.pos) && !opt.correct
|
|
|
+ }"
|
|
|
+ >
|
|
|
+ <i v-if="opt.correct" class="el-icon-success text-success" />
|
|
|
+ <i v-else-if="(answers[q.questionId] || []).includes(opt.pos)" class="el-icon-error text-danger" />
|
|
|
+ <i v-else class="el-icon-circle-check text-muted" />
|
|
|
+ <span class="opt-content">{{ opt.content }}</span>
|
|
|
+ <el-tag v-if="opt.correct" size="mini" type="success" effect="dark" class="mini-tag">正确答案</el-tag>
|
|
|
+ <el-tag v-if="(answers[q.questionId] || []).includes(opt.pos)" size="mini" type="primary" effect="plain" class="mini-tag">考生选择</el-tag>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="auto-mark-result">
|
|
|
+ 系统判定:<span :class="markScores[q.questionId] > 0 ? 'score-win' : 'score-loss'">{{ markScores[q.questionId] }} 分</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+
|
|
|
+ <template v-else-if="q.typeCode === 3">
|
|
|
+ <div class="question-stem">
|
|
|
+ <span class="q-number">{{ q.number }}.</span>
|
|
|
+ <span class="q-score-highlight">({{ q.score }}分)</span>
|
|
|
+ <span class="q-text" v-html="formatStem(q.content)" />
|
|
|
+ <el-tag size="mini" type="primary">填空</el-tag>
|
|
|
+ </div>
|
|
|
+ <div class="question-body subjective-mark-box">
|
|
|
+ <el-row :gutter="20" class="compare-row">
|
|
|
+ <el-col :span="12">
|
|
|
+ <div class="answer-block user-answer-block">
|
|
|
+ <div class="block-title"><i class="el-icon-user" /> 考生作答情况</div>
|
|
|
+ <div class="block-content font-code">{{ answers[q.questionId] || '(未作答)' }}</div>
|
|
|
+ </div>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="12">
|
|
|
+ <div class="answer-block standard-answer-block">
|
|
|
+ <div class="block-title"><i class="el-icon-key" /> 参考答案</div>
|
|
|
+ <div class="block-content font-code">
|
|
|
+ <div v-for="(opt, oIdx) in q.options" :key="oIdx">
|
|
|
+ <span class="text-success font-bold">{{ opt.content }}</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+ <div class="mark-control-bar">
|
|
|
+ <div class="control-item">
|
|
|
+ <span class="control-label">评分:</span>
|
|
|
+ <el-input-number
|
|
|
+ v-model="markScores[q.questionId]"
|
|
|
+ :min="0"
|
|
|
+ :max="q.score"
|
|
|
+ :precision="1"
|
|
|
+ :step="0.5"
|
|
|
+ size="small"
|
|
|
+ />
|
|
|
+ <span class="max-score-hint">/ {{ q.score }} 分</span>
|
|
|
+ </div>
|
|
|
+ <div class="control-item review-item">
|
|
|
+ <span class="control-label">评语:</span>
|
|
|
+ <el-input
|
|
|
+ v-model="markReviews[q.questionId]"
|
|
|
+ placeholder="请输入对该填空题的评语或扣分原因..."
|
|
|
+ size="small"
|
|
|
+ clearable
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+
|
|
|
+ <template v-else-if="q.typeCode === 4">
|
|
|
+ <div class="question-stem">
|
|
|
+ <span class="q-number">{{ q.number }}.</span>
|
|
|
+ <span class="q-score-highlight">({{ q.score }}分)</span>
|
|
|
+ <span class="q-text" v-html="formatStem(q.content)" />
|
|
|
+ <el-tag size="mini" type="purple">问答</el-tag>
|
|
|
+ </div>
|
|
|
+ <div class="question-body subjective-mark-box">
|
|
|
+ <div class="answer-block user-answer-block mb-15">
|
|
|
+ <div class="block-title"><i class="el-icon-user" /> 考生答案</div>
|
|
|
+ <div class="block-content text-pre-wrap">{{ answers[q.questionId] || '(空白未填写)' }}</div>
|
|
|
+ </div>
|
|
|
+ <div class="answer-block standard-answer-block mb-15">
|
|
|
+ <div class="block-title"><i class="el-icon-key" /> 参考答案</div>
|
|
|
+ <div class="block-content text-pre-wrap">{{ q.options && q.options[0] ? q.options[0].content : '暂无详尽解析' }}</div>
|
|
|
+ </div>
|
|
|
+ <div class="mark-control-bar">
|
|
|
+ <div class="control-item">
|
|
|
+ <span class="control-label">评分:</span>
|
|
|
+ <el-input-number
|
|
|
+ v-model="markScores[q.questionId]"
|
|
|
+ :min="0"
|
|
|
+ :max="q.score"
|
|
|
+ :precision="1"
|
|
|
+ :step="1"
|
|
|
+ size="small"
|
|
|
+ />
|
|
|
+ <span class="max-score-hint">/ {{ q.score }} 分</span>
|
|
|
+ </div>
|
|
|
+ <div class="control-item review-item">
|
|
|
+ <span class="control-label">评语:</span>
|
|
|
+ <el-input
|
|
|
+ v-model="markReviews[q.questionId]"
|
|
|
+ type="textarea"
|
|
|
+ :rows="2"
|
|
|
+ placeholder="请输入对此道主观大题的指导意见或评语..."
|
|
|
+ size="small"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+
|
|
|
+ <template v-else-if="q.typeCode === 5">
|
|
|
+ <div class="question-stem material-stem">
|
|
|
+ <span class="material-tag">【组合材料】</span>
|
|
|
+ <div class="q-text" style="white-space: pre-wrap;">{{ q.content }}</div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="sub-questions-container">
|
|
|
+ <div v-for="subQ in q.children" :key="subQ.questionId" class="sub-question-item">
|
|
|
+
|
|
|
+ <div class="question-stem sub-stem">
|
|
|
+ <span class="q-number">{{ subQ.number }}.</span>
|
|
|
+ <span class="q-score-highlight">({{ subQ.score }}分)</span>
|
|
|
+ <span class="q-text">{{ subQ.content }}</span>
|
|
|
+ <el-tag size="mini" effect="plain" type="info" style="margin-left:5px;">{{ subQ.typeStr }}</el-tag>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="question-body">
|
|
|
+ <template v-if="subQ.typeCode === 1 || subQ.typeCode === 2">
|
|
|
+ <div class="options-display-list">
|
|
|
+ <div
|
|
|
+ v-for="opt in subQ.options"
|
|
|
+ :key="opt.pos"
|
|
|
+ class="mark-option-item"
|
|
|
+ :class="{
|
|
|
+ 'opt-correct': opt.correct,
|
|
|
+ 'opt-user-selected': subQ.typeCode === 1 ? (answers[subQ.questionId] == opt.pos) : (answers[subQ.questionId] || []).includes(opt.pos),
|
|
|
+ 'opt-user-wrong': subQ.typeCode === 1 ? (answers[subQ.questionId] == opt.pos && !opt.correct) : ((answers[subQ.questionId] || []).includes(opt.pos) && !opt.correct)
|
|
|
+ }"
|
|
|
+ >
|
|
|
+ <i v-if="opt.correct" class="el-icon-success text-success" />
|
|
|
+ <i v-else-if="subQ.typeCode === 1 ? (answers[subQ.questionId] == opt.pos) : (answers[subQ.questionId] || []).includes(opt.pos)" class="el-icon-error text-danger" />
|
|
|
+ <i v-else class="el-icon-circle-check text-muted" />
|
|
|
+ <span class="opt-content">{{ opt.content }}</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="auto-mark-result">
|
|
|
+ 系统判定:<span :class="markScores[subQ.questionId] > 0 ? 'score-win' : 'score-loss'">{{ markScores[subQ.questionId] }} 分</span>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+
|
|
|
+ <template v-else-if="subQ.typeCode === 3 || subQ.typeCode === 4">
|
|
|
+ <el-row :gutter="20" class="compare-row mb-10">
|
|
|
+ <el-col :span="12">
|
|
|
+ <div class="answer-block user-answer-block">
|
|
|
+ <div class="block-title">考生答案</div>
|
|
|
+ <div class="block-content text-pre-wrap">{{ answers[subQ.questionId] || '(未作答)' }}</div>
|
|
|
+ </div>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="12">
|
|
|
+ <div class="answer-block standard-answer-block">
|
|
|
+ <div class="block-title">参考答案</div>
|
|
|
+ <div class="block-content text-pre-wrap">
|
|
|
+ <template v-if="subQ.typeCode === 3">
|
|
|
+ <div v-for="(opt, oIdx) in subQ.options" :key="oIdx">{{ opt.content }}</div>
|
|
|
+ </template>
|
|
|
+ <template v-else>
|
|
|
+ {{ subQ.options && subQ.options[0] ? subQ.options[0].content : '无解析' }}
|
|
|
+ </template>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+ <div class="mark-control-bar">
|
|
|
+ <div class="control-item">
|
|
|
+ <span class="control-label">评分:</span>
|
|
|
+ <el-input-number v-model="markScores[subQ.questionId]" :min="0" :max="subQ.score" size="mini" />
|
|
|
+ <span class="max-score-hint">/ {{ subQ.score }}分</span>
|
|
|
+ </div>
|
|
|
+ <div class="control-item review-item">
|
|
|
+ <span class="control-label">评语:</span>
|
|
|
+ <el-input v-model="markReviews[subQ.questionId]" placeholder="评语..." size="mini" />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </el-main>
|
|
|
+ </el-container>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script>
|
|
|
+import {getMarkView, submitMarkResult} from '@/api/exam'
|
|
|
+
|
|
|
+export default {
|
|
|
+ name: 'ExamPaperMark',
|
|
|
+ data() {
|
|
|
+ return {
|
|
|
+ queryParams: {
|
|
|
+ examId: 0,
|
|
|
+ userId: 0
|
|
|
+ },
|
|
|
+ remainTime: 0,
|
|
|
+ timeLoading: false,
|
|
|
+ currentActiveId: null,
|
|
|
+
|
|
|
+ answers: {}, // 考生的历史答案库
|
|
|
+ markScores: {}, // 【新加】老师批改分数值:{ [questionId]: score }
|
|
|
+ markReviews: {}, // 【新加】老师填写的评语:{ [questionId]: '评语内容' }
|
|
|
+
|
|
|
+ paperInfo: {},
|
|
|
+ sectionsList: []
|
|
|
+ }
|
|
|
+ },
|
|
|
+ computed: {
|
|
|
+ formatRemainTime() {
|
|
|
+ const hours = Math.floor(this.remainTime / 3600).toString().padStart(2, '0')
|
|
|
+ const mins = Math.floor((this.remainTime % 3600) / 60).toString().padStart(2, '0')
|
|
|
+ const secs = Math.floor(this.remainTime % 60).toString().padStart(2, '0')
|
|
|
+ return `${hours}:${mins}:${secs}`
|
|
|
+ }
|
|
|
+ },
|
|
|
+ created() {
|
|
|
+ document.title = '阅卷'
|
|
|
+ const examId = this.$route.query.examId
|
|
|
+ const userId = this.$route.query.userId || 0
|
|
|
+ if (!examId) {
|
|
|
+ this.$message.error('参数缺失,无法加载阅卷页面!')
|
|
|
+ return
|
|
|
+ }
|
|
|
+ this.queryParams.examId = examId
|
|
|
+ this.queryParams.userId = userId
|
|
|
+ this.loadPaperData()
|
|
|
+ },
|
|
|
+ methods: {
|
|
|
+ async loadPaperData() {
|
|
|
+ try {
|
|
|
+ const resp = await getMarkView(this.queryParams)
|
|
|
+ if (resp.code !== 0) {
|
|
|
+ this.$message.warning(resp.msg)
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ const examData = resp.data
|
|
|
+ this.remainTime = examData.duration
|
|
|
+
|
|
|
+ const remoteData = examData.paperDetail
|
|
|
+ this.paperInfo = {
|
|
|
+ paperId: remoteData.paperId,
|
|
|
+ title: remoteData.title,
|
|
|
+ subjectId: remoteData.subjectId,
|
|
|
+ subjectName: remoteData.subjectName
|
|
|
+ }
|
|
|
+
|
|
|
+ let globalQuestionIndex = 1
|
|
|
+ const localAnswers = {}
|
|
|
+ const localScores = {}
|
|
|
+ const localReviews = {}
|
|
|
+
|
|
|
+ this.sectionsList = remoteData.sections.map(sec => {
|
|
|
+ const flatQuestions = []
|
|
|
+
|
|
|
+ const ProcessedQuestions = sec.questions.map(q => {
|
|
|
+ // 标准化 options
|
|
|
+ if (q.answers && !q.options) {
|
|
|
+ q.options = q.answers
|
|
|
+ }
|
|
|
+
|
|
|
+ // 处理普通非材料题
|
|
|
+ if (q.typeCode !== 5) {
|
|
|
+ q.number = globalQuestionIndex++
|
|
|
+ flatQuestions.push({ questionId: q.questionId, number: q.number, typeCode: q.typeCode })
|
|
|
+
|
|
|
+ // 还原考生提交上来的真实数据
|
|
|
+ localAnswers[q.questionId] = q.userAnswer !== undefined ? q.userAnswer : ''
|
|
|
+ localReviews[q.questionId] = ''
|
|
|
+
|
|
|
+ // 如果是选择题 (1-单选, 2-多选),系统可自动给出判定分数
|
|
|
+ if (q.typeCode === 1) {
|
|
|
+ const correctOpt = (q.options || []).find(o => o.correct)
|
|
|
+ const isRight = correctOpt && String(correctOpt.pos) === String(q.userAnswer)
|
|
|
+ localScores[q.questionId] = isRight ? q.score : 0
|
|
|
+ } else if (q.typeCode === 2) {
|
|
|
+ // 假设多选题 userAnswer 是形如 [2, 4] 的数组或逗号分隔串
|
|
|
+ const userArr = Array.isArray(q.userAnswer) ? q.userAnswer.map(String) : []
|
|
|
+ const correctArr = (q.options || []).filter(o => o.correct).map(o => String(o.pos))
|
|
|
+ const isRight = userArr.length === correctArr.length && userArr.every(v => correctArr.includes(v))
|
|
|
+ localScores[q.questionId] = isRight ? q.score : 0
|
|
|
+ } else {
|
|
|
+ // 主观题(填空/问答)默认初始化为满分或0分,等候老师用 el-input-number 细扣
|
|
|
+ localScores[q.questionId] = 0
|
|
|
+ }
|
|
|
+ } else if (q.typeCode === 5 && q.children) {
|
|
|
+ // 处理组合材料题
|
|
|
+ q.children.forEach(subQ => {
|
|
|
+ if (subQ.answers && !subQ.options) {
|
|
|
+ subQ.options = subQ.answers
|
|
|
+ }
|
|
|
+ subQ.number = globalQuestionIndex++
|
|
|
+ flatQuestions.push({ questionId: subQ.questionId, number: subQ.number, typeCode: subQ.typeCode })
|
|
|
+
|
|
|
+ localAnswers[subQ.questionId] = subQ.userAnswer !== undefined ? subQ.userAnswer : ''
|
|
|
+ localReviews[subQ.questionId] = ''
|
|
|
+
|
|
|
+ // 选择子题自动判分
|
|
|
+ if (subQ.typeCode === 1) {
|
|
|
+ const correctOpt = (subQ.options || []).find(o => o.correct)
|
|
|
+ localScores[subQ.questionId] = (correctOpt && String(correctOpt.pos) === String(subQ.userAnswer)) ? subQ.score : 0
|
|
|
+ } else if (subQ.typeCode === 2) {
|
|
|
+ const userArr = Array.isArray(subQ.userAnswer) ? subQ.userAnswer.map(String) : []
|
|
|
+ const correctArr = (subQ.options || []).filter(o => o.correct).map(o => String(o.pos))
|
|
|
+ const isRight = userArr.length === correctArr.length && userArr.every(v => correctArr.includes(v))
|
|
|
+ localScores[subQ.questionId] = isRight ? subQ.score : 0
|
|
|
+ } else {
|
|
|
+ localScores[subQ.questionId] = 0
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
+ return q
|
|
|
+ })
|
|
|
+
|
|
|
+ return {
|
|
|
+ sectionId: sec.sectionId,
|
|
|
+ sectionName: sec.sectionName,
|
|
|
+ sectionHint: sec.sectionHint,
|
|
|
+ questions: ProcessedQuestions,
|
|
|
+ flatQuestions: flatQuestions
|
|
|
+ }
|
|
|
+ })
|
|
|
+
|
|
|
+ this.answers = localAnswers
|
|
|
+ this.markScores = localScores
|
|
|
+ this.markReviews = localReviews
|
|
|
+ } catch (error) {
|
|
|
+ this.$message.error(error.message || '加载试卷数据失败')
|
|
|
+ console.error(error)
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ handleAnswerChange(qId) {},
|
|
|
+
|
|
|
+ // 侧边栏辅助:检查该题是否分配了分数
|
|
|
+ checkIsGraded(qId) {
|
|
|
+ return this.markScores[qId] !== undefined && this.markScores[qId] !== null
|
|
|
+ },
|
|
|
+
|
|
|
+ scrollToQuestion(qId) {
|
|
|
+ this.currentActiveId = qId
|
|
|
+ const targetEl = document.getElementById(`q-${qId}`)
|
|
|
+ if (targetEl) {
|
|
|
+ targetEl.scrollIntoView({ behavior: 'smooth', block: 'center' })
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ formatStem(content) {
|
|
|
+ if (!content) return ''
|
|
|
+ return content.replace(/(\s*\d+分\s*)/g, '')
|
|
|
+ },
|
|
|
+
|
|
|
+ // 提交批改结果
|
|
|
+ handleConfirmSubmit() {
|
|
|
+ // 组装发回后端的批改JSON数据 payload
|
|
|
+ const gradeResults = Object.keys(this.markScores).map(qId => {
|
|
|
+ return {
|
|
|
+ questionId: Number(qId),
|
|
|
+ score: this.markScores[qId],
|
|
|
+ review: this.markReviews[qId] || ''
|
|
|
+ }
|
|
|
+ })
|
|
|
+
|
|
|
+ this.$confirm('确定保存并提交本次阅卷评分结果吗?', '阅卷确认', {
|
|
|
+ confirmButtonText: '确定提交',
|
|
|
+ cancelButtonText: '再核对下',
|
|
|
+ type: 'success'
|
|
|
+ }).then(() => {
|
|
|
+ const jsonData = {}
|
|
|
+ jsonData.examId = 1
|
|
|
+ jsonData.resultId = 1
|
|
|
+ jsonData.userId = 1
|
|
|
+ jsonData.markResults = gradeResults
|
|
|
+ submitMarkResult(jsonData).then(resp => {
|
|
|
+ if (resp.code === 0) {
|
|
|
+ this.$message.success('阅卷分值已成功提交!')
|
|
|
+ } else {
|
|
|
+ this.$message.warning(resp.msg)
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }).catch(() => {})
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped>
|
|
|
+.exam-container { padding: 20px; background-color: #f5f7fa; min-height: 100vh; }
|
|
|
+.aside-card { background: #fff; padding: 15px; margin-bottom: 20px; border-radius: 6px; box-shadow: 0 2px 12px 0 rgba(0,0,0,0.05); }
|
|
|
+.number-grid { display: grid; grid-template-columns: repeat(5, 15fr); gap: 8px; margin-top: 10px; }
|
|
|
+.number-item { display: inline-block; width: 36px; height: 36px; line-height: 36px; text-align: center; border: 1px solid #dcdfe6; border-radius: 4px; cursor: pointer; font-size: 13px; }
|
|
|
+.number-item.is-graded { background-color: #f0f9eb; border-color: #c2e7b0; color: #67c23a; font-weight: bold; }
|
|
|
+.number-item.is-active { border: 2px solid #409eff !important; }
|
|
|
+.btn-submit { width: 100%; margin-top: 15px; }
|
|
|
+
|
|
|
+.question-card { background: #fff; padding: 20px; margin-bottom: 20px; border-radius: 6px; box-shadow: 0 2px 12px 0 rgba(0,0,0,0.05); }
|
|
|
+.q-score-highlight { color: #67C23A; font-weight: bold; margin-right: 5px; }
|
|
|
+.auto-mark-result { margin-top: 12px; font-size: 13px; background: #f8f9fa; padding: 6px 12px; border-radius: 4px; display: inline-block; }
|
|
|
+.score-win { color: #67c23a; font-weight: bold; }
|
|
|
+.score-loss { color: #f56c6c; font-weight: bold; }
|
|
|
+
|
|
|
+/* 批改选项卡样式 */
|
|
|
+.options-display-list { margin-top: 10px; }
|
|
|
+.mark-option-item { padding: 10px 15px; border: 1px solid #e4e7ed; border-radius: 4px; margin-bottom: 8px; display: flex; align-items: center; font-size: 14px; }
|
|
|
+.mark-option-item i { font-size: 16px; margin-right: 10px; }
|
|
|
+.opt-content { flex: 1; }
|
|
|
+.mini-tag { margin-left: 8px; }
|
|
|
+.text-success { color: #67c23a; }
|
|
|
+.text-danger { color: #f56c6c; }
|
|
|
+.text-muted { color: #909399; }
|
|
|
+
|
|
|
+/* 高亮选中态 */
|
|
|
+.opt-correct { background-color: #f0f9eb; border-color: #c2e7b0; }
|
|
|
+.opt-user-selected { border-left: 4px solid #409eff; }
|
|
|
+.opt-user-wrong { background-color: #fef0f0; border-color: #fde2e2; }
|
|
|
+
|
|
|
+/* 主观题双栏对比区 */
|
|
|
+.subjective-mark-box { margin-top: 15px; }
|
|
|
+.answer-block { border: 1px solid #e4e7ed; border-radius: 4px; overflow: hidden; }
|
|
|
+.user-answer-block .block-title { background: #ecf5ff; color: #409eff; padding: 8px 12px; font-weight: bold; font-size: 13px; }
|
|
|
+.standard-answer-block .block-title { background: #f0f9eb; color: #67c23a; padding: 8px 12px; font-weight: bold; font-size: 13px; }
|
|
|
+.block-content { padding: 12px; font-size: 14px; min-height: 60px; line-height: 1.6; background: #fff; }
|
|
|
+.text-pre-wrap { white-space: pre-wrap; }
|
|
|
+.font-code { font-family: monospace; background-color: #fafafa; }
|
|
|
+.font-bold { font-weight: bold; }
|
|
|
+.mb-10 { margin-bottom: 10px; }
|
|
|
+.mb-15 { margin-bottom: 15px; }
|
|
|
+
|
|
|
+/* 评分控制台 */
|
|
|
+.mark-control-bar { margin-top: 15px; background: #fdf6ec; border: 1px dashed #e6a23c; border-radius: 4px; padding: 15px; display: flex; align-items: center; flex-wrap: wrap; }
|
|
|
+.control-item { display: flex; align-items: center; margin-right: 30px; }
|
|
|
+.control-label { font-size: 14px; font-weight: bold; color: #606266; }
|
|
|
+.max-score-hint { font-size: 13px; color: #909399; margin-left: 8px; }
|
|
|
+.review-item { flex: 1; min-width: 300px; }
|
|
|
+
|
|
|
+/* 组合材料题 */
|
|
|
+.material-stem { background: #f4f4f5; padding: 15px; border-radius: 4px; border-left: 4px solid #909399; margin-bottom: 15px; }
|
|
|
+.material-tag { font-weight: bold; color: #303133; }
|
|
|
+.sub-questions-container { padding-left: 15px; border-left: 2px dashed #dcdfe6; }
|
|
|
+.sub-question-item { margin-bottom: 25px; padding-bottom: 15px; border-bottom: 1px solid #f2f6fc; }
|
|
|
+</style>
|