Files
drl_2/xuexiao/templates/statistics.html

274 lines
12 KiB
HTML
Raw Permalink Normal View History

2026-05-29 10:28:07 +08:00
{% extends "base.html" %}
{% block title %}统计报表{% endblock %}
{% block content %}
<div class="page-toolbar">
<div>
<h4 class="page-title"><i class="bi bi-bar-chart-fill"></i> 统计报表</h4>
<p class="page-sub text-muted">
数据来源:课时核对表 Excel · 当前 <strong class="text-primary">{{ period_label }}</strong>
({{ month_accounts }} 条学员×课程)
</p>
</div>
{% if current_user.has_permission('statistics_export') %}
<a href="{{ url_for('statistics_export', year=year, month=month) }}" class="btn btn-neo-primary btn-sm">
<i class="bi bi-download"></i> 导出
</a>
{% endif %}
</div>
<div class="neo-surface p-3 mb-4">
<form method="GET" action="{{ url_for('statistics') }}" class="row g-3 align-items-end" id="statsPeriodForm">
<div class="col-md-4 col-lg-3">
<label class="form-label fw-semibold mb-1"><i class="bi bi-calendar3"></i> 选择统计月份</label>
{% if available_periods %}
<select class="form-select form-select-lg" name="period" id="statsPeriodSelect" aria-label="统计月份">
{% for p in available_periods %}
<option value="{{ p.year }}-{{ p.month }}"
{% if year == p.year and month == p.month %}selected{% endif %}
{% if p.count < 20 %}data-warn="1"{% endif %}>
{{ p.year }} 年 {{ p.month }} 月({{ p.count }} 条)
</option>
{% endfor %}
</select>
{% else %}
<select class="form-select" disabled><option>尚未导入月度数据</option></select>
{% endif %}
</div>
<div class="col-auto">
<button type="submit" class="btn btn-neo-primary"><i class="bi bi-arrow-repeat"></i> 查看</button>
</div>
{% if available_periods|length > 1 %}
<div class="col-12">
<div class="small text-muted mb-2">快捷切换</div>
<div class="d-flex flex-wrap gap-2">
{% for p in available_periods %}
<a href="{{ url_for('statistics', year=p.year, month=p.month) }}"
class="btn btn-sm {% if year == p.year and month == p.month %}btn-primary{% elif p.count < 20 %}btn-outline-warning{% else %}btn-outline-secondary{% endif %}">
{{ p.year }}-{{ '%02d'|format(p.month) }}
<span class="badge {% if year == p.year and month == p.month %}bg-light text-primary{% else %}bg-secondary{% endif %} ms-1">{{ p.count }}</span>
</a>
{% endfor %}
</div>
</div>
{% endif %}
</form>
{% if not available_periods %}
<p class="text-muted small mb-0 mt-2">请先在 <a href="{{ url_for('keshibiao_index') }}">课时核对表</a> 导入 Excel。</p>
{% endif %}
</div>
<!-- 当月概览 -->
<div class="stat-grid mb-4">
<div class="neo-surface neo-stat">
<div class="stat-icon green"><i class="bi bi-cash-stack"></i></div>
<div>
<p class="stat-value">¥{{ "%.0f"|format(month_recharge) }}</p>
<p class="stat-label">{{ period_label }} 充值</p>
</div>
</div>
<div class="neo-surface neo-stat">
<div class="stat-icon blue"><i class="bi bi-clock-history"></i></div>
<div>
<p class="stat-value">{{ "%.0f"|format(month_consumption) }}</p>
<p class="stat-label">{{ period_label }} 消课(节)</p>
</div>
</div>
<div class="neo-surface neo-stat">
<div class="stat-icon yellow"><i class="bi bi-currency-yen"></i></div>
<div>
<p class="stat-value">¥{{ "%.0f"|format(month_consumed_amount) }}</p>
<p class="stat-label">{{ period_label }} 已消金额</p>
</div>
</div>
<div class="neo-surface neo-stat">
<div class="stat-icon purple"><i class="bi bi-arrow-return-left"></i></div>
<div>
<p class="stat-value">¥{{ "%.0f"|format(month_refund) }}</p>
<p class="stat-label">{{ period_label }} 退费</p>
</div>
</div>
</div>
<div class="stat-grid mb-4">
<div class="neo-surface neo-stat">
<div class="stat-icon green"><i class="bi bi-wallet2"></i></div>
<div>
<p class="stat-value">¥{{ "%.0f"|format(month_end_balance) }}</p>
<p class="stat-label">{{ period_label }} 月末余额合计</p>
</div>
</div>
<div class="neo-surface neo-stat">
<div class="stat-icon blue"><i class="bi bi-person-plus"></i></div>
<div>
<p class="stat-value">{{ month_new_signups }}</p>
<p class="stat-label">{{ period_label }} 新签学员</p>
</div>
</div>
<div class="neo-surface neo-stat">
<div class="stat-icon purple"><i class="bi bi-people-fill"></i></div>
<div>
<p class="stat-value">{{ active_students }}</p>
<p class="stat-label">在读学员</p>
</div>
</div>
<div class="neo-surface neo-stat">
<div class="stat-icon yellow"><i class="bi bi-calendar-range"></i></div>
<div>
<p class="stat-value">¥{{ "%.0f"|format(year_recharge) }}</p>
<p class="stat-label">{{ year }} 年度充值累计</p>
</div>
</div>
</div>
{% if monthly_trend %}
<div class="neo-surface chart-card mb-4">
<div class="chart-header">
<div>
<h5 class="mb-1 fw-bold">月度趋势</h5>
<span class="text-muted" style="font-size:0.85rem;">已导入月份充值金额</span>
</div>
</div>
<div class="chart-bars">
{% set max_r = monthly_trend|map(attribute='recharge')|max %}
{% for item in monthly_trend %}
<div class="chart-col">
<div class="chart-bar-wrap">
<div class="chart-bar {% if item.month == period_label %}active{% endif %}"
style="height: {{ (item.recharge / max_r * 100) if max_r > 0 else 0 }}%;"></div>
</div>
<span class="chart-label">{{ item.label }}</span>
<span class="chart-val">¥{{ "%.0f"|format(item.recharge) }}</span>
</div>
{% endfor %}
</div>
</div>
{% endif %}
<div class="row g-4">
<div class="col-lg-6">
<div class="neo-surface p-4 h-100">
<h6 class="fw-bold mb-3"><i class="bi bi-book"></i> 各课程统计({{ period_label }})</h6>
{% if course_stats %}
<div class="table-responsive">
<table class="table table-sm table-hover mb-0">
<thead class="table-light">
<tr>
<th>课程</th>
<th class="text-end">充值</th>
<th class="text-end">消课</th>
<th class="text-end">退费</th>
<th class="text-end">月末余额</th>
</tr>
</thead>
<tbody>
{% for c in course_stats %}
<tr>
<td><span class="badge bg-secondary-subtle text-dark border">{{ c.code }}</span></td>
<td class="text-end text-success">¥{{ "%.0f"|format(c.recharge) }}</td>
<td class="text-end">{{ "%.0f"|format(c.consumed_hours) }}节</td>
<td class="text-end text-danger">{% if c.refund %}¥{{ "%.0f"|format(c.refund) }}{% else %}-{% endif %}</td>
<td class="text-end fw-semibold">¥{{ "%.0f"|format(c.end_balance) }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<p class="text-muted mb-0">该月暂无课程数据,请先在「课时核对表」导入 Excel。</p>
{% endif %}
</div>
</div>
<div class="col-lg-6">
<div class="neo-surface p-4 h-100">
<h6 class="fw-bold mb-3"><i class="bi bi-arrow-return-left"></i> 退费统计({{ period_label }})</h6>
{% if refund_rows %}
<div class="table-responsive">
<table class="table table-sm table-hover mb-0">
<thead class="table-light">
<tr><th>课程</th><th class="text-end">笔数</th><th class="text-end">退费课时</th><th class="text-end">金额</th></tr>
</thead>
<tbody>
{% for r in refund_rows %}
<tr>
<td>{{ r.name }}</td>
<td class="text-end">{{ r.count }}</td>
<td class="text-end">{{ "%.0f"|format(r.lessons) }}</td>
<td class="text-end text-danger fw-semibold">¥{{ "%.2f"|format(r.amount) }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<p class="text-muted mb-0">该月无退费记录。</p>
{% endif %}
</div>
</div>
<div class="col-lg-6">
<div class="neo-surface p-4 h-100">
<h6 class="fw-bold mb-3"><i class="bi bi-calendar-check"></i> 上课登记({{ period_label }})</h6>
{% if class_activity %}
<div class="table-responsive">
<table class="table table-sm table-hover mb-0">
<thead class="table-light">
<tr><th>课程</th><th class="text-end">登记次数</th><th class="text-end">课时</th></tr>
</thead>
<tbody>
{% for a in class_activity %}
<tr>
<td>{{ a.name }}</td>
<td class="text-end">{{ a.sessions }}</td>
<td class="text-end">{{ "%.0f"|format(a.lessons) }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<p class="text-muted mb-0">该月暂无上课登记解析数据。</p>
{% endif %}
</div>
</div>
<div class="col-lg-6">
<div class="neo-surface p-4 h-100">
<h6 class="fw-bold mb-3"><i class="bi bi-pie-chart"></i> 财务营收对账(已导入全周期)</h6>
<div class="row g-3 text-center">
<div class="col-4">
<p class="text-muted small mb-1">累计充值</p>
<p class="fs-4 fw-bold text-success mb-0">¥{{ "%.0f"|format(total_recharge) }}</p>
</div>
<div class="col-4">
<p class="text-muted small mb-1">累计退费</p>
<p class="fs-4 fw-bold text-danger mb-0">¥{{ "%.0f"|format(total_refund) }}</p>
</div>
<div class="col-4">
<p class="text-muted small mb-1">净收入</p>
<p class="fs-4 fw-bold text-primary mb-0">¥{{ "%.0f"|format(net_income) }}</p>
</div>
</div>
<hr>
<p class="text-muted small mb-0 text-center">
累计已消课金额 ¥{{ "%.0f"|format(total_consumed_amount) }}
· {{ year }} 年度消课 {{ "%.0f"|format(year_consumption) }} 节
· 年度退费 ¥{{ "%.0f"|format(year_refund) }}
</p>
</div>
</div>
</div>
{% endblock %}
{% block extra_js %}
<script>
(function () {
var sel = document.getElementById('statsPeriodSelect');
if (!sel) return;
sel.addEventListener('change', function () {
document.getElementById('statsPeriodForm').submit();
});
})();
</script>
{% endblock %}