Files
zlqy/templates/exam_grade.html
2026-02-27 10:37:11 +08:00

192 lines
9.9 KiB
HTML
Raw Permalink 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.
{% 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 %}