feat: add mine and class filters to statistics page
This commit is contained in:
+30
-5
@@ -380,11 +380,37 @@ def import_students():
|
|||||||
@main_bp.route("/api/statistics/students")
|
@main_bp.route("/api/statistics/students")
|
||||||
def get_student_statistics():
|
def get_student_statistics():
|
||||||
"""获取学员统计数据"""
|
"""获取学员统计数据"""
|
||||||
# 全体学员数量
|
# 获取筛选参数
|
||||||
total_students = Student.query.count()
|
mine_filter = request.args.get("mine", "false") == "true"
|
||||||
|
class_id_filter = request.args.get("class_id", type=int)
|
||||||
|
current_user_id = session.get("user_id")
|
||||||
|
|
||||||
# 全体问题记录
|
# 基础查询:班级
|
||||||
problems = StudentProblem.query.all()
|
class_query = Class.query.filter_by(active=True)
|
||||||
|
if mine_filter and current_user_id:
|
||||||
|
class_query = class_query.filter_by(teacher_id=current_user_id)
|
||||||
|
if class_id_filter:
|
||||||
|
class_query = class_query.filter_by(id=class_id_filter)
|
||||||
|
classes = class_query.all()
|
||||||
|
|
||||||
|
# 获取班级IDs
|
||||||
|
class_ids = [c.id for c in classes]
|
||||||
|
|
||||||
|
# 学员过滤
|
||||||
|
student_query = Student.query
|
||||||
|
if class_ids:
|
||||||
|
student_query = student_query.filter(Student.class_id.in_(class_ids))
|
||||||
|
else:
|
||||||
|
student_query = student_query.filter(Student.id == -1) # 无班级时返回空
|
||||||
|
students = student_query.all()
|
||||||
|
student_ids = [s.id for s in students]
|
||||||
|
|
||||||
|
# 全体学员数量
|
||||||
|
total_students = len(students)
|
||||||
|
|
||||||
|
# 问题记录过滤
|
||||||
|
problem_query = StudentProblem.query.filter(StudentProblem.student_id.in_(student_ids)) if student_ids else StudentProblem.query.filter(StudentProblem.id == -1)
|
||||||
|
problems = problem_query.all()
|
||||||
|
|
||||||
# 问题级别分布(来自 StudentProblem.level)
|
# 问题级别分布(来自 StudentProblem.level)
|
||||||
levels = ["启蒙", "入门", "进阶", "熟练", "精通"]
|
levels = ["启蒙", "入门", "进阶", "熟练", "精通"]
|
||||||
@@ -400,7 +426,6 @@ def get_student_statistics():
|
|||||||
severity_dist[p.severity] += 1
|
severity_dist[p.severity] += 1
|
||||||
|
|
||||||
# 各班级学员数量
|
# 各班级学员数量
|
||||||
classes = Class.query.filter_by(active=True).all()
|
|
||||||
class_student_count = []
|
class_student_count = []
|
||||||
for c in classes:
|
for c in classes:
|
||||||
class_student_count.append({
|
class_student_count.append({
|
||||||
|
|||||||
@@ -6,6 +6,20 @@
|
|||||||
<div class="container-fluid py-4">
|
<div class="container-fluid py-4">
|
||||||
<h2 class="mb-4"><i class="bi bi-bar-chart"></i> 数据统计</h2>
|
<h2 class="mb-4"><i class="bi bi-bar-chart"></i> 数据统计</h2>
|
||||||
|
|
||||||
|
<!-- 筛选工具栏 -->
|
||||||
|
<div class="card mb-4">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="d-flex flex-wrap gap-3 align-items-center">
|
||||||
|
<button class="btn btn-primary btn-sm" id="mineFilterBtn" onclick="toggleMineFilter()">
|
||||||
|
<i class="bi bi-person"></i> 我的
|
||||||
|
</button>
|
||||||
|
<select class="form-select form-select-sm" style="width:auto; min-width:120px;" id="classFilter" onchange="loadStatistics()">
|
||||||
|
<option value="">全部班级</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- 顶部统计卡片 -->
|
<!-- 顶部统计卡片 -->
|
||||||
<div class="row mb-4">
|
<div class="row mb-4">
|
||||||
<div class="col-md-4">
|
<div class="col-md-4">
|
||||||
@@ -131,12 +145,43 @@
|
|||||||
{% block extra_js %}
|
{% block extra_js %}
|
||||||
<script>
|
<script>
|
||||||
let severityChart, levelChart, classStudentChart, classProblemChart, problemNameChart;
|
let severityChart, levelChart, classStudentChart, classProblemChart, problemNameChart;
|
||||||
|
let mineFilterActive = false;
|
||||||
|
|
||||||
|
function toggleMineFilter() {
|
||||||
|
mineFilterActive = !mineFilterActive;
|
||||||
|
const btn = document.getElementById('mineFilterBtn');
|
||||||
|
if (mineFilterActive) {
|
||||||
|
btn.classList.add('active');
|
||||||
|
} else {
|
||||||
|
btn.classList.remove('active');
|
||||||
|
}
|
||||||
|
loadStatistics();
|
||||||
|
}
|
||||||
|
|
||||||
async function loadStatistics() {
|
async function loadStatistics() {
|
||||||
try {
|
try {
|
||||||
const resp = await fetch('/api/statistics/students');
|
const params = new URLSearchParams();
|
||||||
|
if (mineFilterActive) params.set('mine', 'true');
|
||||||
|
const classId = document.getElementById('classFilter').value;
|
||||||
|
if (classId) params.set('class_id', classId);
|
||||||
|
|
||||||
|
const resp = await fetch('/api/statistics/students?' + params.toString());
|
||||||
const data = await resp.json();
|
const data = await resp.json();
|
||||||
|
|
||||||
|
// 填充班级筛选器
|
||||||
|
const classSelect = document.getElementById('classFilter');
|
||||||
|
if (classSelect.options.length <= 1) {
|
||||||
|
for (const c of data.classes || []) {
|
||||||
|
const opt = document.createElement('option');
|
||||||
|
opt.value = c.id;
|
||||||
|
opt.textContent = c.name;
|
||||||
|
classSelect.appendChild(opt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (classId) {
|
||||||
|
classSelect.value = classId;
|
||||||
|
}
|
||||||
|
|
||||||
// 更新顶部卡片
|
// 更新顶部卡片
|
||||||
document.getElementById('totalStudents').textContent = data.total_students || 0;
|
document.getElementById('totalStudents').textContent = data.total_students || 0;
|
||||||
document.getElementById('totalProblems').textContent = data.total_problems || 0;
|
document.getElementById('totalProblems').textContent = data.total_problems || 0;
|
||||||
|
|||||||
Reference in New Issue
Block a user