You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
204 lines
12 KiB
204 lines
12 KiB
<div id="household-results-region" class="space-y-3">
|
|
{% set return_state = {
|
|
'q': search_term,
|
|
'side': side,
|
|
'invite_status': invite_status,
|
|
'attendance_status': attendance_status,
|
|
'tag_option_id': tag_option_id,
|
|
'sort_by': sort_by,
|
|
'sort_order': sort_order,
|
|
'page': page,
|
|
'per_page': per_page,
|
|
} %}
|
|
<div class="rounded-xl border border-neutral-200 bg-white px-3 py-3 shadow-soft dark:border-neutral-700 dark:bg-neutral-800">
|
|
<div class="flex flex-col gap-2 lg:flex-row lg:items-start lg:justify-between">
|
|
<div class="min-w-0 space-y-1">
|
|
<div class="text-sm font-semibold text-neutral-700 dark:text-neutral-200">
|
|
当前结果:{{ filtered_count }} 户(总户数 {{ stats.total_households }})
|
|
</div>
|
|
<div class="flex flex-wrap items-center gap-x-2 gap-y-1 text-xs text-neutral-500 dark:text-neutral-400">
|
|
<span class="inline-flex items-center rounded-full bg-neutral-100 px-2 py-1 text-[11px] font-medium text-neutral-500 dark:bg-neutral-900 dark:text-neutral-300">当前按“{{ sort_field_labels[sort_by] }}”{{ '降序' if sort_order == 'desc' else '升序' }}展示</span>
|
|
</div>
|
|
</div>
|
|
<div class="summary-metric-list lg:justify-end">
|
|
<span class="summary-metric">到场 {{ filtered_stats.total_attending_households }} 户</span>
|
|
<span class="summary-metric">预计 {{ filtered_stats.total_expected_attendees }}</span>
|
|
<span class="summary-metric">儿童 {{ filtered_stats.total_children }}</span>
|
|
<span class="status-badge status-badge-accent">礼金 ¥{{ '%.2f'|format(filtered_stats.total_gift_amount) }}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<section class="table-container hidden shadow-soft md:block">
|
|
<div class="overflow-x-auto">
|
|
<table class="min-w-full text-left text-sm">
|
|
<thead class="table-header">
|
|
<tr>
|
|
<th class="px-3 py-2.5">户主</th>
|
|
<th class="px-3 py-2.5">电话</th>
|
|
<th class="px-3 py-2.5">所属侧</th>
|
|
<th class="px-3 py-2.5">关系 / 标签</th>
|
|
<th class="table-cell-numeric px-3 py-2">预计 / 实际</th>
|
|
<th class="table-cell-numeric px-3 py-2">儿童</th>
|
|
<th class="table-cell-numeric px-3 py-2">礼金</th>
|
|
<th class="px-3 py-2.5">备注</th>
|
|
<th class="px-3 py-2.5">状态</th>
|
|
<th class="px-3 py-2.5">更新</th>
|
|
<th class="table-cell-action px-3 py-2">操作</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for household in households %}
|
|
<tr class="table-row">
|
|
<td class="px-3 py-2">
|
|
<div class="font-medium text-neutral-800 dark:text-neutral-100">{{ household.head_name }}</div>
|
|
<div class="text-xs text-neutral-400 dark:text-neutral-500">{{ household.household_code }}</div>
|
|
</td>
|
|
<td class="px-3 py-2 text-neutral-600 dark:text-neutral-300">{{ household.phone or '未登记' }}</td>
|
|
<td class="px-3 py-2 text-neutral-600 dark:text-neutral-300">{{ household_value_label('side', household.side) }}</td>
|
|
<td class="px-3 py-2 text-neutral-600 dark:text-neutral-300">
|
|
<div class="space-y-1">
|
|
<div>
|
|
<span>{{ household.relation_category_option.option_label if household.relation_category_option else '-' }}</span>
|
|
</div>
|
|
<div class="flex flex-wrap gap-1">
|
|
{% for tag_id in household.tag_option_ids_json or [] %}
|
|
{% for option in tag_options if option.id == tag_id %}
|
|
<span class="status-badge status-badge-muted">{{ option.option_label }}</span>
|
|
{% endfor %}
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
</td>
|
|
<td class="table-cell-numeric px-3 py-2 text-neutral-700 dark:text-neutral-200">{{ household.expected_attendee_count }} / {{ household.actual_attendee_count }}</td>
|
|
<td class="table-cell-numeric px-3 py-2 text-neutral-600 dark:text-neutral-300">{{ household.child_count }}(红包 {{ household.red_packet_child_count }})</td>
|
|
<td class="table-cell-numeric px-3 py-2">
|
|
<span class="status-badge status-badge-accent font-semibold">¥{{ '%.2f'|format(household.total_gift_amount) }}</span>
|
|
</td>
|
|
<td class="max-w-[16rem] px-3 py-2 text-xs text-neutral-500 dark:text-neutral-400">{{ household.note or '-' }}</td>
|
|
<td class="px-3 py-2">
|
|
<div class="flex flex-wrap gap-1">
|
|
<span class="status-badge {% if household.invite_status in ['confirmed', 'invited'] %}status-badge-brand{% else %}status-badge-muted{% endif %}">{{ household_value_label('invite_status', household.invite_status) }}</span>
|
|
<span class="status-badge {% if household.attendance_status in ['attending', 'partial'] %}status-badge-accent{% else %}status-badge-muted{% endif %}">{{ household_value_label('attendance_status', household.attendance_status) }}</span>
|
|
</div>
|
|
</td>
|
|
<td class="px-3 py-2 text-xs text-neutral-500 dark:text-neutral-400">
|
|
<div class="font-medium text-neutral-600 dark:text-neutral-300">{{ household.updated_at.strftime('%m-%d %H:%M') if household.updated_at else '-' }}</div>
|
|
<div>{% if household.updated_by_account %}{{ household.updated_by_account.display_name or household.updated_by_account.username }}{% else %}<span class="text-xs text-warm-400">-</span>{% endif %}</div>
|
|
</td>
|
|
<td class="table-cell-action px-3 py-2">
|
|
<div class="flex justify-end gap-2">
|
|
<a class="btn btn-secondary btn-sm" href="{{ url_for('main.edit_household', household_id=household.id) }}">打开编辑</a>
|
|
<form action="{{ url_for('main.delete_household', household_id=household.id) }}" method="post" onsubmit="return window.confirm('确认删除户 {{ household.head_name }} 吗?');">
|
|
<input type="hidden" name="confirm" value="yes">
|
|
{% for key, value in return_state.items() %}
|
|
<input type="hidden" name="{{ key }}" value="{{ '' if value is none else value }}">
|
|
{% endfor %}
|
|
<button type="submit" class="btn btn-secondary btn-sm text-brand-600 hover:text-brand-700 dark:text-brand-300 dark:hover:text-brand-200">删除</button>
|
|
</form>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
{% else %}
|
|
<tr>
|
|
<td colspan="11" class="empty-state">当前没有匹配的户数据。</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</section>
|
|
|
|
<section class="space-y-3 md:hidden">
|
|
{% for household in households %}
|
|
<article class="card">
|
|
<div class="card-body space-y-3">
|
|
<div class="flex items-start justify-between gap-3">
|
|
<div>
|
|
<h3 class="font-medium text-neutral-800 dark:text-neutral-100">{{ household.head_name }}</h3>
|
|
<p class="text-xs text-neutral-400 dark:text-neutral-500">{{ household.household_code }}</p>
|
|
</div>
|
|
<span class="rounded-full bg-neutral-100 px-2 py-0.5 text-xs text-neutral-600 dark:bg-neutral-700 dark:text-neutral-300">{{ household_value_label('side', household.side) }}</span>
|
|
</div>
|
|
<dl class="grid grid-cols-2 gap-2 text-sm">
|
|
<div>
|
|
<dt class="text-xs text-neutral-400 dark:text-neutral-500">电话</dt>
|
|
<dd class="text-neutral-700 dark:text-neutral-200">{{ household.phone or '未登记' }}</dd>
|
|
</div>
|
|
<div>
|
|
<dt class="text-xs text-neutral-400 dark:text-neutral-500">礼金</dt>
|
|
<dd class="font-medium text-accent-600 dark:text-accent-400">¥{{ '%.2f'|format(household.total_gift_amount) }}</dd>
|
|
</div>
|
|
<div>
|
|
<dt class="text-xs text-neutral-400 dark:text-neutral-500">预计 / 实际</dt>
|
|
<dd class="text-neutral-700 dark:text-neutral-200">{{ household.expected_attendee_count }} / {{ household.actual_attendee_count }}</dd>
|
|
</div>
|
|
<div>
|
|
<dt class="text-xs text-neutral-400 dark:text-neutral-500">儿童</dt>
|
|
<dd class="text-neutral-700 dark:text-neutral-200">{{ household.child_count }}(红包 {{ household.red_packet_child_count }})</dd>
|
|
</div>
|
|
<div class="col-span-2">
|
|
<dt class="text-xs text-neutral-400 dark:text-neutral-500">备注</dt>
|
|
<dd class="text-neutral-700 dark:text-neutral-200">{{ household.note or '-' }}</dd>
|
|
</div>
|
|
</dl>
|
|
{% if household.tag_option_ids_json %}
|
|
<div class="flex flex-wrap gap-1.5">
|
|
{% for tag_id in household.tag_option_ids_json %}
|
|
{% for option in tag_options if option.id == tag_id %}
|
|
<span class="status-badge status-badge-muted">{{ option.option_label }}</span>
|
|
{% endfor %}
|
|
{% endfor %}
|
|
</div>
|
|
{% endif %}
|
|
<div class="flex flex-wrap gap-1.5">
|
|
<span class="status-badge {% if household.invite_status in ['confirmed', 'invited'] %}status-badge-brand{% else %}status-badge-muted{% endif %}">{{ household_value_label('invite_status', household.invite_status) }}</span>
|
|
<span class="status-badge {% if household.attendance_status in ['attending', 'partial'] %}status-badge-accent{% else %}status-badge-muted{% endif %}">{{ household_value_label('attendance_status', household.attendance_status) }}</span>
|
|
</div>
|
|
<div class="grid grid-cols-2 gap-2">
|
|
<a class="btn btn-secondary btn-sm w-full" href="{{ url_for('main.edit_household', household_id=household.id) }}">打开编辑</a>
|
|
<form action="{{ url_for('main.delete_household', household_id=household.id) }}" method="post" onsubmit="return window.confirm('确认删除户 {{ household.head_name }} 吗?');">
|
|
<input type="hidden" name="confirm" value="yes">
|
|
{% for key, value in return_state.items() %}
|
|
<input type="hidden" name="{{ key }}" value="{{ '' if value is none else value }}">
|
|
{% endfor %}
|
|
<button type="submit" class="btn btn-secondary btn-sm w-full text-brand-600 hover:text-brand-700 dark:text-brand-300 dark:hover:text-brand-200">删除</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</article>
|
|
{% else %}
|
|
<div class="card">
|
|
<div class="empty-state">当前没有匹配的户数据。</div>
|
|
</div>
|
|
{% endfor %}
|
|
</section>
|
|
|
|
<section class="card shadow-soft">
|
|
<div class="card-body flex flex-col gap-2.5 !p-3 sm:!p-3.5 lg:flex-row lg:items-center lg:justify-between">
|
|
<div class="text-xs font-medium text-neutral-500 dark:text-neutral-400">
|
|
{% if filtered_count %}
|
|
显示第 {{ page_start }} - {{ page_end }} 户,共 {{ filtered_count }} 户
|
|
{% else %}
|
|
当前没有可分页的数据
|
|
{% endif %}
|
|
</div>
|
|
|
|
<div class="pagination-cluster">
|
|
<label class="flex items-center gap-2 text-xs text-neutral-500 dark:text-neutral-400">
|
|
<span>每页</span>
|
|
<select name="per_page" class="form-select w-24" data-pagination-per-page>
|
|
{% for option in per_page_options %}
|
|
<option value="{{ option }}" {% if per_page == option %}selected{% endif %}>{{ option }} 行</option>
|
|
{% endfor %}
|
|
</select>
|
|
</label>
|
|
|
|
<div class="flex items-center gap-2">
|
|
<span class="rounded-full bg-neutral-100 px-2 py-1 text-xs font-medium text-neutral-500 dark:bg-neutral-900 dark:text-neutral-300">第 {{ page }} / {{ total_pages }} 页</span>
|
|
<button type="button" class="btn btn-secondary btn-sm !px-2.5" data-pagination-page="{{ page - 1 }}" {% if not has_prev %}disabled{% endif %}>上一页</button>
|
|
<button type="button" class="btn btn-secondary btn-sm !px-2.5" data-pagination-page="{{ page + 1 }}" {% if not has_next %}disabled{% endif %}>下一页</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
</div>
|
|
|