Files
piano-plan/app/templates/statistics.html
T

250 lines
8.7 KiB
HTML

{% extends "base.html" %}
{% block title %}数据统计 - 钢琴练习方案系统{% endblock %}
{% block content %}
<div class="container-fluid py-4">
<h2 class="mb-4"><i class="bi bi-bar-chart"></i> 数据统计</h2>
<!-- 顶部统计卡片 -->
<div class="row mb-4">
<div class="col-md-4">
<div class="card text-center">
<div class="card-body">
<h3 class="mb-0" id="totalStudents">-</h3>
<small class="text-muted">学员总数</small>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card text-center">
<div class="card-body">
<h3 class="mb-0" id="totalProblems">-</h3>
<small class="text-muted">问题记录总数</small>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card text-center">
<div class="card-body">
<h3 class="mb-0" id="avgProblems">-</h3>
<small class="text-muted">人均问题数</small>
</div>
</div>
</div>
</div>
<!-- 饼图区 -->
<div class="row mb-4">
<div class="col-md-6">
<div class="card">
<div class="card-body">
<h5 class="card-title"><i class="bi bi-exclamation-triangle"></i> 问题严重程度分布</h5>
<canvas id="severityChart"></canvas>
</div>
</div>
</div>
<div class="col-md-6">
<div class="card">
<div class="card-body">
<h5 class="card-title"><i class="bi bi-speedometer2"></i> 问题级别分布</h5>
<canvas id="levelChart"></canvas>
</div>
</div>
</div>
</div>
<!-- 问题×级别矩阵 -->
<div class="row mb-4">
<div class="col-12">
<div class="card">
<div class="card-body">
<h5 class="card-title"><i class="bi bi-grid"></i> 问题级别×严重程度矩阵</h5>
<div class="table-responsive">
<table class="table table-bordered text-center" id="matrixTable">
<thead>
<tr>
<th>严重程度</th>
<th>启蒙</th>
<th>入门</th>
<th>进阶</th>
<th>熟练</th>
<th>精通</th>
<th>合计</th>
</tr>
</thead>
<tbody id="matrixBody"></tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<!-- 班级统计 -->
<div class="row">
<div class="col-md-6">
<div class="card">
<div class="card-body">
<h5 class="card-title"><i class="bi bi-people"></i> 各班级学员数量</h5>
<canvas id="classStudentChart"></canvas>
</div>
</div>
</div>
<div class="col-md-6">
<div class="card">
<div class="card-body">
<h5 class="card-title"><i class="bi bi-list-check"></i> 各班级问题数量</h5>
<canvas id="classProblemChart"></canvas>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block extra_js %}
<script>
let severityChart, levelChart, classStudentChart, classProblemChart;
async function loadStatistics() {
try {
const resp = await fetch('/api/statistics/students');
const data = await resp.json();
// 更新顶部卡片
document.getElementById('totalStudents').textContent = data.total_students || 0;
document.getElementById('totalProblems').textContent = data.total_problems || 0;
const avg = data.total_students > 0
? (data.total_problems / data.total_students).toFixed(1)
: 0;
document.getElementById('avgProblems').textContent = avg;
// 严重程度饼图
const sevCtx = document.getElementById('severityChart').getContext('2d');
if (severityChart) severityChart.destroy();
severityChart = new Chart(sevCtx, {
type: 'doughnut',
data: {
labels: Object.keys(data.severity_distribution),
datasets: [{
data: Object.values(data.severity_distribution),
backgroundColor: ['#28a745', '#ffc107', '#dc3545'],
}]
},
options: {
responsive: true,
plugins: {
legend: { position: 'bottom' }
}
}
});
// 级别饼图
const lvCtx = document.getElementById('levelChart').getContext('2d');
if (levelChart) levelChart.destroy();
levelChart = new Chart(lvCtx, {
type: 'doughnut',
data: {
labels: Object.keys(data.level_distribution),
datasets: [{
data: Object.values(data.level_distribution),
backgroundColor: ['#17a2b8', '#6610f2', '#e85d04', '#e83e8c', '#20c997', '#6c757d'],
}]
},
options: {
responsive: true,
plugins: {
legend: { position: 'bottom' }
}
}
});
// 矩阵表格
const matrixBody = document.getElementById('matrixBody');
const matrix = data.problem_level_matrix;
const levels = ['启蒙', '入门', '进阶', '熟练', '精通'];
const severities = ['轻微', '中等', '严重'];
let html = '';
let sevTotals = {轻微: 0, 中等: 0, 严重: 0};
let lvTotals = {启蒙: 0, 入门: 0, 进阶: 0, 熟练: 0, 精通: 0};
let grandTotal = 0;
for (const sev of severities) {
html += `<tr><td><strong>${sev}</strong></td>`;
let rowTotal = 0;
for (const lv of levels) {
const count = matrix[sev]?.[lv] || 0;
html += `<td>${count}</td>`;
rowTotal += count;
lvTotals[lv] += count;
grandTotal += count;
}
html += `<td><strong>${rowTotal}</strong></td></tr>`;
sevTotals[sev] = rowTotal;
}
// 合计行
html += `<tr class="table-secondary"><td><strong>合计</strong></td>`;
for (const lv of levels) {
html += `<td><strong>${lvTotals[lv]}</strong></td>`;
}
html += `<td><strong>${grandTotal}</strong></td></tr>`;
matrixBody.innerHTML = html;
// 班级学员条形图
const csCtx = document.getElementById('classStudentChart').getContext('2d');
if (classStudentChart) classStudentChart.destroy();
classStudentChart = new Chart(csCtx, {
type: 'bar',
data: {
labels: data.class_student_count.map(c => c.class_name),
datasets: [{
label: '学员数',
data: data.class_student_count.map(c => c.student_count),
backgroundColor: '#0d6efd',
}]
},
options: {
responsive: true,
plugins: {
legend: { display: false }
},
scales: {
y: { beginAtZero: true }
}
}
});
// 班级问题条形图
const cpCtx = document.getElementById('classProblemChart').getContext('2d');
if (classProblemChart) classProblemChart.destroy();
classProblemChart = new Chart(cpCtx, {
type: 'bar',
data: {
labels: data.class_problem_count.map(c => c.class_name),
datasets: [{
label: '问题数',
data: data.class_problem_count.map(c => c.problem_count),
backgroundColor: '#fd7e14',
}]
},
options: {
responsive: true,
plugins: {
legend: { display: false }
},
scales: {
y: { beginAtZero: true }
}
}
});
} catch (e) {
console.error('加载统计失败:', e);
}
}
document.addEventListener('DOMContentLoaded', loadStatistics);
</script>
{% endblock %}