Files
zlqy/templates/exam_list.html

249 lines
17 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="futuristic-card flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4 p-6">
<div>
<h1 class="text-2xl font-bold flex items-center bg-gradient-to-r from-cyan-400 via-blue-500 to-purple-600 bg-clip-text text-transparent">
<span class="w-10 h-10 bg-gradient-to-br from-cyan-500 to-blue-600 text-white rounded-xl flex items-center justify-center mr-3 shadow-lg shadow-cyan-500/50">📝</span>
考试中心
</h1>
<p class="text-slate-400 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="btn-futuristic inline-flex items-center px-4 py-2.5 text-sm font-medium">
<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="futuristic-card p-5 flex flex-wrap gap-4 items-center justify-between sticky top-20 z-10">
<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-cyan-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-cyan-500/30 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-cyan-500/50 focus:border-cyan-500 bg-slate-900/50 text-slate-200 placeholder-slate-500 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-cyan-500/30 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-cyan-500/50 focus:border-cyan-500 bg-slate-900/50 text-slate-200 hover:bg-slate-800/50 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-cyan-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-cyan-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="btn-futuristic w-full sm:w-auto px-5 py-2.5 text-sm font-medium">
搜索
</button>
{% if search_query or subject_filter %}
<a href="/exams" class="w-full sm:w-auto px-5 py-2.5 bg-slate-800/50 text-slate-300 rounded-xl hover:bg-slate-700/50 text-sm font-medium transition-all text-center border border-cyan-500/30 hover:border-cyan-500/50">
重置
</a>
{% endif %}
</form>
{% if search_query or subject_filter %}
<div class="mt-3 w-full text-xs text-slate-400 bg-slate-900/30 px-3 py-2 rounded-lg border border-cyan-500/20 inline-block">
<span class="font-medium text-cyan-400">筛选结果:</span>
{% if search_query %}包含 "<span class="text-cyan-300">{{ search_query }}</span>"{% endif %}
{% if subject_filter %}{% if search_query %}{% endif %}科目为 "<span class="text-cyan-300">{{ subject_filter }}</span>"{% endif %}
<span class="ml-2 text-slate-500">共找到 {{ 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 futuristic-card border-dashed">
<svg class="w-16 h-16 mb-4 text-cyan-500/30" 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-cyan-500 to-blue-600' %}
{% 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-purple-500 to-indigo-600' %}
{% 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-pink-500 to-rose-600' %}
{% 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-600' %}
{% 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-600' %}
{% 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="futuristic-card-dark group hover-lift flex flex-col relative overflow-hidden transition-all duration-300 transform hover:scale-105">
<!-- 卡片封面海报 -->
<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/10 blur-2xl rounded-full"></div>
{% if exam.status == 'closed' %}
<span class="badge-futuristic absolute top-4 right-4 bg-slate-900/50 backdrop-blur-md text-slate-300 border-slate-700/50">已关闭</span>
{% else %}
<span class="badge-futuristic absolute top-4 right-4 bg-white/90 backdrop-blur-md text-cyan-600 border-cyan-500/30">
<span class="w-1.5 h-1.5 rounded-full bg-cyan-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-gradient-to-br from-slate-800 to-slate-900 rounded-2xl flex items-center justify-center text-cyan-400 shadow-lg shadow-cyan-500/30 border-2 border-cyan-500/30 group-hover:scale-110 group-hover:shadow-cyan-500/50 transition-all 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="badge-futuristic">
{{ exam.subject }}
</span>
</div>
<h3 class="text-lg font-bold mb-3 line-clamp-2 bg-gradient-to-r from-blue-400 via-purple-400 to-pink-400 bg-clip-text text-transparent group-hover:from-blue-300 group-hover:via-purple-300 group-hover:to-pink-300 transition-all flex-1">{{ exam.title }}</h3>
<div class="grid grid-cols-2 gap-3 mb-5 bg-slate-900/30 p-3 rounded-xl border border-cyan-500/20">
<div>
<span class="text-[10px] text-slate-500 block mb-0.5">满分 / 题目</span>
<span class="text-xs font-semibold text-cyan-300">{{ exam.total_score }}分 / {{ exam.questions|fromjson|length }}题</span>
</div>
<div>
<span class="text-[10px] text-slate-500 block mb-0.5">考试时长</span>
<span class="text-xs font-semibold text-cyan-300">{{ exam.duration }} 分钟</span>
</div>
</div>
{% if exam.scheduled_start or exam.scheduled_end %}
<div class="mb-4 text-xs text-slate-400 flex flex-col gap-1 border-l-2 border-cyan-500/50 pl-2">
{% if exam.scheduled_start %}
<div class="flex items-center">
<svg class="w-3.5 h-3.5 mr-1 text-cyan-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-cyan-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-cyan-500/20 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-900/30 px-2.5 py-1.5 rounded-lg border border-cyan-500/30">
{% if sub.graded %}
<span class="text-xs font-medium text-slate-400 mr-2 border-r border-slate-600 pr-2">已批改</span>
<span class="text-sm font-bold text-cyan-400">{{ sub.score }} <span class="text-[10px] text-slate-500 font-normal">/ {{ exam.total_score }}</span></span>
{% else %}
<span class="text-xs font-medium text-amber-400 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="btn-outline-futuristic ml-auto inline-flex items-center px-4 py-2 text-xs font-medium">
查看试卷
</a>
{% else %}
<div class="text-xs text-slate-400 flex items-center">
<span class="w-5 h-5 rounded-full bg-slate-800 flex items-center justify-center mr-1.5 text-[10px] text-cyan-400 border border-cyan-500/30">{{ 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="btn-futuristic ml-auto inline-flex items-center px-5 py-2 text-sm font-medium">
开始考试
</a>
{% else %}
<span class="ml-auto text-sm text-slate-500 font-medium px-4 py-2 bg-slate-800/50 rounded-xl border border-slate-700/50">已关闭</span>
{% endif %}
{% endif %}
</div>
{% if user and can_grade_exam(user, exam) %}
<div class="mt-4 pt-3 border-t border-cyan-500/20 flex flex-wrap gap-2 justify-end">
<a href="/exams/{{ exam.id }}/submissions" class="px-3 py-1.5 bg-slate-800/50 text-slate-300 hover:bg-slate-700/50 hover:text-slate-200 rounded-lg text-xs font-medium transition-colors border border-cyan-500/30">提交情况</a>
<a href="/exams/{{ exam.id }}/print" class="px-3 py-1.5 bg-slate-800/50 text-slate-300 hover:bg-slate-700/50 hover:text-slate-200 rounded-lg text-xs font-medium transition-colors border border-cyan-500/30">打印试卷</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-500/20 text-orange-400 hover:bg-orange-500/30 rounded-lg text-xs font-medium transition-colors border border-orange-500/30">关闭考试</button>
{% else %}
<button onclick="toggleExamStatus({{ exam.id }}, 'available')" class="px-3 py-1.5 bg-green-500/20 text-green-400 hover:bg-green-500/30 rounded-lg text-xs font-medium transition-colors border border-green-500/30">开放考试</button>
{% endif %}
<button onclick="deleteExam({{ exam.id }})" class="px-3 py-1.5 bg-red-500/20 text-red-400 hover:bg-red-500/30 rounded-lg text-xs font-medium transition-colors border border-red-500/30">删除</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 %}