feat: leaderboard and data export

main
ccicnce113424 1 month ago
parent 6dfe402fa7
commit 4e678a7a0f

@ -30,7 +30,108 @@ def rollcall():
@main_bp.route("/stats")
@login_required
def stats():
return render_template("stats.html")
# 排行榜:按总积分、出勤次数、学号排序
students = (
Student.query.filter_by(user_id=current_user.id)
.order_by(
Student.total_score.desc(),
Student.attendance_count.desc(),
Student.student_no.asc(),
)
.all()
)
# 最近点名记录
records = (
db.session.query(RollCall, Student)
.join(Student, RollCall.student_id == Student.id)
.filter(RollCall.user_id == current_user.id)
.order_by(RollCall.call_time.desc())
.limit(50)
.all()
)
return render_template("stats.html", students=students, records=records)
@main_bp.route("/stats/export/students")
@login_required
def export_students():
# 导出当前账号学生积分详单
students = (
Student.query.filter_by(user_id=current_user.id)
.order_by(Student.student_no.asc())
.all()
)
import csv
from io import StringIO
from flask import Response
output = StringIO()
writer = csv.writer(output)
writer.writerow(["学号", "姓名", "专业", "出勤次数", "随机点名次数", "总积分"])
for s in students:
writer.writerow(
[
s.student_no,
s.name,
s.major or "",
s.attendance_count or 0,
s.random_called_count or 0,
f"{(s.total_score or 0):.1f}",
]
)
output.seek(0)
return Response(
output.getvalue().encode("utf-8-sig"),
mimetype="text/csv; charset=utf-8",
headers={
"Content-Disposition": "attachment; filename=students_scores.csv",
},
)
@main_bp.route("/stats/export/rollcalls")
@login_required
def export_rollcalls():
# 导出当前账号点名记录
records = (
db.session.query(RollCall, Student)
.join(Student, RollCall.student_id == Student.id)
.filter(RollCall.user_id == current_user.id)
.order_by(RollCall.call_time.asc())
.all()
)
import csv
from io import StringIO
from flask import Response
output = StringIO()
writer = csv.writer(output)
writer.writerow(["时间", "学号", "姓名", "模式", "状态", "积分变化"])
for rc, s in records:
writer.writerow(
[
rc.call_time.strftime("%Y-%m-%d %H:%M:%S"),
s.student_no,
s.name,
rc.mode,
rc.status,
f"{rc.score_change:.1f}",
]
)
output.seek(0)
return Response(
output.getvalue().encode("utf-8-sig"),
mimetype="text/csv; charset=utf-8",
headers={
"Content-Disposition": "attachment; filename=rollcalls.csv",
},
)
@main_bp.route("/manage")

@ -1,6 +1,91 @@
{% extends 'base.html' %}
{% block title %}统计 - 课堂点名系统{% endblock %}
{% block content %}
<h4>统计页面占位</h4>
<p>上半部分:积分随时间走势图;下半部分:排行榜;右侧:点名记录。</p>
<div class="row mb-3">
<div class="col-md-8">
<h4>积分排行榜</h4>
</div>
<div class="col-md-4 text-end">
<a href="{{ url_for('main.export_students') }}" class="btn btn-sm btn-outline-primary">
导出积分详单
</a>
<a
href="{{ url_for('main.export_rollcalls') }}"
class="btn btn-sm btn-outline-secondary ms-2"
>
导出点名记录
</a>
</div>
</div>
<div class="row">
<div class="col-md-8">
<table class="table table-striped table-bordered">
<thead>
<tr>
<th>名次</th>
<th>学号</th>
<th>姓名</th>
<th>专业</th>
<th>总积分</th>
<th>出勤次数</th>
<th>随机点名次数</th>
</tr>
</thead>
<tbody>
{% for s in students %}
<tr>
<td>{{ loop.index }}</td>
<td>{{ s.student_no }}</td>
<td>{{ s.name }}</td>
<td>{{ s.major }}</td>
<td>{{ '%.1f'|format(s.total_score or 0) }}</td>
<td>{{ s.attendance_count or 0 }}</td>
<td>{{ s.random_called_count or 0 }}</td>
</tr>
{% else %}
<tr>
<td colspan="7" class="text-center">暂无学生数据。</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<div class="col-md-4">
<h5>最近点名记录</h5>
<div class="table-responsive" style="max-height: 500px; overflow-y: auto">
<table class="table table-sm table-bordered">
<thead>
<tr>
<th>时间</th>
<th>姓名</th>
<th>学号</th>
<th>模式</th>
<th>状态</th>
<th>积分</th>
</tr>
</thead>
<tbody>
{% for rc, s in records %}
<tr>
<td>{{ rc.call_time.strftime('%m-%d %H:%M') }}</td>
<td>{{ s.name }}</td>
<td>{{ s.student_no }}</td>
<td>{{ '随机' if rc.mode == 'random' else '顺序' }}</td>
<td>
{% if rc.status == 'absent' %}缺勤{% elif rc.status == 'distracted' %}走神{% else %}到课{% endif %}
</td>
<td>{{ '%.1f'|format(rc.score_change or 0) }}</td>
</tr>
{% else %}
<tr>
<td colspan="6" class="text-center">暂无点名记录。</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
{% endblock %}

Loading…
Cancel
Save