diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 8119c14..9b4bc3d 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -27,7 +27,8 @@ "Bash(git add:*)", "Bash(git push)", "Bash(git commit:*)", - "Bash(git push:*)" + "Bash(git push:*)", + "Bash(sqlite3:*)" ] } } diff --git a/app.py b/app.py index 9255c31..9b14632 100644 --- a/app.py +++ b/app.py @@ -13,7 +13,7 @@ from functools import wraps from flask import ( Flask, render_template, request, jsonify, session, - redirect, url_for, flash, abort, send_from_directory + redirect, url_for, flash, abort, send_from_directory, send_file ) from flask_caching import Cache from werkzeug.utils import secure_filename @@ -27,6 +27,7 @@ from openai import OpenAI as DashScopeClient # 引入数据库模型(添加 TeacherApplication) from models import db, User, Exam, Submission, Draft, Contest, ContestMembership, ContestApplication, Post, Reply, Poll, Report, Bookmark, Reaction, Notification, EditHistory, ContestRegistration, TeacherApplication, Friend, ExamBookmark, ChatRoom, ChatRoomMember, Message, MessageReaction, QuestionBankItem, InviteCode, SystemNotification from flask_socketio import SocketIO, emit, join_room as sio_join, leave_room as sio_leave +from excel_export import export_users_to_excel, export_exams_to_excel, export_submissions_to_excel, export_contests_to_excel, export_posts_to_excel load_dotenv() @@ -2761,9 +2762,18 @@ def api_update_exam_visibility(exam_id): if new_visibility not in ('private', 'public'): return jsonify({'success': False, 'message': '无效的可见性设置'}), 400 - # 检查权限:只有教师和管理员可以设置为公开 - if new_visibility == 'public' and user.get('role') not in ('teacher', 'admin'): - return jsonify({'success': False, 'message': '只有教师和管理员可以创建公开考试'}), 403 + # 检查权限:教师、管理员、杯赛负责人和杯赛老师可以设置为公开 + if new_visibility == 'public': + has_permission = user.get('role') in ('teacher', 'admin') + # 如果是杯赛考试,检查是否为杯赛负责人或老师 + if not has_permission and exam.contest_id: + membership = ContestMembership.query.filter_by( + user_id=user['id'], contest_id=exam.contest_id).first() + if membership and membership.role in ('owner', 'teacher'): + has_permission = True + + if not has_permission: + return jsonify({'success': False, 'message': '只有教师、管理员或杯赛负责人/老师可以创建公开考试'}), 403 exam.visibility = new_visibility db.session.commit() @@ -3303,6 +3313,7 @@ def api_search_posts(): 'content': p.content[:200] + ('...' if len(p.content) > 200 else ''), 'author': p.author.name, 'author_id': p.author_id, + 'author_avatar': p.author.avatar or '', 'author_level': author_level, 'tag': p.tag, 'is_official': p.is_official, @@ -3360,6 +3371,7 @@ def api_get_post(post_id): 'content': post.content, 'author': post.author.name, 'author_id': post.author_id, + 'author_avatar': post.author.avatar or '', 'tag': post.tag, 'is_official': post.is_official, 'pinned': post.pinned, @@ -3380,6 +3392,7 @@ def api_get_post(post_id): 'content': r.content, 'author': r.author.name, 'author_id': r.author_id, + 'author_avatar': r.author.avatar or '', 'likes': r.likes, 'reply_to': r.reply_to, 'created_at': r.created_at.strftime('%Y-%m-%d %H:%M'), @@ -4895,5 +4908,97 @@ with app.app_context(): conn.commit() except Exception: pass +# ========== Excel导出路由 ========== + +@app.route('/admin/export/users') +def admin_export_users(): + """导出用户数据为Excel""" + user = session.get('user') + if not user or user['role'] != 'admin': + return jsonify({'error': '无权限'}), 403 + + users = User.query.order_by(User.created_at.desc()).all() + output = export_users_to_excel(users) + + filename = f"用户数据_{datetime.now().strftime('%Y%m%d_%H%M%S')}.xlsx" + return send_file( + output, + mimetype='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + as_attachment=True, + download_name=filename + ) + +@app.route('/admin/export/exams') +def admin_export_exams(): + """导出考试数据为Excel""" + user = session.get('user') + if not user or user['role'] != 'admin': + return jsonify({'error': '无权限'}), 403 + + exams = Exam.query.order_by(Exam.created_at.desc()).all() + output = export_exams_to_excel(exams) + + filename = f"考试数据_{datetime.now().strftime('%Y%m%d_%H%M%S')}.xlsx" + return send_file( + output, + mimetype='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + as_attachment=True, + download_name=filename + ) + +@app.route('/admin/export/submissions') +def admin_export_submissions(): + """导出提交记录为Excel""" + user = session.get('user') + if not user or user['role'] != 'admin': + return jsonify({'error': '无权限'}), 403 + + submissions = Submission.query.order_by(Submission.submitted_at.desc()).all() + output = export_submissions_to_excel(submissions) + + filename = f"提交记录_{datetime.now().strftime('%Y%m%d_%H%M%S')}.xlsx" + return send_file( + output, + mimetype='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + as_attachment=True, + download_name=filename + ) + +@app.route('/admin/export/contests') +def admin_export_contests(): + """导出杯赛数据为Excel""" + user = session.get('user') + if not user or user['role'] != 'admin': + return jsonify({'error': '无权限'}), 403 + + contests = Contest.query.order_by(Contest.created_at.desc()).all() + output = export_contests_to_excel(contests) + + filename = f"杯赛数据_{datetime.now().strftime('%Y%m%d_%H%M%S')}.xlsx" + return send_file( + output, + mimetype='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + as_attachment=True, + download_name=filename + ) + +@app.route('/admin/export/posts') +def admin_export_posts(): + """导出论坛帖子为Excel""" + user = session.get('user') + if not user or user['role'] != 'admin': + return jsonify({'error': '无权限'}), 403 + + posts = Post.query.order_by(Post.created_at.desc()).all() + output = export_posts_to_excel(posts) + + filename = f"论坛帖子_{datetime.now().strftime('%Y%m%d_%H%M%S')}.xlsx" + return send_file( + output, + mimetype='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + as_attachment=True, + download_name=filename + ) + if __name__ == '__main__': socketio.run(app, debug=True,host='0.0.0.0', port=5080) \ No newline at end of file diff --git a/check_database.py b/check_database.py new file mode 100644 index 0000000..eddac14 --- /dev/null +++ b/check_database.py @@ -0,0 +1,55 @@ +# -*- coding: utf-8 -*- +import sqlite3 +import sys + +# 设置输出编码为UTF-8 +sys.stdout.reconfigure(encoding='utf-8') + +db_path = 'instance/database.db' + +try: + conn = sqlite3.connect(db_path) + cursor = conn.cursor() + + # 检查数据库编码 + cursor.execute('PRAGMA encoding') + encoding = cursor.fetchone()[0] + print(f"数据库编码: {encoding}") + print("-" * 50) + + # 获取所有表 + cursor.execute("SELECT name FROM sqlite_master WHERE type='table'") + tables = [row[0] for row in cursor.fetchall()] + print(f"\n数据表列表 (共{len(tables)}个):") + for table in tables: + print(f" - {table}") + + print("\n" + "=" * 50) + + # 检查每个表的数据 + for table in tables: + cursor.execute(f"SELECT COUNT(*) FROM {table}") + count = cursor.fetchone()[0] + print(f"\n表 '{table}': {count} 条记录") + + # 显示表结构 + cursor.execute(f"PRAGMA table_info({table})") + columns = cursor.fetchall() + print(f" 字段: {', '.join([col[1] for col in columns])}") + + # 显示前3条数据示例 + if count > 0: + cursor.execute(f"SELECT * FROM {table} LIMIT 3") + rows = cursor.fetchall() + print(f" 示例数据 (前{len(rows)}条):") + for i, row in enumerate(rows, 1): + print(f" 记录{i}: {row[:5]}...") # 只显示前5个字段 + + conn.close() + print("\n" + "=" * 50) + print("数据库检查完成!") + +except Exception as e: + print(f"错误: {e}") + import traceback + traceback.print_exc() diff --git a/excel_export.py b/excel_export.py new file mode 100644 index 0000000..e81238d --- /dev/null +++ b/excel_export.py @@ -0,0 +1,217 @@ +# excel_export.py +from openpyxl import Workbook +from openpyxl.styles import Font, Alignment, PatternFill, Border, Side +from datetime import datetime +from io import BytesIO + +def export_users_to_excel(users): + """导出用户数据到Excel""" + wb = Workbook() + ws = wb.active + ws.title = "用户数据" + + # 设置表头样式 + header_fill = PatternFill(start_color="4472C4", end_color="4472C4", fill_type="solid") + header_font = Font(bold=True, color="FFFFFF", size=12) + header_alignment = Alignment(horizontal="center", vertical="center") + + # 表头 + headers = ["ID", "用户名", "邮箱", "手机号", "角色", "状态", "完成考试数", "注册时间"] + for col, header in enumerate(headers, 1): + cell = ws.cell(row=1, column=col, value=header) + cell.fill = header_fill + cell.font = header_font + cell.alignment = header_alignment + + # 数据行 + for row, user in enumerate(users, 2): + ws.cell(row=row, column=1, value=user.id) + ws.cell(row=row, column=2, value=user.name) + ws.cell(row=row, column=3, value=user.email or "") + ws.cell(row=row, column=4, value=user.phone or "") + ws.cell(row=row, column=5, value=user.role) + ws.cell(row=row, column=6, value="已封禁" if user.is_banned else "正常") + ws.cell(row=row, column=7, value=user.completed_exams) + ws.cell(row=row, column=8, value=user.created_at.strftime("%Y-%m-%d %H:%M:%S") if user.created_at else "") + + # 调整列宽 + ws.column_dimensions['A'].width = 8 + ws.column_dimensions['B'].width = 20 + ws.column_dimensions['C'].width = 30 + ws.column_dimensions['D'].width = 15 + ws.column_dimensions['E'].width = 12 + ws.column_dimensions['F'].width = 10 + ws.column_dimensions['G'].width = 12 + ws.column_dimensions['H'].width = 20 + + # 保存到内存 + output = BytesIO() + wb.save(output) + output.seek(0) + return output + +def export_exams_to_excel(exams): + """导出考试数据到Excel""" + wb = Workbook() + ws = wb.active + ws.title = "考试数据" + + header_fill = PatternFill(start_color="4472C4", end_color="4472C4", fill_type="solid") + header_font = Font(bold=True, color="FFFFFF", size=12) + header_alignment = Alignment(horizontal="center", vertical="center") + + headers = ["ID", "考试名称", "创建者ID", "总分", "时长(分钟)", "状态", "提交数", "创建时间"] + for col, header in enumerate(headers, 1): + cell = ws.cell(row=1, column=col, value=header) + cell.fill = header_fill + cell.font = header_font + cell.alignment = header_alignment + + for row, exam in enumerate(exams, 2): + ws.cell(row=row, column=1, value=exam.id) + ws.cell(row=row, column=2, value=exam.title) + ws.cell(row=row, column=3, value=exam.creator_id) + ws.cell(row=row, column=4, value=exam.total_score) + ws.cell(row=row, column=5, value=exam.duration) + ws.cell(row=row, column=6, value=exam.status) + ws.cell(row=row, column=7, value=len(exam.submissions) if exam.submissions else 0) + ws.cell(row=row, column=8, value=exam.created_at.strftime("%Y-%m-%d %H:%M:%S") if exam.created_at else "") + + ws.column_dimensions['A'].width = 8 + ws.column_dimensions['B'].width = 30 + ws.column_dimensions['C'].width = 12 + ws.column_dimensions['D'].width = 10 + ws.column_dimensions['E'].width = 15 + ws.column_dimensions['F'].width = 12 + ws.column_dimensions['G'].width = 10 + ws.column_dimensions['H'].width = 20 + + output = BytesIO() + wb.save(output) + output.seek(0) + return output + +def export_submissions_to_excel(submissions): + """导出提交记录到Excel""" + wb = Workbook() + ws = wb.active + ws.title = "提交记录" + + header_fill = PatternFill(start_color="4472C4", end_color="4472C4", fill_type="solid") + header_font = Font(bold=True, color="FFFFFF", size=12) + header_alignment = Alignment(horizontal="center", vertical="center") + + headers = ["ID", "考试ID", "考试名称", "用户ID", "用户名", "得分", "总分", "状态", "提交时间"] + for col, header in enumerate(headers, 1): + cell = ws.cell(row=1, column=col, value=header) + cell.fill = header_fill + cell.font = header_font + cell.alignment = header_alignment + + for row, sub in enumerate(submissions, 2): + ws.cell(row=row, column=1, value=sub.id) + ws.cell(row=row, column=2, value=sub.exam_id) + ws.cell(row=row, column=3, value=sub.exam.title if sub.exam else "") + ws.cell(row=row, column=4, value=sub.user_id) + ws.cell(row=row, column=5, value=sub.user.name if sub.user else "") + ws.cell(row=row, column=6, value=sub.score if sub.score is not None else "") + ws.cell(row=row, column=7, value=sub.total_score) + ws.cell(row=row, column=8, value=sub.status) + ws.cell(row=row, column=9, value=sub.submitted_at.strftime("%Y-%m-%d %H:%M:%S") if sub.submitted_at else "") + + ws.column_dimensions['A'].width = 8 + ws.column_dimensions['B'].width = 10 + ws.column_dimensions['C'].width = 30 + ws.column_dimensions['D'].width = 10 + ws.column_dimensions['E'].width = 20 + ws.column_dimensions['F'].width = 10 + ws.column_dimensions['G'].width = 10 + ws.column_dimensions['H'].width = 12 + ws.column_dimensions['I'].width = 20 + + output = BytesIO() + wb.save(output) + output.seek(0) + return output + +def export_contests_to_excel(contests): + """导出杯赛数据到Excel""" + wb = Workbook() + ws = wb.active + ws.title = "杯赛数据" + + header_fill = PatternFill(start_color="4472C4", end_color="4472C4", fill_type="solid") + header_font = Font(bold=True, color="FFFFFF", size=12) + header_alignment = Alignment(horizontal="center", vertical="center") + + headers = ["ID", "杯赛名称", "主办方", "开始日期", "结束日期", "状态", "参与人数", "创建时间"] + for col, header in enumerate(headers, 1): + cell = ws.cell(row=1, column=col, value=header) + cell.fill = header_fill + cell.font = header_font + cell.alignment = header_alignment + + for row, contest in enumerate(contests, 2): + ws.cell(row=row, column=1, value=contest.id) + ws.cell(row=row, column=2, value=contest.name) + ws.cell(row=row, column=3, value=contest.organizer or "") + ws.cell(row=row, column=4, value=contest.start_date or "") + ws.cell(row=row, column=5, value=contest.end_date or "") + ws.cell(row=row, column=6, value=contest.status) + ws.cell(row=row, column=7, value=contest.participants) + ws.cell(row=row, column=8, value=contest.created_at.strftime("%Y-%m-%d %H:%M:%S") if contest.created_at else "") + + ws.column_dimensions['A'].width = 8 + ws.column_dimensions['B'].width = 30 + ws.column_dimensions['C'].width = 20 + ws.column_dimensions['D'].width = 15 + ws.column_dimensions['E'].width = 15 + ws.column_dimensions['F'].width = 12 + ws.column_dimensions['G'].width = 12 + ws.column_dimensions['H'].width = 20 + + output = BytesIO() + wb.save(output) + output.seek(0) + return output + +def export_posts_to_excel(posts): + """导出论坛帖子到Excel""" + wb = Workbook() + ws = wb.active + ws.title = "论坛帖子" + + header_fill = PatternFill(start_color="4472C4", end_color="4472C4", fill_type="solid") + header_font = Font(bold=True, color="FFFFFF", size=12) + header_alignment = Alignment(horizontal="center", vertical="center") + + headers = ["ID", "标题", "作者", "分类", "浏览数", "回复数", "点赞数", "创建时间"] + for col, header in enumerate(headers, 1): + cell = ws.cell(row=1, column=col, value=header) + cell.fill = header_fill + cell.font = header_font + cell.alignment = header_alignment + + for row, post in enumerate(posts, 2): + ws.cell(row=row, column=1, value=post.id) + ws.cell(row=row, column=2, value=post.title) + ws.cell(row=row, column=3, value=post.author.name if post.author else "") + ws.cell(row=row, column=4, value=post.category or "") + ws.cell(row=row, column=5, value=post.views) + ws.cell(row=row, column=6, value=post.reply_count) + ws.cell(row=row, column=7, value=post.like_count) + ws.cell(row=row, column=8, value=post.created_at.strftime("%Y-%m-%d %H:%M:%S") if post.created_at else "") + + ws.column_dimensions['A'].width = 8 + ws.column_dimensions['B'].width = 40 + ws.column_dimensions['C'].width = 20 + ws.column_dimensions['D'].width = 15 + ws.column_dimensions['E'].width = 10 + ws.column_dimensions['F'].width = 10 + ws.column_dimensions['G'].width = 10 + ws.column_dimensions['H'].width = 20 + + output = BytesIO() + wb.save(output) + output.seek(0) + return output diff --git a/instance/database.db b/instance/database.db index ccb214d..1be1d0b 100644 Binary files a/instance/database.db and b/instance/database.db differ diff --git a/requirements.txt b/requirements.txt index 3e4c5dc..3dc9c19 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,3 +7,5 @@ flask-socketio>=5.3.0 PyMuPDF>=1.24.0 openai>=1.14.0 Flask-Caching>=2.3.0 +openpyxl>=3.1.0 +Flask-SQLAlchemy>=3.0.0 diff --git a/static/css/style.css b/static/css/style.css index e473834..ca21b9f 100644 --- a/static/css/style.css +++ b/static/css/style.css @@ -463,7 +463,85 @@ button[onclick^="quickScore"]:active { color: rgba(255, 255, 255, 0.8); } -/* 粒子背景容器 */ +/* 聊天界面优化 */ +#messageArea { + overflow-y: auto !important; + overflow-x: hidden; + scroll-behavior: smooth; + display: flex; + flex-direction: column; +} + +#messageArea::-webkit-scrollbar { + width: 8px; +} + +#messageArea::-webkit-scrollbar-track { + background: rgba(15, 23, 42, 0.3); + border-radius: 4px; +} + +#messageArea::-webkit-scrollbar-thumb { + background: rgba(59, 130, 246, 0.5); + border-radius: 4px; +} + +#messageArea::-webkit-scrollbar-thumb:hover { + background: rgba(59, 130, 246, 0.7); +} + +/* 聊天视图布局修复 */ +#chatView { + height: 100%; + max-height: 100%; +} + +#chatView.flex { + display: flex !important; + flex-direction: column !important; +} + +#chatView.hidden { + display: none !important; +} + +/* 确保输入框始终在底部 */ +#chatView > div:last-child { + margin-top: auto; +} + +/* 左侧面板滚动优化 */ +#roomList::-webkit-scrollbar, +#chatNotifPanel::-webkit-scrollbar { + width: 6px; +} + +#roomList::-webkit-scrollbar-track, +#chatNotifPanel::-webkit-scrollbar-track { + background: rgba(15, 23, 42, 0.2); +} + +#roomList::-webkit-scrollbar-thumb, +#chatNotifPanel::-webkit-scrollbar-thumb { + background: rgba(59, 130, 246, 0.4); + border-radius: 3px; +} + +/* 移除hide-scrollbar在聊天区域的影响 */ +.hide-scrollbar { + -ms-overflow-style: none; + scrollbar-width: none; +} + +.hide-scrollbar::-webkit-scrollbar { + display: none; +} + +/* 但是消息区域要显示滚动条 */ +#messageArea.hide-scrollbar::-webkit-scrollbar { + display: block !important; + width: 8px; +} .particles-container { position: fixed; top: 0; @@ -472,4 +550,174 @@ button[onclick^="quickScore"]:active { height: 100%; z-index: -1; pointer-events: none; +} + +/* 响应式表格容器 - 解决表格内容显示不全 */ +.table-responsive { + width: 100%; + overflow-x: auto; + overflow-y: visible; + -webkit-overflow-scrolling: touch; +} + +.table-responsive::-webkit-scrollbar { + height: 10px; +} + +.table-responsive::-webkit-scrollbar-track { + background: rgba(15, 23, 42, 0.3); + border-radius: 5px; +} + +.table-responsive::-webkit-scrollbar-thumb { + background: rgba(59, 130, 246, 0.5); + border-radius: 5px; +} + +.table-responsive::-webkit-scrollbar-thumb:hover { + background: rgba(59, 130, 246, 0.7); +} + +/* 表格优化 - 确保内容完整显示 */ +.table-futuristic { + min-width: 100%; + white-space: nowrap; +} + +.table-futuristic td, +.table-futuristic th { + padding: 12px 16px; + vertical-align: middle; + max-width: none; +} + +/* 文字换行优化 */ +.text-wrap { + white-space: normal; + word-wrap: break-word; + word-break: break-word; +} + +.text-ellipsis-2 { + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; + text-overflow: ellipsis; +} + +.text-ellipsis-3 { + display: -webkit-box; + -webkit-line-clamp: 3; + -webkit-box-orient: vertical; + overflow: hidden; + text-overflow: ellipsis; +} + +/* 管理后台内容区域优化 */ +.admin-content-wrapper { + width: 100%; + max-width: 100%; + overflow-x: hidden; +} + +/* 卡片内容区域优化 */ +.card-content-scrollable { + max-height: 600px; + overflow-y: auto; + overflow-x: hidden; +} + +.card-content-scrollable::-webkit-scrollbar { + width: 8px; +} + +.card-content-scrollable::-webkit-scrollbar-track { + background: rgba(15, 23, 42, 0.2); + border-radius: 4px; +} + +.card-content-scrollable::-webkit-scrollbar-thumb { + background: rgba(59, 130, 246, 0.4); + border-radius: 4px; +} + +/* 移动端优化 */ +@media (max-width: 768px) { + .futuristic-card, + .futuristic-card-dark { + border-radius: 16px; + padding: 16px !important; + } + + .table-futuristic td, + .table-futuristic th { + padding: 8px 12px; + font-size: 0.875rem; + } + + .btn-futuristic, + .btn-outline-futuristic { + padding: 8px 16px; + font-size: 0.875rem; + } +} + +/* 图片容器优化 */ +.img-container { + width: 100%; + max-width: 100%; + overflow: hidden; + position: relative; +} + +.img-container img { + width: 100%; + height: auto; + object-fit: contain; + max-height: 500px; +} + +/* 长文本内容优化 */ +.content-text { + line-height: 1.8; + word-wrap: break-word; + overflow-wrap: break-word; +} + +/* 统计卡片响应式优化 */ +@media (max-width: 640px) { + .grid-cols-2 { + grid-template-columns: repeat(1, minmax(0, 1fr)) !important; + } +} + +/* 侧边栏响应式优化 */ +@media (max-width: 768px) { + .sidebar-futuristic { + position: relative; + width: 100%; + border-right: none; + border-bottom: 1px solid rgba(102, 126, 234, 0.1); + } +} + +/* 防止内容溢出 */ +.overflow-safe { + overflow: hidden; + text-overflow: ellipsis; +} + +/* 弹性容器优化 */ +.flex-container-safe { + display: flex; + flex-wrap: wrap; + gap: 1rem; +} + +/* 网格容器优化 */ +.grid-container-safe { + display: grid; + gap: 1rem; + grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); } \ No newline at end of file diff --git a/static/uploads/1771815228_6668.jpg b/static/uploads/1771815228_6668.jpg deleted file mode 100644 index aebf06b..0000000 Binary files a/static/uploads/1771815228_6668.jpg and /dev/null differ diff --git a/static/uploads/1771817391_3644.jpg b/static/uploads/1771817391_3644.jpg deleted file mode 100644 index aebf06b..0000000 Binary files a/static/uploads/1771817391_3644.jpg and /dev/null differ diff --git a/static/uploads/avatar_11_1772442457.jpeg b/static/uploads/avatar_11_1772442457.jpeg deleted file mode 100644 index 01e0134..0000000 Binary files a/static/uploads/avatar_11_1772442457.jpeg and /dev/null differ diff --git a/static/uploads/avatar_5_1772173016.jpg b/static/uploads/avatar_5_1772173016.jpg deleted file mode 100644 index 95364d0..0000000 Binary files a/static/uploads/avatar_5_1772173016.jpg and /dev/null differ diff --git a/static/uploads/avatar_6_1772170442.jpeg b/static/uploads/avatar_6_1772170442.jpeg deleted file mode 100644 index 0a95b57..0000000 Binary files a/static/uploads/avatar_6_1772170442.jpeg and /dev/null differ diff --git a/static/uploads/avatar_6_1772442512.jpeg b/static/uploads/avatar_6_1772442512.jpeg deleted file mode 100644 index 0a95b57..0000000 Binary files a/static/uploads/avatar_6_1772442512.jpeg and /dev/null differ diff --git a/static/uploads/paper_3_1772239860_2026.pdf b/static/uploads/paper_3_1772239860_2026.pdf deleted file mode 100644 index aa2b0b8..0000000 Binary files a/static/uploads/paper_3_1772239860_2026.pdf and /dev/null differ diff --git a/static/uploads/paper_4_1772202236_20258_.pdf b/static/uploads/paper_4_1772202236_20258_.pdf deleted file mode 100644 index 95910ce..0000000 Binary files a/static/uploads/paper_4_1772202236_20258_.pdf and /dev/null differ diff --git a/static/uploads/paper_4_1772202300_20262.pdf b/static/uploads/paper_4_1772202300_20262.pdf deleted file mode 100644 index 9009f7f..0000000 Binary files a/static/uploads/paper_4_1772202300_20262.pdf and /dev/null differ diff --git a/static/uploads/paper_4_1772331939_20258_.pdf b/static/uploads/paper_4_1772331939_20258_.pdf deleted file mode 100644 index 95910ce..0000000 Binary files a/static/uploads/paper_4_1772331939_20258_.pdf and /dev/null differ diff --git a/static/uploads/paper_4_1772331941_20258_.pdf b/static/uploads/paper_4_1772331941_20258_.pdf deleted file mode 100644 index 95910ce..0000000 Binary files a/static/uploads/paper_4_1772331941_20258_.pdf and /dev/null differ diff --git a/static/uploads/paper_4_1772331962_20262.pdf b/static/uploads/paper_4_1772331962_20262.pdf deleted file mode 100644 index 9009f7f..0000000 Binary files a/static/uploads/paper_4_1772331962_20262.pdf and /dev/null differ diff --git a/static/uploads/pdf_page_03738e6c_3.png b/static/uploads/pdf_page_03738e6c_3.png deleted file mode 100644 index c4d2c91..0000000 Binary files a/static/uploads/pdf_page_03738e6c_3.png and /dev/null differ diff --git a/static/uploads/pdf_page_12f39cbe_4.png b/static/uploads/pdf_page_12f39cbe_4.png deleted file mode 100644 index 219b22e..0000000 Binary files a/static/uploads/pdf_page_12f39cbe_4.png and /dev/null differ diff --git a/static/uploads/pdf_page_165af37b_2.png b/static/uploads/pdf_page_165af37b_2.png deleted file mode 100644 index bad2f65..0000000 Binary files a/static/uploads/pdf_page_165af37b_2.png and /dev/null differ diff --git a/static/uploads/pdf_page_1d5a8760_1.png b/static/uploads/pdf_page_1d5a8760_1.png deleted file mode 100644 index 95e155a..0000000 Binary files a/static/uploads/pdf_page_1d5a8760_1.png and /dev/null differ diff --git a/static/uploads/pdf_page_20e063d5_1.png b/static/uploads/pdf_page_20e063d5_1.png deleted file mode 100644 index 01454ff..0000000 Binary files a/static/uploads/pdf_page_20e063d5_1.png and /dev/null differ diff --git a/static/uploads/pdf_page_221de6aa_3.png b/static/uploads/pdf_page_221de6aa_3.png deleted file mode 100644 index ac0ac8e..0000000 Binary files a/static/uploads/pdf_page_221de6aa_3.png and /dev/null differ diff --git a/static/uploads/pdf_page_24549e30_2.png b/static/uploads/pdf_page_24549e30_2.png deleted file mode 100644 index 3e33e2d..0000000 Binary files a/static/uploads/pdf_page_24549e30_2.png and /dev/null differ diff --git a/static/uploads/pdf_page_28918fb1_1.png b/static/uploads/pdf_page_28918fb1_1.png deleted file mode 100644 index 44403c0..0000000 Binary files a/static/uploads/pdf_page_28918fb1_1.png and /dev/null differ diff --git a/static/uploads/pdf_page_2f7774e7_3.png b/static/uploads/pdf_page_2f7774e7_3.png deleted file mode 100644 index b329e09..0000000 Binary files a/static/uploads/pdf_page_2f7774e7_3.png and /dev/null differ diff --git a/static/uploads/pdf_page_4139831a_2.png b/static/uploads/pdf_page_4139831a_2.png deleted file mode 100644 index 3e33e2d..0000000 Binary files a/static/uploads/pdf_page_4139831a_2.png and /dev/null differ diff --git a/static/uploads/pdf_page_450cf3b5_2.png b/static/uploads/pdf_page_450cf3b5_2.png deleted file mode 100644 index bad2f65..0000000 Binary files a/static/uploads/pdf_page_450cf3b5_2.png and /dev/null differ diff --git a/static/uploads/pdf_page_582668b4_2.png b/static/uploads/pdf_page_582668b4_2.png deleted file mode 100644 index 3e33e2d..0000000 Binary files a/static/uploads/pdf_page_582668b4_2.png and /dev/null differ diff --git a/static/uploads/pdf_page_5a3c6d3a_4.png b/static/uploads/pdf_page_5a3c6d3a_4.png deleted file mode 100644 index 219b22e..0000000 Binary files a/static/uploads/pdf_page_5a3c6d3a_4.png and /dev/null differ diff --git a/static/uploads/pdf_page_6ae215b4_2.png b/static/uploads/pdf_page_6ae215b4_2.png deleted file mode 100644 index fdbbd00..0000000 Binary files a/static/uploads/pdf_page_6ae215b4_2.png and /dev/null differ diff --git a/static/uploads/pdf_page_74ee682a_4.png b/static/uploads/pdf_page_74ee682a_4.png deleted file mode 100644 index 1565c5e..0000000 Binary files a/static/uploads/pdf_page_74ee682a_4.png and /dev/null differ diff --git a/static/uploads/pdf_page_8d7c735e_4.png b/static/uploads/pdf_page_8d7c735e_4.png deleted file mode 100644 index 1565c5e..0000000 Binary files a/static/uploads/pdf_page_8d7c735e_4.png and /dev/null differ diff --git a/static/uploads/pdf_page_8de9cc81_3.png b/static/uploads/pdf_page_8de9cc81_3.png deleted file mode 100644 index ac0ac8e..0000000 Binary files a/static/uploads/pdf_page_8de9cc81_3.png and /dev/null differ diff --git a/static/uploads/pdf_page_9132729b_3.png b/static/uploads/pdf_page_9132729b_3.png deleted file mode 100644 index ac0ac8e..0000000 Binary files a/static/uploads/pdf_page_9132729b_3.png and /dev/null differ diff --git a/static/uploads/pdf_page_9932d23b_2.png b/static/uploads/pdf_page_9932d23b_2.png deleted file mode 100644 index bad2f65..0000000 Binary files a/static/uploads/pdf_page_9932d23b_2.png and /dev/null differ diff --git a/static/uploads/pdf_page_9e8a03f5_1.png b/static/uploads/pdf_page_9e8a03f5_1.png deleted file mode 100644 index 44403c0..0000000 Binary files a/static/uploads/pdf_page_9e8a03f5_1.png and /dev/null differ diff --git a/static/uploads/pdf_page_b2e7c6a9_1.png b/static/uploads/pdf_page_b2e7c6a9_1.png deleted file mode 100644 index 95e155a..0000000 Binary files a/static/uploads/pdf_page_b2e7c6a9_1.png and /dev/null differ diff --git a/static/uploads/pdf_page_cde8ab54_1.png b/static/uploads/pdf_page_cde8ab54_1.png deleted file mode 100644 index 95e155a..0000000 Binary files a/static/uploads/pdf_page_cde8ab54_1.png and /dev/null differ diff --git a/static/uploads/pdf_page_cf136549_3.png b/static/uploads/pdf_page_cf136549_3.png deleted file mode 100644 index b329e09..0000000 Binary files a/static/uploads/pdf_page_cf136549_3.png and /dev/null differ diff --git a/static/uploads/pdf_page_d7bd4cc9_4.png b/static/uploads/pdf_page_d7bd4cc9_4.png deleted file mode 100644 index 219b22e..0000000 Binary files a/static/uploads/pdf_page_d7bd4cc9_4.png and /dev/null differ diff --git a/static/uploads/pdf_page_e824a2ea_3.png b/static/uploads/pdf_page_e824a2ea_3.png deleted file mode 100644 index b329e09..0000000 Binary files a/static/uploads/pdf_page_e824a2ea_3.png and /dev/null differ diff --git a/static/uploads/pdf_page_f07773ac_4.png b/static/uploads/pdf_page_f07773ac_4.png deleted file mode 100644 index 1565c5e..0000000 Binary files a/static/uploads/pdf_page_f07773ac_4.png and /dev/null differ diff --git a/static/uploads/pdf_page_f54ea912_1.png b/static/uploads/pdf_page_f54ea912_1.png deleted file mode 100644 index 44403c0..0000000 Binary files a/static/uploads/pdf_page_f54ea912_1.png and /dev/null differ diff --git a/static/uploads/pdf_page_fe066e89_4.png b/static/uploads/pdf_page_fe066e89_4.png deleted file mode 100644 index e14c612..0000000 Binary files a/static/uploads/pdf_page_fe066e89_4.png and /dev/null differ diff --git a/templates/admin_base.html b/templates/admin_base.html index 90490e1..705e332 100644 --- a/templates/admin_base.html +++ b/templates/admin_base.html @@ -51,9 +51,11 @@
-
+
- {% block admin_content %}{% endblock %} +
+ {% block admin_content %}{% endblock %} +
diff --git a/templates/admin_contests.html b/templates/admin_contests.html index ce97f16..5664901 100644 --- a/templates/admin_contests.html +++ b/templates/admin_contests.html @@ -7,16 +7,16 @@

