You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于Flask-WTF实现点击无刷新添加子表单的技术问询

无刷新添加Flask-WTF FieldList子表单方案

要实现点击按钮不刷新页面添加子表单,核心思路是前端用JavaScript克隆预定义的子表单模板,动态更新字段的命名索引,确保符合Flask-WTF的字段命名规则(plantes-{index}-{field}),后端只需正常处理表单提交即可。

步骤1:修改HTML模板(plantesPresentes.html)

渲染初始子表单的同时,添加一个隐藏的子表单模板,用于克隆:

<form method="POST" id="plantes-form">
    {{ form.hidden_tag() }}

    <!-- 主表单字段:Site de recolte -->
    <div class="form-group">
        {{ form.siterec.label }}
        {{ form.siterec(class_="search") }}
    </div>

    <!-- 子表单容器 -->
    <div id="plantes-container">
        {% for plante_form in form.plantes %}
            <div class="plante-form" data-index="{{ loop.index0 }}">
                <div class="form-group">
                    {{ plante_form.plante.label }}
                    {{ plante_form.plante(class_="search") }}
                </div>
                <div class="form-group">
                    {{ plante_form.diametre.label }}
                    {{ plante_form.diametre() }}
                </div>
                <div class="form-group">
                    {{ plante_form.hauteur.label }}
                    {{ plante_form.hauteur() }}
                </div>
                <div class="form-group">
                    {{ plante_form.nom.label }}
                    {{ plante_form.nom(class_="careful") }}
                </div>
                <div class="form-group">
                    {{ plante_form.statutplante.label }}
                    {{ plante_form.statutplante(class_="search") }}
                </div>
                <div class="form-group">
                    {{ plante_form.partiezone.label }}
                    {{ plante_form.partiezone(class_="search") }}
                </div>
                <div class="form-group">
                    {{ plante_form.quantite.label }}
                    {{ plante_form.quantite() }}
                </div>
                <button type="button" class="remove-plante">Supprimer</button>
            </div>
        {% endfor %}
    </div>

    <!-- 隐藏的子表单模板:用于克隆 -->
    <div id="plante-template" style="display: none;">
        {% set dummy_form = form.plantes.append_entry() %}
        <div class="plante-form">
            <div class="form-group">
                {{ dummy_form.plante.label }}
                {{ dummy_form.plante(class_="search") }}
            </div>
            <div class="form-group">
                {{ dummy_form.diametre.label }}
                {{ dummy_form.diametre() }}
            </div>
            <div class="form-group">
                {{ dummy_form.hauteur.label }}
                {{ dummy_form.hauteur() }}
            </div>
            <div class="form-group">
                {{ dummy_form.nom.label }}
                {{ dummy_form.nom(class_="careful") }}
            </div>
            <div class="form-group">
                {{ dummy_form.statutplante.label }}
                {{ dummy_form.statutplante(class_="search") }}
            </div>
            <div class="form-group">
                {{ dummy_form.partiezone.label }}
                {{ dummy_form.partiezone(class_="search") }}
            </div>
            <div class="form-group">
                {{ dummy_form.quantite.label }}
                {{ dummy_form.quantite() }}
            </div>
            <button type="button" class="remove-plante">Supprimer</button>
        </div>
    </div>

    <!-- 添加按钮:改为type="button"避免触发表单提交 -->
    <button type="button" id="add-plante">Ajout d'une autre plante présente</button>

    <!-- 提交按钮 -->
    <div class="form-group">
        {{ form.submit() }}
    </div>
</form>

步骤2:添加JavaScript逻辑

在模板末尾或单独的JS文件中添加以下代码,实现克隆、索引更新和删除功能:

document.addEventListener('DOMContentLoaded', function() {
    const addBtn = document.getElementById('add-plante');
    const container = document.getElementById('plantes-container');
    const templateHTML = document.getElementById('plante-template').innerHTML;

    // 添加子表单
    addBtn.addEventListener('click', function() {
        const currentCount = container.querySelectorAll('.plante-form').length;
        // 替换模板中的索引占位符(将plantes-0-*替换为plantes-{currentCount}-*)
        const newFormHTML = templateHTML.replace(/plantes-0-/g, `plantes-${currentCount}-`)
                                        .replace(/id="plantes-0-/g, `id="plantes-${currentCount}-`);
        container.insertAdjacentHTML('beforeend', newFormHTML);
    });

    // 删除子表单并更新索引
    container.addEventListener('click', function(e) {
        if (e.target.classList.contains('remove-plante')) {
            e.target.closest('.plante-form').remove();
            updateFormIndices();
        }
    });

    // 更新所有子表单的索引,确保字段命名连续
    function updateFormIndices() {
        const forms = container.querySelectorAll('.plante-form');
        forms.forEach((formEl, index) => {
            formEl.querySelectorAll('input, select').forEach(field => {
                field.name = field.name.replace(/plantes-\d+-/g, `plantes-${index}-`);
                field.id = field.id.replace(/plantes-\d+-/g, `plantes-${index}-`);
            });
            formEl.dataset.index = index;
        });
    }
});

步骤3:简化后端代码

移除原来处理ajoutFlore按钮的逻辑,因为前端不再提交该按钮:

def insertPlantesPresentes():
    form = InsertPlantesForm()
    message = ""

    if form.validate_on_submit():
        # 处理所有子表单数据,form.plantes.data是包含所有子表单的列表
        print(form.plantes.data)
        # 插入数据库的业务逻辑写在这里

    return render_template('html/plantesPresentes.html', form=form, 
                           name="Ajout de plantes sur un site de récolte", message=message)

关键说明

  • Flask-WTF的FieldList字段命名规则是{field_list_name}-{index}-{sub_field_name},前端必须严格遵循这个格式,否则后端无法解析数据。
  • 隐藏模板通过form.plantes.append_entry()生成临时子表单,确保字段的name/id格式正确。
  • 删除子表单后必须更新所有剩余子表单的索引,避免索引断层导致数据解析错误。

内容的提问来源于stack exchange,提问作者eb18

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 19:57:32