131 lines
7.2 KiB
HTML
131 lines
7.2 KiB
HTML
{% extends "base.html" %}
|
||
{% from 'macros/sortable.html' import sort_th %}
|
||
{% block title %}学员课时余额{% endblock %}
|
||
{% block content %}
|
||
<div class="page-toolbar">
|
||
<div>
|
||
<h4 class="page-title"><i class="bi bi-wallet2"></i> 学员课时余额</h4>
|
||
<p class="page-sub text-muted">
|
||
数据来自课时核对表 Excel 导入 · 当前查看
|
||
<strong class="text-primary">{{ year }}-{{ '%02d'|format(month) }}</strong>
|
||
{% if total %}(共 {{ total }} 条学员×课程){% endif %}
|
||
</p>
|
||
</div>
|
||
<div class="d-flex gap-2">
|
||
<a href="{{ url_for('keshibiao_index') }}" class="btn btn-neo-secondary btn-sm"><i class="bi bi-cloud-upload"></i> 导入</a>
|
||
</div>
|
||
</div>
|
||
|
||
{% if period_stats_list %}
|
||
<div class="neo-surface p-3 mb-3">
|
||
<div class="small text-muted mb-2">切换月份(数字为当月导入条数,请选 400+ 条的完整月份)</div>
|
||
<div class="d-flex flex-wrap gap-2">
|
||
{% for p in period_stats_list %}
|
||
<a href="{{ url_for('keshibiao_snapshots', year=p.year, month=p.month, keyword=keyword or '', sort=sort or none, order=order if sort else none) }}"
|
||
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 %}
|
||
|
||
<div class="neo-surface p-3 mb-3">
|
||
<form method="GET" class="row g-2 align-items-end">
|
||
<input type="hidden" name="year" value="{{ year }}">
|
||
<input type="hidden" name="month" value="{{ month }}">
|
||
{% if sort %}<input type="hidden" name="sort" value="{{ sort }}">{% endif %}
|
||
{% if sort %}<input type="hidden" name="order" value="{{ order }}">{% endif %}
|
||
<div class="col-md-6">
|
||
<label class="form-label">搜索学员或课程</label>
|
||
<input type="text" class="form-control" name="keyword" value="{{ keyword or '' }}" placeholder="姓名 / scratch / 大班 …">
|
||
</div>
|
||
<div class="col-md-2">
|
||
<button type="submit" class="btn btn-neo-primary w-100"><i class="bi bi-search"></i> 搜索</button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
|
||
<div class="neo-table-wrap">
|
||
<div class="table-responsive">
|
||
<table class="table table-hover align-middle mb-0 keshi-balance-table">
|
||
<thead class="table-light sticky-top">
|
||
<tr>
|
||
<th>{{ sort_th('keshibiao_snapshots', 'student', '学员', sort, order, list_params, '') }}</th>
|
||
<th>{{ sort_th('keshibiao_snapshots', 'course', '课程', sort, order, list_params, '') }}</th>
|
||
<th class="text-end keshi-col-key">{{ sort_th('keshibiao_snapshots', 'end_lessons', '正课剩余', sort, order, list_params) }}</th>
|
||
<th class="text-end">{{ sort_th('keshibiao_snapshots', 'end_gift', '赠课剩余', sort, order, list_params) }}</th>
|
||
<th class="text-end keshi-col-key">{{ sort_th('keshibiao_snapshots', 'end_total', '总剩余课时', sort, order, list_params) }}</th>
|
||
<th class="text-end keshi-col-money">{{ sort_th('keshibiao_snapshots', 'end_balance', '账户余额', sort, order, list_params, title='折后口径账面余额') }}</th>
|
||
<th class="text-end keshi-col-money">{{ sort_th('keshibiao_snapshots', 'unit_price', '单价/节', sort, order, list_params) }}</th>
|
||
<th class="text-end">{{ sort_th('keshibiao_snapshots', 'consumed', '本月消课', sort, order, list_params) }}</th>
|
||
<th></th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{% for item in rows %}
|
||
{% set s = item.snap %}
|
||
{% set d = item.disp if item.disp is mapping else {} %}
|
||
{% set acc = item.acc %}
|
||
{% set stu = acc.student if acc else none %}
|
||
{% set crs = acc.course if acc else none %}
|
||
<tr>
|
||
<td>
|
||
{% if stu %}
|
||
<a href="{{ url_for('student_detail', id=stu.id) }}" class="fw-semibold text-decoration-none">{{ stu.name }}</a>
|
||
{% else %}<span class="text-muted">—</span>{% endif %}
|
||
</td>
|
||
<td><span class="badge bg-secondary-subtle text-dark border">{{ crs.course_code or crs.name if crs else '—' }}</span></td>
|
||
<td class="text-end keshi-col-key fs-6">{{ fmt_hours(d.get('end_lessons')) }}</td>
|
||
<td class="text-end">{{ fmt_hours(d.get('end_gift_lessons')) }}</td>
|
||
<td class="text-end keshi-col-key fs-5 fw-bold text-primary">{{ fmt_hours(d.get('end_total_lessons')) }}</td>
|
||
<td class="text-end keshi-col-money fs-5 fw-bold text-success">{{ fmt_money(d.get('end_balance')) }}</td>
|
||
<td class="text-end keshi-col-money fw-semibold">{{ fmt_price(d.get('unit_price')) }}</td>
|
||
<td class="text-end text-muted">{{ fmt_hours(s.consumed_lessons) }}</td>
|
||
<td>
|
||
{% if acc %}
|
||
<a href="{{ url_for('keshibiao_account', account_id=acc.id) }}" class="btn btn-sm btn-outline-primary">明细</a>
|
||
{% endif %}
|
||
</td>
|
||
</tr>
|
||
{% else %}
|
||
<tr>
|
||
<td colspan="9" class="text-center py-5">
|
||
<p class="text-muted mb-2">当前月份没有可显示的数据</p>
|
||
{% if period_stats_list %}
|
||
<p class="small">请点上方 <strong>2026-04(492)</strong> 等条数较多的月份;勿选仅 1 条的测试月。</p>
|
||
{% else %}
|
||
<p class="small">请先在「课时核对表」页导入 Excel。</p>
|
||
{% endif %}
|
||
</td>
|
||
</tr>
|
||
{% endfor %}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
|
||
{% if total > per_page %}
|
||
<nav class="mt-3">
|
||
<ul class="pagination justify-content-center">
|
||
{% if page > 1 %}
|
||
<li class="page-item"><a class="page-link" href="{{ url_for('keshibiao_snapshots', page=page-1, **list_params) }}">上一页</a></li>
|
||
{% endif %}
|
||
<li class="page-item disabled"><span class="page-link">第 {{ page }} / {{ ((total-1)//per_page)+1 }} 页</span></li>
|
||
{% if page * per_page < total %}
|
||
<li class="page-item"><a class="page-link" href="{{ url_for('keshibiao_snapshots', page=page+1, **list_params) }}">下一页</a></li>
|
||
{% endif %}
|
||
</ul>
|
||
</nav>
|
||
{% endif %}
|
||
{% endblock %}
|
||
|
||
{% block extra_css %}
|
||
<style>
|
||
.keshi-balance-table .keshi-col-key { min-width: 4.5rem; }
|
||
.keshi-balance-table .keshi-col-money { min-width: 6.5rem; }
|
||
.keshi-balance-table thead.sticky-top { top: 0; z-index: 2; }
|
||
</style>
|
||
{% endblock %}
|