杯赛管理

-
- +
+
- - - - - - + + + + + + @@ -56,8 +56,8 @@ async function loadContests() { : '已废止'; html += ` - - + +
ID名称主办方状态开始日期操作ID名称主办方状态开始日期操作
${c.id}${c.name}${c.organizer || '-'}${c.name}${c.organizer || '-'} ${statusText} ${c.start_date || '-'} diff --git a/templates/admin_dashboard.html b/templates/admin_dashboard.html index 3e95897..b225d7a 100644 --- a/templates/admin_dashboard.html +++ b/templates/admin_dashboard.html @@ -15,16 +15,16 @@ -
+
-
总用户数
+
总用户数
-
-
+
-
@@ -33,9 +33,9 @@
-
赛事总数
+
赛事总数
-
-
+
-
@@ -44,9 +44,9 @@
-
考试总数
+
考试总数
-
-
+
-
@@ -55,9 +55,9 @@
-
社区帖子
+
社区帖子
-
-
+
-
@@ -105,34 +105,34 @@

快捷操作

- diff --git a/templates/admin_exams.html b/templates/admin_exams.html index 330c904..eefa27c 100644 --- a/templates/admin_exams.html +++ b/templates/admin_exams.html @@ -7,17 +7,17 @@

考试管理

-
- +
+
- - - - - - - + + + + + + + @@ -68,7 +68,7 @@ async function loadExams() { const isAvailable = e.status === 'available'; html += ` - + diff --git a/templates/admin_posts.html b/templates/admin_posts.html index 4d0be92..4cf79d4 100644 --- a/templates/admin_posts.html +++ b/templates/admin_posts.html @@ -19,17 +19,17 @@
-
-
ID标题科目出题人状态创建时间操作ID标题科目出题人状态创建时间操作
${e.id}${e.title}${e.title} ${e.subject || '-'} ${e.creator_name || '-'} ${statusText}
+
+
- - - - - - - + + + + + + + @@ -84,7 +84,7 @@ async function loadPosts() { const tagClass = tagClassMap[p.tag] || 'bg-slate-500/20 text-slate-400 border-slate-500/30'; html += ` - +
ID标题作者标签置顶发布时间操作ID标题作者标签置顶发布时间操作
${p.id}${p.title}${p.title} ${p.author} ${p.tag || '-'} diff --git a/templates/admin_teacher_applications.html b/templates/admin_teacher_applications.html index d4459df..5db492a 100644 --- a/templates/admin_teacher_applications.html +++ b/templates/admin_teacher_applications.html @@ -31,18 +31,18 @@