mobile . themes

This commit is contained in:
2026-03-05 22:03:30 +08:00
parent c325007897
commit 2d3163a1a0
15 changed files with 236 additions and 471 deletions

View File

@@ -1,217 +1,78 @@
# excel_export.py
from openpyxl import Workbook
from openpyxl.styles import Font, Alignment, PatternFill, Border, Side
from datetime import datetime
from io import BytesIO
import csv
from io import StringIO, BytesIO
def _to_csv(headers, rows):
"""生成 UTF-8 BOM 编码的 CSV兼容 VSCode 和 Excel"""
buf = StringIO()
writer = csv.writer(buf)
writer.writerow(headers)
writer.writerows(rows)
output = BytesIO()
output.write(b'\xef\xbb\xbf') # UTF-8 BOM
output.write(buf.getvalue().encode('utf-8'))
output.seek(0)
return output
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
rows = []
for u in users:
rows.append([
u.id, u.name, u.email or "", u.phone or "", u.role,
"已封禁" if u.is_banned else "正常", u.completed_exams,
u.created_at.strftime("%Y-%m-%d %H:%M:%S") if u.created_at else ""
])
return _to_csv(headers, rows)
# 数据行
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
rows = []
for e in exams:
rows.append([
e.id, e.title, e.creator_id, e.total_score, e.duration, e.status,
len(e.submissions) if e.submissions else 0,
e.created_at.strftime("%Y-%m-%d %H:%M:%S") if e.created_at else ""
])
return _to_csv(headers, rows)
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
rows = []
for s in submissions:
rows.append([
s.id, s.exam_id, s.exam.title if s.exam else "", s.user_id,
s.user.name if s.user else "", s.score if s.score is not None else "",
s.total_score, s.status,
s.submitted_at.strftime("%Y-%m-%d %H:%M:%S") if s.submitted_at else ""
])
return _to_csv(headers, rows)
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
rows = []
for c in contests:
rows.append([
c.id, c.name, c.organizer or "", c.start_date or "", c.end_date or "",
c.status, c.participants,
c.created_at.strftime("%Y-%m-%d %H:%M:%S") if c.created_at else ""
])
return _to_csv(headers, rows)
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
rows = []
for p in posts:
rows.append([
p.id, p.title, p.author.name if p.author else "", p.category or "",
p.views, p.reply_count, p.like_count,
p.created_at.strftime("%Y-%m-%d %H:%M:%S") if p.created_at else ""
])
return _to_csv(headers, rows)