Files
drl_2/xuexiao/templates/student_detail.html
user9994793890 ee860ce0ae Initial commit
2026-05-29 10:28:07 +08:00

345 lines
17 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{% extends "base.html" %}
{% from 'macros/sortable.html' import sort_th %}
{% set detail_params = {'id': student.id} %}
{% block title %}学员详情 - 学生课程管理系统{% endblock %}
{% block content %}
<div class="d-flex justify-content-between align-items-center mb-3">
<h4><i class="bi bi-person-badge"></i> 学员详情 - {{ student.name }}</h4>
<div>
{% if current_user.has_permission('student_account_adjust') %}
<a href="{{ url_for('account_adjust') }}" class="btn btn-outline-warning btn-sm me-2"><i class="bi bi-sliders"></i> 调课时</a>
{% endif %}
<a href="{{ url_for('student_list') }}" class="btn btn-outline-secondary"><i class="bi bi-arrow-left"></i> 返回列表</a>
</div>
</div>
<!-- 基本信息 -->
<div class="card shadow-sm mb-3">
<div class="card-header bg-primary text-white"><i class="bi bi-person"></i> 基本信息</div>
<div class="card-body">
<div class="row">
<div class="col-md-4"><strong>姓名:</strong>{{ student.name }}</div>
<div class="col-md-4"><strong>来源:</strong>{{ student.source or '新学员' }}</div>
<div class="col-md-4"><strong>状态:</strong>
{% if student.status == 1 %}<span class="badge bg-success">在读</span>{% else %}<span class="badge bg-secondary">停用</span>{% endif %}
</div>
<div class="col-md-4"><strong>出生日期:</strong>{{ student.birthday or '-' }}</div>
<div class="col-md-4"><strong>注册时间:</strong>{{ student.created_at.strftime('%Y-%m-%d') if student.created_at else '-' }}</div>
<div class="col-md-12"><strong>备注:</strong>{{ student.remark or '-' }}</div>
</div>
</div>
</div>
<!-- 课时信息 -->
<div class="card shadow-sm mb-3">
<div class="card-header bg-success text-white d-flex justify-content-between align-items-center">
<span><i class="bi bi-wallet2"></i> 课时信息{% if ledger_period %} <small class="opacity-75">(Excel {{ ledger_period }})</small>{% endif %}</span>
{% if current_user.has_permission('keshibiao_view') %}
<a href="{{ url_for('keshibiao_balances') }}" class="btn btn-sm btn-light">全部学员余额表 →</a>
{% endif %}
</div>
<div class="card-body">
{% if accounts %}
<div class="table-responsive">
<table class="table table-bordered table-hover">
<thead class="table-light">
<tr>
<th>{{ sort_th('student_detail', 'course', '课程', sort, order, detail_params, '') }}</th>
<th>{{ sort_th('student_detail', 'end_lessons', '正课剩余', sort, order, detail_params, '') }}</th>
<th>{{ sort_th('student_detail', 'end_gift', '赠课剩余', sort, order, detail_params, '') }}</th>
<th>{{ sort_th('student_detail', 'end_total', '总剩余', sort, order, detail_params, '') }}</th>
<th>
{{ sort_th('student_detail', 'end_balance', '账户余额', sort, order, detail_params, '', title='折后口径账面余额,非退费金额') }}
<i class="bi bi-info-circle text-muted" data-bs-toggle="tooltip" title="折后口径账面余额,非退费金额"></i>
</th>
<th>
{{ sort_th('student_detail', 'refund_due', '应退金额', sort, order, detail_params, '', title='原价单价×剩余正课,不含赠课') }}
<i class="bi bi-info-circle text-muted" data-bs-toggle="tooltip" title="原价单价×剩余正课,不含赠课"></i>
</th>
<th>{{ sort_th('student_detail', 'unit_price', '单价/节', sort, order, detail_params, '') }}</th>
<th>数据月份</th>
<th>状态</th>
{% if current_user.has_permission('keshibiao_view') %}<th>课时表</th>{% endif %}
</tr>
</thead>
<tbody>
{% for a in accounts %}
{% set lg = account_ledger.get(a.id, {}) %}
{% set rp = refund_previews.get(a.id, {}) if refund_previews else {} %}
{% set total_h = lg.get('end_total_lessons') %}
{% if total_h is none %}{% set total_h = a.total_hours %}{% endif %}
<tr>
<td>{{ a.course.name if a.course else '—' }}{% if a.course and a.course.course_code %} <small class="text-muted">({{ a.course.course_code }})</small>{% endif %}</td>
<td><strong>{{ fmt_hours(lg.get('end_lessons')) }}</strong></td>
<td>{{ fmt_hours(lg.get('end_gift_lessons')) }}</td>
<td class="text-primary fw-semibold">{{ fmt_hours(lg.get('end_total_lessons')) }}</td>
<td class="text-success">{{ fmt_money(lg.get('end_balance')) }}</td>
<td>
{% if rp and rp.has_original and not rp.refund_label %}
<strong class="text-danger">{{ fmt_money(rp.refund_due) }}</strong>
{% elif rp and rp.refund_label %}
<span class="text-muted">{{ rp.refund_label }}</span>
{% else %}
<span class="text-muted">-</span>
{% endif %}
</td>
<td>{{ fmt_price(lg.get('unit_price')) }}</td>
<td><small class="text-muted">{{ lg.get('period') or '—' }}</small></td>
<td>
{% if a.is_stopped %}
<span class="badge bg-warning">停课中</span>
{% elif total_h <= 5 and total_h > 0 %}
<span class="badge bg-danger">课时不足</span>
{% elif total_h == 0 %}
<span class="badge bg-secondary">已用完</span>
{% else %}
<span class="badge bg-success">正常</span>
{% endif %}
{% if a.account_status == 'graduated' %}<span class="badge bg-info">结课</span>{% endif %}
</td>
{% if current_user.has_permission('keshibiao_view') %}
<td><a href="{{ url_for('keshibiao_account', account_id=a.id) }}" class="btn btn-sm btn-outline-primary">流水</a></td>
{% endif %}
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<p class="text-muted">暂无课时账户</p>
{% endif %}
</div>
</div>
{% if accounts and refund_previews %}
{% for a in accounts %}
{% set rp = refund_previews.get(a.id, {}) %}
{% if rp and rp.has_original %}
<div class="refund-preview-card mb-3">
<div class="d-flex justify-content-between align-items-start mb-3">
<h6 class="fw-bold mb-0"><i class="bi bi-calculator"></i> 退费预览(仅供参考)— {{ rp.course_name }}</h6>
<span class="badge bg-secondary-subtle text-muted border">原价口径</span>
</div>
<div class="row g-3 small">
<div class="col-md-4"><span class="text-muted">原价总额</span><div class="fw-semibold">{{ fmt_money(rp.original_total) }}</div></div>
<div class="col-md-4"><span class="text-muted">实付总额</span><div class="fw-semibold">{{ fmt_money(rp.paid_total) }}</div></div>
<div class="col-md-4"><span class="text-muted">优惠金额</span><div class="fw-semibold">{{ fmt_money(rp.discount) }}</div></div>
<div class="col-md-4"><span class="text-muted">原价单价/节</span><div>{{ fmt_price(rp.original_unit) }}</div></div>
<div class="col-md-4"><span class="text-muted">消课单价/节</span><div>{{ fmt_price(rp.consume_unit) }} <small class="text-muted">(实付÷总课时)</small></div></div>
<div class="col-md-4"><span class="text-muted">正课已上</span><div>{{ fmt_hours(rp.consumed_normal) }} 节</div></div>
<div class="col-md-4"><span class="text-muted">正课剩余</span><div>{{ fmt_hours(rp.regular_remaining) }} 节</div></div>
<div class="col-md-4"><span class="text-muted">赠课剩余</span><div>{{ fmt_hours(rp.gift_remaining) }} 节 <small class="text-muted">(退费时清零)</small></div></div>
<div class="col-md-4"><span class="text-muted">账户余额</span><div class="text-success">{{ fmt_money(rp.balance) }} <small class="text-muted">(折后口径)</small></div></div>
</div>
<div class="refund-preview-highlight mt-3 p-3 rounded">
<div class="d-flex justify-content-between align-items-center flex-wrap gap-2">
<span class="fw-bold fs-5">★ 如退费应退</span>
<span class="refund-due-amount">
{% if rp.refund_label %}{{ rp.refund_label }}{% else %}{{ fmt_money(rp.refund_due) }}{% endif %}
</span>
</div>
</div>
<p class="text-muted small mb-0 mt-2">退费金额 = 原价单价 × 剩余正课数,赠课不退款。最终退费金额以实际审批为准。</p>
</div>
{% elif rp and not rp.has_original %}
<div class="refund-preview-card mb-3">
<h6 class="fw-bold text-muted"><i class="bi bi-calculator"></i> 退费预览 — {{ rp.course_name }}</h6>
<p class="text-muted small mb-0">无原价数据(历史数据未录原价),应退相关字段无法计算。</p>
</div>
{% endif %}
{% endfor %}
{% endif %}
<!-- 所在班级 -->
<div class="card shadow-sm mb-3">
<div class="card-header bg-info text-white"><i class="bi bi-grid"></i> 所在班级</div>
<div class="card-body">
{% if class_relations %}
<div class="table-responsive">
<table class="table table-bordered table-hover">
<thead class="table-light">
<tr><th>班级</th><th>课程</th><th>老师</th><th>入班日期</th><th>状态</th></tr>
</thead>
<tbody>
{% for cr in class_relations %}
<tr>
<td>{{ cr.class_.name }}</td>
<td>{{ cr.class_.course.name }}</td>
<td>{{ cr.class_.teacher.name }}</td>
<td>{{ cr.join_date }}</td>
<td>
{% if cr.status == 1 %}<span class="badge bg-success">在读</span>
{% elif cr.status == 2 %}<span class="badge bg-warning">已转出</span>
{% else %}<span class="badge bg-info">已毕业</span>{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<p class="text-muted">暂无班级信息</p>
{% endif %}
</div>
</div>
<!-- 充值记录 -->
<div class="card shadow-sm mb-3">
<div class="card-header"><i class="bi bi-cash-stack"></i> 充值记录</div>
<div class="card-body">
{% if recharge_records %}
<div class="table-responsive">
<table class="table table-bordered table-hover table-sm">
<thead class="table-light">
<tr><th>时间</th><th>课程</th><th>金额</th><th>正课课时</th><th>赠课课时</th><th>支付方式</th><th>备注</th></tr>
</thead>
<tbody>
{% for r in recharge_records %}
<tr>
<td>{{ r.created_at.strftime('%Y-%m-%d %H:%M') }}</td>
<td>{{ r.course.name }}</td>
<td class="text-success">&yen;{{ r.amount }}</td>
<td>{{ r.normal_hours }}</td>
<td class="text-danger">+{{ r.gifted_hours }}</td>
<td>{{ r.payment_method or '-' }}</td>
<td>{{ r.remark or '-' }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<p class="text-muted">暂无充值记录</p>
{% endif %}
</div>
</div>
<!-- 消课记录 -->
<div class="card shadow-sm mb-3">
<div class="card-header"><i class="bi bi-journal-check"></i> 消课记录</div>
<div class="card-body">
{% if consumption_records %}
<div class="table-responsive">
<table class="table table-bordered table-hover table-sm">
<thead class="table-light">
<tr><th>日期</th><th>课程</th><th>消课课时</th><th>正课消耗</th><th>赠课消耗</th><th>类型</th><th>备注</th></tr>
</thead>
<tbody>
{% for c in consumption_records %}
<tr>
<td>{{ c.consume_date }}</td>
<td>{{ c.course.name }}</td>
<td>{{ c.hours_consumed }}</td>
<td>{{ c.normal_consumed }}</td>
<td>{{ c.gifted_consumed }}</td>
<td>
{% if c.consume_type == 'trial' %}<span class="badge bg-secondary">试听</span>
{% elif c.consume_type == 'normal' %}<span class="badge bg-primary">正课</span>
{% else %}<span class="badge bg-warning">混合</span>{% endif %}
{% if c.is_makeup %}<span class="badge bg-info">补录</span>{% endif %}
</td>
<td>{{ c.remark or '-' }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<p class="text-muted">暂无消课记录</p>
{% endif %}
</div>
</div>
<!-- 退费记录 -->
<div class="card shadow-sm mb-3">
<div class="card-header"><i class="bi bi-arrow-return-left"></i> 退费记录</div>
<div class="card-body">
{% if refund_records %}
<div class="table-responsive">
<table class="table table-bordered table-hover table-sm">
<thead class="table-light">
<tr><th>时间</th><th>课程</th><th>退费金额</th><th>扣回赠课</th><th>已消课时金额</th><th>材料费</th><th>原因</th></tr>
</thead>
<tbody>
{% for r in refund_records %}
<tr>
<td>{{ r.created_at.strftime('%Y-%m-%d') }}</td>
<td>{{ r.course.name }}</td>
<td class="text-danger">&yen;{{ r.refund_amount }}</td>
<td>{{ r.deduct_gifted_hours }}</td>
<td>&yen;{{ r.consumed_hours_value }}</td>
<td>&yen;{{ '%.2f'|format((r.material_fee_per_quarter or 0) * (r.quarters or 0)) }}</td>
<td>{{ r.reason or '-' }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<p class="text-muted">暂无退费记录</p>
{% endif %}
</div>
</div>
<!-- 停课记录 -->
<div class="card shadow-sm mb-3">
<div class="card-header"><i class="bi bi-pause-circle"></i> 停课记录</div>
<div class="card-body">
{% if stop_records %}
<div class="table-responsive">
<table class="table table-bordered table-hover table-sm">
<thead class="table-light">
<tr><th>开始日期</th><th>结束日期</th><th>课程</th><th>状态</th><th>原因</th></tr>
</thead>
<tbody>
{% for s in stop_records %}
<tr>
<td>{{ s.start_date }}</td>
<td>{{ s.end_date }}</td>
<td>{{ s.course.name }}</td>
<td>
{% if s.status == 1 %}<span class="badge bg-warning">停课中</span>
{% elif s.status == 2 %}<span class="badge bg-success">已复课</span>
{% else %}<span class="badge bg-secondary">已取消</span>{% endif %}
</td>
<td>{{ s.reason or '-' }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<p class="text-muted">暂无停课记录</p>
{% endif %}
</div>
</div>
{% endblock %}
{% block extra_css %}
<style>
.refund-preview-card {
background: #FFFBEB;
border: 1px solid #FCD34D;
border-radius: 12px;
padding: 1.25rem;
}
.refund-preview-highlight {
background: #FEF3C7;
border: 1px solid #F59E0B;
}
.refund-due-amount {
color: #dc3545;
font-weight: 800;
font-size: 1.5rem;
}
</style>
{% endblock %}
{% block extra_js %}
<script>
document.querySelectorAll('[data-bs-toggle="tooltip"]').forEach(function (el) {
new bootstrap.Tooltip(el);
});
</script>
{% endblock %}