192 lines
9.9 KiB
HTML
192 lines
9.9 KiB
HTML
{% extends "base.html" %}
|
||
{% block title %}批改试卷 - 智联青云{% endblock %}
|
||
{% block content %}
|
||
<div class="max-w-4xl mx-auto space-y-6">
|
||
<div class="flex justify-between items-center">
|
||
<div>
|
||
<h1 class="text-2xl font-bold text-slate-900">批改试卷</h1>
|
||
<p class="text-sm text-slate-500 mt-1">{{ exam.title }} · 考生:{{ submission.user.name if submission.user else '未知' }} · 提交时间:{{ submission.submitted_at }}</p>
|
||
</div>
|
||
<div class="flex items-center space-x-3">
|
||
{% if next_ungraded %}
|
||
<a href="/exams/{{ exam.id }}/grade/{{ next_ungraded }}" class="inline-flex items-center px-3 py-1.5 bg-yellow-50 border border-yellow-300 text-yellow-700 text-sm font-medium rounded-md hover:bg-yellow-100">
|
||
下一个未批改 →
|
||
</a>
|
||
{% endif %}
|
||
<a href="/exams/{{ exam.id }}/submissions" class="text-sm text-slate-500 hover:text-slate-700">← 返回提交列表</a>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 批改状态提示 -->
|
||
{% if submission.graded %}
|
||
<div class="bg-green-50 border border-green-200 rounded-lg p-3 flex items-center justify-between">
|
||
<span class="text-sm text-green-700">该试卷已批改完成,得分:{{ submission.score }}/{{ exam.total_score }},批改人:{{ submission.graded_by }}</span>
|
||
<span class="text-xs text-green-500">可重新批改覆盖</span>
|
||
</div>
|
||
{% endif %}
|
||
|
||
{% for q in questions %}
|
||
<div class="bg-white shadow-sm rounded-lg p-6 border border-slate-200">
|
||
<div class="flex items-start space-x-4">
|
||
<span class="flex-shrink-0 w-8 h-8 bg-slate-100 rounded-full flex items-center justify-center text-slate-600 font-medium">{{ loop.index }}</span>
|
||
<div class="flex-1 space-y-3">
|
||
<div class="flex justify-between items-start">
|
||
<div>
|
||
<span class="text-xs font-medium px-2 py-0.5 rounded
|
||
{% if q.type == 'choice' %}bg-blue-50 text-blue-700
|
||
{% elif q.type == 'fill' %}bg-green-50 text-green-700
|
||
{% else %}bg-purple-50 text-purple-700{% endif %}">
|
||
{% if q.type == 'choice' %}选择题{% elif q.type == 'fill' %}填空题{% else %}解答题{% endif %}
|
||
</span>
|
||
<p class="mt-2 text-lg text-slate-900">{{ q.content }}</p>
|
||
{% if q.get('images') %}
|
||
<div class="mt-2 flex flex-wrap gap-2">
|
||
{% for img in q.images %}
|
||
<img src="{{ img }}" class="max-h-48 rounded border border-slate-200 cursor-pointer" onclick="window.open(this.src)" alt="题目图片">
|
||
{% endfor %}
|
||
</div>
|
||
{% endif %}
|
||
</div>
|
||
<span class="text-sm text-slate-400 whitespace-nowrap ml-4">({{ q.score }}分)</span>
|
||
</div>
|
||
|
||
{% if q.type == 'choice' %}
|
||
<div class="space-y-2">
|
||
{% for opt in q.options %}
|
||
{% set letter = ['A','B','C','D'][loop.index0] %}
|
||
{% set is_answer = letter == q.get('answer','') %}
|
||
{% set is_selected = letter == answers.get(q.id|string,'') %}
|
||
<div class="flex items-center space-x-3 p-2 rounded border
|
||
{% if is_answer %}border-green-300 bg-green-50
|
||
{% elif is_selected and not is_answer %}border-red-300 bg-red-50
|
||
{% else %}border-slate-100{% endif %}">
|
||
<span class="text-sm text-slate-700">{{ letter }}. {{ opt }}</span>
|
||
{% if is_selected %}<span class="text-xs {% if is_answer %}text-green-600{% else %}text-red-500{% endif %} font-medium">← 考生选择</span>{% endif %}
|
||
{% if is_answer %}<span class="text-xs text-green-600 font-medium">✓ 正确</span>{% endif %}
|
||
</div>
|
||
{% endfor %}
|
||
<div class="text-sm text-slate-500 mt-1">
|
||
自动判分:{% if answers.get(q.id|string,'') == q.get('answer','') %}
|
||
<span class="text-green-600 font-medium">+{{ q.score }}分</span>
|
||
{% else %}
|
||
<span class="text-red-500 font-medium">0分</span>
|
||
{% endif %}
|
||
</div>
|
||
</div>
|
||
{% else %}
|
||
<div class="p-3 bg-slate-50 rounded-lg border border-slate-200">
|
||
<div class="text-sm text-slate-500 mb-1">考生答案:</div>
|
||
<div class="text-slate-800 whitespace-pre-wrap">{{ answers.get(q.id|string, '(未作答)') | render_images }}</div>
|
||
</div>
|
||
{% if q.get('answer') %}
|
||
<div class="p-3 bg-green-50 rounded-lg border border-green-200">
|
||
<div class="text-sm text-green-600 mb-1">参考答案:</div>
|
||
<div class="text-green-800 whitespace-pre-wrap">{{ q.answer }}</div>
|
||
</div>
|
||
{% endif %}
|
||
<div class="flex items-center space-x-3 mt-2">
|
||
<label class="text-sm text-slate-600">给分:</label>
|
||
<input type="number" id="score-{{ q.id }}" min="0" max="{{ q.score }}"
|
||
value="{{ question_scores.get(q.id|string, 0) }}"
|
||
class="grade-score w-20 px-2 py-1 border border-slate-300 rounded text-sm" data-max="{{ q.score }}" data-qid="{{ q.id }}">
|
||
<span class="text-sm text-slate-400">/ {{ q.score }}</span>
|
||
<!-- 快速给分按钮 -->
|
||
<div class="flex space-x-1">
|
||
<button type="button" onclick="quickScore('{{ q.id }}', 0)" class="px-2 py-0.5 text-xs rounded border border-red-200 text-red-600 hover:bg-red-50">0分</button>
|
||
<button type="button" onclick="quickScore('{{ q.id }}', {{ (q.score / 2)|int }})" class="px-2 py-0.5 text-xs rounded border border-yellow-200 text-yellow-600 hover:bg-yellow-50">{{ (q.score / 2)|int }}分</button>
|
||
<button type="button" onclick="quickScore('{{ q.id }}', {{ q.score }})" class="px-2 py-0.5 text-xs rounded border border-green-200 text-green-600 hover:bg-green-50">满分</button>
|
||
</div>
|
||
</div>
|
||
{% endif %}
|
||
{% if q.get('explanation') %}
|
||
<div class="p-3 bg-indigo-50 rounded-lg border border-indigo-200 mt-2">
|
||
<div class="text-sm text-indigo-600 mb-1 font-medium">题目解析:</div>
|
||
<div class="text-indigo-900 text-sm whitespace-pre-wrap">{{ q.explanation }}</div>
|
||
</div>
|
||
{% endif %}
|
||
</div>
|
||
|
||
<div class="flex justify-between items-center bg-white shadow-sm rounded-lg p-6 border border-slate-200 sticky bottom-4">
|
||
<div class="text-lg font-medium text-slate-900">总分:<span id="total-score" class="text-primary">0</span> / {{ exam.total_score }}</div>
|
||
<div class="flex items-center space-x-3">
|
||
{% if next_ungraded %}
|
||
<span class="text-sm text-slate-400">批改后自动跳转下一个</span>
|
||
{% endif %}
|
||
<button onclick="submitGrade()" class="px-8 py-3 bg-primary text-white rounded-lg font-medium hover:bg-blue-700">提交批改</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
{% endblock %}
|
||
{% block scripts %}
|
||
<script>
|
||
function quickScore(qid, score) {
|
||
document.getElementById('score-' + qid).value = score;
|
||
recalcTotal();
|
||
}
|
||
|
||
function recalcTotal() {
|
||
let total = 0;
|
||
// 选择题自动得分
|
||
{% for q in questions %}
|
||
{% if q.type == 'choice' %}
|
||
{% if answers.get(q.id|string,'') == q.get('answer','') %}
|
||
total += {{ q.score }};
|
||
{% endif %}
|
||
{% elif q.type == 'fill' %}
|
||
{% set student_ans = answers.get(q.id|string,'').strip() %}
|
||
{% set correct_answers = q.get('answer','').split('|') %}
|
||
{% if student_ans in correct_answers %}
|
||
total += {{ q.score }};
|
||
{% endif %}
|
||
{% endif %}
|
||
{% endfor %}
|
||
// 主观题手动得分
|
||
document.querySelectorAll('.grade-score').forEach(input => {
|
||
total += parseInt(input.value) || 0;
|
||
});
|
||
document.getElementById('total-score').textContent = total;
|
||
}
|
||
|
||
document.querySelectorAll('.grade-score').forEach(input => {
|
||
input.addEventListener('input', recalcTotal);
|
||
});
|
||
recalcTotal();
|
||
|
||
function submitGrade() {
|
||
const scores = {};
|
||
{% for q in questions %}
|
||
{% if q.type == 'choice' %}
|
||
{% if answers.get(q.id|string,'') == q.get('answer','') %}
|
||
scores['{{ q.id }}'] = {{ q.score }};
|
||
{% else %}
|
||
scores['{{ q.id }}'] = 0;
|
||
{% endif %}
|
||
{% elif q.type == 'fill' %}
|
||
{% set student_ans = answers.get(q.id|string,'').strip() %}
|
||
{% set correct_answers = q.get('answer','').split('|') %}
|
||
{% if student_ans in correct_answers %}
|
||
scores['{{ q.id }}'] = {{ q.score }};
|
||
{% else %}
|
||
scores['{{ q.id }}'] = 0;
|
||
{% endif %}
|
||
{% else %}
|
||
scores['{{ q.id }}'] = parseInt(document.getElementById('score-{{ q.id }}').value) || 0;
|
||
{% endif %}
|
||
{% endfor %}
|
||
fetch('/api/exams/{{ exam.id }}/grade/{{ submission.id }}', {
|
||
method:'POST', headers:{'Content-Type':'application/json'},
|
||
body: JSON.stringify({scores})
|
||
}).then(r=>r.json()).then(data=>{
|
||
if(data.success) {
|
||
alert('批改完成!总分:'+data.total_score);
|
||
{% if next_ungraded %}
|
||
window.location.href='/exams/{{ exam.id }}/grade/{{ next_ungraded }}';
|
||
{% else %}
|
||
window.location.href='/exams/{{ exam.id }}/submissions';
|
||
{% endif %}
|
||
}
|
||
else alert(data.message);
|
||
}).catch(()=>alert('批改失败'));
|
||
}
|
||
</script>
|
||
{% endblock %} |