|
|
|
|
@ -4,6 +4,36 @@
|
|
|
|
|
<div class="row mb-3">
|
|
|
|
|
<div class="col-md-8">
|
|
|
|
|
<h4>积分随时间走势图(前几名)</h4>
|
|
|
|
|
<form class="row g-2 mb-2" id="chart-filter-form">
|
|
|
|
|
<div class="col-auto">
|
|
|
|
|
<label class="col-form-label">时间范围</label>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="col-auto">
|
|
|
|
|
<select id="filter-days" class="form-select form-select-sm">
|
|
|
|
|
<option value="">全部</option>
|
|
|
|
|
<option value="7">最近7天</option>
|
|
|
|
|
<option value="30">最近30天</option>
|
|
|
|
|
</select>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="col-auto">
|
|
|
|
|
<label class="col-form-label">前 N 名</label>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="col-auto">
|
|
|
|
|
<input
|
|
|
|
|
type="number"
|
|
|
|
|
id="filter-top-n"
|
|
|
|
|
class="form-control form-control-sm"
|
|
|
|
|
value="5"
|
|
|
|
|
min="1"
|
|
|
|
|
max="10"
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="col-auto">
|
|
|
|
|
<button type="button" id="btn-refresh-chart" class="btn btn-sm btn-outline-primary">
|
|
|
|
|
更新
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
</form>
|
|
|
|
|
<canvas id="scoreChart" height="150"></canvas>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="col-md-4 text-end align-self-end">
|
|
|
|
|
@ -91,8 +121,21 @@
|
|
|
|
|
</div>
|
|
|
|
|
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.1/dist/chart.umd.min.js"></script>
|
|
|
|
|
<script>
|
|
|
|
|
let chartInstance = null;
|
|
|
|
|
|
|
|
|
|
async function loadScoreChart() {
|
|
|
|
|
const resp = await fetch("/api/stats/top_students_timeline");
|
|
|
|
|
const days = document.getElementById("filter-days").value;
|
|
|
|
|
const topNInput = document.getElementById("filter-top-n");
|
|
|
|
|
let topN = parseInt(topNInput.value || "5", 10);
|
|
|
|
|
if (Number.isNaN(topN) || topN <= 0) topN = 5;
|
|
|
|
|
if (topN > 10) topN = 10;
|
|
|
|
|
topNInput.value = topN;
|
|
|
|
|
|
|
|
|
|
const params = new URLSearchParams();
|
|
|
|
|
params.set("top_n", topN.toString());
|
|
|
|
|
if (days) params.set("days", days);
|
|
|
|
|
|
|
|
|
|
const resp = await fetch(`/api/stats/top_students_timeline?${params.toString()}`);
|
|
|
|
|
const data = await resp.json();
|
|
|
|
|
if (!data.labels || data.labels.length === 0) {
|
|
|
|
|
return;
|
|
|
|
|
@ -107,7 +150,11 @@
|
|
|
|
|
"#6610f2",
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
new Chart(ctx, {
|
|
|
|
|
if (chartInstance) {
|
|
|
|
|
chartInstance.destroy();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
chartInstance = new Chart(ctx, {
|
|
|
|
|
type: "line",
|
|
|
|
|
data: {
|
|
|
|
|
labels: data.labels,
|
|
|
|
|
@ -134,6 +181,11 @@
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
document.addEventListener("DOMContentLoaded", loadScoreChart);
|
|
|
|
|
document.addEventListener("DOMContentLoaded", () => {
|
|
|
|
|
document
|
|
|
|
|
.getElementById("btn-refresh-chart")
|
|
|
|
|
.addEventListener("click", loadScoreChart);
|
|
|
|
|
loadScoreChart();
|
|
|
|
|
});
|
|
|
|
|
</script>
|
|
|
|
|
{% endblock %}
|
|
|
|
|
|