first commit
This commit is contained in:
113
templates/admin_exams.html
Normal file
113
templates/admin_exams.html
Normal file
@@ -0,0 +1,113 @@
|
||||
{% extends "admin_base.html" %}
|
||||
|
||||
{% block title %}考试管理 - 智联青云管理后台{% endblock %}
|
||||
|
||||
{% block admin_content %}
|
||||
<div class="space-y-6">
|
||||
<h1 class="text-2xl font-bold text-slate-900">考试管理</h1>
|
||||
|
||||
<div class="bg-white shadow-sm rounded-lg border border-slate-200 overflow-hidden">
|
||||
<table class="min-w-full divide-y divide-slate-200">
|
||||
<thead class="bg-slate-50">
|
||||
<tr>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-slate-500 uppercase">ID</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-slate-500 uppercase">标题</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-slate-500 uppercase">科目</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-slate-500 uppercase">出题人</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-slate-500 uppercase">状态</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-slate-500 uppercase">创建时间</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-slate-500 uppercase">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="exams-tbody" class="bg-white divide-y divide-slate-200"></tbody>
|
||||
</table>
|
||||
<div id="loading-spinner" class="text-center py-8 text-slate-400 hidden">
|
||||
<svg class="animate-spin h-6 w-6 mx-auto" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
||||
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
||||
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
||||
</svg>
|
||||
<span class="text-sm">加载中...</span>
|
||||
</div>
|
||||
<div id="empty-msg" class="text-center py-12 text-slate-400 hidden">暂无考试</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% block scripts %}
|
||||
<script>
|
||||
const statusMap = {
|
||||
'available': ['可用', 'bg-green-100 text-green-800'],
|
||||
'closed': ['已关闭', 'bg-red-100 text-red-800'],
|
||||
'draft': ['草稿', 'bg-yellow-100 text-yellow-800'],
|
||||
'scheduled': ['已排期', 'bg-blue-100 text-blue-800']
|
||||
};
|
||||
|
||||
async function loadExams() {
|
||||
const tbody = document.getElementById('exams-tbody');
|
||||
const spinner = document.getElementById('loading-spinner');
|
||||
const empty = document.getElementById('empty-msg');
|
||||
|
||||
tbody.innerHTML = '';
|
||||
spinner.classList.remove('hidden');
|
||||
empty.classList.add('hidden');
|
||||
|
||||
try {
|
||||
const res = await fetch('/api/admin/exams');
|
||||
const data = await res.json();
|
||||
spinner.classList.add('hidden');
|
||||
|
||||
if (!data.success || !data.exams.length) {
|
||||
empty.classList.remove('hidden');
|
||||
return;
|
||||
}
|
||||
|
||||
let html = '';
|
||||
data.exams.forEach(e => {
|
||||
const [statusText, statusClass] = statusMap[e.status] || [e.status, 'bg-slate-100 text-slate-800'];
|
||||
const isAvailable = e.status === 'available';
|
||||
html += `<tr>
|
||||
<td class="px-6 py-4 text-sm text-slate-900">${e.id}</td>
|
||||
<td class="px-6 py-4 text-sm text-slate-900 max-w-xs truncate">${e.title}</td>
|
||||
<td class="px-6 py-4 text-sm text-slate-500">${e.subject || '-'}</td>
|
||||
<td class="px-6 py-4 text-sm text-slate-500">${e.creator_name || '-'}</td>
|
||||
<td class="px-6 py-4"><span class="px-2 py-1 text-xs rounded-full ${statusClass}">${statusText}</span></td>
|
||||
<td class="px-6 py-4 text-sm text-slate-500">${e.created_at}</td>
|
||||
<td class="px-6 py-4 space-x-2">
|
||||
<button onclick="toggleStatus(${e.id}, '${e.status}')" class="px-2 py-1 text-xs ${isAvailable ? 'bg-yellow-100 text-yellow-700 border-yellow-300' : 'bg-green-100 text-green-700 border-green-300'} border rounded hover:opacity-80">${isAvailable ? '停止' : '恢复'}</button>
|
||||
<button onclick="deleteExam(${e.id}, '${e.title.replace(/'/g, "\\'")}')" class="px-2 py-1 text-xs bg-red-100 text-red-700 border border-red-300 rounded hover:bg-red-200">删除</button>
|
||||
</td>
|
||||
</tr>`;
|
||||
});
|
||||
tbody.innerHTML = html;
|
||||
} catch(e) {
|
||||
spinner.classList.add('hidden');
|
||||
empty.textContent = '加载失败,请稍后重试';
|
||||
empty.classList.remove('hidden');
|
||||
}
|
||||
}
|
||||
async function toggleStatus(id, currentStatus) {
|
||||
const newStatus = currentStatus === 'available' ? 'closed' : 'available';
|
||||
const actionText = currentStatus === 'available' ? '停止' : '恢复';
|
||||
if (!confirm(`确定${actionText}该考试?`)) return;
|
||||
try {
|
||||
const res = await fetch(`/api/admin/exams/${id}/status`, {
|
||||
method: 'PUT',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({status: newStatus})
|
||||
});
|
||||
const data = await res.json();
|
||||
if (data.success) { loadExams(); } else { alert(data.message || '操作失败'); }
|
||||
} catch(e) { alert('网络错误'); }
|
||||
}
|
||||
|
||||
async function deleteExam(id, title) {
|
||||
if (!confirm(`确定删除考试「${title}」?此操作不可恢复!`)) return;
|
||||
try {
|
||||
const res = await fetch(`/api/admin/exams/${id}`, {method: 'DELETE'});
|
||||
const data = await res.json();
|
||||
if (data.success) { loadExams(); } else { alert(data.message || '操作失败'); }
|
||||
} catch(e) { alert('网络错误'); }
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', loadExams);
|
||||
</script>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user