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.
808 lines
44 KiB
808 lines
44 KiB
{% extends "base.html" %}
|
|
|
|
{% block title %}HappyWedding{% endblock %}
|
|
|
|
{% block content %}
|
|
<section class="mb-5 flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between">
|
|
<div>
|
|
<h2 class="page-title">{% if is_create_mode %}最小创建一户{% else %}编辑户信息{% endif %}</h2>
|
|
{% if not is_create_mode %}
|
|
<div class="mt-2 flex flex-wrap items-center gap-2 text-sm text-neutral-500 dark:text-neutral-400">
|
|
<span>当前对象:{{ household.head_name or '新户' }}</span>
|
|
<span class="status-badge status-badge-muted">户编码 {{ household.household_code }}</span>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
<a class="btn btn-secondary shrink-0" href="{{ url_for('main.index') }}">返回管理首页</a>
|
|
</section>
|
|
|
|
<form id="admin-household-edit-form" action="{% if is_create_mode %}{{ url_for('main.create_household') }}{% else %}{{ url_for('main.update_household', household_id=household.id) }}{% endif %}" method="post" class="card card-body space-y-6">
|
|
<input type="hidden" name="version" value="{{ household.version }}">
|
|
|
|
{% if is_create_mode %}
|
|
<input type="hidden" name="side" value="{{ household.side }}">
|
|
<input type="hidden" name="invite_status" value="{{ household.invite_status }}">
|
|
<input type="hidden" name="attendance_status" value="{{ household.attendance_status }}">
|
|
<input type="hidden" name="expected_attendee_count" value="{{ household.expected_attendee_count }}">
|
|
<input type="hidden" name="actual_attendee_count" value="{{ household.actual_attendee_count }}">
|
|
<input type="hidden" name="child_count" value="{{ household.child_count }}">
|
|
<input type="hidden" name="red_packet_child_count" value="{{ household.red_packet_child_count }}">
|
|
<input type="hidden" name="total_gift_amount" value="{{ '%.2f'|format(household.total_gift_amount) }}">
|
|
<input type="hidden" name="favor_status" value="{{ household.favor_status }}">
|
|
<input type="hidden" name="candy_status" value="{{ household.candy_status }}">
|
|
<input type="hidden" name="child_red_packet_status" value="{{ household.child_red_packet_status }}">
|
|
{% endif %}
|
|
|
|
{% if is_create_mode %}
|
|
<section class="card-body space-y-5">
|
|
<div class="grid grid-cols-1 gap-4 md:grid-cols-[minmax(0,1.4fr)_minmax(0,1fr)]">
|
|
<div>
|
|
<label for="head_name" class="form-label">户主姓名</label>
|
|
<input class="form-input" id="head_name" name="head_name" type="text" value="{{ household.head_name }}" required>
|
|
</div>
|
|
<div class="rounded-xl border border-dashed border-neutral-200 bg-neutral-50 p-4 text-sm text-neutral-600 dark:border-neutral-700 dark:bg-neutral-900 dark:text-neutral-300">
|
|
<p class="font-medium text-neutral-700 dark:text-neutral-100">创建后自动完成</p>
|
|
<ul class="mt-2 space-y-1 text-sm">
|
|
<li>自动生成户编码:{{ household.household_code }}</li>
|
|
<li>默认到场状态:待确认</li>
|
|
<li>默认邀请状态:未邀请</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<label for="note" class="form-label">备注 <span class="text-xs text-neutral-400">(可选)</span></label>
|
|
<textarea class="form-textarea" id="note" name="note" rows="3" placeholder="例如:婚礼当天接待时再补电话或关系">{{ household.note or '' }}</textarea>
|
|
</div>
|
|
|
|
<div class="flex flex-wrap items-center gap-4 border-t border-neutral-200 pt-4 dark:border-neutral-700">
|
|
<button type="submit" class="btn btn-primary">创建并继续补充</button>
|
|
<a class="btn btn-secondary" href="{{ url_for('main.index') }}">返回管理首页</a>
|
|
<span class="text-sm text-neutral-500 dark:text-neutral-400">本步骤只做最小创建,其他字段可以稍后补齐。</span>
|
|
</div>
|
|
</section>
|
|
{% else %}
|
|
<div class="grid grid-cols-1 gap-4 md:grid-cols-3">
|
|
<div>
|
|
<label for="head_name" class="form-label">户主姓名</label>
|
|
<input class="form-input" id="head_name" name="head_name" type="text" value="{{ household.head_name }}" required>
|
|
</div>
|
|
<div>
|
|
<label for="phone" class="form-label">联系电话</label>
|
|
<input class="form-input" id="phone" name="phone" type="text" value="{{ household.phone or '' }}">
|
|
</div>
|
|
<div>
|
|
<label for="household_code" class="form-label">户编码 <span class="text-xs text-neutral-400">(次要字段)</span></label>
|
|
<input class="form-input" id="household_code" name="household_code" type="text" value="{{ household.household_code }}" placeholder="留空将自动生成">
|
|
</div>
|
|
</div>
|
|
|
|
<div class="grid grid-cols-1 gap-4 md:grid-cols-3">
|
|
<div>
|
|
<label for="side" class="form-label">所属侧</label>
|
|
<select class="form-select" id="side" name="side">
|
|
{% for value, label in [('groom_side', '男方'), ('bride_side', '女方'), ('both_side', '双方共有')] %}
|
|
<option value="{{ value }}" {% if household.side == value %}selected{% endif %}>{{ label }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
<div>
|
|
<label for="invite_status" class="form-label">邀请状态</label>
|
|
<select class="form-select" id="invite_status" name="invite_status">
|
|
{% for value, label in [('not_invited', '未邀请'), ('invited', '已邀请'), ('confirmed', '已确认'), ('declined', '已婉拒')] %}
|
|
<option value="{{ value }}" {% if household.invite_status == value %}selected{% endif %}>{{ label }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
<div>
|
|
<label for="attendance_status" class="form-label">到场状态</label>
|
|
<select class="form-select" id="attendance_status" name="attendance_status">
|
|
{% for value, label in [('pending', '待确认'), ('attending', '到场'), ('absent', '缺席'), ('partial', '部分到场')] %}
|
|
<option value="{{ value }}" {% if household.attendance_status == value %}selected{% endif %}>{{ label }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="grid grid-cols-1 gap-4 md:grid-cols-2">
|
|
<div>
|
|
<label for="relation_category_option_id" class="form-label">关系分类</label>
|
|
<select class="form-select" id="relation_category_option_id" name="relation_category_option_id">
|
|
<option value="">未选择</option>
|
|
{% for option in relation_category_options %}
|
|
<option value="{{ option.id }}" {% if household.relation_category_option_id == option.id %}selected{% endif %}>{{ option.option_label }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
<div>
|
|
<label for="new_relation_category_label" class="form-label">新增关系分类</label>
|
|
<input class="form-input" id="new_relation_category_label" name="new_relation_category_label" type="text" placeholder="例如:客户、邻居、校友">
|
|
</div>
|
|
</div>
|
|
|
|
<section>
|
|
<div class="mb-3 flex items-center justify-between gap-3">
|
|
<label class="block text-sm font-medium text-neutral-700 dark:text-neutral-200">标签</label>
|
|
<span class="text-xs text-neutral-400 dark:text-neutral-500">可多选,也可直接补新标签</span>
|
|
</div>
|
|
<div class="grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-3">
|
|
{% set selected_tag_ids = household.tag_option_ids_json or [] %}
|
|
{% for option in tag_options %}
|
|
<label class="flex items-center gap-3 rounded-xl border border-neutral-200 bg-neutral-50 px-4 py-3 text-sm text-neutral-700 transition hover:border-brand-300 hover:bg-brand-50/50 dark:border-neutral-700 dark:bg-neutral-900 dark:text-neutral-200 dark:hover:border-brand-500/60 dark:hover:bg-brand-950/20">
|
|
<input type="checkbox" name="tag_option_ids_json" value="{{ option.id }}" {% if option.id in selected_tag_ids %}checked{% endif %} class="h-4 w-4 rounded border-neutral-300 text-brand-600 focus:ring-brand-500 dark:border-neutral-600 dark:bg-neutral-800 dark:text-brand-400">
|
|
<span>{{ option.option_label }}</span>
|
|
</label>
|
|
{% else %}
|
|
<div class="rounded-xl border border-dashed border-neutral-200 bg-neutral-50 px-4 py-3 text-sm text-neutral-500 dark:border-neutral-700 dark:bg-neutral-900 dark:text-neutral-400 sm:col-span-2 lg:col-span-3">
|
|
当前还没有可选标签,可以直接在下方新增。
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
<div class="mt-3">
|
|
<label for="new_tag_labels" class="form-label">新增标签</label>
|
|
<input class="form-input" id="new_tag_labels" name="new_tag_labels" type="text" placeholder="例如:待回访,重点来宾(用逗号分隔)">
|
|
</div>
|
|
</section>
|
|
|
|
<div class="grid grid-cols-2 gap-4 md:grid-cols-4">
|
|
<div>
|
|
<label for="expected_attendee_count" class="form-label">预计人数</label>
|
|
<input class="form-input" id="expected_attendee_count" name="expected_attendee_count" type="number" min="0" value="{{ household.expected_attendee_count }}">
|
|
</div>
|
|
<div>
|
|
<label for="actual_attendee_count" class="form-label">实际到场人数</label>
|
|
<input class="form-input" id="actual_attendee_count" name="actual_attendee_count" type="number" min="0" value="{{ household.actual_attendee_count }}">
|
|
</div>
|
|
<div>
|
|
<label for="child_count" class="form-label">儿童人数</label>
|
|
<input class="form-input" id="child_count" name="child_count" type="number" min="0" value="{{ household.child_count }}">
|
|
</div>
|
|
<div>
|
|
<label for="red_packet_child_count" class="form-label">需红包儿童人数</label>
|
|
<input class="form-input" id="red_packet_child_count" name="red_packet_child_count" type="number" min="0" value="{{ household.red_packet_child_count }}">
|
|
</div>
|
|
</div>
|
|
|
|
<div class="grid grid-cols-1 gap-4 md:grid-cols-3">
|
|
<div>
|
|
<label for="total_gift_amount" class="form-label">礼金金额</label>
|
|
<input class="form-input" id="total_gift_amount" name="total_gift_amount" type="number" min="0" step="0.01" value="{{ '%.2f'|format(household.total_gift_amount) }}">
|
|
{% if gift_records %}
|
|
<p class="mt-2 text-xs text-neutral-500 dark:text-neutral-400">当前已有礼金明细,保存户级信息时会自动按明细重算汇总金额与礼金主状态。</p>
|
|
{% endif %}
|
|
</div>
|
|
<div>
|
|
<label for="gift_method_option_id" class="form-label">礼金方式</label>
|
|
<select class="form-select" id="gift_method_option_id" name="gift_method_option_id">
|
|
<option value="">未选择</option>
|
|
{% for option in gift_method_options %}
|
|
<option value="{{ option.id }}" {% if household.gift_method_option_id == option.id %}selected{% endif %}>{{ option.option_label }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
<div>
|
|
<label for="gift_scene_option_id" class="form-label">礼金记录场景</label>
|
|
<select class="form-select" id="gift_scene_option_id" name="gift_scene_option_id">
|
|
<option value="">未选择</option>
|
|
{% for option in gift_scene_options %}
|
|
<option value="{{ option.id }}" {% if household.gift_scene_option_id == option.id %}selected{% endif %}>{{ option.option_label }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="grid grid-cols-1 gap-4 md:grid-cols-3">
|
|
<div>
|
|
<label for="favor_status" class="form-label">伴手礼状态</label>
|
|
<select class="form-select" id="favor_status" name="favor_status">
|
|
{% for value, label in [('not_given', '未发'), ('given', '已发')] %}
|
|
<option value="{{ value }}" {% if household.favor_status == value %}selected{% endif %}>{{ label }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
<div>
|
|
<label for="candy_status" class="form-label">喜糖状态</label>
|
|
<select class="form-select" id="candy_status" name="candy_status">
|
|
{% for value, label in [('not_given', '未发'), ('given', '已发')] %}
|
|
<option value="{{ value }}" {% if household.candy_status == value %}selected{% endif %}>{{ label }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
<div>
|
|
<label for="child_red_packet_status" class="form-label">儿童红包状态</label>
|
|
<select class="form-select" id="child_red_packet_status" name="child_red_packet_status">
|
|
{% for value, label in [('not_given', '未发'), ('partial', '部分发放'), ('given', '已发')] %}
|
|
<option value="{{ value }}" {% if household.child_red_packet_status == value %}selected{% endif %}>{{ label }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<label for="note" class="form-label">备注</label>
|
|
<textarea class="form-textarea" id="note" name="note" rows="4">{{ household.note or '' }}</textarea>
|
|
</div>
|
|
|
|
<div class="flex flex-wrap items-center gap-4 border-t border-neutral-200 pt-4 dark:border-neutral-700">
|
|
<button type="submit" class="btn btn-primary">{% if is_create_mode %}创建户{% else %}保存修改{% endif %}</button>
|
|
<a class="btn btn-secondary" href="{{ url_for('main.index') }}">返回管理首页</a>
|
|
<span class="text-sm text-neutral-500 dark:text-neutral-400">{% if is_create_mode %}新增模式:保存后会自动进入编辑页{% else %}最近更新时间:{{ household.updated_at.strftime('%Y-%m-%d %H:%M') if household.updated_at else '-' }}{% endif %}</span>
|
|
</div>
|
|
{% endif %}
|
|
</form>
|
|
|
|
{# 家庭成员管理部分 - 仅在编辑模式显示 #}
|
|
{% if not is_create_mode %}
|
|
<div class="mt-8 flex flex-col gap-8">
|
|
<section class="card">
|
|
<div class="card-body">
|
|
<div class="mb-4 flex items-center justify-between border-b border-neutral-200 pb-4 dark:border-neutral-700">
|
|
<div>
|
|
<h3 class="text-lg font-semibold text-neutral-800 dark:text-neutral-100">家庭成员</h3>
|
|
<p class="mt-1 text-sm text-neutral-500 dark:text-neutral-400">管理该户的家庭成员信息,包括到场状态、儿童标识等。</p>
|
|
</div>
|
|
<span class="status-badge status-badge-accent px-3 py-1 text-sm">{{ members|length }} 人</span>
|
|
</div>
|
|
|
|
{# 成员列表 #}
|
|
{% if members %}
|
|
<div class="mb-6 space-y-3">
|
|
{% for member in members %}
|
|
<div class="rounded-xl border border-neutral-200 bg-neutral-50 p-4 transition hover:border-neutral-300 dark:border-neutral-700 dark:bg-neutral-900 dark:hover:border-neutral-600">
|
|
<div class="flex flex-wrap items-start justify-between gap-3">
|
|
<div class="flex-1">
|
|
<div class="flex flex-wrap items-center gap-2">
|
|
<span class="text-base font-medium text-neutral-800 dark:text-neutral-100">{{ member.name }}</span>
|
|
{% if member.relation_to_head %}
|
|
<span class="status-badge status-badge-muted">{{ member.relation_to_head }}</span>
|
|
{% endif %}
|
|
{% if member.is_child %}
|
|
<span class="status-badge status-badge-accent">儿童</span>
|
|
{% endif %}
|
|
{% if member.needs_red_packet %}
|
|
<span class="status-badge status-badge-brand">需红包</span>
|
|
{% endif %}
|
|
</div>
|
|
<div class="mt-1 flex flex-wrap items-center gap-3 text-sm text-neutral-500 dark:text-neutral-400">
|
|
{% if member.gender %}
|
|
<span>{{ gender_labels.get(member.gender, '-') }}</span>
|
|
{% endif %}
|
|
{% if member.age_group %}
|
|
<span>{{ age_group_labels.get(member.age_group, '-') }}</span>
|
|
{% endif %}
|
|
<span class="{% if member.actually_attended %}text-emerald-600 dark:text-emerald-400{% else %}text-neutral-400 dark:text-neutral-500{% endif %}">
|
|
{{ '已到场' if member.actually_attended else '未到场' }}
|
|
</span>
|
|
<span class="{% if member.expected_to_attend %}text-emerald-600 dark:text-emerald-400{% else %}text-neutral-400 dark:text-neutral-500{% endif %}">
|
|
{{ '预计到场' if member.expected_to_attend else '预计不到场' }}
|
|
</span>
|
|
</div>
|
|
{% if member.note %}
|
|
<p class="mt-2 text-sm text-neutral-500 dark:text-neutral-400">备注:{{ member.note }}</p>
|
|
{% endif %}
|
|
</div>
|
|
<div class="flex items-center gap-2">
|
|
<button type="button" onclick="showEditMemberForm({{ member.id }})" class="btn btn-secondary btn-sm">编辑</button>
|
|
<form action="{{ url_for('main.delete_member', household_id=household.id, member_id=member.id) }}" method="post" class="inline" onsubmit="return confirm('确定要删除成员「{{ member.name }}」吗?此操作不可撤销。');">
|
|
<input type="hidden" name="confirm" value="yes">
|
|
<button type="submit" class="btn btn-secondary btn-sm">删除</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
{% else %}
|
|
<div class="mb-6 rounded-xl border border-neutral-200 bg-neutral-50 p-6 text-center dark:border-neutral-700 dark:bg-neutral-900">
|
|
<p class="text-sm text-neutral-500 dark:text-neutral-400">暂无家庭成员记录,点击下方按钮添加。</p>
|
|
</div>
|
|
{% endif %}
|
|
|
|
{# 新增成员表单 #}
|
|
<details class="group rounded-xl border border-neutral-200 bg-neutral-50 dark:border-neutral-700 dark:bg-neutral-900" {% if show_member_form %}open{% endif %}>
|
|
<summary class="cursor-pointer rounded-xl p-4 text-sm font-medium text-neutral-700 transition hover:bg-neutral-100 dark:text-neutral-200 dark:hover:bg-neutral-800">
|
|
<span class="group-open:hidden">+ 添加新成员</span>
|
|
<span class="hidden group-open:inline">收起新增表单</span>
|
|
</summary>
|
|
<form id="new-member-form" action="{{ url_for('main.create_member', household_id=household.id) }}" method="post" class="space-y-4 border-t border-neutral-200 p-4 dark:border-neutral-700">
|
|
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3">
|
|
<div>
|
|
<label for="new_member_name" class="form-label">姓名 <span class="text-brand-500">*</span></label>
|
|
<input class="form-input" id="new_member_name" name="name" type="text" required placeholder="请输入姓名">
|
|
</div>
|
|
<div>
|
|
<label for="new_member_relation" class="form-label">与户主关系</label>
|
|
<select class="form-select" id="new_member_relation" name="relation_to_head">
|
|
<option value="">请选择</option>
|
|
{% for relation in relation_to_head_options %}
|
|
<option value="{{ relation }}">{{ relation }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
<div>
|
|
<label for="new_member_gender" class="form-label">性别</label>
|
|
<select class="form-select" id="new_member_gender" name="gender">
|
|
<option value="">请选择</option>
|
|
{% for value in gender_options %}
|
|
<option value="{{ value }}">{{ gender_labels.get(value, value) }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
</div>
|
|
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3">
|
|
<div>
|
|
<label for="new_member_age_group" class="form-label">年龄段</label>
|
|
<select class="form-select" id="new_member_age_group" name="age_group">
|
|
<option value="">请选择</option>
|
|
{% for value in age_group_options %}
|
|
<option value="{{ value }}">{{ age_group_labels.get(value, value) }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
<div class="flex items-center gap-6 pt-6">
|
|
<label class="flex items-center gap-2 text-sm text-neutral-700 dark:text-neutral-200">
|
|
<input type="checkbox" name="is_child" value="true" class="h-4 w-4 rounded border-neutral-300 text-brand-600 focus:ring-brand-500 dark:border-neutral-600 dark:bg-neutral-800 dark:text-brand-400">
|
|
<span>是儿童</span>
|
|
</label>
|
|
<label class="flex items-center gap-2 text-sm text-neutral-700 dark:text-neutral-200">
|
|
<input type="checkbox" name="needs_red_packet" value="true" class="h-4 w-4 rounded border-neutral-300 text-brand-600 focus:ring-brand-500 dark:border-neutral-600 dark:bg-neutral-800 dark:text-brand-400">
|
|
<span>需红包</span>
|
|
</label>
|
|
</div>
|
|
<div class="flex items-center gap-6 pt-6">
|
|
<label class="flex items-center gap-2 text-sm text-neutral-700 dark:text-neutral-200">
|
|
<input type="checkbox" name="expected_to_attend" value="true" checked class="h-4 w-4 rounded border-neutral-300 text-brand-600 focus:ring-brand-500 dark:border-neutral-600 dark:bg-neutral-800 dark:text-brand-400">
|
|
<span>预计到场</span>
|
|
</label>
|
|
<label class="flex items-center gap-2 text-sm text-neutral-700 dark:text-neutral-200">
|
|
<input type="checkbox" name="actually_attended" value="true" class="h-4 w-4 rounded border-neutral-300 text-brand-600 focus:ring-brand-500 dark:border-neutral-600 dark:bg-neutral-800 dark:text-brand-400">
|
|
<span>实际到场</span>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<label for="new_member_note" class="form-label">备注</label>
|
|
<textarea class="form-textarea" id="new_member_note" name="note" rows="2" placeholder="可选备注信息"></textarea>
|
|
</div>
|
|
<div class="flex items-center gap-3 pt-2">
|
|
<button type="submit" class="btn btn-primary">添加成员</button>
|
|
<button type="reset" class="btn btn-secondary">清空表单</button>
|
|
</div>
|
|
</form>
|
|
</details>
|
|
|
|
{# 编辑成员表单(初始隐藏) #}
|
|
<div id="edit-member-modal" class="hidden fixed inset-0 z-50 flex items-center justify-center bg-black/50 px-4">
|
|
<div class="w-full max-w-xl rounded-2xl border border-neutral-200 bg-white p-6 shadow-xl dark:border-neutral-700 dark:bg-neutral-900">
|
|
<div class="mb-4 flex items-center justify-between border-b border-neutral-200 pb-4 dark:border-neutral-700">
|
|
<h4 class="text-lg font-semibold text-neutral-800 dark:text-neutral-100">编辑成员信息</h4>
|
|
<button type="button" onclick="hideEditMemberForm()" class="text-neutral-400 transition hover:text-neutral-600 dark:text-neutral-500 dark:hover:text-neutral-200">
|
|
<svg class="h-6 w-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path></svg>
|
|
</button>
|
|
</div>
|
|
<form id="edit-member-form" method="post" class="space-y-4">
|
|
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3">
|
|
<div>
|
|
<label for="edit_member_name" class="form-label">姓名</label>
|
|
<input class="form-input" id="edit_member_name" name="name" type="text" required>
|
|
</div>
|
|
<div>
|
|
<label for="edit_member_relation" class="form-label">与户主关系</label>
|
|
<select class="form-select" id="edit_member_relation" name="relation_to_head">
|
|
<option value="">请选择</option>
|
|
{% for relation in relation_to_head_options %}
|
|
<option value="{{ relation }}">{{ relation }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
<div>
|
|
<label for="edit_member_gender" class="form-label">性别</label>
|
|
<select class="form-select" id="edit_member_gender" name="gender">
|
|
<option value="">请选择</option>
|
|
{% for value in gender_options %}
|
|
<option value="{{ value }}">{{ gender_labels.get(value, value) }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
</div>
|
|
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3">
|
|
<div>
|
|
<label for="edit_member_age_group" class="form-label">年龄段</label>
|
|
<select class="form-select" id="edit_member_age_group" name="age_group">
|
|
<option value="">请选择</option>
|
|
{% for value in age_group_options %}
|
|
<option value="{{ value }}">{{ age_group_labels.get(value, value) }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
<div class="flex items-center gap-6 pt-6">
|
|
<label class="flex items-center gap-2 text-sm text-neutral-700 dark:text-neutral-200">
|
|
<input type="checkbox" name="is_child" value="true" id="edit_member_is_child" class="h-4 w-4 rounded border-neutral-300 text-brand-600 focus:ring-brand-500 dark:border-neutral-600 dark:bg-neutral-800 dark:text-brand-400">
|
|
<span>是儿童</span>
|
|
</label>
|
|
<label class="flex items-center gap-2 text-sm text-neutral-700 dark:text-neutral-200">
|
|
<input type="checkbox" name="needs_red_packet" value="true" id="edit_member_needs_red_packet" class="h-4 w-4 rounded border-neutral-300 text-brand-600 focus:ring-brand-500 dark:border-neutral-600 dark:bg-neutral-800 dark:text-brand-400">
|
|
<span>需红包</span>
|
|
</label>
|
|
</div>
|
|
<div class="flex items-center gap-6 pt-6">
|
|
<label class="flex items-center gap-2 text-sm text-neutral-700 dark:text-neutral-200">
|
|
<input type="checkbox" name="expected_to_attend" value="true" id="edit_member_expected_to_attend" class="h-4 w-4 rounded border-neutral-300 text-brand-600 focus:ring-brand-500 dark:border-neutral-600 dark:bg-neutral-800 dark:text-brand-400">
|
|
<span>预计到场</span>
|
|
</label>
|
|
<label class="flex items-center gap-2 text-sm text-neutral-700 dark:text-neutral-200">
|
|
<input type="checkbox" name="actually_attended" value="true" id="edit_member_actually_attended" class="h-4 w-4 rounded border-neutral-300 text-brand-600 focus:ring-brand-500 dark:border-neutral-600 dark:bg-neutral-800 dark:text-brand-400">
|
|
<span>实际到场</span>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<label for="edit_member_note" class="form-label">备注</label>
|
|
<textarea class="form-textarea" id="edit_member_note" name="note" rows="2"></textarea>
|
|
</div>
|
|
<div class="flex items-center gap-3 pt-2">
|
|
<button type="submit" class="btn btn-primary">保存修改</button>
|
|
<button type="button" onclick="hideEditMemberForm()" class="btn btn-secondary">取消</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<section class="card">
|
|
<div class="card-body">
|
|
<div class="mb-4 flex items-center justify-between border-b border-neutral-200 pb-4 dark:border-neutral-700">
|
|
<div>
|
|
<h3 class="text-lg font-semibold text-neutral-800 dark:text-neutral-100">礼金明细</h3>
|
|
<p class="mt-1 text-sm text-neutral-500 dark:text-neutral-400">逐条记录礼金与礼品往来。现金类记录会自动汇总到当前户的礼金金额。</p>
|
|
</div>
|
|
<span class="status-badge status-badge-accent px-3 py-1 text-sm">{{ gift_records|length }} 条</span>
|
|
</div>
|
|
|
|
{% if gift_records %}
|
|
<div class="mb-6 space-y-3">
|
|
{% for record in gift_records %}
|
|
<div class="rounded-xl border border-neutral-200 bg-neutral-50 p-4 transition hover:border-neutral-300 dark:border-neutral-700 dark:bg-neutral-900 dark:hover:border-neutral-600">
|
|
<div class="flex flex-wrap items-start justify-between gap-3">
|
|
<div class="flex-1">
|
|
<div class="flex flex-wrap items-center gap-2">
|
|
<span class="text-base font-medium text-neutral-800 dark:text-neutral-100">{{ gift_record_type_label(record.record_type) }}</span>
|
|
{% if record.amount is not none %}
|
|
<span class="status-badge status-badge-brand">¥{{ '%.2f'|format(record.amount) }}</span>
|
|
{% endif %}
|
|
{% if record.gift_name %}
|
|
<span class="status-badge status-badge-accent">{{ record.gift_name }}</span>
|
|
{% endif %}
|
|
</div>
|
|
<div class="mt-1 flex flex-wrap items-center gap-3 text-sm text-neutral-500 dark:text-neutral-400">
|
|
<span>记录时间:{{ record.record_time.strftime('%Y-%m-%d %H:%M') if record.record_time else '-' }}</span>
|
|
<span>礼金方式:{{ record.method_option.option_label if record.method_option else '-' }}</span>
|
|
<span>记录场景:{{ record.scene_option.option_label if record.scene_option else '-' }}</span>
|
|
<span>礼品估值:{{ '¥%.2f'|format(record.estimated_value) if record.estimated_value is not none else '-' }}</span>
|
|
</div>
|
|
{% if record.note %}
|
|
<p class="mt-2 text-sm text-neutral-500 dark:text-neutral-400">备注:{{ record.note }}</p>
|
|
{% endif %}
|
|
</div>
|
|
<div class="flex items-center gap-2">
|
|
<button type="button" onclick="showEditGiftRecordForm({{ record.id }})" class="btn btn-secondary btn-sm">编辑</button>
|
|
<form action="{{ url_for('main.delete_gift_record', household_id=household.id, record_id=record.id) }}" method="post" class="inline" onsubmit="return confirm('确定要删除这条礼金明细吗?删除后会自动重算礼金汇总。');">
|
|
<input type="hidden" name="confirm" value="yes">
|
|
<button type="submit" class="btn btn-secondary btn-sm">删除</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
{% else %}
|
|
<div class="mb-6 rounded-xl border border-neutral-200 bg-neutral-50 p-6 text-center dark:border-neutral-700 dark:bg-neutral-900">
|
|
<p class="text-sm text-neutral-500 dark:text-neutral-400">当前还没有礼金明细,补录后会自动同步户级礼金汇总。</p>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<details class="group rounded-xl border border-neutral-200 bg-neutral-50 dark:border-neutral-700 dark:bg-neutral-900">
|
|
<summary class="cursor-pointer rounded-xl p-4 text-sm font-medium text-neutral-700 transition hover:bg-neutral-100 dark:text-neutral-200 dark:hover:bg-neutral-800">
|
|
<span class="group-open:hidden">+ 新增礼金明细</span>
|
|
<span class="hidden group-open:inline">收起新增表单</span>
|
|
</summary>
|
|
<form id="new-gift-record-form" action="{{ url_for('main.create_gift_record', household_id=household.id) }}" method="post" class="space-y-4 border-t border-neutral-200 p-4 dark:border-neutral-700">
|
|
<div class="grid grid-cols-1 gap-4 md:grid-cols-2 xl:grid-cols-4">
|
|
<div>
|
|
<label for="new_record_type" class="form-label">记录类型 <span class="text-brand-500">*</span></label>
|
|
<select id="new_record_type" name="record_type" class="form-select">
|
|
{% for value in gift_record_type_options %}
|
|
<option value="{{ value }}" {% if new_gift_record.record_type == value %}selected{% endif %}>{{ gift_record_type_labels.get(value, value) }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
<div>
|
|
<label for="new_amount" class="form-label">礼金金额</label>
|
|
<input id="new_amount" name="amount" type="number" min="0" step="0.01" value="{{ '%.2f'|format(new_gift_record.amount) if new_gift_record.amount is not none else '' }}" class="form-input">
|
|
</div>
|
|
<div>
|
|
<label for="new_gift_name" class="form-label">礼品名称</label>
|
|
<input id="new_gift_name" name="gift_name" type="text" value="{{ new_gift_record.gift_name or '' }}" class="form-input">
|
|
</div>
|
|
<div>
|
|
<label for="new_estimated_value" class="form-label">礼品估值</label>
|
|
<input id="new_estimated_value" name="estimated_value" type="number" min="0" step="0.01" value="{{ '%.2f'|format(new_gift_record.estimated_value) if new_gift_record.estimated_value is not none else '' }}" class="form-input">
|
|
</div>
|
|
</div>
|
|
<div class="grid grid-cols-1 gap-4 md:grid-cols-3">
|
|
<div>
|
|
<label for="new_method_option_id" class="form-label">礼金方式</label>
|
|
<select id="new_method_option_id" name="method_option_id" class="form-select">
|
|
<option value="">未选择</option>
|
|
{% for option in gift_method_options %}
|
|
<option value="{{ option.id }}">{{ option.option_label }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
<div>
|
|
<label for="new_scene_option_id" class="form-label">记录场景</label>
|
|
<select id="new_scene_option_id" name="scene_option_id" class="form-select">
|
|
<option value="">未选择</option>
|
|
{% for option in gift_scene_options %}
|
|
<option value="{{ option.id }}">{{ option.option_label }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
<div>
|
|
<label for="new_record_time" class="form-label">记录时间 <span class="text-brand-500">*</span></label>
|
|
<input id="new_record_time" name="record_time" type="datetime-local" value="{{ new_gift_record.record_time.strftime('%Y-%m-%dT%H:%M') if new_gift_record.record_time else '' }}" class="form-input">
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<label for="new_gift_note" class="form-label">备注</label>
|
|
<textarea id="new_gift_note" name="note" rows="2" class="form-textarea"></textarea>
|
|
</div>
|
|
<div class="flex items-center gap-3 pt-2">
|
|
<button type="submit" class="btn btn-primary">新增礼金明细</button>
|
|
<button type="reset" class="btn btn-secondary">清空表单</button>
|
|
</div>
|
|
</form>
|
|
</details>
|
|
|
|
<div id="edit-gift-record-modal" class="hidden fixed inset-0 z-50 flex items-center justify-center bg-black/50 px-4">
|
|
<div class="w-full max-w-3xl rounded-2xl border border-neutral-200 bg-white p-6 shadow-xl dark:border-neutral-700 dark:bg-neutral-900">
|
|
<div class="mb-4 flex items-center justify-between border-b border-neutral-200 pb-4 dark:border-neutral-700">
|
|
<h4 class="text-lg font-semibold text-neutral-800 dark:text-neutral-100">编辑礼金明细</h4>
|
|
<button type="button" onclick="hideEditGiftRecordForm()" class="text-neutral-400 transition hover:text-neutral-600 dark:text-neutral-500 dark:hover:text-neutral-200">
|
|
<svg class="h-6 w-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path></svg>
|
|
</button>
|
|
</div>
|
|
<form id="edit-gift-record-form" method="post" class="space-y-4">
|
|
<div class="grid grid-cols-1 gap-4 md:grid-cols-2 xl:grid-cols-4">
|
|
<div>
|
|
<label for="edit_record_type" class="form-label">记录类型</label>
|
|
<select id="edit_record_type" name="record_type" class="form-select">
|
|
{% for value in gift_record_type_options %}
|
|
<option value="{{ value }}">{{ gift_record_type_labels.get(value, value) }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
<div>
|
|
<label for="edit_amount" class="form-label">礼金金额</label>
|
|
<input id="edit_amount" name="amount" type="number" min="0" step="0.01" class="form-input">
|
|
</div>
|
|
<div>
|
|
<label for="edit_gift_name" class="form-label">礼品名称</label>
|
|
<input id="edit_gift_name" name="gift_name" type="text" class="form-input">
|
|
</div>
|
|
<div>
|
|
<label for="edit_estimated_value" class="form-label">礼品估值</label>
|
|
<input id="edit_estimated_value" name="estimated_value" type="number" min="0" step="0.01" class="form-input">
|
|
</div>
|
|
</div>
|
|
<div class="grid grid-cols-1 gap-4 md:grid-cols-3">
|
|
<div>
|
|
<label for="edit_method_option_id" class="form-label">礼金方式</label>
|
|
<select id="edit_method_option_id" name="method_option_id" class="form-select">
|
|
<option value="">未选择</option>
|
|
{% for option in gift_method_options %}
|
|
<option value="{{ option.id }}">{{ option.option_label }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
<div>
|
|
<label for="edit_scene_option_id" class="form-label">记录场景</label>
|
|
<select id="edit_scene_option_id" name="scene_option_id" class="form-select">
|
|
<option value="">未选择</option>
|
|
{% for option in gift_scene_options %}
|
|
<option value="{{ option.id }}">{{ option.option_label }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
<div>
|
|
<label for="edit_record_time" class="form-label">记录时间</label>
|
|
<input id="edit_record_time" name="record_time" type="datetime-local" class="form-input">
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<label for="edit_gift_note" class="form-label">备注</label>
|
|
<textarea id="edit_gift_note" name="note" rows="2" class="form-textarea"></textarea>
|
|
</div>
|
|
<div class="flex items-center gap-3 pt-2">
|
|
<button type="submit" class="btn btn-primary">保存礼金明细</button>
|
|
<button type="button" onclick="hideEditGiftRecordForm()" class="btn btn-secondary">取消</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<script id="gift-records-data" type="application/json">
|
|
[
|
|
{% for record in gift_records %}
|
|
{
|
|
"id": {{ record.id }},
|
|
"record_type": {{ record.record_type | tojson }},
|
|
"amount": {{ ('%.2f'|format(record.amount)) | tojson if record.amount is not none else 'null' }},
|
|
"gift_name": {{ record.gift_name | tojson if record.gift_name else 'null' }},
|
|
"estimated_value": {{ ('%.2f'|format(record.estimated_value)) | tojson if record.estimated_value is not none else 'null' }},
|
|
"method_option_id": {{ record.method_option_id | tojson if record.method_option_id is not none else 'null' }},
|
|
"scene_option_id": {{ record.scene_option_id | tojson if record.scene_option_id is not none else 'null' }},
|
|
"record_time": {{ record.record_time.strftime('%Y-%m-%dT%H:%M') | tojson if record.record_time else 'null' }},
|
|
"note": {{ record.note | tojson if record.note else 'null' }}
|
|
}{% if not loop.last %},{% endif %}
|
|
{% endfor %}
|
|
]
|
|
</script>
|
|
</section>
|
|
|
|
{# 成员数据(用于编辑表单) #}
|
|
<script id="members-data" type="application/json">
|
|
[
|
|
{% for member in members %}
|
|
{
|
|
"id": {{ member.id }},
|
|
"name": {{ member.name | tojson }},
|
|
"relation_to_head": {{ member.relation_to_head | tojson if member.relation_to_head else 'null' }},
|
|
"gender": {{ member.gender | tojson if member.gender else 'null' }},
|
|
"age_group": {{ member.age_group | tojson if member.age_group else 'null' }},
|
|
"is_child": {{ 'true' if member.is_child else 'false' }},
|
|
"needs_red_packet": {{ 'true' if member.needs_red_packet else 'false' }},
|
|
"expected_to_attend": {{ 'true' if member.expected_to_attend else 'false' }},
|
|
"actually_attended": {{ 'true' if member.actually_attended else 'false' }},
|
|
"note": {{ member.note | tojson if member.note else 'null' }}
|
|
}{% if not loop.last %},{% endif %}
|
|
{% endfor %}
|
|
]
|
|
</script>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<script>
|
|
(function () {
|
|
const form = document.getElementById('admin-household-edit-form');
|
|
if (!form) {
|
|
return;
|
|
}
|
|
|
|
}());
|
|
|
|
{% if not is_create_mode %}
|
|
// 家庭成员编辑功能
|
|
(function () {
|
|
const membersDataEl = document.getElementById('members-data');
|
|
if (!membersDataEl) {
|
|
return;
|
|
}
|
|
|
|
let membersData = [];
|
|
try {
|
|
membersData = JSON.parse(membersDataEl.textContent || '[]');
|
|
} catch (e) {
|
|
console.error('Failed to parse members data:', e);
|
|
return;
|
|
}
|
|
|
|
const modal = document.getElementById('edit-member-modal');
|
|
const editForm = document.getElementById('edit-member-form');
|
|
if (!modal || !editForm) {
|
|
return;
|
|
}
|
|
|
|
// 显示编辑表单
|
|
window.showEditMemberForm = function (memberId) {
|
|
const member = membersData.find(function (m) { return m.id === memberId; });
|
|
if (!member) {
|
|
return;
|
|
}
|
|
|
|
// 设置表单 action - 动态构建 URL
|
|
editForm.action = "/households/{{ household.id }}/members/" + memberId;
|
|
|
|
// 填充数据
|
|
document.getElementById('edit_member_name').value = member.name;
|
|
document.getElementById('edit_member_relation').value = member.relation_to_head || '';
|
|
document.getElementById('edit_member_gender').value = member.gender || '';
|
|
document.getElementById('edit_member_age_group').value = member.age_group || '';
|
|
document.getElementById('edit_member_is_child').checked = member.is_child;
|
|
document.getElementById('edit_member_needs_red_packet').checked = member.needs_red_packet;
|
|
document.getElementById('edit_member_expected_to_attend').checked = member.expected_to_attend;
|
|
document.getElementById('edit_member_actually_attended').checked = member.actually_attended;
|
|
document.getElementById('edit_member_note').value = member.note || '';
|
|
|
|
// 显示弹窗
|
|
modal.classList.remove('hidden');
|
|
};
|
|
|
|
// 隐藏编辑表单
|
|
window.hideEditMemberForm = function () {
|
|
modal.classList.add('hidden');
|
|
};
|
|
|
|
// 点击背景关闭
|
|
modal.addEventListener('click', function (e) {
|
|
if (e.target === modal) {
|
|
hideEditMemberForm();
|
|
}
|
|
});
|
|
|
|
// ESC 键关闭
|
|
document.addEventListener('keydown', function (e) {
|
|
if (e.key === 'Escape' && !modal.classList.contains('hidden')) {
|
|
hideEditMemberForm();
|
|
}
|
|
});
|
|
}());
|
|
|
|
(function () {
|
|
const recordsDataEl = document.getElementById('gift-records-data');
|
|
if (!recordsDataEl) {
|
|
return;
|
|
}
|
|
|
|
let recordsData = [];
|
|
try {
|
|
recordsData = JSON.parse(recordsDataEl.textContent || '[]');
|
|
} catch (e) {
|
|
console.error('Failed to parse gift records data:', e);
|
|
return;
|
|
}
|
|
|
|
const modal = document.getElementById('edit-gift-record-modal');
|
|
const editForm = document.getElementById('edit-gift-record-form');
|
|
if (!modal || !editForm) {
|
|
return;
|
|
}
|
|
|
|
window.showEditGiftRecordForm = function (recordId) {
|
|
const record = recordsData.find(function (item) { return item.id === recordId; });
|
|
if (!record) {
|
|
return;
|
|
}
|
|
|
|
editForm.action = "/households/{{ household.id }}/gift-records/" + recordId;
|
|
document.getElementById('edit_record_type').value = record.record_type || 'cash_gift';
|
|
document.getElementById('edit_amount').value = record.amount || '';
|
|
document.getElementById('edit_gift_name').value = record.gift_name || '';
|
|
document.getElementById('edit_estimated_value').value = record.estimated_value || '';
|
|
document.getElementById('edit_method_option_id').value = record.method_option_id || '';
|
|
document.getElementById('edit_scene_option_id').value = record.scene_option_id || '';
|
|
document.getElementById('edit_record_time').value = record.record_time || '';
|
|
document.getElementById('edit_gift_note').value = record.note || '';
|
|
modal.classList.remove('hidden');
|
|
};
|
|
|
|
window.hideEditGiftRecordForm = function () {
|
|
modal.classList.add('hidden');
|
|
};
|
|
|
|
modal.addEventListener('click', function (e) {
|
|
if (e.target === modal) {
|
|
hideEditGiftRecordForm();
|
|
}
|
|
});
|
|
|
|
document.addEventListener('keydown', function (e) {
|
|
if (e.key === 'Escape' && !modal.classList.contains('hidden')) {
|
|
hideEditGiftRecordForm();
|
|
}
|
|
});
|
|
}());
|
|
{% endif %}
|
|
</script>
|
|
{% endblock %}
|
|
|