first commit
This commit is contained in:
249
templates/exam_list.html
Normal file
249
templates/exam_list.html
Normal file
@@ -0,0 +1,249 @@
|
||||
{% 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 %}
|
||||
Reference in New Issue
Block a user