mobile app database models admin_contests admin_dashboard admin_exams admin_posts admin_teacher_applications admin_users apply_contest apply_teacher base chat contest_detail contest_list exam_result forum notifications profile themes

This commit is contained in:
2026-03-03 16:02:23 +08:00
parent 0764566742
commit 9a08140623
30 changed files with 1531 additions and 834 deletions

View File

@@ -3,18 +3,18 @@
{% block content %}
<div class="max-w-5xl mx-auto">
{% if need_password %}
<div class="bg-white shadow-sm rounded-lg p-8 border border-slate-200 max-w-md mx-auto mt-12">
<div class="futuristic-card p-8 max-w-md mx-auto mt-12">
<div class="text-center mb-6">
<svg class="w-16 h-16 mx-auto text-amber-500 mb-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"/></svg>
<h2 class="text-xl font-bold text-slate-900">{{ exam.title }}</h2>
<h2 class="text-xl font-bold text-slate-900 bg-gradient-to-r from-purple-600 to-blue-600 bg-clip-text text-transparent">{{ exam.title }}</h2>
<p class="text-sm text-slate-500 mt-1">该考试需要输入密码才能进入</p>
</div>
<div id="password-form">
<input type="password" id="exam-pwd" placeholder="请输入考试密码"
class="w-full px-4 py-3 border border-slate-300 rounded-md text-center text-lg focus:outline-none focus:ring-2 focus:ring-primary focus:border-primary"
class="input-futuristic w-full text-center text-lg"
onkeydown="if(event.key==='Enter')verifyPassword()">
<p id="pwd-error" class="text-red-500 text-sm text-center mt-2 hidden">密码错误,请重试</p>
<button onclick="verifyPassword()" class="w-full mt-4 px-4 py-3 bg-primary text-white rounded-md font-medium hover:bg-blue-700">
<button onclick="verifyPassword()" class="btn-futuristic w-full mt-4">
验证并进入考试
</button>
<a href="/exams" class="block text-center mt-3 text-sm text-slate-500 hover:text-slate-700">返回列表</a>
@@ -36,27 +36,27 @@
}
</script>
{% elif existing_submission %}
<div class="bg-yellow-50 border border-yellow-200 rounded-lg p-6 text-center">
<div class="futuristic-card p-6 text-center">
<p class="text-yellow-800 font-medium">您已提交过该试卷</p>
<a href="/exams/{{ exam.id }}/result" class="mt-3 inline-block px-4 py-2 bg-primary text-white rounded-md text-sm">查看结果</a>
<a href="/exams/{{ exam.id }}/result" class="mt-3 inline-block btn-futuristic text-sm">查看结果</a>
</div>
{% elif exam.status == 'closed' %}
<div class="bg-red-50 border border-red-200 rounded-lg p-6 text-center">
<div class="futuristic-card p-6 text-center">
<p class="text-red-800 font-medium">该考试已关闭</p>
<a href="/exams" class="mt-3 inline-block px-4 py-2 bg-slate-500 text-white rounded-md text-sm">返回列表</a>
<a href="/exams" class="mt-3 inline-block btn-outline-futuristic text-sm">返回列表</a>
</div>
{% elif schedule_status == 'not_started' %}
<div class="bg-blue-50 border border-blue-200 rounded-lg p-6 text-center">
<p class="text-blue-800 font-medium text-xl mb-2">⏰ 考试尚未开始</p>
<div class="futuristic-card p-6 text-center">
<p class="text-blue-800 font-medium text-xl mb-2 bg-gradient-to-r from-blue-600 to-purple-600 bg-clip-text text-transparent">⏰ 考试尚未开始</p>
<p class="text-blue-600">预定开始时间:{{ exam.scheduled_start.strftime('%Y-%m-%d %H:%M') }}</p>
{% if exam.scheduled_end %}
<p class="text-blue-600">预定结束时间:{{ exam.scheduled_end.strftime('%Y-%m-%d %H:%M') }}</p>
{% endif %}
<div class="mt-4">
<p class="text-sm text-blue-500 mb-2">距离开考还有:</p>
<div id="countdown" class="text-3xl font-bold text-blue-700"></div>
<div id="countdown" class="text-3xl font-bold bg-gradient-to-r from-blue-600 to-purple-600 bg-clip-text text-transparent"></div>
</div>
<a href="/exams" class="mt-4 inline-block px-4 py-2 bg-slate-500 text-white rounded-md text-sm">返回列表</a>
<a href="/exams" class="mt-4 inline-block btn-outline-futuristic text-sm">返回列表</a>
</div>
<script>
(function() {
@@ -79,17 +79,17 @@
})();
</script>
{% elif schedule_status == 'ended' %}
<div class="bg-gray-50 border border-gray-200 rounded-lg p-6 text-center">
<div class="futuristic-card p-6 text-center">
<p class="text-gray-800 font-medium">该考试已结束</p>
<p class="text-gray-600 text-sm mt-1">结束时间:{{ exam.scheduled_end.strftime('%Y-%m-%d %H:%M') }}</p>
<a href="/exams" class="mt-3 inline-block px-4 py-2 bg-slate-500 text-white rounded-md text-sm">返回列表</a>
<a href="/exams" class="mt-3 inline-block btn-outline-futuristic text-sm">返回列表</a>
</div>
{% else %}
<!-- 顶部信息栏 -->
<div class="bg-white shadow-sm rounded-lg p-4 border border-slate-200 sticky top-0 z-20">
<div class="futuristic-card p-4 sticky top-0 z-20">
<div class="flex justify-between items-center">
<div>
<h1 class="text-lg font-bold text-slate-900">{{ exam.title }}</h1>
<h1 class="text-lg font-bold bg-gradient-to-r from-purple-600 to-blue-600 bg-clip-text text-transparent">{{ exam.title }}</h1>
<div class="mt-1 text-sm text-slate-500">
{{ exam.subject }} · {{ exam.duration }}分钟 · 满分{{ exam.total_score }}分
{% if exam.scheduled_end %}
@@ -111,15 +111,15 @@
</div>
</div>
<!-- 进度条 -->
<div class="mt-3 w-full bg-slate-100 rounded-full h-1.5">
<div id="progress-bar" class="bg-primary h-1.5 rounded-full transition-all duration-300" style="width:0%"></div>
<div class="mt-3 progress-futuristic">
<div id="progress-bar" class="progress-bar-futuristic transition-all duration-300" style="width:0%"></div>
</div>
</div>
<div class="mt-4 flex gap-4">
<!-- 题号导航面板 -->
<div class="hidden lg:block w-48 flex-shrink-0">
<div class="bg-white shadow-sm rounded-lg p-4 border border-slate-200 sticky top-24">
<div class="futuristic-card p-4 sticky top-24">
<div class="text-sm font-medium text-slate-700 mb-3">题目导航</div>
<div class="grid grid-cols-5 gap-2" id="nav-panel">
{% for q in questions %}
@@ -141,7 +141,7 @@
<!-- 主答题区 -->
<div class="flex-1 min-w-0">
<!-- 移动端题号导航 -->
<div class="lg:hidden mb-4 bg-white shadow-sm rounded-lg p-3 border border-slate-200">
<div class="lg:hidden mb-4 futuristic-card p-3">
<div class="flex flex-wrap gap-1.5" id="nav-panel-mobile">
{% for q in questions %}
<button onclick="goToQuestion({{ loop.index0 }})" id="nav-m-{{ loop.index0 }}"
@@ -154,16 +154,14 @@
<form id="exam-form" onsubmit="handleSubmit(event)">
{% for q in questions %}
<div class="question-card bg-white shadow-sm rounded-lg p-6 border border-slate-200 mb-4" data-index="{{ loop.index0 }}" style="{% if loop.index0 != 0 %}display:none{% endif %}">
<div class="question-card futuristic-card p-6 mb-4" data-index="{{ loop.index0 }}" style="{% if loop.index0 != 0 %}display:none{% endif %}">
<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>
<span class="flex-shrink-0 w-8 h-8 bg-gradient-to-br from-purple-500 to-blue-500 rounded-full flex items-center justify-center text-white font-medium">{{ loop.index }}</span>
<div class="flex-1 space-y-4">
<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 %}">
<span class="badge-futuristic text-xs
{% if q.type == 'choice' %}{% elif q.type == 'fill' %}{% else %}{% endif %}">
{% if q.type == 'choice' %}选择题{% elif q.type == 'fill' %}填空题{% else %}解答题{% endif %}
</span>
<p class="mt-2 text-lg text-slate-900">{{ q.content }}</p>
@@ -187,12 +185,12 @@
{% endfor %}
</div>
{% elif q.type == 'fill' %}
<input type="text" name="q-{{ q.id }}" class="w-full px-3 py-2 border border-slate-300 rounded-md answer-input" placeholder="请输入答案" data-qid="{{ q.id }}" oninput="onAnswerChange({{ q.id }})">
<input type="text" name="q-{{ q.id }}" class="input-futuristic w-full answer-input" placeholder="请输入答案" data-qid="{{ q.id }}" oninput="onAnswerChange({{ q.id }})">
{% else %}
<textarea name="q-{{ q.id }}" rows="6" class="w-full rounded-lg border-slate-300 shadow-sm focus:border-primary focus:ring-primary border px-3 py-2 answer-input" placeholder="请输入您的答案..." data-qid="{{ q.id }}" oninput="onAnswerChange({{ q.id }})"></textarea>
<textarea name="q-{{ q.id }}" rows="6" class="input-futuristic w-full answer-input" placeholder="请输入您的答案..." data-qid="{{ q.id }}" oninput="onAnswerChange({{ q.id }})"></textarea>
<div class="flex items-center gap-2 mt-2">
<button type="button" onclick="examUpload({{ q.id }})" class="inline-flex items-center gap-1 px-3 py-1.5 text-xs border border-slate-200 rounded-lg hover:bg-slate-50 text-slate-600">📷 上传图片</button>
<button type="button" onclick="examCamera({{ q.id }})" class="inline-flex items-center gap-1 px-3 py-1.5 text-xs border border-slate-200 rounded-lg hover:bg-slate-50 text-slate-600">📸 拍照上传</button>
<button type="button" onclick="examUpload({{ q.id }})" class="btn-outline-futuristic inline-flex items-center gap-1 text-xs py-1.5 px-3">📷 上传图片</button>
<button type="button" onclick="examCamera({{ q.id }})" class="btn-outline-futuristic inline-flex items-center gap-1 text-xs py-1.5 px-3">📸 拍照上传</button>
</div>
<div id="img-preview-{{ q.id }}" class="flex flex-wrap gap-2 mt-2"></div>
{% endif %}
@@ -203,16 +201,16 @@
<!-- 分页控制 -->
<div class="flex justify-between items-center mt-4 mb-8">
<button type="button" id="prev-btn" onclick="prevQuestion()" class="px-5 py-2.5 bg-slate-100 text-slate-700 rounded-lg font-medium hover:bg-slate-200 disabled:opacity-40 disabled:cursor-not-allowed" disabled>
<button type="button" id="prev-btn" onclick="prevQuestion()" class="btn-outline-futuristic disabled:opacity-40 disabled:cursor-not-allowed" disabled>
← 上一题
</button>
<span class="text-sm text-slate-500">
<span id="current-num">1</span> / {{ questions|length }} 题
</span>
<button type="button" id="next-btn" onclick="nextQuestion()" class="px-5 py-2.5 bg-primary text-white rounded-lg font-medium hover:bg-blue-700">
<button type="button" id="next-btn" onclick="nextQuestion()" class="btn-futuristic">
下一题 →
</button>
<button type="submit" id="submit-btn" class="px-6 py-2.5 bg-red-600 text-white rounded-lg font-medium hover:bg-red-700 hidden">
<button type="submit" id="submit-btn" class="btn-futuristic bg-gradient-to-r from-red-500 to-pink-600 hidden">
提交试卷
</button>
</div>