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

249 lines
18 KiB
HTML
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.
{% extends "base.html" %}
{% block title %}考试系统 - 智联青云{% endblock %}
{% block content %}
<div class="space-y-8">
<!-- 头部区域 -->
<div class="flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4 bg-white p-6 rounded-2xl shadow-sm border border-slate-100">
<div>
<h1 class="text-2xl font-bold text-slate-900 flex items-center">
<span class="w-10 h-10 bg-indigo-50 text-indigo-600 rounded-xl flex items-center justify-center mr-3 shadow-sm border border-indigo-100">📝</span>
考试中心
</h1>
<p class="text-slate-500 text-sm mt-1 ml-13">海量真题与模拟卷,随时随地进行练习与自测。</p>
</div>
<div class="flex flex-wrap items-center gap-3">
{% if user and (user.role == 'admin' or user.role == 'teacher') %}
<a href="/exams/create" class="inline-flex items-center px-4 py-2.5 bg-primary text-white rounded-xl hover:bg-blue-600 shadow-sm text-sm font-medium transition-all transform hover:-translate-y-0.5">
<svg class="w-4 h-4 mr-1.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"/></svg>
创建新试卷
</a>
{% endif %}
</div>
</div>
<!-- 搜索区域 -->
<div class="bg-white p-5 rounded-2xl shadow-sm border border-slate-100 flex flex-wrap gap-4 items-center justify-between sticky top-20 z-10 glass-panel">
<form method="GET" action="/exams" class="flex flex-wrap items-center gap-3 w-full">
<div class="relative w-full sm:w-auto flex-1">
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<svg class="h-4 w-4 text-slate-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"/></svg>
</div>
<input type="text" name="q" value="{{ search_query or '' }}" placeholder="搜索试卷名称..." class="w-full pl-10 pr-4 py-2.5 border border-slate-200 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-primary/20 focus:border-primary bg-slate-50 transition-all">
</div>
<div class="relative w-full sm:w-auto">
<select name="subject" class="w-full sm:w-32 appearance-none px-4 py-2.5 pl-10 border border-slate-200 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-primary/20 focus:border-primary bg-slate-50 hover:bg-slate-100 transition-colors cursor-pointer">
<option value="">所有科目</option>
{% for subject in all_subjects %}
<option value="{{ subject }}" {% if subject_filter == subject %}selected{% endif %}>{{ subject }}</option>
{% endfor %}
</select>
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<svg class="h-4 w-4 text-slate-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253"/></svg>
</div>
<div class="absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none">
<svg class="h-4 w-4 text-slate-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/></svg>
</div>
</div>
<button type="submit" class="w-full sm:w-auto px-5 py-2.5 bg-slate-800 text-white rounded-xl hover:bg-slate-700 text-sm font-medium transition-colors shadow-sm">
搜索
</button>
{% if search_query or subject_filter %}
<a href="/exams" class="w-full sm:w-auto px-5 py-2.5 bg-slate-100 text-slate-600 rounded-xl hover:bg-slate-200 text-sm font-medium transition-colors text-center border border-slate-200">
重置
</a>
{% endif %}
</form>
{% if search_query or subject_filter %}
<div class="mt-3 w-full text-xs text-slate-500 bg-slate-50 px-3 py-2 rounded-lg border border-slate-100 inline-block">
<span class="font-medium text-slate-700">筛选结果:</span>
{% if search_query %}包含 "<span class="text-primary">{{ search_query }}</span>"{% endif %}
{% if subject_filter %}{% if search_query %}{% endif %}科目为 "<span class="text-primary">{{ subject_filter }}</span>"{% endif %}
<span class="ml-2 text-slate-400">共找到 {{ exams|length }} 份试卷</span>
</div>
{% endif %}
</div>
<!-- 试卷列表 -->
{% if exams|length == 0 %}
<div class="col-span-full py-20 flex flex-col items-center justify-center text-slate-400 bg-white rounded-2xl border border-slate-100 border-dashed">
<svg class="w-16 h-16 mb-4 text-slate-200" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M9 13h6m-3-3v6m5 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/></svg>
<p>暂无符合条件的试卷</p>
{% if user and (user.role == 'admin' or user.role == 'teacher') %}
<p class="text-sm mt-2">点击上方"创建新试卷"按钮开始命题吧</p>
{% endif %}
</div>
{% else %}
<div class="grid gap-6 grid-cols-1 md:grid-cols-2 lg:grid-cols-3">
{% for exam in exams %}
{% set subjectColor = 'blue' %}
{% set subjectGradient = 'from-blue-500 to-cyan-400' %}
{% set subjectIcon = 'M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253' %}
{% if exam.subject == '数学' %}
{% set subjectColor = 'indigo' %}
{% set subjectGradient = 'from-indigo-500 to-purple-400' %}
{% set subjectIcon = 'M9 7h6m0 10v-3m-3 3h.01M9 17h.01M9 14h.01M12 14h.01M15 11h.01M12 11h.01M9 11h.01M7 21h10a2 2 0 002-2V5a2 2 0 00-2-2H7a2 2 0 00-2 2v14a2 2 0 002 2z' %}
{% elif exam.subject == '英语' %}
{% set subjectColor = 'rose' %}
{% set subjectGradient = 'from-rose-500 to-pink-400' %}
{% set subjectIcon = 'M3 5h12M9 3v2m1.048 9.5A18.022 18.022 0 016.412 9m6.088 9h7M11 21l5-10 5 10M12.751 5C11.783 10.77 8.07 15.61 3 18.129' %}
{% elif exam.subject in ['物理', '化学', '生物'] %}
{% set subjectColor = 'emerald' %}
{% set subjectGradient = 'from-emerald-500 to-teal-400' %}
{% set subjectIcon = 'M19.428 15.428a2 2 0 00-1.022-.547l-2.387-.477a6 6 0 00-3.86.517l-.318.158a6 6 0 01-3.86.517L6.05 15.21a2 2 0 00-1.806.547M8 4h8l-1 1v5.172a2 2 0 00.586 1.414l5 5c1.26 1.26.367 3.414-1.415 3.414H4.828c-1.782 0-2.674-2.154-1.414-3.414l5-5A2 2 0 009 10.172V5L8 4z' %}
{% elif exam.subject in ['历史', '地理', '政治'] %}
{% set subjectColor = 'amber' %}
{% set subjectGradient = 'from-amber-500 to-orange-400' %}
{% set subjectIcon = 'M3.055 11H5a2 2 0 012 2v1a2 2 0 002 2 2 2 0 012 2v2.945M8 3.935V5.5A2.5 2.5 0 0010.5 8h.5a2 2 0 012 2 2 2 0 104 0 2 2 0 012-2h1.064M15 20.488V18a2 2 0 012-2h3.064M21 12a9 9 0 11-18 0 9 9 0 0118 0z' %}
{% endif %}
<div class="group bg-white rounded-3xl shadow-sm border border-slate-100 overflow-hidden hover-card-up flex flex-col relative">
<!-- 卡片封面海报 -->
<div class="relative h-28 bg-gradient-to-r {{ subjectGradient }} overflow-hidden">
<div class="absolute inset-0 bg-grid-pattern opacity-10"></div>
<div class="absolute -bottom-10 -right-10 w-32 h-32 bg-white/20 blur-2xl rounded-full"></div>
{% if exam.status == 'closed' %}
<span class="absolute top-4 right-4 bg-slate-900/50 backdrop-blur-md text-white text-xs font-medium px-3 py-1 rounded-full shadow-sm border border-white/10">已关闭</span>
{% else %}
<span class="absolute top-4 right-4 bg-white/90 backdrop-blur-md text-{{subjectColor}}-600 text-xs font-bold px-3 py-1 rounded-full shadow-sm flex items-center">
<span class="w-1.5 h-1.5 rounded-full bg-{{subjectColor}}-500 mr-1.5 animate-pulse"></span>进行中
</span>
{% endif %}
</div>
<!-- 悬浮图标 -->
<div class="absolute top-[4.5rem] left-6">
<div class="w-14 h-14 bg-white rounded-2xl flex items-center justify-center text-{{subjectColor}}-500 shadow-md border-4 border-white group-hover:scale-110 transition-transform duration-300">
<svg class="w-7 h-7" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="{{subjectIcon}}"/></svg>
</div>
</div>
<div class="p-6 pt-12 flex-1 flex flex-col">
<div class="flex items-start mb-3">
<span class="inline-flex items-center px-2.5 py-1 rounded-lg bg-slate-100 text-slate-600 text-xs font-medium border border-slate-200">
{{ exam.subject }}
</span>
</div>
<h3 class="text-lg font-bold text-slate-900 mb-3 line-clamp-2 group-hover:text-{{subjectColor}}-600 transition-colors flex-1">{{ exam.title }}</h3>
<div class="grid grid-cols-2 gap-3 mb-5 bg-slate-50 p-3 rounded-xl border border-slate-100">
<div>
<span class="text-[10px] text-slate-400 block mb-0.5">满分 / 题目</span>
<span class="text-xs font-semibold text-slate-700">{{ exam.total_score }}分 / {{ exam.questions|fromjson|length }}题</span>
</div>
<div>
<span class="text-[10px] text-slate-400 block mb-0.5">考试时长</span>
<span class="text-xs font-semibold text-slate-700">{{ exam.duration }} 分钟</span>
</div>
</div>
{% if exam.scheduled_start or exam.scheduled_end %}
<div class="mb-4 text-xs text-slate-500 flex flex-col gap-1 border-l-2 border-{{subjectColor}}-200 pl-2">
{% if exam.scheduled_start %}
<div class="flex items-center">
<svg class="w-3.5 h-3.5 mr-1 text-slate-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
开始:{{ exam.scheduled_start.strftime('%m-%d %H:%M') }}
</div>
{% endif %}
{% if exam.scheduled_end %}
<div class="flex items-center">
<svg class="w-3.5 h-3.5 mr-1 text-slate-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
截止:{{ exam.scheduled_end.strftime('%m-%d %H:%M') }}
</div>
{% endif %}
</div>
{% endif %}
<div class="mt-auto pt-4 border-t border-slate-100 flex items-center justify-between gap-2 flex-wrap">
{% set sub = user_submissions.get(exam.id) %}
{% if sub %}
<div class="flex items-center bg-slate-50 px-2.5 py-1.5 rounded-lg border border-slate-200">
{% if sub.graded %}
<span class="text-xs font-medium text-slate-600 mr-2 border-r border-slate-200 pr-2">已批改</span>
<span class="text-sm font-bold text-{{subjectColor}}-600">{{ sub.score }} <span class="text-[10px] text-slate-400 font-normal">/ {{ exam.total_score }}</span></span>
{% else %}
<span class="text-xs font-medium text-amber-600 flex items-center">
<svg class="w-3.5 h-3.5 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
待批改
</span>
{% endif %}
</div>
<a href="/exams/{{ exam.id }}/result" class="ml-auto inline-flex items-center px-4 py-2 border border-slate-200 text-xs font-medium rounded-xl text-slate-700 bg-white hover:bg-slate-50 hover:border-slate-300 transition-colors shadow-sm">
查看试卷
</a>
{% else %}
<div class="text-xs text-slate-400 flex items-center">
<span class="w-5 h-5 rounded-full bg-slate-100 flex items-center justify-center mr-1.5 text-[10px]">{{ exam.creator.name[0] if exam.creator else '?' }}</span>
{{ exam.creator.name if exam.creator else '未知出题人' }}
</div>
{% if exam.status != 'closed' %}
<a href="/exams/{{ exam.id }}" class="ml-auto inline-flex items-center px-5 py-2 border border-transparent text-sm font-medium rounded-xl shadow-sm text-white bg-{{subjectColor}}-600 hover:bg-{{subjectColor}}-700 transition-colors transform hover:-translate-y-0.5">
开始考试
</a>
{% else %}
<span class="ml-auto text-sm text-slate-400 font-medium px-4 py-2 bg-slate-50 rounded-xl">已关闭</span>
{% endif %}
{% endif %}
</div>
{% if user and (user.role == 'admin' or user.role == 'teacher') %}
<div class="mt-4 pt-3 border-t border-slate-100/50 flex flex-wrap gap-2 justify-end">
<a href="/exams/{{ exam.id }}/submissions" class="px-3 py-1.5 bg-slate-50 text-slate-600 hover:bg-slate-100 hover:text-slate-900 rounded-lg text-xs font-medium transition-colors border border-slate-200">提交情况</a>
<a href="/exams/{{ exam.id }}/print" class="px-3 py-1.5 bg-slate-50 text-slate-600 hover:bg-slate-100 hover:text-slate-900 rounded-lg text-xs font-medium transition-colors border border-slate-200">打印试卷</a>
{% if user.role == 'admin' or exam.creator_id == user.id %}
{% if exam.status == 'available' %}
<button onclick="toggleExamStatus({{ exam.id }}, 'closed')" class="px-3 py-1.5 bg-orange-50 text-orange-600 hover:bg-orange-100 rounded-lg text-xs font-medium transition-colors border border-orange-200">关闭考试</button>
{% else %}
<button onclick="toggleExamStatus({{ exam.id }}, 'available')" class="px-3 py-1.5 bg-green-50 text-green-600 hover:bg-green-100 rounded-lg text-xs font-medium transition-colors border border-green-200">开放考试</button>
{% endif %}
<button onclick="deleteExam({{ exam.id }})" class="px-3 py-1.5 bg-red-50 text-red-600 hover:bg-red-100 rounded-lg text-xs font-medium transition-colors border border-red-200">删除</button>
{% endif %}
</div>
{% endif %}
</div>
</div>
{% endfor %}
</div>
{% endif %}
</div>
{% endblock %}
{% block scripts %}
<script>
function toggleExamStatus(examId, status) {
const label = status === 'closed' ? '关闭' : '开放';
if (!confirm(`确定${label}该考试?`)) return;
fetch(`/api/exams/${examId}/status`, {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({ status: status })
}).then(r => r.json()).then(data => {
if (data.success) {
location.reload();
} else {
alert(data.message);
}
}).catch(() => alert('操作失败'));
}
function deleteExam(examId) {
if (!confirm('确定删除该试卷?此操作不可恢复,所有提交记录也将被删除。')) return;
fetch(`/api/exams/${examId}`, {
method: 'DELETE'
}).then(r => r.json()).then(data => {
if (data.success) {
location.reload();
} else {
alert(data.message);
}
}).catch(() => alert('删除失败'));
}
</script>
{% endblock %}