first commit

This commit is contained in:
2026-02-27 10:37:11 +08:00
commit 74f19aad0b
86 changed files with 18642 additions and 0 deletions

View File

@@ -0,0 +1,230 @@
{% extends "admin_base.html" %}
{% block title %}管理后台 - 智联青云{% endblock %}
{% block admin_content %}
<div class="space-y-8">
<div class="flex items-center justify-between">
<div>
<h1 class="text-2xl font-extrabold text-slate-900 tracking-tight">数据概览</h1>
<p class="text-sm text-slate-500 mt-1 font-medium">查看平台运行状态与核心数据</p>
</div>
<button onclick="loadDashboard()" class="w-10 h-10 rounded-xl bg-white text-slate-500 shadow-sm border border-slate-100 hover:text-indigo-600 hover:bg-indigo-50 transition-all flex items-center justify-center group" title="刷新数据">
<svg class="w-5 h-5 group-hover:rotate-180 transition-transform duration-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/></svg>
</button>
</div>
<!-- 统计卡片 -->
<div class="grid grid-cols-2 md:grid-cols-4 gap-4 md:gap-6">
<div class="bg-gradient-to-br from-indigo-50 to-white rounded-2xl p-6 shadow-sm border border-indigo-100/50 relative overflow-hidden group hover:-translate-y-1 transition-transform">
<div class="absolute -right-4 -bottom-4 w-24 h-24 bg-indigo-500/5 rounded-full blur-2xl group-hover:bg-indigo-500/10 transition-colors"></div>
<div class="flex items-center gap-3 mb-3 relative z-10">
<div class="w-10 h-10 rounded-xl bg-indigo-100 text-indigo-600 flex items-center justify-center shadow-inner">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z"/></svg>
</div>
<div class="text-sm font-bold text-slate-600">总用户数</div>
</div>
<div id="stat-users" class="text-3xl font-black text-slate-900 relative z-10">-</div>
</div>
<div class="bg-gradient-to-br from-purple-50 to-white rounded-2xl p-6 shadow-sm border border-purple-100/50 relative overflow-hidden group hover:-translate-y-1 transition-transform">
<div class="absolute -right-4 -bottom-4 w-24 h-24 bg-purple-500/5 rounded-full blur-2xl group-hover:bg-purple-500/10 transition-colors"></div>
<div class="flex items-center gap-3 mb-3 relative z-10">
<div class="w-10 h-10 rounded-xl bg-purple-100 text-purple-600 flex items-center justify-center shadow-inner">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 002-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10"/></svg>
</div>
<div class="text-sm font-bold text-slate-600">赛事总数</div>
</div>
<div id="stat-contests" class="text-3xl font-black text-slate-900 relative z-10">-</div>
</div>
<div class="bg-gradient-to-br from-emerald-50 to-white rounded-2xl p-6 shadow-sm border border-emerald-100/50 relative overflow-hidden group hover:-translate-y-1 transition-transform">
<div class="absolute -right-4 -bottom-4 w-24 h-24 bg-emerald-500/5 rounded-full blur-2xl group-hover:bg-emerald-500/10 transition-colors"></div>
<div class="flex items-center gap-3 mb-3 relative z-10">
<div class="w-10 h-10 rounded-xl bg-emerald-100 text-emerald-600 flex items-center justify-center shadow-inner">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 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>
</div>
<div class="text-sm font-bold text-slate-600">考试总数</div>
</div>
<div id="stat-exams" class="text-3xl font-black text-slate-900 relative z-10">-</div>
</div>
<div class="bg-gradient-to-br from-amber-50 to-white rounded-2xl p-6 shadow-sm border border-amber-100/50 relative overflow-hidden group hover:-translate-y-1 transition-transform">
<div class="absolute -right-4 -bottom-4 w-24 h-24 bg-amber-500/5 rounded-full blur-2xl group-hover:bg-amber-500/10 transition-colors"></div>
<div class="flex items-center gap-3 mb-3 relative z-10">
<div class="w-10 h-10 rounded-xl bg-amber-100 text-amber-600 flex items-center justify-center shadow-inner">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 8h2a2 2 0 012 2v6a2 2 0 01-2 2h-2v4l-4-4H9a1.994 1.994 0 01-1.414-.586m0 0L11 14h4a2 2 0 002-2V6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2v4l.586-.586z"/></svg>
</div>
<div class="text-sm font-bold text-slate-600">社区帖子</div>
</div>
<div id="stat-posts" class="text-3xl font-black text-slate-900 relative z-10">-</div>
</div>
</div>
<!-- 待处理 + 最近活动 -->
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
<!-- 待处理事项 -->
<div class="bg-white rounded-3xl p-6 shadow-sm border border-slate-100 flex flex-col h-full">
<div class="flex items-center gap-2 mb-6">
<div class="w-8 h-8 rounded-lg bg-rose-100 text-rose-500 flex items-center justify-center shadow-inner">
<svg class="w-4 h-4" 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>
</div>
<h2 class="text-lg font-bold text-slate-800">待处理事项</h2>
</div>
<div id="pending-items" class="space-y-3 flex-1">
<div class="flex flex-col items-center justify-center h-48 text-slate-400 border-2 border-dashed border-slate-100 rounded-2xl">
<svg class="animate-spin h-6 w-6 text-indigo-500 mb-2" 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 font-medium">加载中...</span>
</div>
</div>
</div>
<!-- 最近活动 -->
<div class="bg-white rounded-3xl p-6 shadow-sm border border-slate-100 flex flex-col h-full">
<div class="flex items-center gap-2 mb-6">
<div class="w-8 h-8 rounded-lg bg-blue-100 text-blue-500 flex items-center justify-center shadow-inner">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"/></svg>
</div>
<h2 class="text-lg font-bold text-slate-800">最近活动日志</h2>
</div>
<div id="recent-activities" class="space-y-4 flex-1 relative pl-3">
<div class="absolute left-[19px] top-2 bottom-2 w-0.5 bg-slate-100"></div>
<div class="flex flex-col items-center justify-center h-48 text-slate-400 border-2 border-dashed border-slate-100 rounded-2xl ml-4">
<svg class="animate-spin h-6 w-6 text-indigo-500 mb-2" 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 font-medium">加载中...</span>
</div>
</div>
</div>
</div>
<!-- 快捷导航 -->
<div>
<div class="flex items-center gap-2 mb-6">
<div class="w-8 h-8 rounded-lg bg-teal-100 text-teal-600 flex items-center justify-center shadow-inner">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"/></svg>
</div>
<h2 class="text-lg font-bold text-slate-800">快捷操作</h2>
</div>
<div class="grid grid-cols-2 md:grid-cols-4 lg:grid-cols-7 gap-4">
<a href="/admin/contests" class="bg-white rounded-2xl p-5 shadow-sm border border-slate-100 hover:border-indigo-200 hover:shadow-md hover:-translate-y-1 transition-all text-center group flex flex-col items-center">
<div class="w-12 h-12 rounded-xl bg-indigo-50 flex items-center justify-center text-2xl mb-3 group-hover:scale-110 transition-transform">🏆</div>
<div class="text-sm font-bold text-slate-700">杯赛管理</div>
</a>
<a href="/admin/contest-applications" class="bg-white rounded-2xl p-5 shadow-sm border border-slate-100 hover:border-orange-200 hover:shadow-md hover:-translate-y-1 transition-all text-center group flex flex-col items-center">
<div class="w-12 h-12 rounded-xl bg-orange-50 flex items-center justify-center text-2xl mb-3 group-hover:scale-110 transition-transform">📋</div>
<div class="text-sm font-bold text-slate-700">杯赛申请</div>
</a>
<a href="/admin/teacher-applications" class="bg-white rounded-2xl p-5 shadow-sm border border-slate-100 hover:border-purple-200 hover:shadow-md hover:-translate-y-1 transition-all text-center group flex flex-col items-center">
<div class="w-12 h-12 rounded-xl bg-purple-50 flex items-center justify-center text-2xl mb-3 group-hover:scale-110 transition-transform">👨‍🏫</div>
<div class="text-sm font-bold text-slate-700">教师申请</div>
</a>
<a href="/admin/exams" class="bg-white rounded-2xl p-5 shadow-sm border border-slate-100 hover:border-emerald-200 hover:shadow-md hover:-translate-y-1 transition-all text-center group flex flex-col items-center">
<div class="w-12 h-12 rounded-xl bg-emerald-50 flex items-center justify-center text-2xl mb-3 group-hover:scale-110 transition-transform">📝</div>
<div class="text-sm font-bold text-slate-700">考试管理</div>
</a>
<a href="/admin/users" class="bg-white rounded-2xl p-5 shadow-sm border border-slate-100 hover:border-blue-200 hover:shadow-md hover:-translate-y-1 transition-all text-center group flex flex-col items-center">
<div class="w-12 h-12 rounded-xl bg-blue-50 flex items-center justify-center text-2xl mb-3 group-hover:scale-110 transition-transform">👥</div>
<div class="text-sm font-bold text-slate-700">用户管理</div>
</a>
<a href="/admin/posts" class="bg-white rounded-2xl p-5 shadow-sm border border-slate-100 hover:border-amber-200 hover:shadow-md hover:-translate-y-1 transition-all text-center group flex flex-col items-center">
<div class="w-12 h-12 rounded-xl bg-amber-50 flex items-center justify-center text-2xl mb-3 group-hover:scale-110 transition-transform">💬</div>
<div class="text-sm font-bold text-slate-700">帖子管理</div>
</a>
<a href="/admin/notifications" class="bg-white rounded-2xl p-5 shadow-sm border border-slate-100 hover:border-rose-200 hover:shadow-md hover:-translate-y-1 transition-all text-center group flex flex-col items-center">
<div class="w-12 h-12 rounded-xl bg-rose-50 flex items-center justify-center text-2xl mb-3 group-hover:scale-110 transition-transform">📢</div>
<div class="text-sm font-bold text-slate-700">通知管理</div>
</a>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
<script>
async function loadDashboard() {
// 加载统计数据
try {
const res = await fetch('/api/admin/stats');
const data = await res.json();
if (data.success) {
document.getElementById('stat-users').textContent = data.stats.users;
document.getElementById('stat-contests').textContent = data.stats.contests;
document.getElementById('stat-exams').textContent = data.stats.exams;
document.getElementById('stat-posts').textContent = data.stats.posts;
// 待处理事项
const pending = document.getElementById('pending-items');
const teacherApps = data.stats.pending_teacher_apps || 0;
const contestApps = data.stats.pending_contest_apps || 0;
if (teacherApps === 0 && contestApps === 0) {
pending.innerHTML = `
<div class="flex flex-col items-center justify-center h-48 text-slate-400 border-2 border-dashed border-slate-100 rounded-2xl bg-slate-50/50">
<div class="w-12 h-12 bg-white rounded-full flex items-center justify-center text-2xl shadow-sm mb-3">☕</div>
<span class="text-sm font-bold">太棒了,所有事项都已处理完毕!</span>
</div>`;
} else {
let html = '';
if (teacherApps > 0) {
html += `<a href="/admin/teacher-applications" class="flex items-center justify-between p-4 bg-gradient-to-r from-orange-50 to-white border border-orange-200/60 rounded-xl hover:shadow-md hover:border-orange-300 transition-all group">
<div class="flex items-center gap-3">
<div class="w-10 h-10 rounded-full bg-orange-100 flex items-center justify-center text-orange-600 group-hover:scale-110 transition-transform">👨‍🏫</div>
<div>
<div class="text-sm font-bold text-slate-800">待审核教师申请</div>
<div class="text-xs text-slate-500 mt-0.5">需要您的审批决定</div>
</div>
</div>
<span class="px-3 py-1 text-xs font-bold bg-orange-500 text-white rounded-full shadow-sm shadow-orange-200 animate-pulse">${teacherApps} 项</span>
</a>`;
}
if (contestApps > 0) {
html += `<a href="/admin/contest-applications" class="flex items-center justify-between p-4 bg-gradient-to-r from-indigo-50 to-white border border-indigo-200/60 rounded-xl hover:shadow-md hover:border-indigo-300 transition-all group mt-3">
<div class="flex items-center gap-3">
<div class="w-10 h-10 rounded-full bg-indigo-100 flex items-center justify-center text-indigo-600 group-hover:scale-110 transition-transform">🏆</div>
<div>
<div class="text-sm font-bold text-slate-800">待审核杯赛申请</div>
<div class="text-xs text-slate-500 mt-0.5">有新的杯赛创建请求</div>
</div>
</div>
<span class="px-3 py-1 text-xs font-bold bg-indigo-500 text-white rounded-full shadow-sm shadow-indigo-200 animate-pulse">${contestApps} 项</span>
</a>`;
}
pending.innerHTML = html;
}
}
} catch(e) { console.error('加载统计失败:', e); }
// 加载最近活动
try {
const res = await fetch('/api/admin/recent-activities');
const data = await res.json();
const container = document.getElementById('recent-activities');
if (data.success && data.activities.length > 0) {
container.innerHTML = data.activities.map(a =>
`<div class="flex items-start gap-4 relative z-10 group">
<div class="w-3 h-3 rounded-full bg-white border-2 border-indigo-400 mt-1.5 flex-shrink-0 group-hover:scale-125 transition-transform group-hover:bg-indigo-400 group-hover:border-indigo-100"></div>
<div class="bg-slate-50 border border-slate-100 rounded-xl p-3 flex-1 group-hover:bg-white group-hover:shadow-sm transition-all group-hover:border-indigo-100">
<div class="flex items-center justify-between gap-4 mb-1">
<span class="text-xs font-bold text-slate-400 flex items-center gap-1.5"><svg class="w-3.5 h-3.5" 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>${a.time}</span>
</div>
<div class="text-sm text-slate-700 leading-relaxed">
<span class="font-bold text-indigo-600 bg-indigo-50 px-2 py-0.5 rounded-md border border-indigo-100 mr-1">${a.user}</span>
${a.action}
</div>
</div>
</div>`
).join('');
} else {
container.innerHTML = `
<div class="flex flex-col items-center justify-center h-48 text-slate-400 border-2 border-dashed border-slate-100 rounded-2xl bg-slate-50/50 relative z-10 ml-4">
<div class="w-12 h-12 bg-white rounded-full flex items-center justify-center text-2xl shadow-sm mb-3">📭</div>
<span class="text-sm font-bold">暂无近期活动记录</span>
</div>`;
// Hide the timeline line if no activities
const line = container.previousElementSibling?.classList.contains('absolute') ? container.previousElementSibling : null;
if(line) line.style.display = 'none';
}
} catch(e) { console.error('加载活动失败:', e); }
}
document.addEventListener('DOMContentLoaded', loadDashboard);
</script>
{% endblock %}