PHP项目Symfony form与动态表头

wen PHP项目 2

本文目录导读:

PHP项目Symfony form与动态表头

  1. 基础架构设计
  2. 动态行表单类型
  3. 控制器处理
  4. Twig模板
  5. 高级特性:通过AJAX动态更新
  6. 数据库存储方案
  7. 性能优化建议

在Symfony项目中处理带有动态表头的表单是一个常见需求,尤其是在处理数据表、报表或用户自定义字段时,以下是一个完整的解决方案:

基础架构设计

实体类结构

// src/Entity/DynamicTable.php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity]
class DynamicTable
{
    #[ORM\Id]
    #[ORM\GeneratedValue]
    #[ORM\Column(type: 'integer')]
    private int $id;
    #[ORM\Column(type: 'json')]
    private array $headers = [];
    #[ORM\Column(type: 'json')]
    private array $rows = [];
    // getters/setters...
}

表单类型

// src/Form/DynamicTableType.php
namespace App\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class DynamicTableType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options): void
    {
        $headers = $options['headers'];
        $builder
            ->add('headers', CollectionType::class, [
                'entry_type' => TextType::class,
                'allow_add' => true,
                'allow_delete' => true,
                'label' => false,
                'entry_options' => [
                    'attr' => ['class' => 'header-input']
                ]
            ])
            ->add('rows', CollectionType::class, [
                'entry_type' => DynamicTableRowType::class,
                'entry_options' => ['headers' => $headers],
                'allow_add' => true,
                'allow_delete' => true,
                'label' => false,
            ]);
    }
    public function configureOptions(OptionsResolver $resolver): void
    {
        $resolver->setDefaults([
            'data_class' => DynamicTable::class,
            'headers' => [],
        ]);
    }
}

动态行表单类型

// src/Form/DynamicTableRowType.php
namespace App\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class DynamicTableRowType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options): void
    {
        $headers = $options['headers'];
        foreach ($headers as $index => $header) {
            $builder->add('cell_' . $index, TextType::class, [
                'label' => $header,
                'attr' => ['class' => 'table-cell'],
                'required' => false,
            ]);
        }
    }
    public function configureOptions(OptionsResolver $resolver): void
    {
        $resolver->setDefaults([
            'headers' => [],
        ]);
    }
}

控制器处理

// src/Controller/DynamicTableController.php
namespace App\Controller;
use App\Entity\DynamicTable;
use App\Form\DynamicTableType;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
class DynamicTableController extends AbstractController
{
    #[Route('/dynamic-table/new', name: 'dynamic_table_new')]
    public function new(Request $request, EntityManagerInterface $em): Response
    {
        // 初始表头(可以从数据库或配置中读取)
        $initialHeaders = ['Name', 'Email', 'Phone'];
        $dynamicTable = new DynamicTable();
        $dynamicTable->setHeaders($initialHeaders);
        $form = $this->createForm(DynamicTableType::class, $dynamicTable, [
            'headers' => $initialHeaders,
        ]);
        $form->handleRequest($request);
        if ($form->isSubmitted() && $form->isValid()) {
            $em->persist($dynamicTable);
            $em->flush();
            return $this->redirectToRoute('dynamic_table_show', ['id' => $dynamicTable->getId()]);
        }
        return $this->render('dynamic_table/new.html.twig', [
            'form' => $form->createView(),
            'headers' => $initialHeaders,
        ]);
    }
    // 更新表头的AJAX端点
    #[Route('/dynamic-table/update-headers', name: 'dynamic_table_update_headers', methods: ['POST'])]
    public function updateHeaders(Request $request): Response
    {
        $headers = json_decode($request->getContent(), true);
        // 处理表头更新逻辑
        return $this->json(['success' => true]);
    }
}

Twig模板

