mobile . themes

This commit is contained in:
2026-03-05 12:57:55 +08:00
parent b5b9f7db06
commit c325007897
62 changed files with 835 additions and 150 deletions

113
app.py
View File

@@ -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)