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

<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>