{# templates/dynamic_table/new.html.twig #}
{% extends 'base.html.twig' %}
{% block body %}
    <div class="container mt-5">
        <h1>动态表头表单</h1>
        {{ form_start(form) }}
        <div class="mb-4">
            <h3>表头配置</h3>
            <div id="headers-container" 
                 data-prototype="{{ form_widget(form.headers.vars.prototype)|e('html_attr') }}">
                {% for header in form.headers %}
                    <div class="header-item mb-2">
                        {{ form_widget(header) }}
                        <button type="button" class="btn btn-danger btn-sm remove-header">删除</button>
                    </div>
                {% endfor %}
            </div>
            <button type="button" class="btn btn-primary mt-2" id="add-header">添加表头</button>
        </div>
        <div class="mb-4">
            <h3>数据行</h3>
            <table class="table" id="data-table">
                <thead>
                    <tr id="table-headers">
                        {% for header in headers %}
                            <th>{{ header }}</th>
                        {% endfor %}
                        <th>操作</th>
                    </tr>
                </thead>
                <tbody id="table-body" 
                       data-prototype="{{ form_widget(form.rows.vars.prototype)|e('html_attr') }}">
                    {% for row in form.rows %}
                        <tr class="data-row">
                            {% for cell in row %}
                                <td>{{ form_widget(cell) }}</td>
                            {% endfor %}
                            <td>
                                <button type="button" class="btn btn-danger btn-sm remove-row">删除</button>
                            </td>
                        </tr>
                    {% endfor %}
                </tbody>
            </table>
            <button type="button" class="btn btn-success" id="add-row">添加行</button>
        </div>
        <button type="submit" class="btn btn-primary">保存</button>
        {{ form_end(form) }}
    </div>
{% endblock %}
{% block javascripts %}
    <script>
        // JavaScript 处理动态添加/删除
        document.addEventListener('DOMContentLoaded', function() {
            const headersContainer = document.getElementById('headers-container');
            const tableBody = document.getElementById('table-body');
            const addHeaderBtn = document.getElementById('add-header');
            const addRowBtn = document.getElementById('add-row');
            // 添加表头
            addHeaderBtn.addEventListener('click', function() {
                const prototype = headersContainer.dataset.prototype;
                const newHeader = prototype.replace(/__name__/g, headersContainer.children.length);
                const div = document.createElement('div');
                div.className = 'header-item mb-2';
                div.innerHTML = newHeader;
                div.innerHTML += '<button type="button" class="btn btn-danger btn-sm remove-header">删除</button>';
                headersContainer.appendChild(div);
                updateTableHeaders();
            });
            // 删除表头
            headersContainer.addEventListener('click', function(e) {
                if (e.target.classList.contains('remove-header')) {
                    e.target.parentElement.remove();
                    updateTableHeaders();
                }
            });
            // 添加行
            addRowBtn.addEventListener('click', function() {
                const prototype = tableBody.dataset.prototype;
                const newRow = prototype.replace(/__name__/g, tableBody.children.length);
                const tr = document.createElement('tr');
                tr.className = 'data-row';
                tr.innerHTML = newRow;
                tr.innerHTML += '<td><button type="button" class="btn btn-danger btn-sm remove-row">删除</button></td>';
                tableBody.appendChild(tr);
            });
            // 删除行
            tableBody.addEventListener('click', function(e) {
                if (e.target.classList.contains('remove-row')) {
                    e.target.closest('tr').remove();
                }
            });
            // 更新表头显示
            function updateTableHeaders() {
                const headers = [];
                document.querySelectorAll('.header-item input').forEach(function(input) {
                    headers.push(input.value);
                });
                const thead = document.getElementById('table-headers');
                thead.innerHTML = '';
                headers.forEach(function(header) {
                    const th = document.createElement('th');
                    th.textContent = header;
                    thead.appendChild(th);
                });
                // 添加操作列
                const th = document.createElement('th');
                th.textContent = '操作';
                thead.appendChild(th);
            }
        });
    </script>
{% endblock %}

高级特性:通过AJAX动态更新

// public/js/dynamic-table.js
class DynamicTableManager {
    constructor(options) {
        this.container = options.container;
        this.url = options.updateUrl || '/dynamic-table/update-headers';
        this.csrfToken = options.csrfToken;
    }
    updateHeaders(headers) {
        fetch(this.url, {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
                'X-CSRF-TOKEN': this.csrfToken
            },
            body: JSON.stringify({
                headers: headers
            })
        })
        .then(response => response.json())
        .then(data => {
            if (data.success) {
                // 更新UI
                this.refreshTableStructure(headers);
            }
        })
        .catch(error => console.error('Error:', error));
    }
    refreshTableStructure(headers) {
        // 重新渲染表头和现有数据
        // 可以使用Turbo或自定义渲染
    }
}

数据库存储方案

-- 创建动态表
CREATE TABLE dynamic_table (
    id INT AUTO_INCREMENT PRIMARY KEY,
    headers JSON,
    rows JSON,
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
-- 或使用更灵活的方案
CREATE TABLE dynamic_table_headers (
    id INT AUTO_INCREMENT PRIMARY KEY,
    table_id INT,
    header_name VARCHAR(255),
    header_order INT,
    header_type VARCHAR(50) DEFAULT 'text'
);
CREATE TABLE dynamic_table_data (
    id INT AUTO_INCREMENT PRIMARY KEY,
    table_id INT,
    row_order INT,
    cell_data JSON
);

性能优化建议

  1. 缓存头信息:使用Redis缓存表头配置
  2. 懒加载数据:对于大量数据,使用分页或无限滚动
  3. 索引优化:适当建立索引,特别是JSON字段
  4. 批量操作:处理大量行时使用批量插入

这个方案提供了一个完整的动态表头表单解决方案,可以根据具体需求进行调整和扩展。

抱歉,评论功能暂时关闭!